嵌入式日志库ulog的使用和解析

嵌入式日志信息保存调试(ulog)

获取

项目地址:https://github.com/rdpoor/ulog

uLog 为嵌入式微控制器或任何资源有限的系统提供结构化的日志记录机制。它继承了流行的 Log4cLog4j 平台背后的一些概念,但开销更低。

使用方法

ulog中的ulog.culog.h文件移植进入自己的工程。

  • 打开使能ulog函数使能,在ulog.h中
#define ULOG_ENABLED
  • 日志输出等级
typedef enum {
  ULOG_TRACE_LEVEL=100,
  ULOG_DEBUG_LEVEL,
  ULOG_INFO_LEVEL,
  ULOG_WARNING_LEVEL,
  ULOG_ERROR_LEVEL,
  ULOG_CRITICAL_LEVEL,
  ULOG_ALWAYS_LEVEL
} ulog_level_t;
  • main函数分析

记住一个点:自定义里面的等级A,只有我们使用的ULOG_XXX函数中XXX的等级 >= A 的时候,该函数才会被真正的执行

#include <stdio.h>
#include <string.h>
#include "ulog.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

// 用户自定义用于调试的函数函数
void my_console_logger(ulog_level_t severity, char *msg)
{
  printf("console: %s [%s]: %s\n",
         "time", // user defined function
         ulog_level_name(severity),
         msg);
}
// 用户自定义用于将日志存储进文件的函数
void my_file_logger(ulog_level_t severity, char *msg)
{
  int fd = open("file.txt",O_RWRD|O_CRATE,0655);
  printf(,"file: %s [%s]: %s\n",
         "time", // user defined function
         ulog_level_name(severity),
         msg)
  close(fd);
}

int main()
{
  int arg = 42;
    
  // ulog的初始化
  ULOG_INIT();

  // 订阅my_console_logger函数,给予相应的等级为 ULOG_WARNING_LEVEL
   ULOG_SUBSCRIBE(my_console_logger, ULOG_WARNING_LEVEL);

  // 订阅my_file_logger函数,给予相应的等级为 ULOG_DEBUG_LEVEL
  ULOG_SUBSCRIBE(my_file_logger, ULOG_DEBUG_LEVEL);
  
  // 只会执行 my_file_logger的函数,理由是 ULOG_WARNING_LEVEL > ULOG_INFO_LEVEL 的等级
  // 但是 ULOG_DEBUG_LEVEL < ULOG_INFO_LEVEL 的等级
  // 自定义函数相应等级只有小于等于的时候才会执行相应的操作函数
  ULOG_INFO("Info, arg=%d", arg);         // logs to file but not console
  // ULOG_WARNING_LEVEL > ULOG_DEBUG_LEVEL
  ULOG_CRITICAL("Critical, arg=%d", arg); // logs to file and console

  // 改变my_console_logger函数的操作等级为 ULOG_INFO_LEVEL
  ULOG_SUBSCRIBE(my_console_logger, ULOG_INFO_LEVEL);
  // 改变后两个自定义函数的操作等级都小于等于 ULOG_INFO_LEVEL,所以两个函数都会执行
  ULOG_INFO("Info, arg=%d", arg); // logs to file and console

  // 取消 my_file_logger 函数的订阅操作
  ULOG_UNSUBSCRIBE(my_file_logger);
  // 其中my_file_logger取消了,又 ULOG_INFO_LEVEL >= ULOG_INFO_LEVEL, 所以打印调试信息
  ULOG_INFO("Info, arg=%d", arg); 
}

自定义日志函数编写

  • windows
char* get_timestamp()
{
    time_t ti_t;
    struct tm s_tm;

    time(&ti_t);
    localtime_s(&s_tm, &ti_t);

    char* time_chr = (char*)malloc(sizeof(char)*20);
    memset(time_chr,0,20);
    sprintf(time_chr, "%d-%d-%d %d:%d:%d",
        s_tm.tm_year + 1900, s_tm.tm_mon + 1, s_tm.tm_mday, 
        s_tm.tm_hour, s_tm.tm_min, s_tm.tm_sec);
    return time_chr;
}

void my_console_logger(ulog_level_t severity, const char* msg) {
    char *get_time = get_timestamp();
    printf("%s [%s]: %s\n",
        get_time,    // user defined function
        ulog_level_name(severity),
        msg);
    free(get_time);
}
  • linux
char *get_timestamp()
{
    time_t tt;
    struct tm *t;
    time(&tt);
    char *time_chr = (char *)malloc(sizeof(char) * 20);
    memset(time_chr, 0, 20);
    t = localtime(&tt);

    sprintf(time_chr, "%d-%d-%d %d:%d:%d", t->tm_year+1900,t->tm_mon + 1,
                t->tm_mday,(t->tm_hour + 15)%24,t->tm_min,t->tm_sec);
    return time_chr;
}

