STM32F103基于HAL与FreeRTOS的USB CDC/UART调试函数求助
嘿,我看你在基于STM32F103、HAL库和FreeRTOS做开发,还写了个带条件编译的串口调试函数——先把你给出的头文件定义补全并格式化一下,方便咱们分析:
#if ( DEBUG == DEBUG_ENABLE ) /* printf definition */ #define DEBUG_MSG( FLAG, fmt, ...) do { \ if( FLAG ) \ printf( "[%08d]%s:%d:%s(): " fmt "\r\n", \ HAL_GetTick(), __FILE__, __LINE__, __func__, ##__VA_ARGS__); \ }while( 0 ); #else #define DEBUG_MSG(FLAG, fmt, ...) do {} while(0) #endif
结合你提到的“遇到瓶颈”,我猜大概率是FreeRTOS多任务环境下的线程安全、实时性影响或者输出可靠性问题,给你几个针对性的解决思路:
解决多任务下的输出错乱问题
FreeRTOS里多个任务同时调用DEBUG_MSG的话,直接用printf(不管重定向到UART还是USB CDC)很容易出现日志串线的情况。你可以给调试输出加个互斥锁:先创建一个全局互斥量xSemaphoreHandle debug_mutex;,初始化的时候调用debug_mutex = xSemaphoreCreateMutex();,然后在DEBUG_MSG里包裹锁操作:#define DEBUG_MSG( FLAG, fmt, ...) do { \ if( FLAG && debug_mutex != NULL ) { \ if(xSemaphoreTake(debug_mutex, portMAX_DELAY) == pdTRUE) { \ printf( "[%08d]%s:%d:%s(): " fmt "\r\n", \ HAL_GetTick(), __FILE__, __LINE__, __func__, ##__VA_ARGS__); \ xSemaphoreGive(debug_mutex); \ } \ } \ }while( 0 );更优雅的方式是做个日志任务:把要输出的日志内容通过消息队列发送给专门的低优先级日志任务,由它统一处理输出,这样完全避免多任务冲突,还不影响高优先级任务的实时性。
避免调试输出阻塞实时任务
如果直接在高优先级任务里调用printf,UART/USB CDC的发送过程可能会阻塞任务(尤其是USB CDC的批量传输,偶尔会有延迟)。你可以用环形缓冲区缓存日志内容,后台用DMA或者低优先级任务来发送缓存里的数据。比如把printf重定向到环形缓冲区,然后在空闲任务里检查缓冲区,有数据就通过HAL库发送出去。提升调试宏的灵活性
当前的FLAG只是简单的开关,如果想按模块或级别控制日志输出,可以扩展成日志级别机制:typedef enum { DEBUG_LEVEL_NONE = 0, DEBUG_LEVEL_ERROR, DEBUG_LEVEL_WARN, DEBUG_LEVEL_INFO, DEBUG_LEVEL_DEBUG } DebugLevel_t; #define DEBUG_CUR_LEVEL DEBUG_LEVEL_INFO #define DEBUG_MSG( LEVEL, fmt, ...) do { \ if( LEVEL <= DEBUG_CUR_LEVEL && debug_mutex != NULL ) { \ if(xSemaphoreTake(debug_mutex, portMAX_DELAY) == pdTRUE) { \ printf( "[%08d][%d]%s:%d:%s(): " fmt "\r\n", \ HAL_GetTick(), LEVEL, __FILE__, __LINE__, __func__, ##__VA_ARGS__); \ xSemaphoreGive(debug_mutex); \ } \ } \ }while( 0 );这样你可以通过修改
DEBUG_CUR_LEVEL来控制不同级别的日志输出,比如只看错误日志就设为DEBUG_LEVEL_ERROR。适配UART和USB CDC的切换
如果需要同时支持UART和USB CDC输出,可以在宏里加个通道选择的配置,比如:#define DEBUG_CHANNEL_UART 1 #define DEBUG_CHANNEL_CDC 2 #define DEBUG_CUR_CHANNEL DEBUG_CHANNEL_UART // 然后在输出的时候根据通道选择对应的发送函数 #if DEBUG_CUR_CHANNEL == DEBUG_CHANNEL_UART #define DEBUG_SEND(str, len) HAL_UART_Transmit(&huart1, (uint8_t*)str, len, HAL_MAX_DELAY) #elif DEBUG_CUR_CHANNEL == DEBUG_CHANNEL_CDC #define DEBUG_SEND(str, len) CDC_Transmit_FS((uint8_t*)str, len) #endif注意替换成你实际使用的UART句柄和CDC发送函数,这样不用修改业务代码就能切换输出通道。
内容的提问来源于stack exchange,提问作者yldzmuhammed

