这篇文章注重思想的讲解,理解下来肯定对uart协议有一个更深的认识。
uart协议,通常用在嵌入式设备之间的通信。像下面这样:
问题一:uart是全双工还是半双工?
你完全可以将两个设备想象成两个人,上图中的两条线想象成A和B的对话。A对B说话和B对A说话的一个场景。问大家一个问题,你对A说话的同时能不能听到A跟你说话?当然可以嘛。所以你在说话的同时还能接收到别人说的话,我们可以推出uart协议当然也是全双工协议,因为设备A在发送消息的同时还能接收设备B的消息。大家也可以简单记一下,如果某个通信协议使用两根线来作为数据线,那这个协议多半就是全双工协议。
问题二:波特率是个啥东西?
这样说吧,人说话的正常频率是500~3000Hz(百度来的),你可以将波特率比做这个频率。
当你以正常说话频率跟A(是个正常人)说话的时候A能听到吗?当然能。
那如果一只蝙蝠跟A(是个正常人)说话A听得到吗?当然不能。
我又问,如果设备A以一个正常波特率跟设备B通信设备B能听到吗?如果设备B设置的波特率跟设备A设置的波特率几乎相等还是可以听到的,注意这两个波特率大小可以有差值。但是差值大了设备B肯定会听错设备A发送过来的信息,就好比一个人跟你说话,他用的说话频率比常人高,但是你还是可以将就听到,但是有很大可能你会听错。所以我们可以得出,设备A和设备B设置的波特率大小应该相等,当然,如果大小相差非常小一般也没问题,比如在某些情况下相差1到小几十也是问题不大的。好了,说回波特率,波特率为每一秒传输多少个bit。如果波特率为9600,那就是1s传输9600bit的数据,我们可以求出1/9600就为传输一个bit数据需要花费的时间,也就是104us左右。这个数据下面编程要使用
问题三:uart是同步还是异步通信:
我直接说是异步通信吧,原因很简单,设备A和设备B在通信的时候他们之间没有时钟线,所以他们没有公共的时钟,没有公共的时钟线,他们就只能使用不同的时钟源了,所以他们是异步通信。
所以记住:一般来说一个通信协议如果有一根是时钟线,那么他们多半是同步通信。
废话少说编程了:
先来看时序:注意这里每一个数据的发送周期都是104us,对应波特率9600
这是我画的一个最常用的时序图,其实还有校验位我没有画出来,因为一般平时不会用到。
上面的时序就为一包数据,也可以理解成是设备A对设备B通信的最小单位,一共有10位也就是10个bit。
直接上代码了:
1.发送数据到其他设备:
void uart_send_a_pack(uint8_t data){
uint8_t i = 0;
//开始
set_high();//拉高uart_tx数据线,根据自己板子的引脚图来找到uart_tx数据线所对应的管脚
delay_us(104);// 1/9066约等于104us
set_low(); //拉低uart_tx,因为时序图写了拉低就开始传输
delay_us(104);
for(i = 0; i < 8; i++){
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_9, (0x01 & (data >> i)));
delay_us(104);
}
//拉高停止
set_high();
delay_us(104);//一包数据发送结束
}
大家注意上面代码中每次对数据线的操作周期都是104us。
2.接收其他设备的数据:
下面代码中read()其实就是返回uart_rx数据线的电平状态
uint8_t uart_read_a_pack(){
while(read()){}//如果检测到起始位(0)就跳出循环。没有检测到起始位就一直循环
uint8_t i = 0;
uint8_t data = 0;
//开始采集数据
for(i = 0; i < 8; i++){
delay_us(104);
data += (read() << i);
}
delay_us(104);
//读停止位
if(read()){
return data; //如果读到停止位那就返回data
}else{
return 0;//如果没有读到停止位就返回0,这里处理得不够好,大家可以自行提升一下
}
}
上面我们已经实现了可以发送一个字节给其他设备
这下我们可以直接用自己的函数来做重定向:
int fputc(int ch, FILE* f){
uart_send_a_pack(ch);
return ch;
}
之后我们可以愉快的用printf函数通过uart发送消息到其他设备了。
当然你也可以选择可以封装发送字符串的函数,比如这样:
void uart_send_string(uint8_t* stringData){
for(uint32_t i = 0; i < strlen((char*)stringData); i++){
uart_send_a_pack(stringData[i]);
}
}
封装自己的接收uart消息的函数,如下:
void uart_read_string(uint8_t* data, uint8_t len){
for(uint8_t i = 0; i < len; i++){
data[i] = uart_read_a_pack();
}
}