[其他] 嵌入式系统日志记录的简易方法

[复制链接]
1212|10
 楼主| 豆杀包 发表于 2022-5-24 10:46 | 显示全部楼层 |阅读模式
很多场景都需要记录日志,在嵌入式系统中,特别是单片机这种存储资源有限的环境下,就需要一种轻量级的存储方法。

系统日志
在嵌入式设备应用场景中,系统日志时常可以监控设备软件的运行状态,及时记录问题点以及关键信息,方便开发人员后期定位以及解决问题。
本文将讲述一种简易的系统日志记录方法,用于保存设备的系统日志,视具体嵌入式设备情况而定,可存储在MCU内部Flash、外部Flash、EEPROM等,本文采用外部Flash作为示例展开介绍。

 楼主| 豆杀包 发表于 2022-5-24 10:47 | 显示全部楼层
思路分析对于系统日志可以当成文件系统,可以划分为三个重要部分:目录区、参数区、日志区。
  • 目录区:根据日期进行归类,记录当天的日志的存储地址、日志索引、日志大小,通过目录可以获取整个日志文件的概况;
  • 参数区:存储记录日志写位置、目录项个数、写状态等参数;
  • 日志区:这是我们主要的存储区,记录系统的日志,支持环写。这三个区域都需要占用部分内存,可以自行分配大小。


实现的效果如下图所示,设置通过指令可查询到整个日志目录区的概况。查询系统日志目录:AT+CATALOG?LOG_ID:存储日志按日期分类,该ID用于查询对应日期日志,从1开始计数;LOG_DATE:系统日志存储日期;LOG_ADDR:系统日志存储外部FLASH地址;LOG_OFFSET:系统日志存储偏移量(各日期日志大小,单位:字节)。 49358628c472267c64.png 查询指定日期系统日志:AT+CATALOG=<LOG_ID>LOG_ID:在查询系统日志目录时获取,当LOG_ID为0时,为查询整个系统日志。 86734628c472e26164.png 另外提供移除系统日志(清除日志目录)指令:AT+RMLOG,后面将讲述具体实现。
 楼主| 豆杀包 发表于 2022-5-24 10:48 | 显示全部楼层



FLASH内存划分FLASH内存需要看具体设备进行合理划分,目录区、参数区与日志区实现环形存储,延长擦写寿命。

  1. #define FLASH_SECTOR_SIZE      ((uint32_t)0x001000)
  2. #define FLASH_BLOCK_32K_SIZE    ((uint32_t)0x008000)
  3. #define FLASH_BLOCK_64K_SIZE    ((uint32_t)0x010000)
  4. #define SECTOR_MASK               (FLASH_SECTOR_SIZE - 1)         /*扇区掩码 ------*/
  5. #define SECTOR_BASE(addr)         (addr & (~SECTOR_MASK))        /*扇区的基地址 --*/
  6. #define SECTOR_OFFSET(addr)       (addr & SECTOR_MASK)           /*扇区内的偏移 --*/

  7. #define BLOCK_32K_BASE(addr)    (addr & (~(FLASH_BLOCK_32K_SIZE)))
  8. #define BLOCK_64K_BASE(addr)    (addr & (~(FLASH_BLOCK_64K_SIZE)))

  9. typedef enum {
  10.     FLASH_BLOCK_4K  = 0,          /**< flash erase block size 4k */
  11.     FLASH_BLOCK_32K = 1,          /**< flash erase block size 32k */
  12.     FLASH_BLOCK_64K = 2           /**< flash erase block size 64k */
  13. }flash_block_t;

  14. /* flash 空间索引 */
  15. typedef enum{
  16.     FLASH_CATALOG_ZONE = 0,
  17.     FLASH_SYSLOG_PARA_ZONE,
  18.     FLASH_SYSLOG_ZONE,
  19.     FLASH_ZONEX,
  20. }flash_zone_e;

  21. typedef struct{
  22.     flash_zone_e zone;
  23.     uint32_t start_address;
  24.     uint32_t end_address;
  25. }flash_table_t;

  26. /* 地址划分 */
  27. static const flash_table_t flash_table[] = {
  28.   { .zone = FLASH_CATALOG_ZONE,       .start_address = 0x03200000, .end_address = 0x032FFFFF},  
  29.   { .zone = FLASH_SYSLOG_PARA_ZONE,   .start_address = 0x03300000, .end_address = 0x033FFFFF},  
  30.   { .zone = FLASH_SYSLOG_ZONE,        .start_address = 0x03400000, .end_address = 0x03FFFFFF},  
  31. };


 楼主| 豆杀包 发表于 2022-5-24 10:49 | 显示全部楼层
