一种简易的嵌入式设备系统日志记录方法

在嵌入式设备应用场景中,系统日志时常可以监控设备软件的运行状态,及时记录问题点以及关键信息,方便开发人员后期定位以及解决问题。
系统日志
本文将讲述一种简易的系统日志记录方法,用于保存设备的系统日志,视具体嵌入式设备情况而定,可存储在mcu内部flash、外部flash、eeprom等,本文采用外部flash作为示例展开介绍。
思路分析
对于系统日志可以当成文件系统,可以划分为三个重要部分:目录区、参数区、日志区。目录区:根据日期进行归类,记录当天的日志的存储地址、日志索引、日志大小,通过目录可以获取整个日志文件的概况;参数区:存储记录日志写位置、目录项个数、写状态等参数;日志区:这是我们主要的存储区,记录系统的日志,支持环写。这三个区域都需要占用部分内存,可以自行分配大小。
实现的效果如下图所示,设置通过指令可查询到整个日志目录区的概况。
查询系统日志目录:
at+catalog?
log_id:存储日志按日期分类,该id用于查询对应日期日志,从1开始计数;
log_date:系统日志存储日期;
log_addr:系统日志存储外部flash地址;
log_offset:系统日志存储偏移量(各日期日志大小,单位:字节)。
另外提供移除系统日志(清除日志目录)指令:at+rmlog,后面将讲述具体实现。
flash内存划分
flash内存需要看具体设备进行合理划分,目录区、参数区与日志区实现环形存储,延长擦写寿命。
#define flash_sector_size     ((uint32_t)0x001000)#define flash_block_32k_size  ((uint32_t)0x008000)#define flash_block_64k_size  ((uint32_t)0x010000)#define sector_mask           (flash_sector_size - 1)    /*扇区掩码 ------*/#define sector_base(addr)     (addr & (~sector_mask))    /*扇区的基地址 --*/#define sector_offset(addr)   (addr & sector_mask)       /*扇区内的偏移 --*/#define block_32k_base(addr)  (addr & (~(flash_block_32k_size)))#define block_64k_base(addr)  (addr & (~(flash_block_64k_size)))typedef enum {    flash_block_4k  = 0,    /**< flash erase block size 4k */    flash_block_32k = 1,    /**< flash erase block size 32k */    flash_block_64k = 2     /**< flash erase block size 64k */}flash_block_t;/* flash 空间索引 */typedef enum{    flash_catalog_zone = 0,    flash_syslog_para_zone,    flash_syslog_zone,    flash_zonex,}flash_zone_e;typedef struct{    flash_zone_e zone;    uint32_t start_address;    uint32_t end_address;}flash_table_t;/* 地址划分 */static const flash_table_t flash_table[] = {  { .zone = flash_catalog_zone,      .start_address = 0x03200000,  .end_address = 0x032fffff},    { .zone = flash_syslog_para_zone,  .start_address = 0x03300000,  .end_address = 0x033fffff},    { .zone = flash_syslog_zone,       .start_address = 0x03400000,  .end_address = 0x03ffffff},  };  
flash底层实现擦除、读写操作接口,由读者自行实现。
flash_table_t *get_flash_table(flash_zone_e zone){    int i = 0;    for (i = 0; i  flash_table_tmp->end_address)         return -1;    return bsp_spi_flash_erase(address, block_type);}int flash_write(flash_zone_e zone, uint32_t address, const uint8_t*data, uint32_t length){    flash_table_t *flash_table_tmp = get_flash_table(zone);         if (flash_table_tmp == null)        return -1;    if ((address start_address) || ((address + length) > flash_table_tmp->end_address))        return -1;    return bsp_spi_flash_buffer_write(address, (uint8_t *)data, length);}int flash_read(flash_zone_e zone, uint32_t address, uint8_t*buffer, uint32_t length){    flash_table_t *flash_table_tmp = get_flash_table(zone);      if (flash_table_tmp == null)        return -1;    if ((address start_address) || ((address + length) > flash_table_tmp->end_address))        return -1;        bsp_spi_flash_buffer_read(buffer, address, length);    return 0;}  参数与结构体定义
日志数据存储时间戳,便于问题定位,需要实现rtc接口调用。typedef struct {    uint16_t  year;    /* 年份:yyyy */    uint8_t   month;   /* 月份:mm */    uint8_t   day;     /* 日:dd */    uint8_t   hour;    /* 小时:hh */    uint8_t   minute;  /* 分钟:mm */    uint8_t   second;  /* 秒:ss */}time_t;   int bsp_rtc_get_time(time_t *date);  
参数区应当保证数据的正确性,应加入参数校验存储,定义校验结构体。
#define system_log_magic_param  0x87654321  /* 日志参数标识符 */typedef struct {    uint32_t magic;  /* 参数标识符 */    uint16_t crc;    /* 校验值 */    uint16_t len;    /* 参数长度 */} single_sav_t;  
参数区需记录当前日志记录的写位置,以及目录项个数,还有日志区和目录区环写状态,并且存储最新时间等等。
/* 日志区参数 */typedef struct {    uint32_t write_pos;             /* 写位置 */    uint32_t catalog_num;           /* 目录项个数 */    uint8_t  log_cyclic_status;     /* 系统日志环形写状态 */       uint8_t  catalog_cyclic_status; /* 日志目录环形写状态 */    time_t   log_latest_time;       /* 存储最新时间 */}system_log_t;/* 目录区参数 */typedef struct {    uint32_t log_id;     /* 日志索引 */     uint32_t log_addr;   /* 日志地址 */    uint32_t log_offset; /* 日志偏移大小,单位:字节 */    time_t   log_time;   /* 日志存储时间 */}system_catalog_t;/* 系统日志参数 */typedef struct {    single_sav_t crc_val;    system_log_t system_log;    system_catalog_t system_catalog;}sys_log_param_t;typedef struct {    uint8_t  system_log_print_enable; /* 系统日志打印使能 */    uint16_t system_log_print_id;     /* 打印指定id系统日志 */    uint32_t system_log_param_addr;   /* 当前日志写地址 */} sys_ram_t;sys_ram_t sysram;sys_log_param_t syslogparam;sys_ram_t *gp_sys_ram = &sysram;sys_log_param_t *gp_sys_log = &syslogparam;  实现接口说明
crc校验接口,可以自定义实现。/* 16位crc校验高位表 */static const uint8_t auchcrchi[] = {0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40};/* 16位crc校验低位表 */static const uint8_t auchcrclo[] = {0x00,0xc0,0xc1,0x01,0xc3,0x03,0x02,0xc2,0xc6,0x06,0x07,0xc7,0x05,0xc5,0xc4,0x04,0xcc,0x0c,0x0d,0xcd,0x0f,0xcf,0xce,0x0e,0x0a,0xca,0xcb,0x0b,0xc9,0x09,0x08,0xc8,0xd8,0x18,0x19,0xd9,0x1b,0xdb,0xda,0x1a,0x1e,0xde,0xdf,0x1f,0xdd,0x1d,0x1c,0xdc,0x14,0xd4,0xd5,0x15,0xd7,0x17,0x16,0xd6,0xd2,0x12,0x13,0xd3,0x11,0xd1,0xd0,0x10,0xf0,0x30,0x31,0xf1,0x33,0xf3,0xf2,0x32,0x36,0xf6,0xf7,0x37,0xf5,0x35,0x34,0xf4,0x3c,0xfc,0xfd,0x3d,0xff,0x3f,0x3e,0xfe,0xfa,0x3a,0x3b,0xfb,0x39,0xf9,0xf8,0x38,0x28,0xe8,0xe9,0x29,0xeb,0x2b,0x2a,0xea,0xee,0x2e,0x2f,0xef,0x2d,0xed,0xec,0x2c,0xe4,0x24,0x25,0xe5,0x27,0xe7,0xe6,0x26,0x22,0xe2,0xe3,0x23,0xe1,0x21,0x20,0xe0,0xa0,0x60,0x61,0xa1,0x63,0xa3,0xa2,0x62,0x66,0xa6,0xa7,0x67,0xa5,0x65,0x64,0xa4,0x6c,0xac,0xad,0x6d,0xaf,0x6f,0x6e,0xae,0xaa,0x6a,0x6b,0xab,0x69,0xa9,0xa8,0x68,0x78,0xb8,0xb9,0x79,0xbb,0x7b,0x7a,0xba,0xbe,0x7e,0x7f,0xbf,0x7d,0xbd,0xbc,0x7c,0xb4,0x74,0x75,0xb5,0x77,0xb7,0xb6,0x76,0x72,0xb2,0xb3,0x73,0xb1,0x71,0x70,0xb0,0x50,0x90,0x91,0x51,0x93,0x53,0x52,0x92,0x96,0x56,0x57,0x97,0x55,0x95,0x94,0x54,0x9c,0x5c,0x5d,0x9d,0x5f,0x9f,0x9e,0x5e,0x5a,0x9a,0x9b,0x5b,0x99,0x59,0x58,0x98,0x88,0x48,0x49,0x89,0x4b,0x8b,0x8a,0x4a,0x4e,0x8e,0x8f,0x4f,0x8d,0x4d,0x4c,0x8c,0x44,0x84,0x85,0x45,0x87,0x47,0x46,0x86,0x82,0x42,0x43,0x83,0x41,0x81,0x80,0x40};/* 实现crc功能函数 */static uint16_t crc16(uint8_t* puchmsg, uint16_t usdatalen){    uint8_t  uchcrchi = 0xff;    uint8_t  uchcrclo = 0xff;    uint16_t uindex;        while(usdatalen --)     {        uindex = uchcrchi^*(puchmsg ++);        uchcrchi = uchcrclo^auchcrchi[uindex];        uchcrclo = auchcrclo[uindex];    }        return uchcrchi crc_val.len = sizeof(sys_log_param_t) - sizeof(single_sav_t);    gp_sys_log->crc_val.crc = crc16(&pdata[sizeof(single_sav_t)], gp_sys_log->crc_val.len);    start_addr = gp_sys_ram->system_log_param_addr;    /* 剩余内存不够写,则重新从起始地址开始写,实现环形存储功能  */    if ((start_addr + len) > flash_tmp->end_address)     {         start_addr = flash_tmp->start_address;    }    gp_sys_ram->system_log_param_addr = start_addr + len;    /* 首地址存储,擦除整个系统日志参数存储区,如果划分的内存较大,可能出现第一次擦写等待时间较长,       但实际应用嵌入式设备应该不会占用太多的内存存储系统日志,只当为辅助使用,有额外应用可自行实现 */    if (flash_tmp->start_address == start_addr)     {        /*for (i = flash_tmp->start_address; i end_address; i+= flash_sector_size)             flash_erase(flash_syslog_para_zone, sector_base(i), flash_block_4k);        */        addr = flash_tmp->start_address;        do         {            if ((addr + flash_block_64k_size) end_address)             {                flash_erase(flash_syslog_para_zone, block_64k_base(i), flash_block_64k);                addr += flash_block_64k_size;            }             else if ((addr + flash_block_32k_size) end_address)             {                flash_erase(flash_syslog_para_zone, block_32k_base(i), flash_block_32k);                addr += flash_block_32k_size;            }             else if ((addr + flash_sector_size) end_address)             {                flash_erase(flash_syslog_para_zone, sector_base(i), flash_block_4k);                addr += flash_sector_size;            }             else             {                break;            }        }         while (addr end_address);     }    remainbyte = flash_sector_size - (start_addr % flash_sector_size);    if (remainbyte > len)     {        remainbyte = len;    }    while (1)     {        flash_write(flash_syslog_para_zone, start_addr, pdata, remainbyte);        if (remainbyte == len)         {            break;        }         else         {            pdata += remainbyte;            start_addr += remainbyte;            len -= remainbyte;            remainbyte = (len > flash_sector_size) ? flash_sector_size : len;        }    }}  
导入系统日志默认参数接口,初始化默认参数或者移除日志。
void load_system_log_default_param(void){    /* 系统日志默认参数 */    /* 目录环写状态标志 */    gp_sys_log->system_log.catalog_cyclic_status = 0x00;    /* 目录项个数 */    gp_sys_log->system_log.catalog_num = 0;    /* 日志环写标志 , 1:环写状态 */    gp_sys_log->system_log.log_cyclic_status = 0;    /* 设置默认值,实际会重新从rtc获取最新时间 */    gp_sys_log->system_log.log_latest_time.year = 2019;    gp_sys_log->system_log.log_latest_time.month = 5;    gp_sys_log->system_log.log_latest_time.day = 8;    gp_sys_log->system_log.log_latest_time.hour = 13;    gp_sys_log->system_log.log_latest_time.minute = 14;    gp_sys_log->system_log.log_latest_time.second = 10;    /* 日志写位置从0开始 */    gp_sys_log->system_log.write_pos = 0;        gp_sys_log->system_catalog.log_addr = 0;    gp_sys_log->system_catalog.log_id = 0;    gp_sys_log->system_catalog.log_offset = 0;    gp_sys_log->system_catalog.log_time.year = 2019;    gp_sys_log->system_catalog.log_time.month = 5;    gp_sys_log->system_catalog.log_time.day = 8;    gp_sys_log->system_catalog.log_time.hour = 12;    gp_sys_log->system_catalog.log_time.minute = 12;    gp_sys_log->system_catalog.log_time.second = 14;        gp_sys_log->crc_val.magic = system_log_magic_param;    /* 导入默认参数后进行保存 */    save_system_log_param();}  
设备开机或者复位都会进行导入系统日志参数操作,恢复日志读写参数,参数区为频繁读写操作区域,每一次写操作都会进行一次偏移,有效的导入参数方法是从参数区结束地址到起始地址进行扫描,扫描不到合法的参数则会导入默认日志参数。
/* 参数初始化,在终端启动时调用 */int load_system_log_param(void){    uint32_t i = 0;    single_sav_t psav;    uint32_t end_addr;    uint32_t interal = sizeof(sys_log_param_t);    int data_len = sizeof(sys_log_param_t) - sizeof(single_sav_t);    uint8_t *pram = (uint8_t *)&syslogparam;    flash_table_t *flash_tmp = get_flash_table(flash_syslog_para_zone);        end_addr =flash_tmp->end_address - (flash_tmp->end_address - flash_tmp->start_address) % interal;    for (i = end_addr - interal; i > flash_tmp->start_address; i -= interal)     {        flash_read(flash_syslog_para_zone, i, (uint8_t *)&psav, sizeof(single_sav_t));        if ((psav.magic == system_log_magic_param) && (psav.len ==data_len))         {               flash_read(flash_syslog_para_zone, i + sizeof(single_sav_t), &pram[sizeof(single_sav_t)], data_len);            if (psav.crc != crc16(&pram[sizeof(single_sav_t)], data_len))                 continue;            gp_sys_ram->system_log_param_addr = i;            log_info(load system log param addr[0x%08x]!, gp_sys_ram->system_log_param_addr);            return 0;        }    }        /* 扫描不到合法的参数,导入默认系统日志参数 */    load_system_log_default_param();    /* 获取日志写地址 */    gp_sys_ram->system_log_param_addr = flash_tmp->start_address;    log_info(load system log param addr(default)[0x%08x]!, gp_sys_ram->system_log_param_addr);    return 1;}  
读写系统日志目录接口,读写指定日志索引目录信息。实际实现会定义最新的目录信息存储在日志参数区,当日期发生改变,则表示当前目录信息已经完结,将最新的目录信息录入日志目录区保存,最多每天写入一次目录区。
/* 读取日志目录区指定日志索引目录信息 */int system_catalog_read(system_catalog_t *catalog, uint32_t id){    uint32_t addr;    int rlen = sizeof(system_catalog_t);    uint8_t *pbuf = (uint8_t *)catalog;    flash_table_t *flash_tmp = get_flash_table(flash_catalog_zone);    if (0 == id)         return -1;    addr = flash_tmp->start_address + (rlen * (id - 1));    if (addr > flash_tmp->end_address)         return -1;            return flash_read(flash_catalog_zone, addr, pbuf, rlen);}/* 写日志目录区目录信息 */int system_catalog_write(system_catalog_t *catalog, uint32_t id){    uint32_t start_offset;    uint32_t start_addr;    uint32_t start_base;    uint32_t remainbyte;    int wlen = sizeof(system_catalog_t);    uint8_t *pdata = (uint8_t *)catalog;    flash_table_t *flash_tmp = get_flash_table(flash_catalog_zone);        if (0 == id)         return -1;    start_addr = flash_tmp->start_address + wlen * (id - 1);    if ((start_addr + wlen) > flash_tmp->end_address)     {        start_addr = flash_tmp->start_address;    }        /* 本扇区剩余空间大小 */    remainbyte = flash_sector_size - (start_addr % flash_sector_size);    /* 写入数据长度小于本扇区剩余长度,直接写入 */    if (remainbyte > wlen)     {        remainbyte = wlen;    }    /* 写目录次数不会太频繁,视具体情况改写操作实现 */    while (1)     {        start_base = sector_base(start_addr);        start_offset = sector_offset(start_addr);        flash_read(flash_catalog_zone, start_base, sector_buf, flash_sector_size);        flash_erase(flash_catalog_zone, start_base, flash_block_4k);        memcpy((char *)§or_buf[start_offset], pdata, remainbyte);        flash_write(flash_catalog_zone, start_base, sector_buf, flash_sector_size);        if (remainbyte == wlen)         {            break;        }         else         {            pdata += remainbyte;            start_addr += remainbyte;            wlen -= remainbyte;            remainbyte = (wlen > flash_sector_size) ? flash_sector_size : wlen;        }    }        return 0;}  
打印系统日志目录区信息,可实现通过指令查询到目录区信息。
int system_catalog_all_print(void){    int i = 0;    system_catalog_t catalog;    printf(system log command information:);    printf(query specifies log : at+catalog=);    printf(query all log : at+catalog=);    printf(query all system catalog:);    printf(log_id  log_date  log_addr  log_offset );    for (i = 0; i system_log.catalog_num; i++)      {        /* 当前最新目录信息 */          if (i == (gp_sys_log->system_catalog.log_id - 1))         {            catalog = gp_sys_log->system_catalog; /* 获取当前最新目录信息 */        }         else         {            system_catalog_read(&catalog, i + 1);        }        printf(%d  %04d-%02d-%02d  0x%08x  %d ,             catalog.log_id, catalog.log_time.year, catalog.log_time.month, catalog.log_time.day,             catalog.log_addr, catalog.log_offset);        memset((char *)&catalog, 0, sizeof(system_catalog_t));    }    return 0;}  
读取指定日志目录索引信息接口,可指定日志索引或者读取全部日志数据。
int system_log_task(int argc){    int rlen = 0;    uint32_t offset, start_addr, end_addr;    system_catalog_t catalog;    flash_table_t *flash_tmp =get_flash_table(flash_syslog_zone);        if (0 == gp_sys_ram->system_log_print_enable)         return 1;    gp_sys_ram->system_log_print_enable = 0x00;    if (gp_sys_ram->system_log_print_id == all_log_print)     {        /* log回环写标志,打印整个log存储区 */        if (0x01 == gp_sys_log->system_log.log_cyclic_status)         {             start_addr = flash_tmp->start_address;            end_addr = flash_tmp->end_address;            offset = end_addr - start_addr;        }         else         {            start_addr = flash_tmp->start_address;            end_addr = start_addr + gp_sys_log->system_log.write_pos;            offset = gp_sys_log->system_log.write_pos;        }    }     else     {   /* 读取指定id日志 */        if (gp_sys_ram->system_log_print_id == gp_sys_log->system_catalog.log_id)         {            catalog = gp_sys_log->system_catalog;        }         else         {            system_catalog_read(&catalog, gp_sys_ram->system_log_print_id);        }        start_addr = catalog.log_addr;        offset = catalog.log_offset;    }     if (0 == offset)        return 1;    while (1)     {        rlen = (offset > 512) ? 512 : offset;        system_log_read(sector_buf, start_addr, rlen);        hal_delay(80);        /* 目录信息通过调式串口打印 */        bsp_debug_send(sector_buf, rlen);        start_addr += rlen;        offset -= rlen;        if (0 == offset)             break;    }    return 0;}  
存储系统日志接口,实现更新存储日期,当写位置为扇区地址,则擦除一个扇区作为存储日志,这样避免每写一次就擦除一次。
int system_log_write(uint8_t *wbuf, int wlen){    uint32_t start_addr;    uint8_t *pdata = wbuf;    uint32_t remainbyte;    int system_catalog_max_id;    flash_table_t *flash_tmp =get_flash_table(flash_syslog_zone);        /* 计算目录区的最大存储目录项个数 */    system_catalog_max_id = ((flash_tmp->end_address - flash_tmp->start_address) / sizeof(system_catalog_t));    start_addr = flash_tmp->start_address + gp_sys_log->system_log.write_pos;    /* 存储数据地址大于规划内存地址范围处理 */    if ((start_addr + wlen) > flash_tmp->end_address)     {         start_addr = flash_tmp->start_address;        /* 写位置偏移量重置 */        gp_sys_log->system_log.write_pos = 0;        /* log回环存储标志置位 */        gp_sys_log->system_log.log_cyclic_status = 0x01;     }    /* 写位置偏移 */    gp_sys_log->system_log.write_pos += wlen;     if ((gp_sys_log->system_log.log_latest_time.year != gp_sys_log->system_catalog.log_time.year) ||        (gp_sys_log->system_log.log_latest_time.month != gp_sys_log->system_catalog.log_time.month) ||        (gp_sys_log->system_log.log_latest_time.day != gp_sys_log->system_catalog.log_time.day))     {        /* 日期改变,记录目录信息,当log_id为0,则不写入 */        system_catalog_write(&gp_sys_log->system_catalog, gp_sys_log->system_catalog.log_id);        /* 记录存储日期 */        gp_sys_log->system_catalog.log_time = gp_sys_log->system_log.log_latest_time;                if ((gp_sys_log->system_catalog.log_id + 1) >= system_catalog_max_id)         {            gp_sys_log->system_log.catalog_num = system_catalog_max_id; /* 目录循环写,目录数应为最大 */            gp_sys_log->system_log.catalog_cyclic_status = 1; /* 目录回环写标志 */        }         else         {            if (0 == gp_sys_log->system_log.catalog_cyclic_status)             {                /* 获取目录数 */                gp_sys_log->system_log.catalog_num = gp_sys_log->system_catalog.log_id + 1;             }        }                /* 存储最新目录项信息 */        gp_sys_log->system_catalog.log_id = (gp_sys_log->system_catalog.log_id + 1) % system_catalog_max_id;        gp_sys_log->system_catalog.log_addr = start_addr;        gp_sys_log->system_catalog.log_offset = wlen;     }     else     {        gp_sys_log->system_catalog.log_offset += wlen;     }        /* 写位置为存储起始地址并且不为扇区首地址 */    if ((flash_tmp->start_address == start_addr) && (sector_offset(flash_tmp->start_address)))    {        flash_read(flash_syslog_zone, sector_base(start_addr), sector_buf, flash_sector_size);        flash_erase(flash_syslog_zone, sector_base(start_addr), flash_block_4k);        /* 将扇区头部至起始地址区间的数据回写 */        flash_write(flash_syslog_zone, sector_base(start_addr), §or_buf[0], sector_offset(start_addr));     }    /* 写位置为扇区首地址,则擦除一个扇区的存储区    */    if (0 == sector_offset(start_addr))     {        flash_erase(flash_syslog_zone, sector_base(start_addr), flash_block_4k);    }    /* 本扇区剩余空间大小 */    remainbyte = flash_sector_size - (start_addr % flash_sector_size);    /* 写入数据长度小于本扇区剩余长度,直接写入 */    if (remainbyte > wlen)     {        remainbyte = wlen;    }    while (1)     {        flash_write(flash_syslog_zone, start_addr, pdata, remainbyte);        if (remainbyte == wlen)         {            break;        }         else         {            pdata += remainbyte;            start_addr += remainbyte;            wlen -= remainbyte;            remainbyte = (wlen > flash_sector_size) ? flash_sector_size : wlen;            /* 扇区首地址则擦除整个扇区,该扇区数据不保存 */            if (0 == sector_offset(start_addr))             {                flash_erase(flash_syslog_zone, sector_base(start_addr), flash_block_4k);            }        }    }    /* 环形存储参数 */    save_system_log_param();    return 0;}  系统调试对接
为了更好记录系统日志,将应用调试等级结合一块,实现记录错误调试信息以及需要保存的关键信息。定义的调试等级有:关闭调试等级、错误调试等级、警告调试等级、关键调试等级、debug调试等级,而log_record_level将主动保存日志并输出信息,log_error_level会存储对应的日志信息,但需要根据应用调试等级输出信息。设置与读取应用调试等级由读者自行定义。#define log_close_level  0x00  /* 关闭调试信息 */#define log_error_level  0x01  /* 错误调试信息 */#define log_warn_level   0x02  /* 警告调试信息 */#define log_info_level   0x03  /* 关键调试信息 */#define log_debug_level  0x04  /* debug调试信息 */#define log_record_level 0x10  /* 保存日志并输出信息 */ #define log_print_level  0xff#define set_log_level(level) (gp_sys_param->system_print_level = level)#define get_log_level()      (gp_sys_param->system_print_level)#define log_debug(fmt, args...)  log_format(log_debug_level, fmt, ##args)#define log_info(fmt, args...)   log_format(log_info_level, fmt, ##args)#define log_warn(fmt, args...)   log_format(log_warn_level, fmt, ##args)#define log_error(fmt, args...)  log_format(log_error_level, fmt, ##args)#define log_record(fmt, args...) log_format(log_record_level, fmt, ##args)#define printf(fmt, args...)     log_format(log_print_level, fmt, ##args)typedef struct {    int level;    char *fmt_str;}system_print_fmt_t;system_print_fmt_t system_print_fmt_list[] = {    { .level = log_error_level,  .fmt_str = :},    { .level = log_warn_level,   .fmt_str = :},    { .level = log_info_level,   .fmt_str = :},    { .level = log_debug_level,  .fmt_str = :},    { .level = log_record_level, .fmt_str = :},};int log_format(uint8_t level, const char *fmt, ...){    #define time_prefix_size  (21)    #define print_max_size    (1024 + time_prefix_size)        va_list args;    int num = 0, i = 0, fmt_index = 0;    int fmt_str_len = 0, ret = -1;    int file_str_len = 0, line_str_len = 0;    char line_buf[20] = {0};    static char buf[print_max_size];    static queuehandle_t sem = null;    time_t time = {0};        /* 针对os系统 */    if (null == sem)     {          sem = xsemaphorecreatecounting(1, 1); /* always think of success */    }        xsemaphoretake(sem, portmax_delay);    ret = -1;    fmt_str_len = 0;    if (level != log_print_level)     {        if ((get_log_level() < level) && (level != log_record_level) && (level != log_error_level))            goto exit_end;        for (i = 0; i  system_print_fmt_list_max)         {            goto exit_end;        }        fmt_str_len = strlen(system_print_fmt_list[fmt_index].fmt_str);        strncpy((char *)&buf[time_prefix_size], system_print_fmt_list[fmt_index].fmt_str, fmt_str_len);    }    va_start(args, fmt);    num = vsnprintf((char *)&buf[fmt_str_len + time_prefix_size], print_max_size - fmt_str_len - time_prefix_size - 2, fmt, args);    va_end(args);    if (num <= 0)     {        goto exit_end;    }    if (level != log_print_level)     {        num += fmt_str_len;        buf[num + time_prefix_size] = '';        buf[num + time_prefix_size + 1] = '';        num += 2;    }    if ((get_log_level() system_log.log_latest_time = time;        system_log_write((uint8_t *)buf, num + time_prefix_size);    } exit_end:    xsemaphoregive(sem);    return ret;}  
结语
本文提供的一种简易嵌入式设备系统日志记录方法,代码量不多,实现简单,针对不同的设备需要合理规划内存使用,根据软件运行状态,合适加入调试信息并保存对应的日志信息,方便开发人员了解系统或软件运行状况,协助开发分析数据资源从而更好完善系统,提高定位以及解决问题的效果。


如何辨别视频线的好坏
智能微水测量仪的特性与参数
高性能线性霍尔传感器AH693实现全自动洗衣机水位控制
油烟在线监测仪,治理油烟污染刻不容缓
关于无人驾驶的未来的发展分析
一种简易的嵌入式设备系统日志记录方法
分布式能源行业的发展面临的挑战与机遇
Qorvo适用于5G的100MHzET 以低风险方式部署高性能5G设备
Reno3系列手机将于12月31日正式开售售价3399元起
尼康D5怎么样?尼康D5评测:ISO 3280000是数码相机当今之最
你不知道的STM32知识汇总
浪潮董事长发表更好实现数据共享文章
AI+和+AI的差别是什么?
AP2813 DCDC降压恒流芯片 两路输出 一路恒流 一路瀑闪 电动摩托汽车灯方案
焊点缺陷检测系统的简单介绍
锂电池发展趋势看翘,老牌韩系电池企业或因政治问题被中国排挤
苹果13有哪些机型
研究人员开发紫外线LED灯,可在30秒内灭掉新冠病毒
面向IPU、SmartNIC和5G网络的英特尔Agilex 7 FPGA和eASIC设备
单片机点亮数码管程序设计解析