从底层理解stm32USART串口通信
以前学串口通信踩过很多坑,过了一段时间又有些忘了,现在问了几个很强很强的人差不多弄懂了,现在写一写总结,免得以后又忘了。
基本知识:
1、TDR和RDR都是USART_DR寄存器的缓冲区,指的是USART_DR的0到8位,TDR和RDR共用一片物理空间。
2、
通过向数据寄存器写入数据来将 TXE 位清零。
通过软件对 USART_DR 寄存器执行读操作将 RXNE 位清零.
3、TXEIE和TCIE的意义是,TXEIE允许在TXE标志为1时产生中断,而TCIE允许在TC标志为1时产生中断,RXNEIE同理。
4、
5、
6、
有三个主要的寄存器USART_SR,USART_DR,USART_CR1 。USART_SR的S代表STATUS,这个寄存器用来记录串口是否收到了数据。含有RXNE,,TC,TXNE。USART_CR1 寄存器中含有TCIE ,TXEIE,RXNEIE等串口中断使能位。
USART_DR是用来储存数据的寄存器,它的0到8位是用来储存数据的,叫做缓冲区,也称为RDR,TDR,在接受的时候成为RDR,在发送的时候称为TDR,其实是一个东西,这也是为什么TDR与RDR共用同一片物理空间的原因。其余位不用考虑。同时在程序里可以设置,传输的时候是8位还是9位,个人建议传输都用8位,因为现在编码大多采用以字节为单位的方式,在用9位的情况下,如果发生上溢错误,就会改变bit的解码顺序,造成后面传来的数据解码混乱。而如果用8位,丢掉一个字节,后面的字节还是可以正常解码出来。
7、
8、不论是发送数据还是接受数据,一个串口只有一个中断函数。
接受数据的过程:
RX设置为floating浮空状态,适于被动地在高电平低电平之间的快速转换,它的状态是由发送端传来的电平高低来决定的。
数据通过串口线一位一位传过来,先传到移位寄存器,“移位”就是形容一位一位bit传过来的过程,一个数据帧有起始位,数据位,校验位,停止位。当移位寄存器识别了这个数据帧之后,通过“并行通信”的方式“一次性”传递给RDR寄存器,就是把八个数据位一次性传给RDR寄存器,我们从内部总线向RDR寄存器写入数据的时候也是并行通信。这也就是为什么在参考手册中讲(第三张图片),RDR寄存器在移位寄存器和内部总线之间提供了并行接口。
为什么数据可以有秩序地传输呢?因为移位寄存器通过起始位,校验位,停止位来“辨识”这个数据帧,一个没有起始位,校验位,停止位(根据通信协议的规定而变化)的裸露数据是不可能由移位寄存器传给RDR寄存器的,这也就是为什么当一个数据发生错误的时候,后面的数据继续传输不会受到前面的影响。(我之前在考虑前一个数据的后半部分与后一个数据的前半部分进行组合形成错误数据帧的情况,我想多了。)
当移位寄存器中的数据位传到RDR中时,RXNE会硬件置为1,这个所谓的硬件置1就是靠单片机“自动”完成的,就相当于提醒我们缓冲区有数据了,我们要去读了。这个时候如果USART_CR1的RXNEIE接受中断使能位开启了,也就是手册中的软件置1,而所谓的软件置1就是指人为去设置,那么就会进入串口中断。我们在串口中断中去读取RDR缓冲区里面的数据内容,而一旦读取了RDR中的内容,RXNE标志位会硬件置0。下次还有数据传进来的时候,RXNE又会置1。如此循环往复。
如果串口中断的时间太久了,或者说迟迟没有读取RDR寄存器的内容,那么就会发生上溢错误。上溢错误在第3张图中有简介,这个时候移位寄存器不会把数据位通过并行通信传给RDR,而是被后面传入的数据所覆盖,这里的覆盖是一位一位的,有人可能会问,如果数据位有一半被后面传来的数据覆盖了,同时这个时候软件进行了读取,RXNE置为0,那么这一半的数据位还会进入RDR吗?我认为不会出现这样的,如果整个数据帧有一位被上溢错误给破坏,那么整个数据帧都会被丢弃,从下一个完整地数据帧开始传送。理由是这样不会干扰到后面的数据传输。
发送数据的过程:
TX引脚设置为推挽输出模式。发送数据类似于我们从RDR寄存器读取数据,我们发数据就是往TDR寄存器中写数据(这个赋值语句是通过并行通信实现的),RDR和TDR寄存器表示的物理空间是一样的。然后TDR寄存器会通过并行通信把数据传给移位寄存器。这个时候TXE会置1,再次向TDR写入数据会使TXE置0,TDR进入移位寄存器之后,会一位一位发送,如果移位寄存器把数据帧发送完了,同时TDR为空,即TXE仍为1(意味着不仅移位寄存器没事干了,DR寄存器也没事干了,全弄完了),那么TC标志位置1。
具体的过程是:所有位发送结束时(送出停止位后)硬件会设置TC标志,当移位寄存器把数据帧的停止位送出之后,去检测TXE是否为1,如果TXE为1,则TC标志位置1,产生中断
TXE--写寄存器DR清零,(完全由硬件控制,有东西就是0,没东西就是1,无法软件清零)
TC-- 第一种方法,读SR寄存器后写寄存器DR清零(好麻烦有木有),第二种方法,也可软件手动清零,直接手动暴力清零。
1、只利用TC中断来发送信息
void USART_Config()//开启中断,开启串口
{
USART_ITConfig(USART1, USART_IT_TC, ENABLE);//Tramsimssion Complete后,才产生中断. 开TC中断必须放在这里,否则还是会丢失第一字节
USART_Cmd(USART1, ENABLE); //使能USART1
}
void USART_SendDataString( u8 *pData )//发送信息的函数。
{
pDataByte = pData;
USART_ClearFlag(USART1, USART_FLAG_TC);//清除传输完成标志位,否则可能会丢失第1个字节的数据.网友提供.//在发送之前先把标志位清空一下,保险一点,没有太大含义
USART_SendData(USART1, *(pDataByte++) ); //必须要++,不然会把第一个字符t发送两次。附加注释:这句话作为一个引线来触发这个串口中断。
}
void USART1_IRQHandler(void)
{
if( USART_GetITStatus(USART1, USART_IT_TC) == SET )//读SR寄存器
{
if( *pDataByte == '\0' )
USART_ClearFlag(USART1, USART_FLAG_TC);发完了 把标志位手动清零,因为之后不会再向DR寄存器里面写内容了,无法通过第一种方式将TC标志位清零。
else
USART_SendData(USART1, *pDataByte++ );//向DR寄存器写内容,同时
TC标志位会清零。
}
}
extern u8 *pDataByte;在主函数中写好这个指针变量,在USART.C文件中作为外部变量引进来。
C语言小提醒: *p = *(p+1); 而且是后加加。
2、只利用TXE中断来发送信息
void USART_SendDataString( u8 *pData )
{
pDataByte = pData;
USART_ITConfig(USART1, USART_IT_TXE, ENABLE);//只要发送寄存器为空,就会一直有中断,因此,要是不发送数据时,把发送中断关闭,只在开始发送时,才打开。
}
void USART1_IRQHandler(void)
{
if( USART_GetITStatus(USART1, USART_IT_TXE) == SET )
{
if( *pDataByte == '\0' )//待发送的字节发到末尾NULL了
USART_ITConfig(USART1, USART_IT_TXE, DISABLE);//因为是 发送寄存器空 的中断,所以发完字符串后必须关掉,否则只要空了,就会进中断
else
USART_SendData(USART1, *pDataByte++ );
}
}
个人感觉第二种发送方式好一点点。通常的协议一般是8位发送,一位停止位,无数据流控制,无校验位。
代码参考自:https://blog.csdn.net/zyboy2000/article/details/7566647