Flash底层实现擦除、读写操作接口,由读者自行实现。
  1. flash_table_t *get_flash_table(flash_zone_e zone)
  2. {
  3.   int i = 0;
  4.   for (i = 0; i < flash_zone_count; i++) {
  5.     if (zone == flash_table[i].zone)
  6.       return (flash_table_t *)&flash_table[i];
  7.   }

  8.   return NULL;  
  9. }

  10. int flash_erase(flash_zone_e zone, uint32_t address, flash_block_t block_type)
  11. {
  12.   flash_table_t *flash_table_tmp = get_flash_table(zone);

  13.   if (flash_table_tmp == NULL)
  14.     return -1;

  15.   if (address < flash_table_tmp->start_address ||address > flash_table_tmp->end_address)
  16.     return -1;

  17.   return bsp_spi_flash_erase(address, block_type);
  18. }

  19. int flash_write(flash_zone_e zone, uint32_t address, const uint8_t*data, uint32_t length)
  20. {
  21.   flash_table_t *flash_table_tmp = get_flash_table(zone);

  22.   if (flash_table_tmp == NULL)
  23.      return -1;

  24.   if ((address < flash_table_tmp->start_address) ||((address + length) > flash_table_tmp->end_address))
  25.      return -1;

  26.   return bsp_spi_flash_buffer_write(address, (uint8_t *)data, length);
  27. }

  28. int flash_read(flash_zone_e zone, uint32_t address, uint8_t*buffer, uint32_t length)
  29. {
  30.   flash_table_t *flash_table_tmp = get_flash_table(zone);

  31.   if (flash_table_tmp == NULL)
  32.     return -1;

  33.   if ((address < flash_table_tmp->start_address) ||((address + length) > flash_table_tmp->end_address))
  34.     return -1;

  35.   bsp_spi_flash_buffer_read(buffer, address, length);
  36.   return 0;
  37. }


 楼主| 豆杀包 发表于 2022-5-24 10:50 | 显示全部楼层
参数与结构体定义日志数据存储时间戳,便于问题定位,需要实现RTC接口调用。

  1. typedef struct {
  2.   uint16_t   Year;    /* 年份:YYYY */
  3.   uint8_t    Month;    /* 月份:MM */
  4.   uint8_t    Day;    /* 日:DD */
  5.   uint8_t     Hour;    /* 小时:HH */
  6.   uint8_t     Minute;    /* 分钟:MM */
  7.   uint8_t   Second;    /* 秒:SS */
  8. }time_t;   

  9. int bsp_rtc_get_time(time_t *date);
参数区应当保证数据的正确性,应加入参数校验存储,定义校验结构体。
  1. #define SYSTEM_LOG_MAGIC_PARAM    0x87654321  /* 日志参数标识符 */
  2. typedef struct {
  3.   uint32_t magic;    /* 参数标识符 */
  4.   uint16_t crc;    /* 校验值 */
  5.   uint16_t len;    /* 参数长度 */
  6. } single_sav_t;
参数区需记录当前日志记录的写位置,以及目录项个数,还有日志区和目录区环写状态,并且存储最新时间等等。

  1. /* 日志区参数 */
  2. typedef struct {
  3.   uint32_t   write_pos;             /* 写位置 */
  4.   uint32_t   catalog_num;            /* 目录项个数 */
  5.   uint8_t    log_cyclic_status;    /* 系统日志环形写状态 */   
  6.   uint8_t    catalog_cyclic_status; /* 日志目录环形写状态 */
  7.   time_t     log_latest_time;     /* 存储最新时间 */
  8. }system_log_t;

  9. /* 目录区参数 */
  10. typedef struct {
  11.   uint32_t log_id;     /* 日志索引 */  
  12.   uint32_t log_addr;    /* 日志地址 */
  13.   uint32_t log_offset;  /* 日志偏移大小,单位:字节 */
  14.   time_t   log_time;    /* 日志存储时间 */
  15. }system_catalog_t;

  16. /* 系统日志参数 */
  17. typedef struct {
  18.   single_sav_t crc_val;
  19.   system_log_t system_log;
  20.   system_catalog_t system_catalog;
  21. }sys_log_param_t;

  22. typedef struct {
  23.   uint8_t system_log_print_enable; /* 系统日志打印使能 */
  24.   uint16_t system_log_print_id;    /* 打印指定id系统日志 */
  25.   uint32_t system_log_param_addr;  /* 当前日志写地址 */
  26. } sys_ram_t;

  27. sys_ram_t  SysRam;
  28. sys_log_param_t SysLogParam;

  29. sys_ram_t  *gp_sys_ram = &SysRam;
  30. sys_log_param_t *gp_sys_log = &SysLogParam;


 楼主| 豆杀包 发表于 2022-5-24 10:51 | 显示全部楼层
