You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

STM32 FreeRTOS自定义UART日志器出现多帧数据合并异常问题求助

STM32 FreeRTOS自定义UART日志器出现多帧数据合并异常问题求助

我在STM32上基于FreeRTOS做了一个自定义UART日志器,用DMA实现串口传输。现在遇到一个棘手的问题:两个任务每隔500ms分别发送固定格式的日志消息,还有一个任务每隔10ms负责把队列里的消息通过串口发送到PC,但有时候不同任务的消息帧会莫名合并在一起。比如任务1的[ data ]:task1belbel和任务2的[status]:task2blabla,会被混拼成[ data ]:tas[ data ]:ta[status]:task2blabla这种错乱的内容。我已经尝试用互斥锁、消息队列和信号量做同步,但问题依然存在,麻烦大家帮我排查下代码里的问题。


我的代码实现如下:

Logger.c

#include "Logger.h"
#include "BSP_Logger.h"
#include "cmsis_os.h"
#include "string.h"

#define PREFIX_SIZE 9 /* size of a message descriptor*/

/* transmission buffer , non circular*/
uint8_t TransmitBuf[40];

extern osMutexId_t LoggerMutexHandle;
extern osMessageQueueId_t LoggerQueueHandle;
extern osSemaphoreId_t LoggerSemHandle;

/* message descriptors */
const char * const logPrefix[]={"[error ]:", "[status]:", "[ info ]:", "[ data ]:", "unknown: "};
const char * const task2comp={"[status]:task2blabla"};
const char * const task1comp={"[ data ]:task1belbel"};

/** @brief: function to place a message in a send queue
 * @param: str - pointer to a data to place in a queue
 * @param: len - lenght of data to place in a queue
 * @param: type - type of descriptor to add to a message
 * @retval: 0 - data is placed in a queue , 1 - data is not placed
 */
uint8_t LoggerSend(const char *str, uint8_t len,LogType type) {
    LogDesctiptor msg;
    if(str==NULL || len == 0) return 1; // Error: null string or zero length
    if(type>LOG_DATA) type=LOG_UNKNOWN;
    msg.type=type;

    if(osMutexAcquire(LoggerMutexHandle,0)!=osOK) return 1;

    if(len<=QUEUE_MAX_SIZE)
        msg.size=len;
    else
        msg.size=QUEUE_MAX_SIZE;
    memcpy(&msg.payload,str,msg.size);

    if(osMessageQueuePut(LoggerQueueHandle,&msg,0,0)!=osOK) return 1;

    osMutexRelease(LoggerMutexHandle);
    return 0;
}

/** @brief: function to transmit a single message */
void LoggerTransmit(void){
    if(osSemaphoreAcquire(LoggerSemHandle,0)==osOK){
        if(osMessageQueueGetCount(LoggerQueueHandle)){
            if(osMutexAcquire(LoggerMutexHandle,0)==osOK){
                LogDesctiptor msg;
                osMessageQueueGet(LoggerQueueHandle,&msg,NULL,0);
                memcpy(TransmitBuf,logPrefix[msg.type],PREFIX_SIZE);
                memcpy((TransmitBuf+PREFIX_SIZE),&msg.payload,msg.size);
                BSP_LoggerTransmit(TransmitBuf,PREFIX_SIZE+msg.size);

                osSemaphoreRelease(LoggerSemHandle);
                osMutexRelease(LoggerMutexHandle);
            } else
                osSemaphoreRelease(LoggerSemHandle);
        } else
            osSemaphoreRelease(LoggerSemHandle);
    }
}

Logger.h

#ifndef __LOGGER_H
#define __LOGGER_H
/** @brief: driver for printing debug messages by USART multithread access supported */
#include <stdint.h>

#define QUEUE_MAX_SIZE 20

typedef enum __attribute__((packed)){
    LOG_ERROR=0,
    LOG_STATUS=1,
    LOG_INFO=2,
    LOG_DATA=3,
    LOG_UNKNOWN=4
}LogType;

typedef struct{
    LogType type;
    uint8_t size;
    uint8_t payload[QUEUE_MAX_SIZE];
}LogDesctiptor;

/** @brief: function copies data to local buffer , transmission is delayed */
uint8_t LoggerSend(const char *str, uint8_t len,LogType type);
/** @brief: function that sends data from local buffer */
void LoggerTransmit(void);

#endif /* __LOGGER_H */

BSP_Logger.c

