stm32串口占用时间、消耗CPU及最大中断时间的深度分析

  • Post author:
  • Post category:其他


1、stm32串口发送占用的时间

答:做嵌入式以来,一直自认为在两个MCU之间的串口通信很占用时间,让我感觉很是不爽。经过一番查找资料后发现并没有我想象的那么糟糕。

串口发送数据:

发送数据在软件层面来看是按照字节来发送的。USARTx->DR = (Data & (uint16_t)0x01FF);CPU只需要把一个字节的数据填充到DR寄存器中就可以了,然后具体的发送过程是由硬件来完成,单字节的发送过程中不消耗CPU。但是为什么我们使用串口来发送连续的数据时为什么还是感觉到这么慢,而且连续发送的过程中CPU不能去做其他任务呢?究其原因是因为我们为保证数据发送的完整性在发送过程中加入了while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET){} 这条语句来等待发送结束。让CPU白白浪费在while的死循环中,等待的时间为 1(字节)x10(位) / 使用的波特率bps = 死循环时间。如果使用115200bps波特率的话,等待时间为86us。。由此来看其实发送数据的时间几乎全部都花费在了等待上(两次发送的这段时间间隔是必须要有的,但是这段时间的CPU可以去做其他任务)。时间间隔必须要有是因为在硬件发送的过程中是需要这段时间来按位发送电平信息的。如下图所示:发送完一个字节(10位,开始位+8bit数据+结束位)产生一个TXE标志位,然后接着发送下一字节数据

串口接收数据:

串口接收数据在软件层面来看也是按照字节接收,return (uint16_t)(USARTx->DR & (uint16_t)0x01FF);返回的就是DR寄存器的数据,接收中断是在接收完1字节后来产生中断,接收在软件层面来看只是从DR寄存器取出数值这个动作而已,几乎也不占用CPU时间,具体的接收过程是由硬件来完成,在硬件层面是按照位来接收的,硬件接收过程也是需要时间的,中间传输过程占用时间与发送占用时间是一样的。

从软件层面来说,发送跟接收几乎都不占用时间,但是要给出足够的时间来让硬件完成传输。

2、最大中断时间

接收中断的最大中断时间是硬件传输一个字节所需要的时间,根据所选用的波特率不同会有所不同。一般来说中断时间越短越好,中断时间过长会造成其他的处理程序无法正常运行

3、探索一种有效的串口通信方式

个人感触:

感觉在发送的时候CPU一直死循环,在高频率的多串口数据的交互的系统里面很浪费CPU,个人一直很想把这部分时间利用起来,想在发送时把要发送的数据丢进一个队列中,队列中有数据就赋值DR然后让硬件来发送,CPU继续处理其他任务,等硬件发送一个字节完毕任务直接切换回队列赋值DR的任务中,让硬件继续发送,CPU继续其他任务,,如此往复,直到发送完成。。。不知可行否?

网上收集的关于接收部分的处理的办法:

实例一:

void USART1_IRQHandler(u8 GetData)

{

u8 BackData;

if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //中断产生

{

USART_ClearITPendingBit(USART1,USART_IT_RXNE); //清除中断标志.

GetData = UART1_GetByte(BackData); //也行GetData=USART1->DR;

USART1_SendByte(GetData); //发送数据

GPIO_SetBits(GPIOE, GPIO_Pin_8 ); //LED闪烁,接收成功发送完成

delay(1000);

GPIO_ResetBits(GPIOE, GPIO_Pin_8 );

}

}

这是最基本的,将数据接收完成后又发送出去,接收和发送在中断函数里执行,main函数里无其他要处理的。

优点:简单,适合很少量数据传输。

缺点:无缓存区,并且对数据的正确性没有判断,数据量稍大可能导致数据丢失 。

实例二:

void USART2_IRQHandler()

{

if(USART_GetITStatus(USART2,USART_IT_RXNE) != RESET) //中断产生

{

USART_ClearITPendingBit(USART2,USART_IT_RXNE); //清除中断标志

Uart2_Buffer[Uart2_Rx_Num] = USART_ReceiveData(USART2);

Uart2_Rx_Num++;

}

if((Uart2_Buffer[0] == 0x5A)&&(Uart2_Buffer[Uart2_Rx_Num-1] == 0xA5)) //判断最后接收的数据是否为设定值,确定数据正确性

Uart2_Sta=1;

if(USART_GetFlagStatus(USART2,USART_FLAG_ORE) == SET) //溢出

{

USART_ClearFlag(USART2,USART_FLAG_ORE); //读SR

USART_ReceiveData(USART2); //读DR

}

}

