RT-Thread 在SD卡实现ulog+时间戳保存不同日志方法

1.问题背景:

在项目开发和维护中,总是会遇到一些问题:

为什么我的代码烧录进去没反应?

遇到这个问题,我们可以通过打印一下串口信息来快速定位问题。

但如果是产品出售阶段,我的代码怎么跑着跑着就死机了?

你不可能让串口一直连着控制台吧?

或者我们知道了某一个模块有问题,我们需要找那个模块相对应的代码。

你指望从串口猛猛输出的众多log信息中得到想要的模块数据吗?

因此我们需要一个保存log的文件,并且需要根据不同的日志分类 保存到不同的log文件中。

以前RT-Thread的开发者们根据这些问题设计了一个软件包------ulog

后面发现这个ulog使用的效果很不错,就把它吸收到内核中了。

2.ulog简介

可以直接查看RT-Thread手册对ulog的描述ulog 日志

我们可以打开rt-thread\components\utilities\ulog\backend\console_be.c文件,就能看到以下代码

cpp 复制代码
/*
 * Copyright (c) 2006-2022, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2018-09-04     armink       the first version
 */

#include <rthw.h>
#include <ulog.h>

#ifdef ULOG_BACKEND_USING_CONSOLE

#if defined(ULOG_ASYNC_OUTPUT_BY_THREAD) && ULOG_ASYNC_OUTPUT_THREAD_STACK < 384
#error "The thread stack size must more than 384 when using async output by thread (ULOG_ASYNC_OUTPUT_BY_THREAD)"
#endif

static struct ulog_backend console = { 0 };

void ulog_console_backend_output(struct ulog_backend *backend, rt_uint32_t level, const char *tag, rt_bool_t is_raw,
        const char *log, rt_size_t len)
{
#ifdef RT_USING_DEVICE
    rt_device_t dev = rt_console_get_device();

    if (dev == RT_NULL)
    {
        rt_hw_console_output(log);
    }
    else
    {
        rt_uint16_t old_flag = dev->open_flag;

        dev->open_flag |= RT_DEVICE_FLAG_STREAM;
        rt_device_write(dev, 0, log, len);
        dev->open_flag = old_flag;
    }
#else
    rt_hw_console_output(log);
#endif

}

int ulog_console_backend_init(void)
{
    ulog_init();
    console.output = ulog_console_backend_output;

    ulog_backend_register(&console, "console", RT_TRUE);

    return 0;
}
INIT_PREV_EXPORT(ulog_console_backend_init);

#endif /* ULOG_BACKEND_USING_CONSOLE */

我们的LOG为什么可以上传到控制台msh?就是因为项目生成的时候就帮我们注册了一个ulog后端。

这个后端是最简单的一种

因为它不需要过滤各种信息,只需要将各类log像rt_kprintf一样打印出来就可以了。

并且它还不需要保存成文件供我们查阅。

因此如果我们需要实现类似的log输出功能,我们不仅需要和console一样注册一个后端,还需要初始化文件进行log的保存。

然后我们的文件肯定是有最大值的,超过了最大值之后ulog就会新开一个log文件进行保存,我们可以打开rt-thread\components\utilities\ulog\backend\file_be.c查看具体的转文件保存的函数

cpp 复制代码
/* rotate the log file xxx_n-1.log => xxx_n.log, and xxx.log => xxx_0.log */
static rt_bool_t ulog_file_rotate(struct ulog_file_be *be)
{
#define SUFFIX_LEN          10 //保存文件的最大值
    /* mv xxx_n-1.log => xxx_n.log, and xxx.log => xxx_0.log */
    static char old_path[ULOG_FILE_PATH_LEN], new_path[ULOG_FILE_PATH_LEN];
    int index = 0, err = 0, file_fd = 0;
    rt_bool_t result = RT_FALSE;
    size_t base_len = 0;

    rt_snprintf(old_path, ULOG_FILE_PATH_LEN, "%s/%s", be->cur_log_dir_path, be->parent.name);
    rt_snprintf(new_path, ULOG_FILE_PATH_LEN, "%s/%s", be->cur_log_dir_path, be->parent.name);
    base_len = rt_strlen(be->cur_log_dir_path) + rt_strlen(be->parent.name) + 1;

    if (be->cur_log_file_fd >= 0)
    {
        close(be->cur_log_file_fd);
    }

    for (index = be->file_max_num - 2; index >= 0; --index)
    {
        rt_snprintf(old_path + base_len, SUFFIX_LEN, index ? "_%d.log" : ".log", index - 1);
        rt_snprintf(new_path + base_len, SUFFIX_LEN, "_%d.log", index);
        /* remove the old file */
        if ((file_fd = open(new_path, O_RDONLY)) >= 0)
        {
            close(file_fd);
            unlink(new_path);
        }
        /* change the new log file to old file name */
        if ((file_fd = open(old_path , O_RDONLY)) >= 0)
        {
            close(file_fd);
            err = dfs_file_rename(old_path, new_path);
        }

        if (err < 0)
        {
            result = RT_FALSE;
            goto __exit;
        }

        result = RT_TRUE;
    }

__exit:
    /* reopen the file */
    be->cur_log_file_fd = open(be->cur_log_file_path, O_CREAT | O_RDWR | O_APPEND);

    return result;
}

3.具体步骤

3.1 配置RT-Thread setting

SD卡和RTC的初始化可以看我之前的文章,这边不再赘述,有点费篇幅了