实现接口说明CRC校验接口,可以自定义实现。


  1. /* 16位CRC校验高位表 */
  2. static const uint8_t auchCRCHi[]={
  3. 0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,
  4. 0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,
  5. 0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,
  6. 0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,
  7. 0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,
  8. 0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,
  9. 0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,
  10. 0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,

  11. 0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,
  12. 0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,
  13. 0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,
  14. 0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,
  15. 0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,
  16. 0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,
  17. 0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,
  18. 0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40
  19. };

  20. /* 16位CRC校验低位表 */
  21. static const uint8_t auchCRCLo[]={
  22. 0x00,0xc0,0xc1,0x01,0xc3,0x03,0x02,0xc2,0xc6,0x06,0x07,0xc7,0x05,0xc5,0xc4,0x04,
  23. 0xcc,0x0c,0x0d,0xcd,0x0f,0xcf,0xce,0x0e,0x0a,0xca,0xcb,0x0b,0xc9,0x09,0x08,0xc8,
  24. 0xd8,0x18,0x19,0xd9,0x1b,0xdb,0xda,0x1a,0x1e,0xde,0xdf,0x1f,0xdd,0x1d,0x1c,0xdc,
  25. 0x14,0xd4,0xd5,0x15,0xd7,0x17,0x16,0xd6,0xd2,0x12,0x13,0xd3,0x11,0xd1,0xd0,0x10,
  26. 0xf0,0x30,0x31,0xf1,0x33,0xf3,0xf2,0x32,0x36,0xf6,0xf7,0x37,0xf5,0x35,0x34,0xf4,
  27. 0x3c,0xfc,0xfd,0x3d,0xff,0x3f,0x3e,0xfe,0xfa,0x3a,0x3b,0xfb,0x39,0xf9,0xf8,0x38,
  28. 0x28,0xe8,0xe9,0x29,0xeb,0x2b,0x2a,0xea,0xee,0x2e,0x2f,0xef,0x2d,0xed,0xec,0x2c,
  29. 0xe4,0x24,0x25,0xe5,0x27,0xe7,0xe6,0x26,0x22,0xe2,0xe3,0x23,0xe1,0x21,0x20,0xe0,

  30. 0xa0,0x60,0x61,0xa1,0x63,0xa3,0xa2,0x62,0x66,0xa6,0xa7,0x67,0xa5,0x65,0x64,0xa4,
  31. 0x6c,0xac,0xad,0x6d,0xaf,0x6f,0x6e,0xae,0xaa,0x6a,0x6b,0xab,0x69,0xa9,0xa8,0x68,
  32. 0x78,0xb8,0xb9,0x79,0xbb,0x7b,0x7a,0xba,0xbe,0x7e,0x7f,0xbf,0x7d,0xbd,0xbc,0x7c,
  33. 0xb4,0x74,0x75,0xb5,0x77,0xb7,0xb6,0x76,0x72,0xb2,0xb3,0x73,0xb1,0x71,0x70,0xb0,
  34. 0x50,0x90,0x91,0x51,0x93,0x53,0x52,0x92,0x96,0x56,0x57,0x97,0x55,0x95,0x94,0x54,
  35. 0x9c,0x5c,0x5d,0x9d,0x5f,0x9f,0x9e,0x5e,0x5a,0x9a,0x9b,0x5b,0x99,0x59,0x58,0x98,
  36. 0x88,0x48,0x49,0x89,0x4b,0x8b,0x8a,0x4a,0x4e,0x8e,0x8f,0x4f,0x8d,0x4d,0x4c,0x8c,
  37. 0x44,0x84,0x85,0x45,0x87,0x47,0x46,0x86,0x82,0x42,0x43,0x83,0x41,0x81,0x80,0x40
  38. };

  39. /* 实现crc功能函数 */
  40. static uint16_t CRC16(uint8_t* puchMsg, uint16_t usDataLen)
  41. {
  42.   uint8_t uchCRCHi=0xff;
  43.   uint8_t uchCRCLo=0xff;
  44.   uint16_t uIndex;

  45.   while(usDataLen--) {
  46.     uIndex=uchCRCHi^*(puchMsg++);
  47.     uchCRCHi=uchCRCLo^auchCRCHi[uIndex];
  48.     uchCRCLo=auchCRCLo[uIndex];
  49.   }

  50.   return uchCRCHi<<8|uchCRCLo;
  51. }
