关于STM32 USART(串口)发送数据丢失第一帧的真实原因探索

自我介绍

为什么要写个自我介绍?俗话说,光说不练假把式,光练不说傻把式, 因为本人一直很自负,感觉什么东西都可以很快上手(当然非对口专业知识要求高的技能除外),如果我不把这自负表现出来,感觉会憋出内伤。

本人于2015年毕业于电子科技大学,开始的理想是进入一家国外的游戏公司,但理想很丰满,现实很骨感,给育碧、2k Game、Gameloft、EA都投了简历,但都被无情pass掉了(在此吐槽一下,这些公司真的是想在校园里招本科生吗?明明是研究生的计算机图形学却成了这些公司对本科生的要求)。后来毕业了也没找到工作,为了自力更生,就随便去了家电气公司,也就是我现在的公司。

这是家才创建的公司,业务是做智能应急疏散照明系统这一块,虽然老总也给我说以后要搞物联网,但我感觉这是当初忽悠我来公司的。公司对软件开发的认识基本停留在零,也不愿意花钱请个高级软件工程师,因此我成了公司的独苗,既要负责高级程序的开发,又要进行底层开发。虽然在学校里没用过C#,但语言都有相通之处,因此对于现在公司的上位机程序的开发还是游刃有余,但底层开发的经验基本没用。虽然之前在一家公司的试用期中,我开始接触了单片机,但因为我本人不喜欢纯拷贝别人的代码,所以当公司要我给51单片机写个串口通信的时候,浏览了下STC的数据手册就模棱两可的进行开发,然后卡住了,对比了网上的那些程序,感觉自己的步骤没有错啊,这次事件让自诩学习能力强的我备受打击,于是从那家公司辞职了,现在回想起来,可能当时我没注意到51单片机的串口的波特率发生器只能使用T1,而我一直使用T0作为波特率发生器。

最近开始接触STM32系列芯片,因为有固件库对底层寄存器的操作进行了封装,很接近高级编程,学起来跟用起来都很方便,而且现在很多移动芯片也是基于ARM架构的,所以学好ARM芯片也是敲开很多大型企业的敲门砖(我就想用它敲开华为的大门)。

问题描述

网上搜索“STM32 BUG”,就会搜到困扰很多STM32开发者的的两个问题,一个是关于I2C的,另一个就是关于串口发送数据丢失第一帧的问题。因为我还接触I2C,所以第一个问题是怎么回事我不知道。虽然第二个问题,网上也有问题原因的解答,而且按照这些答案中的方式来编写串口发送程序可以防止丢失第一帧数据。因为我不想照搬别人的程序,因此在机缘巧合中,让我发现了对这个问题的另一种解释,而且这种解释推翻了之前网友们提供的解释。

问题分析

以下是官方的STM32的参考手册对串口发送数据的过程描述:

  1. 通过在USART_CR1寄存器上置位UE位来激活USART
  2. 编程USART_CR1的M位来定义字长。
  3. 在USART_CR2中编程停止位的位数。
  4. 如果采用多缓冲器通信,配置USART_CR3中的DMA使能位(DMAT)。按多缓冲器通信中的描述配置DMA寄存器。
  5. 设置USART_CR1中的TE位,发送一个空闲帧作为第一次数据发送。
  6. 利用USART_BRR寄存器选择要求的波特率。
  7. 把要发送的数据写进USART_DR寄存器(此动作清除TXE位)。在只有一个缓冲器的情况下,对每个待发送的数据重复步骤7。

对于导致串口丢失数据的步骤我同意网友的观点,那就是步骤5中,在置位TE的时候,发送了一个空闲帧作为第一次数据发送。但对于为何发送空闲帧会导致用户数据丢失第一帧的原因,我和网友的看法不一致。

网友的观点可以概括为两个字,覆盖。这是网友的普遍看法,http://blog.csdn.net/kevinhg/article/details/40991655,看到一些网友引用了这篇博客,而且很多网友的观点基本跟这篇博客中的一致。从这篇博客中可以看出,网友觉得是在发送第一个数据的时候,没有读USART_SR,所以TC仍然处于置位,在写入第二个数据的时候,就覆盖掉了第一个数据。

于是我就按照这篇博客中的解释,写了个发送程序,关键代码如下:

	/* 发送数据 */
		if(USART_GetFlagStatus(USART3,USART_FLAG_TC)!=RESET)
		{
			if(TxCount3<buf3_len)
			{
				USART_SendData(USART3,rx3_buf[TxCount3]);
				TxCount1=USART_GetFlagStatus(USART3,USART_FLAG_TC);
				TxCount3++;
			}
			else
			{
				USART_ITConfig(USART3,USART_IT_TXE,DISABLE);		//发送完成,关闭TXE,避免TDR空中断
			}
		}