STM32H750 + RT-Thread studio实现SDMMC功能(二)功能配置------SDMMC功能实现-CSDN博客

使用cubemx和rt-thread studio联合配置(以RTC时钟为例)_rt-thread studio cubemx-CSDN博客

3.2 注册后端设备

本章我们注册两个后端设备进行比较

cpp 复制代码
/*
 * Copyright (c) 2006-2021, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2025-11-02     Administrator       the first version
 */


#include <rtthread.h>
#include <ulog_be.h>

/*
* 后端注册表
*/
struct _log_file
{
    const char *name;                 //文件名
    ulog_backend_t backend;
    struct ulog_file_be *file_be;
    const char *dir_path;             //保存路径
    rt_size_t max_num;                //保存最大文件数量
    rt_size_t max_size;                //保存最大文件大小
    rt_size_t buf_size;                //文件保存缓存大小
};
/*
* 文件后端标识
*/
typedef enum
{
    sys_id = 0,
    dht_id = 1,
}ulog_file_be_name;

#define ROOT_PATH "/sdcard/log" //设置保存路径
#define FILE_SIZE 512 * 1024   //设置单个文件大小
#define BUFF_SIZE 512           //设备缓存区大小

static struct ulog_backend sys_log_backend;
static struct ulog_file_be sys_log_file;

static struct ulog_backend dht_log_backend;
static struct ulog_file_be dht_log_file;

static struct _log_file table[] =
{
    {"sys"      ,&sys_log_backend,&sys_log_file,ROOT_PATH,10,FILE_SIZE,BUFF_SIZE},
    {"dht"      ,&dht_log_backend,&dht_log_file,ROOT_PATH,5 ,FILE_SIZE,BUFF_SIZE},
};

#define SYS_TAG  "main"
static rt_bool_t sys_log_file_backend_filter(struct ulog_backend *backend, rt_uint32_t level, const char *tag, rt_bool_t is_raw,
        const char *log, rt_size_t len){
    if (rt_strncmp(tag,SYS_TAG, sizeof(SYS_TAG)) == 0)//排除带有"main"标签输出
        return RT_TRUE;
    else
        return RT_FALSE;
}

#define DHT_TAG  "main"
static rt_bool_t dht_log_file_backend_filter(struct ulog_backend *backend, rt_uint32_t level, const char *tag, rt_bool_t is_raw,
        const char *log, rt_size_t len){
    if (rt_strncmp(tag,DHT_TAG, sizeof(DHT_TAG)) == 0)//排除除了"main"的标签输出
        return RT_FALSE;
    else
        return RT_TRUE;
}

/* Private function prototypes -----------------------------------------------*/
/**
  * @brief  系统日志文件后端初始化.
  * @param  None.
  * @retval None.
  * @note   None.
*/
int sys_log_file_backend_init(void)
{
    struct ulog_file_be *file_be = &sys_log_file;
    uint8_t id = sys_id;
    file_be->parent = sys_log_backend;

    //滤波函数设置
    ulog_backend_filter_t filter = sys_log_file_backend_filter;
    ulog_backend_set_filter(&file_be->parent,filter);

    ulog_file_backend_init( file_be,
                            table[id].name,
                            table[id].dir_path,
                            table[id].max_num,
                            table[id].max_size,
                            table[id].buf_size);

    ulog_file_backend_enable(file_be); //必须使能才能有效
    return RT_EOK;
}
INIT_APP_EXPORT(sys_log_file_backend_init);

int dht_log_file_backend_init(void)
{
  struct ulog_file_be *file_be = &dht_log_file;
  uint8_t id = dht_id;
  file_be->parent = dht_log_backend;

  //滤波函数设置
  ulog_backend_filter_t filter = dht_log_file_backend_filter;
  ulog_backend_set_filter(&file_be->parent,filter);

  ulog_file_backend_init( file_be,
                          table[id].name,
                          table[id].dir_path,
                          table[id].max_num,
                          table[id].max_size,
                          table[id].buf_size);

  ulog_file_backend_enable(file_be);
  return RT_EOK;
}
INIT_APP_EXPORT(dht_log_file_backend_init);

4.功能展示

相关推荐
切糕师学AI4 小时前
STM32是什么?
stm32·单片机·嵌入式硬件
普中科技6 小时前
【普中STM32F1xx开发攻略--标准库版】-- 第 12 章 STM32 时钟系统
stm32·单片机·嵌入式硬件·arm·时钟树·普中科技
齐落山大勇9 小时前
STM32的ADC(遥杆的控制)
stm32·单片机·嵌入式硬件
huaijin6229 小时前
ESP32在arduino环境下的离线安装 -- 理论上多个版本都有效
stm32·单片机·嵌入式硬件
云山工作室14 小时前
基于单片机的牧场奶牛养殖系统设计(论文+源码)
stm32·单片机·嵌入式硬件·毕业设计·毕设
CosimaLi21 小时前
STM32F10x硬件I2C
stm32·单片机·嵌入式硬件
GilgameshJSS1 天前
STM32H743-ARM例程36-DNS
c语言·arm开发·stm32·单片机·嵌入式硬件
Jie_jiejiayou1 天前
STM32F10xxx启动模式配置与ISP一键下载
stm32·isp·烧录模式
GilgameshJSS2 天前
STM32H743-ARM例程33-TOUCH
c语言·arm开发·stm32·单片机·嵌入式硬件