保存系统日志参数,每实现写日志操作后都需要保存当前的参数值,防止意外丢失。
  1. void save_system_log_param(void)
  2. {
  3.   uint32_t i = 0;
  4.   uint32_t addr = 0;
  5.   uint32_t remainbyte = 0;
  6.   uint32_t start_addr;
  7.   int len = sizeof(sys_log_param_t);
  8.   uint8_t *pdata = (uint8_t *)&SysLogParam;
  9.   flash_table_t *flash_tmp = get_flash_table(FLASH_SYSLOG_PARA_ZONE);

  10.   /* 校验参数 */
  11.   gp_sys_log->crc_val.magic = SYSTEM_LOG_MAGIC_PARAM;
  12.   gp_sys_log->crc_val.len = sizeof(sys_log_param_t) - sizeof(single_sav_t);
  13.   gp_sys_log->crc_val.crc = CRC16(&pdata[sizeof(single_sav_t)], gp_sys_log->crc_val.len);

  14.   start_addr = gp_sys_ram->system_log_param_addr;
  15.   /* 剩余内存不够写,则重新从起始地址开始写,实现环形存储功能  */
  16.   if ((start_addr + len) > flash_tmp->end_address) {
  17.     start_addr = flash_tmp->start_address;
  18.   }
  19.   gp_sys_ram->system_log_param_addr = start_addr + len;
  20.   /* 首地址存储,擦除整个系统日志参数存储区,如果划分的内存较大,可能出现第一次擦写等待时间较长,
  21.      但实际应用嵌入式设备应该不会占用太多的内存存储系统日志,只当为辅助使用,有额外应用可自行实现 */
  22.   if (flash_tmp->start_address == start_addr) {
  23.     /*for (i = flash_tmp->start_address; i < flash_tmp->end_address; i+= FLASH_SECTOR_SIZE)
  24.       flash_erase(FLASH_SYSLOG_PARA_ZONE, SECTOR_BASE(i), FLASH_BLOCK_4K);
  25.     */
  26.     addr = flash_tmp->start_address;
  27.     do {
  28.       if ((addr + FLASH_BLOCK_64K_SIZE) <= flash_tmp->end_address) {
  29.         flash_erase(FLASH_SYSLOG_PARA_ZONE, BLOCK_64K_BASE(i), FLASH_BLOCK_64K);
  30.         addr += FLASH_BLOCK_64K_SIZE;
  31.       } else if ((addr + FLASH_BLOCK_32K_SIZE) <= flash_tmp->end_address) {
  32.         flash_erase(FLASH_SYSLOG_PARA_ZONE, BLOCK_32K_BASE(i), FLASH_BLOCK_32K);
  33.         addr += FLASH_BLOCK_32K_SIZE;
  34.       } else if ((addr + FLASH_SECTOR_SIZE) <= flash_tmp->end_address) {
  35.         flash_erase(FLASH_SYSLOG_PARA_ZONE, SECTOR_BASE(i), FLASH_BLOCK_4K);
  36.         addr += FLASH_SECTOR_SIZE;
  37.       } else {
  38.         break;
  39.       }
  40.     } while (addr < flash_tmp->end_address);  
  41.   }

  42.   remainbyte = FLASH_SECTOR_SIZE - (start_addr % FLASH_SECTOR_SIZE);
  43.   if (remainbyte > len) {
  44.     remainbyte = len;
  45.   }
  46.   while (1) {
  47.     flash_write(FLASH_SYSLOG_PARA_ZONE, start_addr, pdata, remainbyte);
  48.     if (remainbyte == len) {
  49.       break;
  50.     } else {
  51.       pdata += remainbyte;
  52.       start_addr += remainbyte;
  53.       len -= remainbyte;
  54.       remainbyte = (len > FLASH_SECTOR_SIZE) ? FLASH_SECTOR_SIZE : len;
  55.     }
  56.   }
  57. }
导入系统日志默认参数接口,初始化默认参数或者移除日志。

  1. void load_system_log_default_param(void)
  2. {
  3.   /* 系统日志默认参数 */
  4.   /* 目录环写状态标志 */
  5.   gp_sys_log->system_log.catalog_cyclic_status = 0x00;
  6.   /* 目录项个数 */
  7.   gp_sys_log->system_log.catalog_num = 0;
  8.   /* 日志环写标志 , 1:环写状态 */
  9.   gp_sys_log->system_log.log_cyclic_status = 0;
  10.   /* 设置默认值,实际会重新从RTC获取最新时间 */
  11.   gp_sys_log->system_log.log_latest_time.Year = 2019;
  12.   gp_sys_log->system_log.log_latest_time.Month = 5;
  13.   gp_sys_log->system_log.log_latest_time.Day = 8;
  14.   gp_sys_log->system_log.log_latest_time.Hour = 13;
  15.   gp_sys_log->system_log.log_latest_time.Minute = 14;
  16.   gp_sys_log->system_log.log_latest_time.Second = 10;
  17.   /* 日志写位置从0开始 */
  18.   gp_sys_log->system_log.write_pos = 0;

  19.   gp_sys_log->system_catalog.log_addr = 0;
  20.   gp_sys_log->system_catalog.log_id = 0;
  21.   gp_sys_log->system_catalog.log_offset = 0;
  22.   gp_sys_log->system_catalog.log_time.Year = 2019;
  23.   gp_sys_log->system_catalog.log_time.Month = 5;
  24.   gp_sys_log->system_catalog.log_time.Day = 8;
  25.   gp_sys_log->system_catalog.log_time.Hour = 12;
  26.   gp_sys_log->system_catalog.log_time.Minute = 12;
  27.   gp_sys_log->system_catalog.log_time.Second = 14;

  28.   gp_sys_log->crc_val.magic = SYSTEM_LOG_MAGIC_PARAM;

  29.   /* 导入默认参数后进行保存 */
  30.   save_system_log_param();
  31. }


 楼主| 豆杀包 发表于 2022-5-24 10:52 | 显示全部楼层
