串口HAL库发送问题

发布于:2025-09-07 ⋅ 阅读:(21) ⋅ 点赞:(0)

        想了很久,不知道该标题起的是否合适,该篇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  B

    vTaskDelay(1000);
  }
}

        上述代码展示了一个示例RTOS任务,间隔一秒向串口发送字符串"TEST RUNNING.\n",和字符'B'。理想情况下,串口接收到的数据应该如下所示:

TEST RUNNING.
B

        但将程序烧入芯片后,串口接收到的数据如下图所示:

        可以看到发送的顺序不仅出现错误,发送的字符数据也出现了错误。

原因

        在这里需要先知道HAL_USART_Transmit_ITHAL_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);
  }
}

 只要前后留出一定的时间裕度即可。运行结果如下:

     


网站公告

今日签到

点亮在社区的每一天
去签到