关于嵌入式系统日志打印的一点建议

虽然现在的嵌入式设备配置越来越高。

比如目前在创客圈比较流行且配置比较高的有:

Raspberry Pi,pcduino,Cubieboard,switf board,Beaglebone black ,Edison,Galileo 等。

这些开发板跟早期的pc来说,性能有得一比,一个printf可以对整个系统来说,占用的cup资源不会很多,对这个系统的运行影响基本可以忽略不计。

但是如果在系统资源比较紧缺的MCU系统中,主频可能只有几十khz 或者几百mhz,而大量调用日志输出(printf等相关函数),那么就会影响到这个系统的运行效率,严重时会出现明显的延时等情况。

那平时我们在开发阶段应该怎么规范我们的日志呢,下面我给出我个人的一点小建议,大家有什么好的建议希望也提出来,相互学习。

  1. 日志的输出尽量不要直接采用系统的输出函数如(printf等相关函数)

   而是封装自己的日志输出接口,比如为:alex_log( char*pLog,... );

   这样子用alex_log();代替printf(),

   而在alex_log( )接口里面重定义数据流的接口,可以把数据流导入到uart,tcp,udp,屏幕,任何你想在那个你想看的client端去。

   为什么要这么做呢?

   这样做可以灵活变化日志的显示终端。

   因为嵌入式的运行环境是千千万万的,不像PC或者手机端,大部分都有一个显示屏可以看,如果在一个只有uart口的设备,那么我们可以通过把数据导到uart来,然后通过串口助手工具来查看。或者对于wifi设备,可以把log导到socket的某个接口,这样不用直接硬件连接,可以在pc或者手机端查看log,等等。

  2.日志格式的规范

   "日志模块+日志级别+日志内容”

    一下为日志例子:

    " LOCALDEBUGGAgent Get Hal wifiStatus :0002    

    "LOCALINFOwifiStatus : 000a new:0002"                  

     :CLOUDCRITICALGAgent Cloud Pong …”               

那么接口可以这么设计alex_log( module,level,const char *format,…  );

参数解说:

moduel:决定摸个函数功能模块的log是否打开。

level    : 决定打开的日志级别,debug 信息,还是warning信息,还是error信息,还是普通info.等。

后面剩下的就是变参的输入了,跟printf函数参数一样。

   为什么这么设计呢?

1.为什么设置功能模块的日志开关:

当我们的函数设计有多个功能函数模块的时候,模块功能模块出现问题,这个时候想通过log来定位,而这个时候我们只是关心此模块,那么可以先把其他模块的日志功能关闭掉,只是打开关心模块的日志,不然有A,B,C,D。4个模块,A模块出现问题,那么把B,C,D的日志功能关闭,剩下A模块的功能,当我们看日志的时候,就不用从一大堆日志找出A模块的日志,这样子发现问题的效率就大大提升了。

2.为什么设置日志等级的开关:

日志等级关系到日志的重要性,如果是非常重要的日志,比如error级别,我们在定位问题的时候可以只打开此级别的日志,而不打印普通日志级别的,当我们在处于debug阶段,我们可以把debug功能打开,尽可能看到详细的日志。

而当我们准备发布版本的时候,可以把一些debug信息关闭,或者就是不输出日志。

3.通过开关来关闭或打开日志功能

        也就是第二点提到的 module level 设置。