if( Uart2_Sta )

{

for(Uart2_Tx_Num=0;Uart2_Tx_Num < Uart2_Rx_Num;Uart2_Tx_Num++)

USART2_SendByte(Uart2_Buffer[Uart2_Tx_Num]); //发送数据

Uart2_Rx_Num = 0; //初始化

Uart2_Tx_Num = 0;

Uart2_Sta = 0;

}

这是加了数据头和数据尾的接收方式,数据头和尾的个数可增加,此处只用于调试之用。中断函数用于接收数据以及判断数据的头尾,第二个函数在main函数里按照查询方式执行。

优点:较简单,采用缓存区接收,对提高数据的正确行有一定的改善 。

缺点:要是第一次数据接收错误,回不到初始化状态,必须复位操作 。

实例三:

vvoid USART2_IRQHandler()

{

if(USART_GetITStatus(USART2,USART_IT_RXNE) != RESET) //中断产生

{

USART_ClearITPendingBit(USART2,USART_IT_RXNE); //清除中断标志.

Uart2_Buffer[Uart2_Rx] = USART_ReceiveData(USART2);

Uart2_Rx++;

Uart2_Rx &= 0x3F; //判断是否计数到最大

}

if(USART_GetFlagStatus(USART2,USART_FLAG_ORE) == SET) //溢出

{

USART_ClearFlag(USART2,USART_FLAG_ORE); //读SR

USART_ReceiveData(USART2); //读DR

}

}

if( Uart2_Tx != Uart2_Rx )

{

USART2_SendByte(Uart2_Buffer[Uart2_Tx]); //发送数据

Uart2_Tx++;

Uart2_Tx &= 0x3F; //判断是否计数到最大

}

采用FIFO方式接收数据,由0x3F可知此处最大接收量为64个,可变,中断函数只负责收,另一函数在main函数里执行,FIFO方式发送。

优点:发送和接收都很自由,中断占用时间少,有利于MCU处理其它。

缺点:对数据的正确性没有判断,一概全部接收。

实例四:

void USART2_IRQHandler()

{

if(USART_GetITStatus(USART2,USART_IT_RXNE) != RESET) //中断产生

{

USART_ClearITPendingBit(USART2,USART_IT_RXNE); //清除中断标志

Uart2_Buffer[Uart2_Rx] = USART_ReceiveData(USART2);

Uart2_Rx++;

Uart2_Rx &= 0xFF;

}

if(Uart2_Buffer[Uart2_Rx-1] == 0x5A) //头

Uart2_Tx = Uart2_Rx-1;

if((Uart2_Buffer[Uart2_Tx] == 0x5A)&&(Uart2_Buffer[Uart2_Rx-1] == 0xA5)) //检测到头的情况下检测到尾

{

Uart2_Len = Uart2_Rx-1- Uart2_Tx; //长度

Uart2_Sta=1; //标志位

}

if(USART_GetFlagStatus(USART2,USART_FLAG_ORE) == SET) //溢出

{

USART_ClearFlag(USART2,USART_FLAG_ORE); //读SR

USART_ReceiveData(USART2); //读DR

}

}

if( Uart2_Sta )

{

for(tx2=0;tx2 <= Uart2_Len;tx2++,Uart2_Tx++)

USART2_SendByte(Uart2_Buffer[Uart2_Tx]); //发送数据

Uart2_Rx = 0; //初始化

Uart2_Tx = 0;

Uart2_Sta = 0;

}

数据采用数据包的形式接收,接收后存放于缓存区,通过判断数据头和数据尾(可变)来判断数据的“包”及有效性,中断函数用于接收数据和判断头尾以及数据包长度,另一函数在main函数里执行,负责发送该段数据。

优点:适合打包传输,稳定性和可靠性很有保证,可随意发送,自动挑选有效数据。

缺点:缓存区数据长度要根据“包裹”长度设定, 要是多次接收后无头无尾,到有头有尾的那一段数据恰好跨越缓存区最前和最后位置时,可能导致本次数据丢失,不过这种情况几乎没有可能。