void my_console_logger(ulog_level_t severity, const char *msg)
{

    char *get_time = get_timestamp();
    printf("%s [%s]: %s\n",
           get_time, // user defined function
           ulog_level_name(severity),
           msg);
    free(get_time);
}

void my_file_logger(ulog_level_t severity, const char *msg)
{
    char *get_time = get_timestamp();
    FILE* fp = fopen("my_test.txt", "a+"); // 打开文件my_test.txt 这里文件可以跟改为时间
    fprintf(fp, "%s [%s]: %s\n",
            get_time, // user defined function
            ulog_level_name(severity),
            msg);
    free(get_time);
}

执行后,文件中的内容类似以下,这个格式是按照自己喜好在日志函数中自己编写。

2023-4-18 20:23:14 [INFO]: Info, arg=42
2023-4-18 20:23:14 [CRITICAL]: main.c:67 arg=42
2023-4-18 20:23:14 [INFO]: main.c:66 arg=42

源码分析

  • ulog_message
#define ULOG_DEBUG(...) ulog_message(ULOG_DEBUG_LEVEL, __VA_ARGS__)
// 这里的...表示的是可变参数  __VA_ARGS__ 这个也是对应的表示可变参数 这里也可以改为
// #define ULOG_DEBUG(...) ulog_message(ULOG_DEBUG_LEVEL, const char* fmt, ...)

void ulog_message(ulog_level_t severity, const char* fmt, ...) {
    // va_list 是一个字符指针,可以理解为指向当前参数的一个指针,取参必须通过这个指针进行
    va_list ap;
    int i;
    // 然后应该对ap 进行初始化,让它指向可变参数表里面的第一个参数,也就是const char* fmt
    va_start(ap, fmt);
    // 依次获取ap的类型,并进行sprintf函数类型功能组合成字符串,
    // 最大长度 ULOG_MAX_MESSAGE_LENGTH,超过会被截断
    vsnprintf(s_message, ULOG_MAX_MESSAGE_LENGTH, fmt, ap);
    // 将这个 ap 指针关掉,以免发生危险
    va_end(ap);

    for (i = 0; i < ULOG_MAX_SUBSCRIBERS; i++) {
        // 函数是依次执行的,那个函数先注册,那个先运行
        if (s_subscribers[i].fn != NULL) {
            // 只有使用的输出等级大于自定义函数等级的时候,就会执行
            if (severity >= s_subscribers[i].threshold) {
                s_subscribers[i].fn(severity, s_message);
            }
        }
    }
}
  • ulog_err_t
// 错误标志枚举
typedef enum {
    ULOG_ERR_NONE = 0,
    ULOG_ERR_SUBSCRIBERS_EXCEEDED,
    ULOG_ERR_NOT_SUBSCRIBED,
} ulog_err_t;
  • ulog_subscribe、ulog_unsubscribe
// 执行函数指针
typedef void (*ulog_function_t)(ulog_level_t severity, char* msg);
// 执行结构体
typedef struct {
    ulog_function_t fn;
    ulog_level_t threshold;
} subscriber_t;
// 执行结构体数组
#define ULOG_MAX_SUBSCRIBERS 6
static subscriber_t s_subscribers[ULOG_MAX_SUBSCRIBERS];

// search the s_subscribers table to install or update fn
// 订阅函数,也就是将函数放入执行结构体数组中,并赋予相应的输出等级
ulog_err_t ulog_subscribe(ulog_function_t fn, ulog_level_t threshold) {
    int available_slot = -1;
    int i;
    for (i = 0; i < ULOG_MAX_SUBSCRIBERS; i++) {
        if (s_subscribers[i].fn == fn) {
            // 执行结构体数组已经含有该执行函数,进行更新即可
            // already subscribed: update threshold and return immediately.
            s_subscribers[i].threshold = threshold;
            return ULOG_ERR_NONE;

        }
        else if (s_subscribers[i].fn == NULL) {
            // found a free slot
            // 找到数组中最近的一个未使用的执行结构体,赋予标志信息
            available_slot = i;
        }
    }
    // fn is not yet a subscriber.  assign if possible.
    if (available_slot == -1) {
        // 执行结构体数组中已经存储满
        return ULOG_ERR_SUBSCRIBERS_EXCEEDED;
    }
    // 将执行函数信息,放入执行结构体数组
    s_subscribers[available_slot].fn = fn;
    s_subscribers[available_slot].threshold = threshold;
    return ULOG_ERR_NONE;
}