设备开机或者复位都会进行导入系统日志参数操作,恢复日志读写参数,参数区为频繁读写操作区域,每一次写操作都会进行一次偏移,有效的导入参数方法是从参数区结束地址到起始地址进行扫描,扫描不到合法的参数则会导入默认日志参数。

  1. /* 参数初始化,在终端启动时调用 */
  2. int load_system_log_param(void)
  3. {
  4.   uint32_t i = 0;
  5.   single_sav_t psav;
  6.   uint32_t end_addr;
  7.   uint32_t interal = sizeof(sys_log_param_t);
  8.   int data_len = sizeof(sys_log_param_t) - sizeof(single_sav_t);
  9.   uint8_t *pram = (uint8_t *)&SysLogParam;
  10.   flash_table_t *flash_tmp = get_flash_table(FLASH_SYSLOG_PARA_ZONE);

  11.   end_addr =flash_tmp->end_address - (flash_tmp->end_address - flash_tmp->start_address) % interal;
  12.   for (i = end_addr - interal; i > flash_tmp->start_address; i -= interal) {
  13.     flash_read(FLASH_SYSLOG_PARA_ZONE, i, (uint8_t *)&psav, sizeof(single_sav_t));
  14.     if ((psav.magic == SYSTEM_LOG_MAGIC_PARAM) && (psav.len ==data_len)) {      
  15.       flash_read(FLASH_SYSLOG_PARA_ZONE, i + sizeof(single_sav_t), &pram[sizeof(single_sav_t)], data_len);
  16.       if (psav.crc != CRC16(&pram[sizeof(single_sav_t)], data_len))
  17.         continue;
  18.       gp_sys_ram->system_log_param_addr = i;
  19.       log_info("Load System Log Param Addr[0x%08x]!", gp_sys_ram->system_log_param_addr);
  20.       return 0;
  21.     }
  22.   }

  23.   /* 扫描不到合法的参数,导入默认系统日志参数 */
  24.   load_system_log_default_param();
  25.   /* 获取日志写地址 */
  26.   gp_sys_ram->system_log_param_addr = flash_tmp->start_address;
  27.   log_info("Load System Log Param Addr(Default)[0x%08x]!", gp_sys_ram->system_log_param_addr);
  28.   return 1;
  29. }


 楼主| 豆杀包 发表于 2022-5-24 10:53 | 显示全部楼层
读写系统日志目录接口,读写指定日志索引目录信息。实际实现会定义最新的目录信息存储在日志参数区,当日期发生改变,则表示当前目录信息已经完结,将最新的目录信息录入日志目录区保存,最多每天写入一次目录区。
  1. /* 读取日志目录区指定日志索引目录信息 */
  2. int system_catalog_read(system_catalog_t *catalog, uint32_t id)
  3. {
  4.   uint32_t addr;
  5.   int rlen = sizeof(system_catalog_t);
  6.   uint8_t *pbuf = (uint8_t *)catalog;
  7.   flash_table_t *flash_tmp = get_flash_table(FLASH_CATALOG_ZONE);

  8.   if (0 == id)
  9.     return -1;
  10.   addr = flash_tmp->start_address + (rlen * (id - 1));
  11.   if (addr > flash_tmp->end_address)
  12.     return -1;

  13.   return flash_read(FLASH_CATALOG_ZONE, addr, pbuf, rlen);
  14. }

  15. /* 写日志目录区目录信息 */
  16. int system_catalog_write(system_catalog_t *catalog, uint32_t id)
  17. {
  18.   uint32_t start_offset;
  19.   uint32_t start_addr;
  20.   uint32_t start_base;
  21.   uint32_t remainbyte;
  22.   int wlen = sizeof(system_catalog_t);
  23.   uint8_t *pdata = (uint8_t *)catalog;
  24.   flash_table_t *flash_tmp = get_flash_table(FLASH_CATALOG_ZONE);

  25.   if (0 == id) return -1;
  26.   start_addr = flash_tmp->start_address + wlen * (id - 1);
  27.   if ((start_addr + wlen) > flash_tmp->end_address) {
  28.     start_addr = flash_tmp->start_address;
  29.   }

  30.   /* 本扇区剩余空间大小 */
  31.   remainbyte = FLASH_SECTOR_SIZE - (start_addr % FLASH_SECTOR_SIZE);
  32.   /* 写入数据长度小于本扇区剩余长度,直接写入 */
  33.   if (remainbyte > wlen) {
  34.     remainbyte = wlen;
  35.   }
  36.   /* 写目录次数不会太频繁,视具体情况改写操作实现 */
  37.   while (1) {
  38.     start_base = SECTOR_BASE(start_addr);
  39.       start_offset = SECTOR_OFFSET(start_addr);
  40.     flash_read(FLASH_CATALOG_ZONE, start_base, sector_buf, FLASH_SECTOR_SIZE);
  41.     flash_erase(FLASH_CATALOG_ZONE, start_base, FLASH_BLOCK_4K);
  42.     memcpy((char *)§or_buf[start_offset], pdata, remainbyte);
  43.     flash_write(FLASH_CATALOG_ZONE, start_base, sector_buf, FLASH_SECTOR_SIZE);
  44.     if (remainbyte == wlen) {
  45.       break;
  46.     } else {
  47.       pdata += remainbyte;
  48.       start_addr += remainbyte;
  49.       wlen -= remainbyte;
  50.       remainbyte = (wlen > FLASH_SECTOR_SIZE) ? FLASH_SECTOR_SIZE : wlen;
  51.     }
  52.   }

  53.   return 0;
  54. }