#include "cmsis_os.h"
#include "BSP_Logger.h"
extern UART_HandleTypeDef huart2;
extern osSemaphoreId_t LoggerSemHandle;

void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart){
    if(huart==&huart2){
        osSemaphoreRelease(LoggerSemHandle);
    }
}

void BSP_LoggerTransmit(const uint8_t * buf,uint8_t size){
    HAL_UART_Transmit_DMA(&huart2,buf,size);
}

问题根源分析

经过排查,核心问题出在信号量的逻辑误用,还有几个细节需要修正:

  1. 信号量的使用完全反向
    你设计信号量的初衷是同步DMA传输完成,但在LoggerTransmit中,刚启动DMA就立即释放了信号量。这会导致:

    • DMA是异步传输,启动后函数立即返回,此时数据还在后台发送
    • 信号量被提前释放后,下一次定时的LoggerTransmit会立即获取信号量,启动新的DMA传输
    • 上一次的DMA还未完成,全局TransmitBuf被新消息覆盖,或者HAL库直接中断当前传输,最终导致发送内容错乱、帧合并
  2. TransmitBuf的线程安全问题
    全局非环形的TransmitBuf在DMA传输期间绝对不能被修改,但信号量的错误使用导致新消息会覆盖正在传输的数据,直接引发内容混叠。

  3. 冗余的互斥锁
    FreeRTOS消息队列本身是线程安全的,osMessageQueuePut/osMessageQueueGet不需要额外互斥锁保护,你添加的互斥锁属于冗余操作。


修正后的解决方案

1. 修复信号量核心逻辑

信号量要用来保证DMA传输的独占性:只有上一次传输完成,才能启动下一次。正确流程:

  • 初始化时信号量设为二值信号量(初始计数1)
  • LoggerTransmit先获取信号量(等待上一次传输完成)
  • 启动DMA后不要释放信号量,留到HAL_UART_TxCpltCallback中完成传输后再释放

修正后的LoggerTransmit:

void LoggerTransmit(void){
    // 等待信号量(用永久等待避免消息丢失,可根据需求调整超时)
    if(osSemaphoreAcquire(LoggerSemHandle, osWaitForever) == osOK){
        if(osMessageQueueGetCount(LoggerQueueHandle) > 0){
            LogDesctiptor msg;
            if(osMessageQueueGet(LoggerQueueHandle, &msg, NULL, 0) == osOK){
                memcpy(TransmitBuf, logPrefix[msg.type], PREFIX_SIZE);
                memcpy(TransmitBuf + PREFIX_SIZE, msg.payload, msg.size);
                BSP_LoggerTransmit(TransmitBuf, PREFIX_SIZE + msg.size);
                // 此处不释放信号量,等待DMA完成回调释放
            } else {
                // 取队列失败,释放信号量避免死锁
                osSemaphoreRelease(LoggerSemHandle);
            }
        } else {
            // 队列无消息,释放信号量
            osSemaphoreRelease(LoggerSemHandle);
        }
    }
}

2. 移除冗余互斥锁

修正后的LoggerSend:

uint8_t LoggerSend(const char *str, uint8_t len, LogType type) {
    if(str == NULL || len == 0) return 1;
    if(type > LOG_DATA) type = LOG_UNKNOWN;

    LogDesctiptor msg;
    msg.type = type;
    msg.size = (len <= QUEUE_MAX_SIZE) ? len : QUEUE_MAX_SIZE;
    memcpy(msg.payload, str, msg.size);

    // 队列操作线程安全,用永久等待避免消息丢失
    if(osMessageQueuePut(LoggerQueueHandle, &msg, 0, osWaitForever) != osOK){
        return 1;
    }
    return 0;
}

3. 额外优化建议

  • 把LoggerTransmit从定时任务改成队列触发:让线程在osMessageQueueGet处等待,有新消息时再唤醒,比定时轮询更高效
  • 确保LoggerSemHandle初始化为二值信号量(计数1)

修正后流程说明

  1. 初始状态:信号量计数1,允许第一次传输
  2. LoggerTransmit获取信号量,计数变为0,禁止新传输
  3. 启动DMA异步发送,函数返回
  4. DMA传输完成,进入HAL_UART_TxCpltCallback释放信号量,计数变回1
  5. 下一次LoggerTransmit可以获取信号量,启动新的传输

这样就保证了同一时间只有一次DMA传输在进行,TransmitBuf不会被中途覆盖,彻底解决帧合并问题。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.08 09:33:03