STM32串口DMA连续发送两帧,导致数据部分覆盖的问题

问题描述

使用STM32的串口进行DMA发送(Noraml模式),在某个任务中连续调用两次发送函数log_printf(),但是发回的数据在串口调试助手上显示与预期不符。第一次发送的数据有一部分被第二次发送的数据覆盖,如图所示:
失败
任务代码如下:

/* Log_Task function */
void Log_Task(void const * argument)
{
  /* USER CODE BEGIN Log_Task */
  /* Infinite loop */
  for(;;)
  {
      if(router_rx_flag == 1)
      {
          router_rx_flag = 0;
          log_printf("Get ok\r\n");
          log_printf("%s",router_rx_buffer);
      }
    osDelay(100);
  }
  /* USER CODE END Log_Task */
}

从代码中可以看出,期望的结果应该是下图这样:

成功


log_printf函数代码如下:

/* 
 * 名称: log_printf
 * 功能: 在串口1上打印出日志内容
 * 输入: 格式化输出的字符串
 * 输出: 无
 */
void log_printf(const char *format ,... )
{
    va_list arg;
    static char tx_buffer[256]={""};

    //把数据处理后放进缓冲区
    va_start(arg, format);
    vsprintf((char *)tx_buffer, format, arg);
    va_end(arg);

    //开始发送数据
    send_to_router((u8 *)tx_buffer,strlen(tx_buffer));
}

send_to_router函数代码如下:

void send_to_router(unsigned char *buffer,unsigned int length)
{
    //等待上一次的数据发送完毕
    while(HAL_DMA_GetState(&hdma_usart1_tx) == HAL_DMA_STATE_BUSY)  osDelay(1);

    /* 关闭DMA */
    __HAL_DMA_DISABLE(&hdma_usart1_tx);

    //开始发送数据
    HAL_UART_Transmit_DMA(&huart1,buffer,length);
    //  while(HAL_DMA_GetState(&hdma_usart1_tx) == HAL_DMA_STATE_BUSY)  osDelay(1); /* 放在此处可以保证每次发送完全,但会占用时间 */
}

串口中断接收处理函数如下:

/* 
 * 名称: router_parse
 * 功能: 接收路由器数据的解析,在回调函数中调用
 * 输入: 空闲中断时串口1接收的数据长度
 * 输出: 无
 */
void router_parse(uint16_t buffer_len)
{
    char *p_start = NULL,*p_end = NULL;

    /* 只提取一帧NMEA数据,$开头,\n结尾 */
    p_start = strchr(usart1_rx_buffer,'$');
    if(p_start != NULL)
    {
        p_end = strchr(p_start,'\n');
        if(p_end != NULL)
        {
            memcpy(router_rx_buffer, p_start, (p_end - p_start + 1)); /* 保存数据 */
            router_rx_flag = 1;
        }
    }
}

分析过程

以前一直以为是send_to_router函数中的

    //等待上一次的数据发送完毕
    while(HAL_DMA_GetState(&hdma_usart1_tx) == HAL_DMA_STATE_BUSY)  osDelay(1);

这一句的问题,即由于某种原因导致DMA缓存中数据未发送完全,但DMA状态却被释放了,结果重新开始了新一轮的发送,导致上次数据的后半部分被覆盖。但无论如何调试,都无法证实这个猜想,DMA外设没有出过任何异常。
今天仔细观察了一下,“Getckey”和“Get ok\r\n”和”$Mickey\r\n“,为什是第二次发送的内容的后半部覆盖了第一次发送的内容,一般不应该是前半部分”(美元符号,此处会排版出错)Mic”吗?问题的原因可能与状态位无关。于是我再审视了一下send_to_router函数:void send_to_router(unsigned char *buffer,unsigned int length)突然间想到,入参只是一个指针,发送缓存区在log_printf函数中

static char tx_buffer[256]={""};

整理一下,整个发送过程流程如下:

  1. log_printf(“Get ok\r\n”);时,“Get ok\r\n”被装进了tx_buffer,附带一个发送长度8字节。
  2. send_to_router函数中,HAL_UART_Transmit_DMA(&huart1,buffer,length);开启了这个8个字节的发送。
  3. 8个字节可能只完成了“Get”的发送, log_printf(“%s”,router_rx_buffer);(即log_printf(“$Mickey\r\n“);)已经开始执行。
  4. $Mickey\r\n“被装进tx_buffer,附带一个发送长度9字节。
  5. send_to_router函数中,因为上一次数据还没有发送完全,进入DMA状态等待循环。但是DMA发送指针char *buffer原本指向的那个地址的内容” ok\r\n“已经被”ckey\r\n“代替,所以就变成了”Getckey\r“。由于显示原因,只看到”Getckey“。

解决办法

while(HAL_DMA_GetState(&hdma_usart1_tx) == HAL_DMA_STATE_BUSY) osDelay(1);这一句放到缓存区tx_buffer装载步骤之前即可:

/* 
 * 名称: log_printf
 * 功能: 在串口1上打印出日志内容
 * 输入: 格式化输出的字符串
 * 输出: 无
 */
void log_printf(const char *format ,... )
{
    va_list arg;
    static char tx_buffer[256]={""};

    //等待上一次的数据发送完毕
    while(HAL_DMA_GetState(&hdma_usart1_tx) == HAL_DMA_STATE_BUSY)  osDelay(1);

    //把数据处理后放进缓冲区
    va_start(arg, format);
    vsprintf((char *)tx_buffer, format, arg);
    va_end(arg);

    //开始发送数据
    send_to_router((u8 *)tx_buffer,strlen(tx_buffer));
}

至于send_to_router函数中的该代码,保留或删除都可以。

后言

很久以前就开始使用STM32的DMA串口发送功能,套路基本上就是曾经的博文《iar中使用DMA+printf+uart1》所描述的那样。后来开始用STM32CubeMX了,把之前的例程稍微做了一些修改,调试成功之后,就一直沿用至今。期间,这个问题困扰了我很久,虽然在写代码时稍微注意一下就可避免其发生,但做技术的人都明白:千里之堤,溃于蝼蚁,放过任何一个小细节都可能在将来引发重大灾难。很庆幸今天能够找到问题的原因。
再回去看来一遍《iar中使用DMA+printf+uart1》,其实这个问题的答案很早就写在里面了。。。
找个时间,我会专门写一篇使用DMA串口Normal模式发送的博文,还是以Cube来创建工程。届时,再用一个例程完整复现和解决这个问题。

  • 7
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值