打印系统日志目录区信息,可实现通过指令查询到目录区信息。

  1. int system_catalog_all_print(void)
  2. {
  3.   int i = 0;
  4.   system_catalog_t catalog;

  5.   printf("System Log Command Information:\r\n");
  6.   printf("Query Specifies Log : AT+CATALOG=<LOG_ID><CR><LF>\r\n");
  7.   printf("Query All Log : AT+CATALOG=<0><CR><LF>\r\n\r\n");
  8.   printf("Query All System Catalog:\r\n");
  9.   printf("LOG_ID    LOG_DATE    LOG_ADDR    LOG_OFFSET  \r\n");
  10.   for (i = 0; i < gp_sys_log->system_log.catalog_num; i++) {
  11.     /* 当前最新目录信息 */   
  12.     if (i == (gp_sys_log->system_catalog.log_id - 1)) {
  13.       catalog = gp_sys_log->system_catalog; /* 获取当前最新目录信息 */
  14.     } else {
  15.       system_catalog_read(&catalog, i + 1);
  16.     }
  17.     printf("%d    %04d-%02d-%02d    0x%08X    %d  \r\n",
  18.       catalog.log_id, catalog.log_time.Year, catalog.log_time.Month, catalog.log_time.Day,
  19.       catalog.log_addr, catalog.log_offset);
  20.     memset((char *)&catalog, 0, sizeof(system_catalog_t));
  21.   }
  22.   return 0;
  23. }


 楼主| 豆杀包 发表于 2022-5-24 10:53 | 显示全部楼层
读取指定日志目录索引信息接口,可指定日志索引或者读取全部日志数据。
  1. int system_log_task(int argc)
  2. {
  3.   int rlen = 0;
  4.   uint32_t offset, start_addr, end_addr;
  5.   system_catalog_t catalog;
  6.   flash_table_t *flash_tmp =get_flash_table(FLASH_SYSLOG_ZONE);

  7.   if (0 == gp_sys_ram->system_log_print_enable)
  8.     return 1;

  9.   gp_sys_ram->system_log_print_enable = 0x00;
  10.   if (gp_sys_ram->system_log_print_id == ALL_LOG_PRINT) {
  11.     /* log回环写标志,打印整个LOG存储区 */
  12.     if (0x01 == gp_sys_log->system_log.log_cyclic_status) {
  13.       start_addr = flash_tmp->start_address;
  14.       end_addr = flash_tmp->end_address;
  15.       offset = end_addr - start_addr;
  16.     } else {
  17.       start_addr = flash_tmp->start_address;
  18.       end_addr = start_addr + gp_sys_log->system_log.write_pos;
  19.       offset = gp_sys_log->system_log.write_pos;
  20.     }
  21.   } else { /* 读取指定ID日志 */
  22.     if (gp_sys_ram->system_log_print_id == gp_sys_log->system_catalog.log_id) {
  23.       catalog = gp_sys_log->system_catalog;
  24.     } else {
  25.       system_catalog_read(&catalog, gp_sys_ram->system_log_print_id);
  26.     }
  27.     start_addr = catalog.log_addr;
  28.     offset = catalog.log_offset;
  29.   }  

  30.   if (0 == offset)
  31.     return 1;

  32.   while (1) {
  33.     rlen = (offset > 512) ? 512 : offset;
  34.     system_log_read(sector_buf, start_addr, rlen);
  35.     HAL_Delay(80);
  36.     /* 目录信息通过调式串口打印 */
  37.     bsp_debug_send(sector_buf, rlen);
  38.     start_addr += rlen;
  39.     offset -= rlen;
  40.     if (0 == offset)
  41.       break;
  42.   }
  43.   return 0;
  44. }
