硬件开发1-51单片机3-串口
UART:通用异步收发器
- 包含 2 个串口:1 个用于 ISP 下载程序,1 个用于与主机通信
- 主要信号线:RXD (接收信号线)、TXD (发送信号线)
- 通信特性:全双工、串行、异步
一、通信方式
1、单工
- 发送方与接收方是固定的
- 数据只能 A是发 B是收(固定的)
- 数据传输通过一根信号线实现
- 数据传输的方向是固定的单向的
2、半双工(如 I2C)
- 通信的双方既可以作为发送方,也可以作为接收方
- 数据传输通过一根信号线实现
- 数据传输的方向可以是双向,但同一时刻的传输方向呈现单一性
- 典型例子 I2C
- A 给 B 发的时候,B 不能给 A 发,B 只能去接收数据
3、全双工(如 UART)
- 通信的双方既可以作为发送方,也可以作为接收方
- 数据传输通过两根信号线实现(TXD RXD)
- 数据的传输在任意时刻都是双向的
- 例(URT)
二、数据传输的顺序
UART 传输遵循 LSB 优先原则(低位先行),先发低位数据
数据位置
三、数据传输的形式
1、串行:
通过一根信号线传输,按先后次序逐个 bit 发送
- 概念:通过一根信号线传输数据,按照先后次序逐个bit逐个bit去发送数据
- 优缺点:
- 传输速率慢
- 硬件成本低,实现简单
- 传输距离远,抗干扰性好
- (例RS485 差分)(1km工业级)
2、并行:
通过多根信号线同时传输多个 bit
- 概念:通过多根信号线同时去传输数据
- 优缺点:
- 传输速度快
- 硬件成本高,实现复杂
- 传输距离近,抗干扰弱
- 理论传输距离30m,距离增加容易并行线路间信号偏移,易受电磁波干扰,会造成数据受干扰
3、串口通信时序图:
四、串行传输和并行传输的区别
特性 | 串行(如 UART) | 并行 |
---|---|---|
传输速率 | 较慢(单线逐个 bit 传输) | 较快(多线同时传输) |
硬件成本 | 低,实现简单 | 高,实现复杂 |
传输距离 | 远,抗干扰性好(如 RS485 差分) | 近(通常 30 米内),抗干扰性差 |
干扰问题 | 较少 | 距离增加后易产生信号偏移和电磁干扰 |
五、串口通信时序
1、空闲状态为高电平
2、高电平变为低电平(发送低电平信号)代表起始位,准备开始通信
3、发送数据(通常 8bit,遵循 LSB 低位先行原则)
4、发送 1bit 校验位(奇偶校验)
5、发送 1bit 停止位,代表本次通信结束
六、奇偶校验
1、局限性:
无法检测偶数个 bit 出错
2、奇校验:
校验位为 '1',数据位中 '1' 的个数加上校验位 '1' 的总数为奇数时,校验通过
3、偶校验:
校验位为 '0',数据位中 '1' 的个数加上校验位 '0' 的总数为偶数时,校验通过
七、串口通信参数
1、波特率:
bps(bit per second),每秒传输的 bit 数量
常见值:2400、4800、9600、115200
2、数据位:
通常为 8bit
3、停止位:
通常为 1bit
4、校验位:
None(N):无校验
Even(E):偶校验
Odd(O):奇校验
典型参数组合:
9600, 8, N, 1
2400, 8, E, 1
115200, 8, O, 1
八、同步通信与异步通信
通信方式 | 时钟线 | 同步方式 |
---|---|---|
I2C | 有(SCL) | 同步 |
SPI | 有(SCLK) | 同步 |
UART | 无 | 异步 |
1、同步通信:
双方通过共享时钟线约定通信频率,同步发送 / 接收数据
2、异步通信:
无共享时钟线,通过设置相同波特率实现同步(如 UART 发送和接收端均设置为 2400bps)
九、串口寄存器配置
串口的波特率发生器依赖于定时器 1(Timer1)
1、SCON/PCON 串口控制寄存器:
PCON 寄存器 bit6 置 0:通过 SCON 寄存器中 SM0 和 SM1 指定串口工作方式
SCON 寄存器 bit6 和 bit7 清 0
SCON 寄存器 SM1(bit6)置 1,SM0(bit7)清 0:串口工作在 8 位 UART 模式
SCON 寄存器 REN(bit4)置 1:允许串口接收数据
SCON 寄存器 TI(bit1):串口 8 位数据发送完毕后硬件自动置 1,需软件清 0(查询用)
SCON 寄存器 RI(bit0):串口 8 位数据接收完毕后硬件自动置 1,需软件清 0(查询用)
PCON 寄存器 SMOD(bit7)置 1:波特率加倍
2、定时器寄存器配置:
TMOD(定时器模式选择寄存器):
高四位清 0(针对定时器 1)
bit5 置 1,bit4 清 0:定时器 1 工作在 8 位自动重装载模式
写入定时器初值到 TL1 和 TH1
TCON 寄存器 bit6 置 1:允许定时器 1 开始计数
3、中断寄存器配置:
IE(中断控制寄存器):
bit7(EA)置 1:CPU 允许响应所有中断
bit3(ET1)置 1:允许定时器 1 产生中断
十、练习
题目:
主机发送指令,从机解析主机发送的指令并获得功能码,根据功能码完成对外设的控制,并回复应答给主机 (功能码01:LED控制 功能码02:数码管控制 功能码03:蜂鸣器控制)
注意点:
1、发送缓冲区模式
HEX 模式:以十六进制数值(0 - 9, A - F)显示原始字节(Byte)。比如字符 A ,其 ASCII 码是 0x41 ,在 HEX 模式下就显示为 41
文本模式:将字节直接转换为 ASCII 字符(可打印字符优先)来显示数据。比如字节 ,在文本模式下会显示为字符A.
2、数码管的显示—视觉暂留
数码管一次只能显示一位数字,若要显示多位数字,就需要利用循环不断刷新来实现。具体来说,就是通过循环依次选中不同的数码管位,然后在该位上显示对应的数字,并且每次显示后稍作延迟,由于人眼存在视觉暂留效应,就会感觉所有数字是同时显示的。
具体实际应用,结合digital.c的内容和main.c的调用去理解
Digiter_show
函数通过while
循环对输入的数字n
进行处理:每次取n
的个位数字m
,先清空显示(P0 = 0
),然后通过select_bit(t++)
选中当前要显示的位,再用select_seg(m)
在该位上显示数字m
,之后延迟一段时间(delay(100)
),最后将n
除以 10 去掉已经处理过的个位。这样循环往复,直到n
变为 0,从而实现了多位数字的动态显示效果,通过不断刷新各个位来让肉眼看起来是同时显示多位数字。
3、类型转换
- 网络传输的字节本就是 0~255 的无符号值
- 但 C 语言的
char
可能会把大于 127 的值当成负数 - 强制转换确保了我们用 "无符号视角" 去解读这些字节,和十六进制常量的比较才会正确
例0xBB
这个值:
- 作为有符号 char 是 - 69
- 作为无符号 char 是 187
- 只有用无符号方式比较,才能正确等于十六进制的
0xBB
(187)
代码中的recv_buffer,会将接收到的16进制强制转换成10进制
main.c
#include <reg51.h>
#include <stdio.h>
#include <string.h>
#include "uart.h"
#include "delay.h"
#include "led.h"
#include "digital.h"
#include "time.h"#define DEV_ADDRESS 0x01#define HZ200 63035 //0x01
#define HZ400 64285 //0x02
#define HZ600 64702 //0x03
#define HZ800 64910 //0x04
#define HZ1000 65035 //0x05//解析主机发过来的指令,并返回一个功能码
int parse(void)
{int ret = 0;unsigned char sum = 0;int i = 0;if ((unsigned char)recv_buffer[0] == 0xAA && (unsigned char)recv_buffer[6] == 0xBB){if ((unsigned char)recv_buffer[1] == 0x01){for (i = 0; i < 5 ; i++){sum += (unsigned char)recv_buffer[i];}if (sum == (unsigned char)recv_buffer[5]){ret = recv_buffer[2];}}}return ret;
}//根据功能码执行主机下发的控制指令
void do_handler(unsigned int n)
{int i = 0;switch (n){case 1:led_show(recv_buffer[4]);break;case 2:while(1){Digiter_show(recv_buffer[4]); }break;case 3:switch (recv_buffer[4]){case 1:h_z = HZ200;break;case 2:h_z = HZ400;break;case 3:h_z = HZ600;break;case 4:h_z = HZ800;break;case 5:h_z = HZ1000;break;default:break;}Timer0_Init();break;default:break;}
}//从机实现对应功能 并给主机答复
//1.将主机发的内容拷贝到要发送给的数组中,并将功能码改为0x81
//2.判断起始和停止位
//3.判断地址码是否相等,是->将前五位求和->赋值给校验码位
//4.利用数组法发送至主机
void callback(void)
{xdata char send_buffer[10];unsigned char sum = 0;int i = 0;memcpy(send_buffer,recv_buffer,7);send_buffer[2] |= (1 << 7); //将功能码改成0x81if((unsigned char)send_buffer[0] == 0xAA && (unsigned char)send_buffer[6] == 0xBB){if ((unsigned char)send_buffer == DEV_ADDRESS){for (i = 0; i < 5; i++){sum += send_buffer[i];}send_buffer[5] = sum;}} Uart_SendBuffer(send_buffer,7);
}int main(void)
{int ret = 0;Uart_Init();Led_Init();while (1){if (pos != 0){delay(0xAFF);ret = parse();if ( ret != 0){do_handler(ret); }if (ret != 0){callback();}pos = 0;}}return 0;
}
uart.c
#include <reg51.h>xdata char recv_buffer[32];
unsigned int pos = 0;// 串口接收服务
void uart_RecvHandler(void) interrupt 4
{if ((SCON & (1 << 0)) == 1){if (pos < 32){recv_buffer[pos++] = SBUF;recv_buffer[pos] = 0;}SCON &= ~(1 << 0);}
}//串口初始化
void Uart_Init(void)
{//将scon寄存器中的bit6和bit7清0SCON &= ~(3 << 6);//串口工作模式选择:SMO:0 SM1:1 代表串口工作在8位UART模式SCON |= (1 << 6);// 允许串口接收数据SCON |= (1 << 4); // 串口波特率加倍PCON &= ~(1 << 6);PCON |= (1 << 7);// TMOD寄存器高四位清0// 定时器1工作在8位自动重装模式TMOD &= ~(0x0F << 4); TMOD |= (1 << 5); // 2 ^ 8 - 2 ^ smod * focs/32/bps/12 bps:2400TL1 = 230; TH1 = 230;// 允许定时器1开始计数TCON |= (1 << 6); // 允许CPU响应中断 + 允许串口产生中断IE |= (1 << 7) | (1 << 4);
}void Uart_SendChar(unsigned char ch)
{SBUF = ch;while ((SCON & (1 << 1)) == 0);SCON &= ~(1 << 1);}void Uart_SendStr(const char *p)
{while (*p){Uart_SendChar(*p++);}
}void Uart_SendBuffer(const char *p,int len)
{while(len--){Uart_SendChar(*p++);}
}
uart.h
#ifndef UART_H__
#define UART_H__extern void Uart_Init(void);
extern void Uart_SendChar(unsigned char ch);
extern void Uart_SendStr(const char *p);
extern void Uart_SendBuffer(const char *p,int len);
extern xdata char recv_buffer[32];
extern unsigned int pos;#endif
led.c
#include <reg51.h>//灯 初始化
void led_init(void)
{P2 = 0xFF;
}//灯 全亮
void led_allon(void)
{P2 = 0;
}//灯 全灭
void led_alloff(void)
{P2 = 0xFF;
}//灯 翻转
void led_nor(void)
{P2 = P2 ^ 0xFF;
}//某一位bit 亮
void led_show(unsigned int n)
{P2 = ~n;}
led.h
#ifndef LED_H__
#define LED_H__extern void led_init(void);
extern void led_allon(void);
extern void led_alloff(void);
extern void led_show(unsigned int n);
extern void led_nor(void);#endif
time.c
#include <reg51.h>
#include "led.h"#define HZ200 63035
#define HZ400 64285
#define HZ600 64702
#define HZ800 64910
#define HZ1000 65035unsigned int h_z = 0;void Timer0_handler(void) interrupt 1
{TH0 = h_z >> 8;TL0 = h_z;P2 ^= (1 << 1);}void Timer0_Init(void)
{TMOD &= ~(0x0F << 0);TMOD |= (1 << 0);TH0 = h_z >> 8;TL0 = h_z;TCON |= (1 << 4);IE |= (1 << 7) | (1 << 1);}
time.h
#ifndef TIMER_H__
#define TIMER_H__extern unsigned int h_z;
extern void Timer0_Init(void);#endif
delay.c
void delay(unsigned int n)
{while(n--);
}
delay.h
#ifndef DELAY_H__
#define DELAY_H__extern void delay(unsigned int n);#endif