这几天看完了正点原子STM32的串口通讯部分的内容,总感觉很多东西似是而非,前后花了好几天研究了下,这篇博客很多内容是从其他博客上整理来的,并非完全原创,由于前后查了几天好多篇博客,摘抄的谁的也不好找了,看到的可以提醒一下,只希望自己整理的内容能帮到其他的初学者。
经提醒第二部分来源于这篇博客 ,感兴趣的可以去原文看看。
1、实验内容梳理
首先结合串口调试助手对实验进行说明,以便后续结合代码熟悉整个流程。整个实验其实就是通过串口调试助手向单片机发送数据,然后单片机将接收到的数据返回给上位机并加以显示。
简单来串口调试助手说其实就是用于上位机和下位机通信用的一个桥梁软件,功能主要有两个这也是本实验的两个步骤:
- 人工发送数据给单片机处理,即通过串口调试助手的下方窗口编辑数据,然后点击发送按钮,就能发送数据给单片机;
- 接受单片机发送的数据显示给你看,即通过串口调试助手上方窗口,将单片机发回给上位机的数据进行显示;
关于串口调试助手,还应知道:
- 发送英文字符需要用一个字符即8位,发送汉字需要两个字符即16位,如上图,发送汉字“宋”实际是发送“CB(1100 1011)CE(1100 1110)”而发送英文字符S实际是发送“53(0101 0011)”,本质上没有太大区别;
- 勾选了下方“发送新行”后,XCOM就会再你输入的需要发送的数据后自动加上一个回车(0X0D+0X0A),如果不勾选则我们在手动输入完“宋S”后还需敲一个回车键只有这样点击发送后,调试助手上方窗体才能将其显示,这是因为我们在程序的串口中断中自定义了一个数据接收协议,即只有当接受的数据以回车结尾(0X0D+0X0A),串口才认可数据接受完毕。
2、对于串口中断函数(自定义数据接收协议)的理解
正点原子的例程中通过语句USART_ITConfig(USART1, USART_IT_RXNE, ENABLE)开启相关中断,当读数据寄存器非空,即单片机一接收到数据时,便会触发串口1的中断函数。
改协议的核心是定义了一个16位的变量USART_RX_STA,该变量的0-13位用于存储接收到的数据,最后的14、15两位作用在于,当14、15位依次接收到da0x0d和0x0a时,依次将这两位置1,作为判断数据是否接收完的标志位。
以下是我看到的一个注释比较详细的代码,和一个实例,结合两者就可以很好的理解这个过程和代码,
void USART1_IRQHandler(void) //串口1中断服务程序
{
u8 Res; //定义unsigned char型字符Res
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
//接收中断(接收到的数据必须是0x0d 0x0a结尾)
//这里判断发送接收完成的依据就是串口数据0x0d 0x0a,
//0x0d是CR(carriage return)回车的意思,光标回到最左边,
//0x0a是LF(line feed)换行的意思,光标到达下一行,
//但是在PC上回车和换行是在一起的就是按下回车按键
//当然可以更改程序使用其他进行判断例如使用0x2a也就是*进行结束判断
{
Res =USART_ReceiveData(USART1);//(USART1->DR);
//读取接收到的数据,存放到变量Res中
if((USART_RX_STA&0x8000)==0)
//判断接收是否未完成
//接收完成未清除标志位,还是会不断进入到接收中断,所以使用标志进行判断,
//当接收完成便不会跳入到判断,从而不执行任何指令,空等待
//使用条件判断是否已经接收完数据,这里判断接收完的依据就是收到了0x0a;
//具体判断在后面
{
if(USART_RX_STA&0x4000)
//如果接收到了0x0d,那么再进一步执行是否接收到0x0a的判断
{
if(Res!=0x0a)USART_RX_STA=0;
//没有接收到0x0a那么说明,数据未正确传输或者接收错误,重新开始判断,
//但是这里没有将接收到的数据进行清空,也没有退出接收中断,此程序只是从头开始执行接收判断
else USART_RX_STA|=0x8000;
//接收完成了,收到了0x0a那么标志位USART_RX_STA将会变成0x8000,将不再进行数据检测与存储
}
else
//还没收到0X0D,说明数据还未发送结束继续进行数据的检测与存储
{
if(Res==0x0d)USART_RX_STA|=0x4000;
//收到了数据0x0d,标志位USART_RX_STA变成0x4000
else
{
//如果没有接收到数据0x0d,执行判断是否存储数组已满,已满则重新开始接收
USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;
//将接收到的数据写进数组,标志位USART_RX_STA与上0X3FFF清除前两位以防止标志位与8000和4000冲突
USART_RX_STA++;
//数组地址加一,向后排
if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;
//接收数据错误,超出数组大小,又开始接收向数组重新写
}
}
}
}
}
假设我们发送的数据是“abcd”经过串口调试助手加上0X0D+0X0A后发送给单片机,即单片机要接受的数据是“abcd”+“0X0D+0X0A”
(1)当接收到“a”时读寄存器非空,RXNE为1,第一次进入串口中断处理函数,我们先判断是否接是因为USART1接受到了数据产生的中断,如果是,则将USART1接受到的一位数据“a”存入变量Res里(Res = “a”)
if(USART_GetFlagStatus(USART1, USART_IT_RXNE) != RESET)
(2)然后我们来判断接收的一系列数据是否没接收完(即)。(当然没有啦,我们还有b、c、d三个数据没有接收)。这个时候呢,USART_RX_STA——这个在全部函数之间实现消息传递的变量的值仍然为0,和0x8000相与以后为0,那么执行该if语句的内层函数。
if((USART_RX_STA&0x8000)==0) //接收未完成
(3)进入该if语句的内层语句后判断语句如下,这里判断USART_RX_STA的第14位是否为1,如果我们接收到了回车,即0x0d那么USART_RX_STA的第14位会置1。在我们接收第一个数据a时USART_RX_STA当然还为0,(我们后面的的b、c、d、3个数据还都没接收了,当然不会收到0x0d)USART_RX_STA和0x400相与为0,该判断语句就为假,执行下面的else语句。
if(USART_RX_STA&0x4000)//接收到了0x0d
(4)该else语句的内层语句是一个if-else语句
if(Res==0x0d)USART_RX_STA|=0x4000;
这里我就不再赘述,我们接收的是数据a,还没有接收到0x0d,执行else语句。
USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ; // 0x3ff = 0011 1111 1111 1111
USART_RX_STA++;
if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收
USART_RX_STA的bit0~bit13代表的是接收到的有效数据个数,这里USART_RX_STA值仍为0,USART_RX_STA & 0X3FFF = 0 ,然后USART_RX_BUF[USART_RX_STA&0X3FFF]=Res,意思就是将Res里的数据存放到USART_RX_BUF[0]里了,并且USART_RX_STA自增1。
此时USART_RX_STA = 1这样在接收到下一个数据b后USART_RX_STA&0X3FFF = 1,将b存入到了USART_RX_BUF[1]里,一直循环下去,直到我们接收到了0x0d(Res = 0x0d)。
我们可以从上面程序里找到如下代码:
else //还没收到0X0D
{
if(Res==0x0d)USART_RX_STA|=0x4000; //再次判断这次接收到的是不是0x0d
当接收到0x0d并且程序执行到这一步一时USART_RX_STA = 4,此时该if语句成立,执行USART_RX_STA|=0x4000,即0000 0000 0000 0100 | 0100 0000 0000 0000 = 0100 0000 0000 0100 ,我们可以清晰的看到bit13~0位是4,代表接收到了4个数据(a,b,c,d),第14位为1,是因为接收到了数据0x0d,也和最上面给的表对上了,然后程序向下执行,接收到了0x0d(回车),那下一个就是接收0x0a(换行)了,Res = 0x0a:
if(USART_RX_STA&0x4000)//接收到了0x0d 0100 0000 0000 0000
{
if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始--接收到了0X0d但是没有接受到0x0a
else USART_RX_STA|=0x8000; //接收完成了--0x0d后面是0x0a
}
这里第一个判断语句if(USART_RX_STA&0x4000),当然为真,因为Res = 0x0a,那么执行else语句USART_RX_STA|=0x8000,即0100 0000 0000 0100 | 1000 0000 0000 0000 = 1100 0000 0000 0100。到了这一步,就说明这一串数据已经完完全全的接收完了USART_RX_STA = 1100 0000 0000 0100,最高位为1:代表接收到了0x0a,第十四位为1:代表接收到了0x0d,第0位到第13位为4,代表接收到了4位有效数据(a、b、c、d)
3、对main函数的理解
main函数如下,大致的执行流程为:
int main(void)
{
u8 t;
u8 len;
u16 times=0;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2
delay_init(168); //延时初始化
uart_init(115200); //串口初始化波特率为115200
LED_Init(); //初始化与LED连接的硬件接口
while(1)
{
if(USART_RX_STA&0x8000)
{
len=USART_RX_STA&0x3fff;//得到此次接收到的数据长度
printf("\r\n您发送的消息为:\r\n");
while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);
for(t=0;t<len;t++)
{
USART_SendData(USART1, USART_RX_BUF[t]); //向串口1发送数据
while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);//等待发送结束
}
printf("\r\n\r\n");//插入换行
USART_RX_STA=0;
}else
{
times++;
if(times%300==0)
{
printf("\r\nALIENTEK 探索者STM32F407开发板 串口实验\r\n");
printf("正点原子@ALIENTEK\r\n\r\n");
}
if(times%200==0)printf("请输入数据,以回车键结束\r\n");
if(times%30==0)LED0=!LED0;//闪烁LED,提示系统正在运行.
delay_ms(10);
}
}
}
- 完成串口的相关设置,使能相应中断,以及初始化过程,然后进入while等待;
- 当在串口调试助手的想窗体输入想要发送的数据,并点击发送后,当单片机开始接受数据时便会触发中断函数USART1 IRQHandler( )对数据进行接收并最终存储进缓存RX_BUF_USART中,其在usart.h中的生命为extern u8 USART_RX_BUF[USART_REC_LEN]。其中USART_REC_LEN=200
- 通过语if(USART RX STA&0x8000)判断数据是否完成接受存入缓存数组RX_BUF_USART中,如果完成了,则利用重定向的printf函数向上位机发送语句“\r\n您发送的消息为:\r\n” , 如下图标号1区域(此处实验现象异常,后面会有解释)然后通过for循环将缓存RX_BUF_USART中的数据依次通过USART_SendData()函数发给上位机,发给上位机的数据将在串口调试助手上显示。所有数据发送完毕后,再次通过printf函数向上位机发送"\r\n\r\n"即在串口调试助手上窗体光标在原来位置的基础上下移两行。如下图标号2区域所示。
- 当数据没有接受完,或者没有向单片机发送数据时,单片机便会通过printf函数向上位机按照一定时间间隔发送“\r\nALIENTEK 探索者STM32F407开发板 串口实验\r\n”、“正点原子@ALIENTEK\r\n\r\n”和“请输入数据,以回车键结束\r\n”语句。
注意:重定向的printf()函数本质上还是通过USART_SendData()向上位机发送数据,且此处发送的“\r\n”与中断函数里的需要作为接受完成标志位的“\r\n”(0X0D+0X0A),只是单纯的表示换行的转义字符,电脑上位机接收到后会将光标下移两行,视觉上就是空一行显示在串口调试助手上。
4、实验异常现象及解决办法
上面有提到过图片标号1的区域出现了异常实验现象,即单片机利用printf("\r\n您发送的消息为:\r\n")语句显示的结果中语句末尾的“\r\n”并没有起到作用,即让光标移至下一行后再显示“逆光”。
这种现象的原因是因为printf语句中的“\r\n”发送未完成,寄存器又被后面要发送的数据覆盖,解决方法就是在USART_SendData(USART1, USART_RX_BUF[t]);前面在多加一句 while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);//等待发送结束
for(t=0;t<len;t++)
{
while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);
USART_SendData(USART1, USART_RX_BUF[t]); //向串口1发送数据
while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);//等待发送结束
}
修改后实验现象恢复正常如下:
我尝试过在printf语句的后面利用延时的方式解决问题,但是只有第一次能够正常显示,后面再次发送,现实的数据就会存在乱码的问题。
5、USART-FLAG-TXE与USART-FLAG-TC标志位
我觉得这两个标志位的使用应当严格区分开,当个附加知识记录一下:
USART-FLAG-TXE发送缓冲区空标志:说明可以往数据寄存器写入数据了,但并不代码数据发送完成了。
USART-FLAG-TC发送完成标志:这个才是代表USART在缓冲区的数据发送完成了,即从机接收到了数据。
这两个标志的区别在于:它们分别表示数据在发送过程中,在两个不同的阶段中的完成情况.TXE表示数据被从发送缓冲区中取走,转移到的移位寄存器中,此时发送缓冲是空的,可以向其中补充新的数据了。而 TC则表示最后放入发送缓冲区的数据已经完成了从移位寄存器向发送信号线Tx上的转移。所以,判定数据最终发送完成的标志是TC,而不是IXE.