存储系统日志接口,实现更新存储日期,当写位置为扇区地址,则擦除一个扇区作为存储日志,这样避免每写一次就擦除一次。

  1. int system_log_write(uint8_t *wbuf, int wlen)
  2. {
  3.   uint32_t start_addr;
  4.   uint8_t *pdata = wbuf;
  5.   uint32_t remainbyte;
  6.   int system_catalog_max_id;
  7.   flash_table_t *flash_tmp =get_flash_table(FLASH_SYSLOG_ZONE);

  8.   /* 计算目录区的最大存储目录项个数 */
  9.   system_catalog_max_id = ((flash_tmp->end_address - flash_tmp->start_address) / sizeof(system_catalog_t));
  10.   start_addr = flash_tmp->start_address + gp_sys_log->system_log.write_pos;
  11.   /* 存储数据地址大于规划内存地址范围处理 */
  12.   if ((start_addr + wlen) > flash_tmp->end_address) {
  13.     start_addr = flash_tmp->start_address;
  14.     /* 写位置偏移量重置 */
  15.     gp_sys_log->system_log.write_pos = 0;
  16.     /* LOG回环存储标志置位 */
  17.     gp_sys_log->system_log.log_cyclic_status = 0x01;
  18.   }
  19.   /* 写位置偏移 */
  20.   gp_sys_log->system_log.write_pos += wlen;

  21.   if ((gp_sys_log->system_log.log_latest_time.Year != gp_sys_log->system_catalog.log_time.Year) ||
  22.     (gp_sys_log->system_log.log_latest_time.Month != gp_sys_log->system_catalog.log_time.Month) ||
  23.     (gp_sys_log->system_log.log_latest_time.Day != gp_sys_log->system_catalog.log_time.Day)) {

  24.     /* 日期改变,记录目录信息,当log_id为0,则不写入 */
  25.     system_catalog_write(&gp_sys_log->system_catalog, gp_sys_log->system_catalog.log_id);
  26.     /* 记录存储日期 */
  27.     gp_sys_log->system_catalog.log_time = gp_sys_log->system_log.log_latest_time;

  28.     if ((gp_sys_log->system_catalog.log_id + 1) >= system_catalog_max_id) {
  29.       gp_sys_log->system_log.catalog_num = system_catalog_max_id; /* 目录循环写,目录数应为最大 */
  30.       gp_sys_log->system_log.catalog_cyclic_status = 1; /* 目录回环写标志 */
  31.     } else {
  32.       if (0 == gp_sys_log->system_log.catalog_cyclic_status) {
  33.         /* 获取目录数 */
  34.         gp_sys_log->system_log.catalog_num = gp_sys_log->system_catalog.log_id + 1;
  35.       }
  36.     }

  37.     /* 存储最新目录项信息 */
  38.     gp_sys_log->system_catalog.log_id = (gp_sys_log->system_catalog.log_id + 1) % system_catalog_max_id;
  39.     gp_sys_log->system_catalog.log_addr = start_addr;
  40.     gp_sys_log->system_catalog.log_offset = wlen;
  41.   } else {
  42.     gp_sys_log->system_catalog.log_offset += wlen;
  43.   }

  44.   /* 写位置为存储起始地址并且不为扇区首地址 */
  45.   if ((flash_tmp->start_address == start_addr) && (SECTOR_OFFSET(flash_tmp->start_address))){
  46.     flash_read(FLASH_SYSLOG_ZONE, SECTOR_BASE(start_addr), sector_buf, FLASH_SECTOR_SIZE);
  47.     flash_erase(FLASH_SYSLOG_ZONE, SECTOR_BASE(start_addr), FLASH_BLOCK_4K);
  48.     /* 将扇区头部至起始地址区间的数据回写 */
  49.     flash_write(FLASH_SYSLOG_ZONE, SECTOR_BASE(start_addr), §or_buf[0], SECTOR_OFFSET(start_addr));
  50.   }
  51.   /* 写位置为扇区首地址,则擦除一个扇区的存储区    */
  52.   if (0 == SECTOR_OFFSET(start_addr)) {
  53.     flash_erase(FLASH_SYSLOG_ZONE, SECTOR_BASE(start_addr), FLASH_BLOCK_4K);
  54.   }

  55.   /* 本扇区剩余空间大小 */
  56.   remainbyte = FLASH_SECTOR_SIZE - (start_addr % FLASH_SECTOR_SIZE);
  57.   /* 写入数据长度小于本扇区剩余长度,直接写入 */
  58.   if (remainbyte > wlen) {
  59.     remainbyte = wlen;
  60.   }
  61.   while (1) {
  62.     flash_write(FLASH_SYSLOG_ZONE, start_addr, pdata, remainbyte);
  63.     if (remainbyte == wlen) {
  64.       break;
  65.     } else {
  66.       pdata += remainbyte;
  67.       start_addr += remainbyte;
  68.       wlen -= remainbyte;
  69.       remainbyte = (wlen > FLASH_SECTOR_SIZE) ? FLASH_SECTOR_SIZE : wlen;
  70.       /* 扇区首地址则擦除整个扇区,该扇区数据不保存 */
  71.       if (0 == SECTOR_OFFSET(start_addr)) {
  72.         flash_erase(FLASH_SYSLOG_ZONE, SECTOR_BASE(start_addr), FLASH_BLOCK_4K);
  73.       }
  74.     }
  75.   }

  76.   /* 环形存储参数 */
  77.   save_system_log_param();
  78.   return 0;
  79. }


 楼主| 豆杀包 发表于 2022-5-24 10:54 | 显示全部楼层