if语句中我读取USART_SR的状态,然后发送第一个数据,根据官方参考手册的说明,此时TC被清零,那么就不存在第二个数据覆盖第一个数据的情况发生,但实际上,第一帧数据仍然丢失了。网友可能会说,按照这种方式,向USART_DR中写入第一个数据并没有让TC清零,那么以下是我修改了的程序,主要监视向USART_DR中写入第一个数据后,TC是否被清理:

		if(USART_GetFlagStatus(USART3,USART_FLAG_TC)!=RESET)
		{
			if(TxCount3<buf3_len)
			{
				USART_SendData(USART3,rx3_buf[TxCount3]);
				TxCount1=USART_GetFlagStatus(USART3,USART_FLAG_TC);
				TxCount3++;
			}
			else<span style="color:#ff6666;">
</span>			{
				USART_ITConfig(USART3,USART_IT_TXE,DISABLE);		//发送完成,关闭TXE,避免TDR空中断
			}
		}

调试截图如下:

从监视窗口中可以看到,TxCount3=0,表示我发送的是第一个数据,而向USART_DR中写入了第一个数据后,TxCount1=0,说明TC被清零,跟官方参考手册说法一致,那么就不应该出现第二个数据覆盖第一个数据的啊。

因为缺少对硬件的具体实现的了解,我的观点只是一种猜测。在置位TE后,TC与TXE都是被置位的(这解释了为何置位TE后,并使能TXE,TC会不断进入中断程序),并且发送一帧空闲帧作为第一个数据,根据官方参考手册,TC置位表示发送移位寄存器已空,可以向USART_DR中写入数据,照理说此时可以写入用户的第一个数据,但真实情况是发送移位寄存器还装有空闲帧的数据,即空闲帧并未发送完成(这解释了为何置位TE后,延时一段时间再发送数据就不会丢失第一个数据,因为这段延时期间空闲帧发送完成了),在空闲帧还没发送完成的时候,推测官方提供了对移位寄存器的硬件保护,即即使TC置位,此时USART_DR中的数据也不会装入发送移位寄存器,而是被忽略掉了。推测发送移位寄存器的硬件保护条件:移位寄存器为发送完成,TC已经被置位,虽然想写个程序验证下,但TC只能由硬件置位。

第一个数据被写进了TDR中:

	/* 发送数据 */
		if(USART_GetFlagStatus(USART3,USART_FLAG_TXE)!=RESET)
		{
			if(TxCount3<buf3_len)
			{
				USART_SendData(USART3,rx3_buf[TxCount3]);
				TxCount1=USART_GetFlagStatus(USART3,USART_FLAG_TXE);
				TxCount3++;
			}
			else
			{
				USART_ITConfig(USART3,USART_IT_TXE,DISABLE);		//发送完成,关闭TXE,避免TDR空中断
			}
		}

进入了if 的内部,说明之前TXE被置位,而写入第一个数据后,TxCount1=0,即TXE被清零,说明第一个数据写入了TDR中。

因此在之前推测基础上进一步补充串口的细节。当发送移位寄存器发送完成后,会先读取TC的状态,若TC已被置位,则串口忽略TDR中的数据(这也符合官方参考文档的描述,当TC置位时,表示发送移位寄存器为空,向USART_DR中写入数据会直接放进发送移位寄存器,而存放了第一个数据的TDR被忽略掉了,这也解释了第一个数据丢失的问题),若TC未被置位,检测TXE是否置位,若TXE置位,则置位TC,若TXE复位,则从TDR中载入数据到发送移位寄存器,载入结束后,置位TXE。

所以,其实这并不是什么STM32的串口发送bug,而是STM32提供了对发送移位寄存器的非空保护(该非空并不是说非零,记录移位的计数器非零),而其执行过程都是按照了官方参考文档的描述。非要说是bug的话,就是STM32置位TE后,在发送空闲帧的过程中,不应将TC置位。

总结

当发送移位寄存器发送完成后,会先读取TC的状态,若TC已被置位,则串口忽略TDR中的数据(这也符合官方参考文档的描述,当TC置位时,表示发送移位寄存器为空,向USART_DR中写入数据会直接放进发送移位寄存器,而存放了第一个数据的TDR被忽略掉了,这也解释了第一个数据丢失的问题),若TC未被置位,检测TXE是否置位,若TXE置位,则置位TC,若TXE复位,则从TDR中载入数据到发送移位寄存器,载入结束后,置位TXE。

时间: 2024-11-02 09:49:15

关于STM32 USART(串口)发送数据丢失第一帧的真实原因探索的相关文章

STM32的串口