// search the s_subscribers table to remove
// 取消订阅,就是将该函数从执行结构体数组中删除
ulog_err_t ulog_unsubscribe(ulog_function_t fn) {
    int i;
    for (i = 0; i < ULOG_MAX_SUBSCRIBERS; i++) {
        if (s_subscribers[i].fn == fn) {
            s_subscribers[i].fn = NULL;    // mark as empty
            return ULOG_ERR_NONE;
        }
    }
    return ULOG_ERR_NOT_SUBSCRIBED;
}
  • ulog_level_name
// 获取相应的输出等级字符串
const char* ulog_level_name(ulog_level_t severity) {
    switch (severity) {
    case ULOG_TRACE_LEVEL: return "TRACE";
    case ULOG_DEBUG_LEVEL: return "DEBUG";
    case ULOG_INFO_LEVEL: return "INFO";
    case ULOG_WARNING_LEVEL: return "WARNING";
    case ULOG_ERROR_LEVEL: return "ERROR";
    case ULOG_CRITICAL_LEVEL: return "CRITICAL";
    case ULOG_ALWAYS_LEVEL: return "ALWAYS";
    default: return "UNKNOWN";
    }
}
03-09
<think>好的,我需要回答用户关于SITL(Software In The Loop)的使用教程配置指南的问题。首先,我得确保自己正确理解SITL的概念。根据用户提供的引用,SITL是一种软件在环模拟技术,允许在模拟环境中运行真实代码,比如在无人机开发中,PX4固件可以在Gazebo等模拟器中测试,而无需硬件[^1][^3][^4]。 接下来,我应该整理SITL的基本概念。用户提供的引用中提到,SITL通过硬件抽象层(如AP_HAL)实现跨平台兼容,模拟传感器硬件接口[^2]。这可能意味着配置时需要设置环境变量或特定库来模拟硬件。 然后,用户需要教程配置指南。根据引用,QGroundControl与AirSim的整合指南可能是一个起点。同时,CrazySim的例子说明可能需要使用Gazebo模型预测控制进行测试。此外,PX4的SITL配置提到模拟器后台运行,只需轻量级进程重启,这减少了调试时间[^4]。 我需要分步骤解释配置过程。可能包括安装依赖项、下载固件、配置模拟器(如Gazebo)、设置环境变量、启动SITL实例,以及连接地面控制站如QGroundControl。还要提到调试技巧,比如快速重启PX4进程[^4]。 同时,用户可能遇到的问题包括环境配置错误、模拟器与固件版本不兼容,或者传感器数据模拟不准确。需要提醒用户检查日志,确保依赖项版本匹配,并参考官方文档。 最后,生成相关问题时要涵盖SITL的优势、硬件抽象层的作用、常用工具、调试技巧以及与HITL的区别。这些可以帮助用户进一步理解相关概念应用。</think>### Software In The Loop (SITL) 使用教程配置指南 #### 1. SITL 基本概念 SITL 是一种通过软件模拟硬件环境的测试方法,允许开发者在计算机上直接运行嵌入式代码(如无人机飞控系统),无需依赖物理硬件。其核心是通过硬件抽象层(如 AP_HAL)实现代码的跨平台兼容性,模拟传感器输入执行器输出。例如,PX4 飞控的 SITL 模式可在 Gazebo 或 AirSim 中运行,实时验证控制算法传感器数据处理逻辑[^3]。 #### 2. 配置步骤 **环境准备**(以 PX4-SITL 为例): 1. **安装依赖项**: ```bash sudo apt-get install git cmake python3-pip pip3 install px4tools ``` 2. **下载 PX4 固件**: ```bash git clone https://github.com/PX4/PX4-Autopilot.git cd PX4-Autopilot make submodulesclean ``` 3. **配置模拟器**(如 Gazebo): ```bash source Tools/setup_gazebo.bash make px4_sitl_default gazebo ``` 4. **启动 SITL 实例**: ```bash ./Tools/sitl_run.sh ./build/px4_sitl_default/bin/px4 none gazebo iris ``` 此时 Gazebo 将加载无人机模型,并显示传感器数据与控制信号。 **连接地面站**: - 使用 QGroundControl 连接本地 UDP 端口(默认 14550),实时监控飞行状态或发送指令。 #### 3. 调试与优化 - **快速迭代**:修改代码后仅需重启 PX4 进程,无需重新启动模拟器[^4]。 - **日志分析**:通过 `ulog` 文件记录飞行数据,使用 `pyulog` 工具解析异常。 - **传感器模拟**:在 `init.d-posix/rcS` 中调整模拟传感器的噪声参数,贴近真实场景。 #### 4. 典型问题 - **端口冲突**:若 QGroundControl 无法连接,检查防火墙或更换 UDP 端口。 - **依赖版本冲突**:确保 Gazebo 版本与 PX4 兼容(推荐 Gazebo 9 或 11)。 - **硬件抽象层配置**:若控制逻辑异常,检查 AP_HAL 的模拟执行器响应函数。 --- ###
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值