系统调试对接为了更好记录系统日志,将应用调试等级结合一块,实现记录错误调试信息以及需要保存的关键信息。定义的调试等级有:关闭调试等级、错误调试等级、警告调试等级、关键调试等级、debug调试等级,而LOG_RECORD_LEVEL将主动保存日志并输出信息,LOG_ERROR_LEVEL会存储对应的日志信息,但需要根据应用调试等级输出信息。设置与读取应用调试等级由读者自行定义。

  1. #define LOG_CLOSE_LEVEL        0x00 /* 关闭调试信息 */
  2. #define LOG_ERROR_LEVEL        0x01 /* 错误调试信息 */
  3. #define LOG_WARN_LEVEL        0x02 /* 警告调试信息 */
  4. #define LOG_INFO_LEVEL        0x03 /* 关键调试信息 */
  5. #define LOG_DEBUG_LEVEL        0x04 /* debug调试信息 */
  6. #define LOG_RECORD_LEVEL      0x10 /* 保存日志并输出信息 */  
  7. #define LOG_PRINT_LEVEL        0xff

  8. #define SET_LOG_LEVEL(LEVEL)    (gp_sys_param->system_print_level = LEVEL)
  9. #define GET_LOG_LEVEL()        (gp_sys_param->system_print_level)

  10. #define log_debug(fmt, args...)    log_format(LOG_DEBUG_LEVEL, fmt, ##args)
  11. #define log_info(fmt, args...)    log_format(LOG_INFO_LEVEL, fmt, ##args)
  12. #define log_warn(fmt, args...)    log_format(LOG_WARN_LEVEL, fmt, ##args)
  13. #define log_error(fmt, args...)    log_format(LOG_ERROR_LEVEL, fmt, ##args)
  14. #define log_record(fmt, args...)  log_format(LOG_RECORD_LEVEL, fmt, ##args)
  15. #define printf(fmt, args...)    log_format(LOG_PRINT_LEVEL, fmt, ##args)

  16. typedef struct {
  17.   int level;
  18.   char *fmt_str;
  19. }system_print_fmt_t;

  20. system_print_fmt_t system_print_fmt_list[] = {
  21.   { .level = LOG_ERROR_LEVEL,   .fmt_str = "<error>:"},
  22.   { .level = LOG_WARN_LEVEL,    .fmt_str = "<warn>:"},
  23.   { .level = LOG_INFO_LEVEL,    .fmt_str = "<info>:"},
  24.   { .level = LOG_DEBUG_LEVEL,   .fmt_str = "<debug>:"},
  25.   { .level = LOG_RECORD_LEVEL,  .fmt_str = "<record>:"},
  26. };

  27. int log_format(uint8_t level, const char *fmt, ...)
  28. {
  29.   #define TIME_PREFIX_SIZE  (21)
  30.   #define PRINT_MAX_SIZE    (1024 + TIME_PREFIX_SIZE)

  31.     va_list args;
  32.     int num = 0, i = 0, fmt_index = 0;
  33.     int fmt_str_len = 0, ret = -1;
  34.     int file_str_len = 0, line_str_len = 0;
  35.     char line_buf[20] = {0};
  36.     static char buf[PRINT_MAX_SIZE];
  37.   static QueueHandle_t sem = NULL;
  38.   time_t time = {0};

  39.   /* 针对os系统 */
  40.   if (NULL == sem) {
  41.         sem = xSemaphoreCreateCounting(1, 1); /* always think of success */
  42.   }

  43.   xSemaphoreTake(sem, portMAX_DELAY);

  44.   ret = -1;
  45.   fmt_str_len = 0;
  46.   if (level != LOG_PRINT_LEVEL) {
  47.     if ((GET_LOG_LEVEL() < level) && (level != LOG_RECORD_LEVEL) && (level != LOG_ERROR_LEVEL))
  48.       goto exit_end;

  49.     for (i = 0; i < SYSTEM_PRINT_FMT_LIST_MAX; i++) {
  50.       if (level == system_print_fmt_list[i].level) {
  51.         fmt_index = i;
  52.         break;
  53.       }
  54.     }
  55.     if (i > SYSTEM_PRINT_FMT_LIST_MAX) {
  56.       goto exit_end;
  57.     }

  58.     fmt_str_len = strlen(system_print_fmt_list[fmt_index].fmt_str);
  59.     strncpy((char *)&buf[TIME_PREFIX_SIZE], system_print_fmt_list[fmt_index].fmt_str, fmt_str_len);
  60.   }

  61.     va_start(args, fmt);
  62.     num = vsnprintf((char *)&buf[fmt_str_len + TIME_PREFIX_SIZE], PRINT_MAX_SIZE - fmt_str_len - TIME_PREFIX_SIZE - 2, fmt, args);
  63.     va_end(args);

  64.     if (num <= 0) {
  65.     goto exit_end;
  66.     }

  67.   if (level != LOG_PRINT_LEVEL) {
  68.     num += fmt_str_len;
  69.     buf[num + TIME_PREFIX_SIZE] = '\r';
  70.     buf[num + TIME_PREFIX_SIZE + 1] = '\n';
  71.     num += 2;
  72.   }

  73.   if ((GET_LOG_LEVEL() < level) && (level == LOG_ERROR_LEVEL)) {
  74.     //do nothing
  75.   } else {
  76.     ret = bsp_debug_send((uint8_t*)&buf[TIME_PREFIX_SIZE], num);  
  77.   }

  78.   if ((LOG_ERROR_LEVEL == level) || (LOG_RECORD_LEVEL == level)) {
  79.     bsp_rtc_get_time(&time);
  80.     sprintf(&buf[0], "[%04d-%02d-%02d %02d:%02d:%02d",
  81.       time.Year, time.Month, time.Day,time.Hour, time.Minute, time.Second);
  82.     buf[TIME_PREFIX_SIZE - 1] = ']';
  83.     gp_sys_log->system_log.log_latest_time = time;
  84.     system_log_write((uint8_t *)buf, num + TIME_PREFIX_SIZE);
  85.   }  

  86. exit_end:
  87.   xSemaphoreGive(sem);
  88.   return ret;
  89. }


 楼主| 豆杀包 发表于 2022-5-24 10:55 | 显示全部楼层
结语本文提供的一种简易嵌入式设备系统日志记录方法,代码量不多,实现简单,针对不同的设备需要合理规划内存使用。
根据软件运行状态,合适加入调试信息并保存对应的日志信息,方便开发人员了解系统或软件运行状况,协助开发分析数据资源从而更好完善系统,提高定位以及解决问题的效果。
来源地址:
https://blog.csdn.net/LiaRonBob/article/details/102766871

您需要登录后才可以回帖 登录 | 注册

本版积分规则

49

主题

323

帖子

0

粉丝
快速回复 在线客服 返回列表 返回顶部