STM32 USART串口通信
1、原理
1、USART串口协议
同步:有单独的时钟线,接收方可以在时钟信号的指引下进行采样
异步:双方需要约定一个采样频率,并添加一些帧头帧尾等进行采样位置的对齐
单端:引脚的高低电平都是相对于GND的电压差,需要接GND引脚
差分:抗干扰,靠两个差分引脚的电压差来传输信号,可以不需要GND
点对点:直接传输数据
多设备:需要寻址,来确定通信对象
串口通信TX、RX、GND必须要接
如果设备1、设备2都有独立供电,VCC可以不接
如果其中一个设备没有供电,如设备1为STM32,设备2为蓝牙串口模块,就需要将它们的VCC接在一起,STM32通过这根线向右边的子模块供电
波特率:异步通信通信速率,每秒传输码元的个数、bit/s。高电平表示1,低电平表示0。决定了每个多久发送一位
起始位:串口的空闲状态是高电平,起始位为低电平,通知接收设备这一帧数据要开始了
停止位:用于数据帧间隔,固定为高电平
2、USART串口外设
同步模式多了一个时钟输出,不支持时钟输入,并不支持两个USART之间的通信
波特率发生器:用来配置波特率,相当于分频器,最常用(9600,115200)。
数据位长度:8、9位
停止位长度:在进行连续发送时,帧的间隔
硬件流控制:防止接收方处理慢而导致数据丢失的问题
USART资源:USART1是APB2总线上的设备,USART2、3都是APB1总线上的设备
TDR和RDR占用同一个地址,在程序上表示为一个寄存器,数据寄存器DR,在实际的硬件中,是两个寄存器,一个用于发送,一个用于接收,TDR只写,RDR只读。写操作,数据写道TDR;读操作,数据从RDR中读出
发送端:把一个字节的数据一位一位地向右移出去(低位先行),对应串口协议的数据位波形。 当硬件检测到写入数据,会检查当前移位寄存器是否有数据正在移位,如果没有该数据会立刻全部被发送到发送移位寄存器,准备发送。当数据从TDR发送到移位寄存器时,会置标志位TEX(TX Empty),TDR为空,TEX置1,就可以在TDR里写入下一个数据了,此时发送移位寄存器中的数据还没有发送出去。当数据移位完成后,新的数据就会再次自动地从TDR转移到发送移位寄存器里。可以保证连续发送数据时,数据帧之间不会有空闲。
接收端:从高位到低位方向移动,一个字节移位完成之后,这一个字节的数据就会整体移到接收数据寄存器RDR,转移过程中会置标志位RXNE(RX Not Empty),接收数据寄存器非空。RXNE置1时,就可以把数据读走了
硬件流控制:避免发送设备发的太快,接收设备来不及处理,导致丢弃或覆盖
nRTS:接对方的CTS,能接受的时候RTS置低电平,请求对方发送,对方的CTS接收到之后,就可以发送数据;当数据处理不过来,如接收数据寄存器一直没有读,又有数据过来了,RTS置高电平,对方CTS接收到之后,暂停发送数据,直到RTS置低电平。
nCTS:清除发送,用于接收其他设备的nRTS。
SCLK:用于产生同步的时钟信号,配合发送移位寄存器输出,发送寄存器移位一次,同步时钟电平就跳变一个周期,时钟告诉对方,移出去一位数据。只支持输出,不支持输入,两个USART之间不能实现同步的串口通信。用于兼容别的协议,如SPI,和自适应波特率,如接收设备不确定发送设备给的是什么波特率,可以测量时钟周期,计算得出波特率。
唤醒单元:实现串口挂载多设备,一条总线上接多个从设备,每个设备分配一个地址,想跟某个设备通信,就先进行寻址,确定通讯对象。如给设备分配一个地址,当发送指定地址时,此设备唤醒开始工作;没收到地址就保持沉默。
状态寄存器:TEX和RXNE是判断发送状态和接收状态的必要标志位。
USART中断控制:配置中断是否能通向NVIC。
波特率发生器:相当于分频器,对APB时钟进行分频,得到发送和接收移位的时钟。USART1挂载在APB2,PCLK2的时钟,72MHZ;其余USART挂载在APB1,PCLK1的时钟,36MHZ。之后这个时钟会进行分频,除USARTDIV的分频系数,分频完之后再除16。TE为1,发送器使能,RE为1接收器使能。
3、基本结构
4、数据帧
输出定时翻转TX引脚高低电平;输入要保证采样频率和波特率一致,还要保证每次输入采样的位置正好处于每一位的正中间,这样高低电平读进来才最可靠。
空闲帧、断开帧用于局域网协议
数据长度:8位(有、无校验)、9位(有、无校验),最好选择9位字长有校验,8位字长无校验。串口传输的数据类型一般为uint8_t。
一个停止位和一个数据位时长一样,一般使用一个停止位
5、输入电路噪声处理
以波特率16倍频率采样,一位的时间进行16次采样。
为了避免噪声影响,接收电路在第一次遇到下降沿之后,第3、5、7次进行采样,第8、9、10次进行采样,这两批采样都至少有2个0。如果只用2个0,会在状态寄存器里置一个NE(Noise Error),噪声标志位。如果少于2个0,电路忽略前面的数据,重新开始捕捉下降沿。
6、数据采样
三次采样中,两次及以上为1,就认为收到了1;两次及以上为0,就认为收到了0。两次时,噪声标志位NE也会置1。
7、波特率发生器
DIV_Mantissa:DIV整数部分(二进制)
DIV_Fraction:DIV小数部分(二进制)
因为它内部还有一个16倍波特率的采样时钟,所以要多除16。
8、数据模式
可以以16进制数和字符的方式进行发送。
数据在线路中传输得形式是16进制数。
2、代码
1、初始化
1、初始化时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
2、初始化GPIO引脚
串口空闲状态是高电平,不使用下拉输入
GPIO输出模式使用复用推挽输出,因为在gpio的电路中,是由输出数据寄存器控制,外设无法干预,但使用复用推挽输出后,输出数据寄存器会被断开,由外设控制输出控制
GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);
3、初始化USART
USART_InitTypeDef USART_InitStruct;USART_InitStruct.USART_BaudRate=9600;USART_InitStruct.USART_HardwareFlowControl=USART_HardwareFlowControl_None;//想同时接收和发送可以使用按位或USART_InitStruct.USART_Mode=USART_Mode_Tx;USART_InitStruct.USART_Parity=USART_Parity_No;USART_InitStruct.USART_StopBits=USART_StopBits_1;USART_InitStruct.USART_WordLength=USART_WordLength_8b;USART_Init(USART1, &USART_InitStruct);USART_Cmd(USART1, ENABLE);
2、发送数据
1、发送一位数据
//串口一次移动8位数据
void Serial_SendData(uint8_t Byte)
{//数据写入发送数据寄存器TDRUSART_SendData(USART1, Byte);//等待TDR中的数据转移到移位寄存器while(USART_GetFlagStatus(USART1, USART_FLAG_TXE)==RESET);
}
2、发送一个数组
void Serial_SendArray(uint8_t *Array, uint16_t Lenth)
{uint16_t i=0;for(i=0;i<Lenth;i++){Serial_SendData(Array[i]);}
}
3、发送一个字符串
void Serial_SendString(char* string)
{uint16_t i;for(i=0;string[i]!='\0';i++){Serial_SendData(string[i]);}
}
4、发送一个数字
一位一位地发送
uint32_t Serial_pow(uint32_t Num, uint16_t t)
{uint32_t result=1;while(t--){result*=Num;}return result;
}
void Serial_SendNum(uint32_t Num, uint8_t Lenth)
{uint8_t i;for(i=0;i<Lenth;i++){//Lenth-i-1 表示第一位数Serial_SendData(Num/Serial_pow(10,Lenth-i-1)%10+'0');}
5、重定向printf到串口
在工程设置->target里把Use MicroLib勾选上
#include <stdio.h>
//fputc是printf的底层,把fput重定向到串口,printf就重定向到串口
int fputc(int ch, FILE *f)
{Serial_SendData(ch);return ch;
}
3、接收数据
1、初始化RX
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);USART_InitStruct.USART_Mode=USART_Mode_Rx|USART_Mode_Tx;
2、串口接收
1、查询,在主函数里不断判断RXNE标志位
while(1){if(USART_GetFlagStatus(USART1, USART_FLAG_RXNE)){RxData=USART_ReceiveData(USART1);}OLED_ShowHexNum(1,1,RxData,4);}
2、中断
开启RXNE标志位到NVIC的输出,RXNE一旦置1,就会向NVIC申请中断
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//分配抢占优先级和响应优先级个数NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);NVIC_InitTypeDef NVIC_InitStructure;//指定中断通道NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//指定该通道是否为抢占优先级或响应优先级NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;);
//暂存数据,标志位
uint8_t RxData;
uint8_t RxFlag;uint8_t Serial_GetRxData()
{return RxData;
}
//每次获取标志位后清零
uint8_t Serial_GetRxFlag()
{if(RxFlag==1){RxFlag=0;return 1;}else{return 0;}
}
//收到USART的中断后,将标志位暂存
void USART1_IRQHandler()
{RxData = USART_ReceiveData(USART1);RxFlag = 1;USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
4、收发一个数据包
1、HEX数据包
传输直接,解析数据简单,适合模块发送原始的数据如陀螺仪、温湿度传感器
2、文本数据包
数据直观易理解,灵活,适合输入指令进行人机交互的场合,如蓝牙模块AT指令
3、HEX数据包收发
//只存载荷数据
uint8_t TxPacket[4];
uint8_t RxPacket[4];
uint8_t RxFlag;
中断函数内,用状态机表示状态
void USART1_IRQHandler()
{static uint8_t RxState=0;static uint8_t Rxnum=0;//每次从串口中接收一个字节uint8_t RxData = USART_ReceiveData(USART1);switch(RxState){case 0: //收到包头,进入转移状态if(RxData==0xFF){RxState=1;}break;case 1:RxPacket[Rxnum]=RxData;Rxnum++;if(Rxnum>=4){Rxnum=0;RxState=2;}break;case 2:if(RxData==0xFE){RxState=0;//全部接收到,置接收标志位RxFlag=1;}break;default:break;}USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
4、文本数据包接收
void USART1_IRQHandler()
{static uint8_t RxState=0;static uint8_t Rxnum=0;//每次从串口中接收一个字节uint8_t RxData = USART_ReceiveData(USART1);switch(RxState){case 0: //收到包头,进入转移状态//同时避免发送太快,第一组数据还没处理完就来第二组的情况if(RxData=='@' && RxFlag==0){RxState=1;Rxnum=0;}break;case 1://载荷字符数量不确定,先判断是不是包尾if(RxData=='\r'){RxState=2;}else{RxPacket[Rxnum]=RxData;Rxnum++;}break;case 2://等待第二个包尾if(RxData=='\n'){RxState=0;//全部接收到,置接收标志位RxFlag=1;//字符数组最后,添加字符串结束标志位RxPacket[Rxnum]='\0';}break;default:break;}USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
while(1){if(Serial_GetRxFlag()==1){OLED_ShowString(4,1," ");OLED_ShowString(4,1,RxPacket);//判断传入文本是否和目标命令匹配if(strcmp(RxPacket, "LED_ON")==0){LED1_ON();Serial_SendString("LED1_ON_OK\r\n");OLED_ShowString(2,1," ");OLED_ShowString(2,1,"LED1_ON_OK\r\n");}else if(strcmp(RxPacket, "LED_OFF")==0){LED1_OFF();Serial_SendString("LED1_ON_OFF\r\n");OLED_ShowString(2,1," ");OLED_ShowString(2,1,"LED1_ON_OFF\r\n");}else{Serial_SendString("CommandError\r\n");OLED_ShowString(2,1," ");OLED_ShowString(2,1,"CommandError\r\n");}}}