一:2个状态位_itstatus与_flagstatus的区别: _flagstatus:只是读状态标志,不管中断是否使能或发生.例如使用查询方式发送数据就需要读改状态位. _itstatus:和中断相关,除了读状态位外还涉及对控制寄存器的操作,使用中断方式必须使用该状态位进行中断是否发生的判断和状态位的清0. 二两个发送中断的区别:TC和TXE 串口数据发送的过程是:先写数据到DR寄存器->移位寄存器->TX管脚.当数据从DR寄存器移出到移位寄存器(即DR寄存器空)时TXE就置位,优点是能保

stm32串口发送数据复位 第一个数据丢失

http://blog.csdn.net/kevinhg/article/details/40991655 STM32串口发送必须先检测状态,否则第一个字节无法发出,发送完毕,必须检测发送状态是否完成,否则,发送不成功,使用stm32f10x调试串口通讯时,发现一个出错的现象,硬件复位重启之后,发送测试数据0x01 0x02 0x03 0x04..接收端收到的数据为:0x02 0x03 0x04,第一个数据丢失.换成发送别的数值的数据,如0x06 0x0ff,则接收到0x0ff,0x06丢失.错

STM32学习笔记——USART串口(向原子哥和火哥学习)

一.USART简介 通用同步异步收发器(USART)提供了一种灵活的方法与使用工业标准NRZ异步串行数据格式的外部设备之间进行全双工数据交换.USART利用分数波特率发生器提供宽范围的波特率选择. STM32 的串口资源相当丰富的,功能也相当强劲.STM32F103ZET6 最多可提供 5 路串口,有分数波特率发生器,支持同步单向通信和半双工单线通信,支持LIN(局部互连网),智能卡协议和IrDA(红外数据组织)SIR ENDEC规范,以及调制解调器(CTS/RTS)操作.它还允许多处理器通信.

[stm32] USART USART1收发功能工程

>_<!功能:PC端发送一个特定的字符:0x0d 0x0a,单片机则返回一句话,如图: >_<!知识: 1.复用功能I/O和调试配置(AFIO)  为了优化外设数目,可以把一些复用功能重新映射到其他引脚上.设置复用重映射和调试I/O配置寄存器(AFIO_MAPR)(参见0节)实现引脚的重新映射.这时,复用功能不再映射到它们的原始分配上. 2.嵌套向量中断控制器(NVIC) l 43 个可屏蔽中断通道(不包含16 个Cortex-M3 的中断线):  l 16 个可编程的优先等级: 

[ZigBee] 7、ZigBee之UART剖析(ONLY串口发送)

综述:USART0和USART1是串行通信接口,它们能够分别运行于异步UART模式或者同步SPI 模式.两个USART具有同样的功能,可以设置在单独的I/O 引脚. 1.UART 模式 UART 模式提供异步串行接口.在UART 模式中,接口使用2 线或者含有引脚RXD.TXD.可选RTS 和CTS 的4 线. UART 模式的操作具有下列特点: ● 8 位或者9 位负载数据● 奇校验.偶校验或者无奇偶校验● 配置起始位和停止位电平● 配置LSB 或者MSB 首先传送● 独立收发中断● 独立收发

STM32的串口采用DMA方式接收数据测试(转)

STM32的串口采用DMA方式接收数据测试 本文博客链接:http://blog.csdn.net/jdh99,作者:jdh,转载请注明.   参考链接:http://www.amobbs.com/forum.PHP?mod=viewthread&tid=5511863&highlight=dma%E6%8E%A5%E6%94%B6   环境: 主机:WINXP 开发环境:MDK4.23 MCU:STM32F103CBT6 说明: 串口可以配置成用DMA的方式接收数据,不过DMA需要定长才

stm32的串口接收字符串以十六进制数

#include "pbdata.h" uint8_t TxBuffer1[] = "USART Interrupt Example: This isUSART1 DEMO"; uint8_t RxBuffer1[],rec_f,tx_flag; volatile uint8_t TxCounter1 = 0x00; volatile uint8_t RxCounter1 = 0x00; uint32_t Rec_Len; int main(void) { u8 a

串口发送代码讲解

新建bsp_usart.c和bsp_usart.h,添加到工程,魔术棒添加头文件所在的文件夹. bsp_usart.c #include "bsp_usart.h" void USART_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; // 打开串口GPIO的时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,

AVR单片机教程——串口发送

到目前为止,我们的开发板只能处理很小量的数据:读取几个引脚电平,输出几个LED,顶多用数码管显示一个两位数字.至于输入一个指令.输出一条调试信息,甚至用scanf和printf来输入输出,在已经接触过的这些器件上是难以想象的.而本讲"串口发送"与下一讲"串口接收",将打开这一扇大门. 硬件 本讲的主题是UART(Universal Asynchronous Receiver-Transmitter,通用异步收发器),俗称串口.实际上串口是串行接口的统称,在单片机领域