<think>好的,我现在需要解决用户的问题,即在STM32上使用FreeRTOS实现异步日志记录,并希望看到示例代码。首先,我需要回忆一下FreeRTOS的相关知识,尤其是任务、队列、以及异步处理机制。 用户提到的是异步日志打印,这意味着日志的生成和实际输出(如串口发送)应该在不同的任务中执行,以避免阻塞主任务或高优先级任务。通常,实现异步日志可以采用生产者-消费者模型,其中日志的生产者将日志消息发送到一个队列,而消费者任务从队列中取出消息并处理(比如通过串口发送)。这样,生成日志的任务不需要等待IO操作完成,提高了系统的响应速度。 接下来,我需要考虑如何在FreeRTOS中实现这一点。FreeRTOS提供了队列(Queue)机制,可以用于任务间的通信。因此,创建一个队列来存储日志消息是合理的。日志消息的结构需要包含必要的信息,比如消息内容、时间戳、等级等。但为了简化示例,可能只需要存储字符串即可,不过需要注意内存管理,尤其是动态内存分配在嵌入式系统中的风险。 然后,考虑如何设计日志任务。日志任务应该持续等待队列中的消息,一旦有消息到达,就取出并通过串口发送。这里需要使用xQueueReceive函数,并且设置适当的阻塞时间,比如portMAX_DELAY,这样任务在没有消息时会进入阻塞状态,不占用CPU资源。 另外,需要考虑日志消息的生成。用户可能希望有一个日志函数(如log_printf),该函数将格式化的字符串写入队列。这里需要注意,由于在中断服务例程(ISR)中也可能需要记录日志,所以需要考虑是否使用中断安全的队列操作函数(如xQueueSendFromISR)。不过用户的问题可能主要集中在任务间的日志,所以先处理任务部分。 接下来,编写示例代码。需要包括队列的创建、日志任务的创建、日志函数的实现以及串口初始化的部分。需要注意,在STM32中,串口发送通常采用阻塞或非阻塞方式,但在FreeRTOS任务中,使用阻塞方式发送可能会影响其他任务,因此可能需要使用DMA传输,但这会增加复杂性。为了简化示例,可以暂时使用阻塞发送,但需要说明可能的改进方向。 此外,动态内存分配的问题需要注意。在创建队列时,如果使用静态内存分配会更安全,但需要提前分配存储区域。不过示例中使用动态分配可能更简洁,但需要提醒用户在实际项目中考虑内存管理。 可能出现的问题包括队列溢出,当生产速度超过消费速度时,队列可能会满,导致日志丢失。因此,需要处理队列满的情况,比如丢弃新日志或等待空间。在示例中,使用xQueueSend的阻塞时间为0,即不等待,这样当队列满时立即返回错误,用户可以根据需要处理。 还需要考虑日志消息的缓冲区大小。在log_printf函数中,使用vsnprintf来格式化字符串,避免缓冲区溢出。这里需要定义一个固定大小的缓冲区,比如128字节,并确保不超过队列中每个元素的大小。 最后,测试代码是否能够在STM32上运行,需要包含正确的FreeRTOS配置,比如足够的堆空间、正确的串口初始化,以及任务优先级的合理设置。可能还需要调整栈大小,防止栈溢出。 总结步骤: 1. 创建日志队列,用于存储日志消息。 2. 创建日志处理任务,负责从队列取出消息并发送到串口。 3. 实现日志函数,将格式化后的消息发送到队列。 4. 初始化串口外设。 5. 在应用代码中调用日志函数,测试异步日志功能。 需要注意的细节包括队列大小、消息结构、内存分配、任务优先级以及错误处理。同时,需要提供示例代码,并解释关键部分,帮助用户理解实现方式。</think>### 基于FreeRTOS的STM32异步日志实现方法 #### 1. 实现原理 异步日志通过分离日志生成和输出过程实现非阻塞操作。使用FreeRTOS队列作为缓冲区,由独立任务处理实际输出,避免串口发送阻塞高优先级任务[^2]。 #### 2. 实现步骤 **2.1 定义日志队列** ```c #define LOG_QUEUE_LENGTH 20 #define LOG_MESSAGE_LENGTH 128 QueueHandle_t xLogQueue; // 初始化队列(在main函数中调用) void log_init(void) { xLogQueue = xQueueCreate(LOG_QUEUE_LENGTH, sizeof(char[LOG_MESSAGE_LENGTH])); } ``` **2.2 创建日志处理任务** ```c void vLogTask(void *pvParameters) { char msg[LOG_MESSAGE_LENGTH]; while(1) { if(xQueueReceive(xLogQueue, msg, portMAX_DELAY) == pdPASS) { HAL_UART_Transmit(&huart1, (uint8_t*)msg, strlen(msg), 100); } } } // 在main中创建任务 xTaskCreate(vLogTask, "Logger", 256, NULL, tskIDLE_PRIORITY + 1, NULL); ``` **2.3 实现日志打印函数** ```c void log_printf(const char *format, ...) { char buffer[LOG_MESSAGE_LENGTH]; va_list args; va_start(args, format); vsnprintf(buffer, LOG_MESSAGE_LENGTH, format, args); va_end(args); xQueueSend(xLogQueue, buffer, 0); } ``` #### 3. 使用示例 ```c void main_task(void *pvParameters) { log_init(); while(1) { log_printf("[INFO] System started, tick: %lu\r\n", xTaskGetTickCount()); vTaskDelay(pdMS_TO_TICKS(1000)); } } ``` #### 4. 关键优化点 - 使用DMA传输提升效率 - 添加日志等级过滤 - 实现环形缓冲区备份 - 增加队列满处理策略 ```c // 增强版发送(带超时保护) BaseType_t log_send(const char* msg) { return xQueueSend(xLogQueue, msg, pdMS_TO_TICKS(10)); } ``` #### 5. 配置建议 1. 在`FreeRTOSConfig.h`设置合适堆大小: ```c #define configTOTAL_HEAP_SIZE (30 * 1024) ``` 2. 串口建议使用DMA模式 3. 任务优先级应低于关键实时任务
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值