想了很久,不知道该标题起的是否合适,该篇Blog用于记录在使用HAL库的USART
模块时实际遇到的一个涉及发送方式的问题,用于提醒自身同时也希望能帮到各位。
程序问题叙述
先来看一段代码:
void CusUSART_SendByte_IT( uint8_t Byte )
{ while((husart1.State != HAL_USART_STATE_READY));HAL_USART_Transmit_IT(&husart1, &Byte, 1);
}void test_task( void )
{for( ; ; ){HAL_USART_Transmit_IT(&husart1, "TEST RUNNING.\n", strlen("TEST RUNNING.\n"));vTaskDelay(1000);CusUSART_SendByte_IT( 0x42 ); // ASCII: 0x42 BvTaskDelay(1000);}
}
上述代码展示了一个示例RTOS任务,间隔一秒向串口发送字符串"TEST RUNNING.\n
",和字符'B
'。理想情况下,串口接收到的数据应该如下所示:
TEST RUNNING.
B
但将程序烧入芯片后,串口接收到的数据如下图所示:
可以看到发送的顺序不仅出现错误,发送的字符数据也出现了错误。
原因
在这里需要先知道HAL_USART_Transmit_IT
与HAL_USART_Transmit
方法的本质区别.
HAL_USART_Transmit
本质是以阻塞方式通过 USART
发送数据。也就是说该方法会将指定的数据缓冲区中的数据通过 USART
发送出去,且CPU
会一直等待直到所有数据都发送完成才会返回。
HAL_USART_Transmit_IT
该方法本质是以非阻塞方式通过USART
发送数据。也就是说该方法只是将缓冲区地址和大小设置好,启动USART
传输后会立即返回,实际的传输由后台硬件中断完成,并不会等待USART发送完成再返回!。数据传输完成后,HAL 库会调用回调函数(HAL_USART_TxCpltCallback
)通知传输完成。
了解了两种方法的区别之后,问题就很明显了:
void CusUSART_SendByte_IT( uint8_t Byte )
{ while((husart1.State != HAL_USART_STATE_READY));HAL_USART_Transmit_IT(&husart1, &Byte, 1);
}
在该函数中,待发送的字节 Byte
为局部变量,由外部传入。作为局部变量,当调用栈结束后即被销毁。当Transmit_IT
方法调用后,将局部变量Byte
的地址传入,将传输请求提交给后台后便返回。此时,字符 Byte
的传输并未真正开始抑或是开始了并未完成传输。而此时在HAL_USART_Transmit_IT
返回后,该函数(Cus)已经结束,Byte
已经被销毁。进而后台真正开始发送时,访问的地址是已经被销毁了的,自然发送的就是错误数据。
知晓问题之后,对其作如下修改:
void CusUSART_SendByte_IT( uint8_t Byte )
{ while((husart1.State != HAL_USART_STATE_READY));HAL_USART_Transmit_IT(&husart1, &Byte, 1);for(uint32_t i = 0; i < 5000; i++){ } // 简单阻塞延时.
}
只要保证在发送完毕前变量不被销毁即可。当然这种修改方式并不好,我们做如下修改。
void CusUSART_SendByte_IT( uint8_t Byte )
{ static uint8_t temp;while((husart1.State != HAL_USART_STATE_READY));temp = Byte;HAL_USART_Transmit_IT(&husart1, &temp, 1);
}
此时,再将程序烧入,即可得到串口接收到的数据如下:
至于乱序问题,我先把初始化代码贴出:
void CusUSART_Init( uint32_t baudrate )
{.......HAL_USART_Transmit_IT(&husart1, "USART Init OK!", strlen("USART Init OK!"));
}
在该初始化函数中,有一行关键代码HAL_USART_Transmit_IT(&husart1, "USART Init OK!", strlen("USART Init OK!"));
初始化函数被调用后,紧接着就启动USART
任务开始传输. 而初始化函数末尾使用了Transmit_IT
方法发送初始化完毕信息,但是要注意!该方法是非阻塞的!当任务启动开始运行后:
void test_task( void )
{for( ; ; ){// 此处距离初始化函数中的异步IT发送方法的时间间隔太近了!HAL_USART_Transmit_IT(&husart1, "TEST RUNNING.\n", strlen("TEST RUNNING.\n"));vTaskDelay(1000);CusUSART_SendByte_IT( 0x42 );vTaskDelay(1000);}
}
"USART Init OK!"
还在发送过程中时就进入了任务中,"TEST RUNNING.\n"
再次发出传输请求,但是由于后台仍在处理"USART Init OK!"
,因此此次传输请求便被取消了,这也是为什么会先输出B的关键原因所在!
知道了原因,那修改自然不难:
最简单的便是在问题处代码前直接加入延时。
vTaskDelay(1000);HAL_USART_Transmit_IT(&husart1, "TEST RUNNING.\n", strlen("TEST RUNNING.\n"));
亦或是在提交发送请求后进行检查
void test_task( void )
{for( ; ; ){while(HAL_USART_Transmit_IT(&husart1, (uint8_t *)"TEST RUNNING.\n", strlen("TEST RUNNING.\n")) != HAL_OK){vTaskDelay(1);}vTaskDelay(1000);CusUSART_SendByte_IT( 0x42 );vTaskDelay(1000);}
}
只要前后留出一定的时间裕度即可。运行结果如下: