Simple Embedded System Logging Method

Follow+Star public number, don’t miss wonderful content
Simple Embedded System Logging Method
Source | CSDD
Author | JustCasper
In embedded devices, many scenarios require logging, especially in environments with limited storage resources like microcontrollers, which necessitates a lightweight storage method.

1. System Log

In embedded device applications, system logs can often monitor the running status of device software, timely record problem points and key information, facilitating developers to locate and solve problems later.
This article will describe a simple method for logging system logs, used to save the system logs of devices, depending on the specific embedded device, which can be stored in internal Flash, external Flash, EEPROM, etc. This article uses external Flash as an example for introduction.

2. Thought Analysis

System logs can be treated as a file system, which can be divided into three important parts: directory area, parameter area, and log area.
  • Directory area: classified by date, records the storage address, log index, and log size of the logs for the day. The directory can provide an overview of the entire log file;
  • Parameter area: stores parameters such as the log write position, number of directory items, write status, etc.;
  • Log area: this is our main storage area, recording the system logs, supporting circular writing. These three areas need to occupy some memory, and their sizes can be allocated by oneself.
The achieved effect is shown in the figure below, and the entire log directory area can be queried through commands.
Query system log directory: AT+CATALOG?
LOG_ID: stores logs classified by date; this ID is used to query logs for the corresponding date, starting from 1;
LOG_DATE: date of system log storage;
LOG_ADDR: external FLASH address where the system log is stored;
LOG_OFFSET: offset of the system log storage (the size of each date log, in bytes).
Simple Embedded System Logging Method
Query system log for a specific date: AT+CATALOG=<LOG_ID>
LOG_ID: obtained when querying the system log directory; when LOG_ID is 0, it queries the entire system log.
Simple Embedded System Logging Method
Additionally, a command to remove system logs (clear log directory) is provided: AT+RMLOG, which will be discussed in detail later.

3. Flash Memory Partitioning

FLASH memory needs to be reasonably partitioned according to the specific device, with the directory area, parameter area, and log area implementing circular storage to extend erase life.
#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)         /* Sector mask ------*/#define SECTOR_BASE(addr)         (addr & (~SECTOR_MASK))        /* Base address of the sector --*/#define SECTOR_OFFSET(addr)       (addr & SECTOR_MASK)           /* Offset within the sector --*/
#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,          /**&lt; flash erase block size 4k */    FLASH_BLOCK_32K = 1,          /**&lt; flash erase block size 32k */    FLASH_BLOCK_64K = 2           /**&lt; flash erase block size 64k */}flash_block_t;
/* Flash space index */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;
/* Address partitioning */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 low-level implementation of erase, read and write operation interfaces, to be implemented by the reader.
flash_table_t *get_flash_table(flash_zone_e zone){  int i = 0;  for (i = 0; i &lt; flash_zone_count; i++) {    if (zone == flash_table[i].zone)       return (flash_table_t *)&flash_table[i];  }
  return NULL;  }
int flash_erase(flash_zone_e zone, uint32_t address, flash_block_t block_type){  flash_table_t *flash_table_tmp = get_flash_table(zone);
  if (flash_table_tmp == NULL)    return -1;
  if (address &lt; flash_table_tmp-&gt;start_address ||address &gt; flash_table_tmp-&gt;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 &lt; flash_table_tmp-&gt;start_address) ||((address + length) &gt; flash_table_tmp-&gt;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 &lt; flash_table_tmp-&gt;start_address) ||((address + length) &gt; flash_table_tmp-&gt;end_address))    return -1;
  bsp_spi_flash_buffer_read(buffer, address, length);  return 0;}
Parameter and structure definition
Log data storage timestamp, which is convenient for problem location, requires RTC interface call implementation.
typedef struct {  uint16_t   Year;    /* Year:YYYY */  uint8_t    Month;    /* Month:MM */  uint8_t    Day;    /* Day:DD */  uint8_t     Hour;    /* Hour:HH */  uint8_t     Minute;    /* Minute:MM */  uint8_t   Second;    /* Second:SS */}time_t;   
int bsp_rtc_get_time(time_t *date);
The parameter area should ensure data integrity and should include parameter verification storage, defining the verification structure.
#define SYSTEM_LOG_MAGIC_PARAM    0x87654321  /* Log parameter identifier */typedef struct {  uint32_t magic;    /* Parameter identifier */  uint16_t crc;    /* Checksum */  uint16_t len;    /* Parameter length */} single_sav_t;
The parameter area needs to record the current log write position, the number of directory items, the circular write status of the log area and directory area, and store the latest time, etc.
/* Log area parameters */typedef struct {  uint32_t   write_pos;             /* Write position */  uint32_t   catalog_num;            /* Number of directory items */  uint8_t    log_cyclic_status;    /* System log circular write status */     uint8_t    catalog_cyclic_status; /* Log directory circular write status */  time_t     log_latest_time;     /* Store latest time */}system_log_t;
/* Directory area parameters */typedef struct {  uint32_t log_id;     /* Log index */    uint32_t log_addr;    /* Log address */  uint32_t log_offset;  /* Log offset size, in bytes */  time_t   log_time;    /* Log storage time */}system_catalog_t;
/* System log parameters */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; /* System log print enable */  uint16_t system_log_print_id;    /* Print specified id system log */  uint32_t system_log_param_addr;  /* Current log write address */} 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;

4. Implementation Interface Description

CRC verification interface, can be implemented as needed.
/* 16-bit CRC verification high table */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,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-bit CRC verification low table */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};
/* Implement CRC function */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&lt;&lt;8|uchCRCLo;}
Save system log parameters; after each log write operation, the current parameter values need to be saved to prevent accidental loss.
void save_system_log_param(void){  uint32_t i = 0;  uint32_t addr = 0;  uint32_t remainbyte = 0;  uint32_t start_addr;  int len = sizeof(sys_log_param_t);  uint8_t *pdata = (uint8_t *)&SysLogParam;  flash_table_t *flash_tmp = get_flash_table(FLASH_SYSLOG_PARA_ZONE);
  /* Verify parameters */  gp_sys_log-&gt;crc_val.magic = SYSTEM_LOG_MAGIC_PARAM;  gp_sys_log-&gt;crc_val.len = sizeof(sys_log_param_t) - sizeof(single_sav_t);  gp_sys_log-&gt;crc_val.crc = CRC16(&pdata[sizeof(single_sav_t)], gp_sys_log-&gt;crc_val.len);
  start_addr = gp_sys_ram-&gt;system_log_param_addr;  /* If there is not enough remaining memory to write, start writing from the beginning address to implement circular storage functionality  */  if ((start_addr + len) &gt; flash_tmp-&gt;end_address) {     start_addr = flash_tmp-&gt;start_address;  }  gp_sys_ram-&gt;system_log_param_addr = start_addr + len;  /* Store from the first address, erase the entire system log parameter storage area. If the allocated memory is larger, the first erase may take longer, but in actual applications, embedded devices should not occupy too much memory to store system logs, only as auxiliary use, and additional applications can be implemented by themselves */  if (flash_tmp-&gt;start_address == start_addr) {    /*for (i = flash_tmp-&gt;start_address; i &lt; flash_tmp-&gt;end_address; i+= FLASH_SECTOR_SIZE)       flash_erase(FLASH_SYSLOG_PARA_ZONE, SECTOR_BASE(i), FLASH_BLOCK_4K);    */    addr = flash_tmp-&gt;start_address;    do {      if ((addr + FLASH_BLOCK_64K_SIZE) &lt;= flash_tmp-&gt;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) &lt;= flash_tmp-&gt;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) &lt;= flash_tmp-&gt;end_address) {        flash_erase(FLASH_SYSLOG_PARA_ZONE, SECTOR_BASE(i), FLASH_BLOCK_4K);        addr += FLASH_SECTOR_SIZE;      } else {        break;      }    } while (addr &lt; flash_tmp-&gt;end_address);    }
  remainbyte = FLASH_SECTOR_SIZE - (start_addr % FLASH_SECTOR_SIZE);  if (remainbyte &gt; 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 &gt; FLASH_SECTOR_SIZE) ? FLASH_SECTOR_SIZE : len;    }  }}
Import system log default parameters interface, initialize default parameters or remove logs.
void load_system_log_default_param(void){  /* Default parameters for system logs */  /* Directory circular write status flag */  gp_sys_log-&gt;system_log.catalog_cyclic_status = 0x00;  /* Number of directory items */  gp_sys_log-&gt;system_log.catalog_num = 0;  /* Circular write flag for logs, 1: circular write status */  gp_sys_log-&gt;system_log.log_cyclic_status = 0;  /* Set default values, actual will re-obtain the latest time from RTC */  gp_sys_log-&gt;system_log.log_latest_time.Year = 2019;  gp_sys_log-&gt;system_log.log_latest_time.Month = 5;  gp_sys_log-&gt;system_log.log_latest_time.Day = 8;  gp_sys_log-&gt;system_log.log_latest_time.Hour = 13;  gp_sys_log-&gt;system_log.log_latest_time.Minute = 14;  gp_sys_log-&gt;system_log.log_latest_time.Second = 10;  /* Log write position starts from 0 */  gp_sys_log-&gt;system_log.write_pos = 0;
  gp_sys_log-&gt;system_catalog.log_addr = 0;  gp_sys_log-&gt;system_catalog.log_id = 0;  gp_sys_log-&gt;system_catalog.log_offset = 0;  gp_sys_log-&gt;system_catalog.log_time.Year = 2019;  gp_sys_log-&gt;system_catalog.log_time.Month = 5;  gp_sys_log-&gt;system_catalog.log_time.Day = 8;  gp_sys_log-&gt;system_catalog.log_time.Hour = 12;  gp_sys_log-&gt;system_catalog.log_time.Minute = 12;  gp_sys_log-&gt;system_catalog.log_time.Second = 14;
  gp_sys_log-&gt;crc_val.magic = SYSTEM_LOG_MAGIC_PARAM;
  /* Save after importing default parameters */  save_system_log_param();}
The device will perform the operation of importing system log parameters during boot or reset, restoring log read and write parameters. The parameter area is a frequently read and written operation area, and each write operation will perform an offset. An effective method to import parameters is to scan from the end address of the parameter area to the starting address. If no valid parameters are found, the default log parameters will be imported.
/* Parameter initialization, called at terminal startup */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-&gt;end_address - (flash_tmp-&gt;end_address - flash_tmp-&gt;start_address) % interal;  for (i = end_addr - interal; i &gt; flash_tmp-&gt;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-&gt;system_log_param_addr = i;      log_info("Load System Log Param Addr[0x%08x]!", gp_sys_ram-&gt;system_log_param_addr);      return 0;    }  }
  /* If no valid parameters are found, import default system log parameters */  load_system_log_default_param();  /* Get log write address */  gp_sys_ram-&gt;system_log_param_addr = flash_tmp-&gt;start_address;  log_info("Load System Log Param Addr(Default)[0x%08x]!", gp_sys_ram-&gt;system_log_param_addr);  return 1;}
Read and write system log directory interface, read and write specified log index directory information. The actual implementation will define the latest directory information stored in the log parameter area. When the date changes, it indicates that the current directory information has been completed, and the latest directory information will be recorded in the log directory area for storage, with a maximum of one directory written per day.
/* Read specified log index directory information from the log directory area */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-&gt;start_address + (rlen * (id - 1));  if (addr &gt; flash_tmp-&gt;end_address)     return -1;
  return flash_read(FLASH_CATALOG_ZONE, addr, pbuf, rlen);}
/* Write directory information to the log directory area */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-&gt;start_address + wlen * (id - 1);  if ((start_addr + wlen) &gt; flash_tmp-&gt;end_address) {    start_addr = flash_tmp-&gt;start_address;  }
  /* Remaining space size in this sector */  remainbyte = FLASH_SECTOR_SIZE - (start_addr % FLASH_SECTOR_SIZE);  /* If the data length to be written is less than the remaining length of this sector, write directly */  if (remainbyte &gt; wlen) {    remainbyte = wlen;  }  /* The number of times to write the directory will not be too frequent, modify the implementation according to specific conditions */  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 *)&sector_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 &gt; FLASH_SECTOR_SIZE) ? FLASH_SECTOR_SIZE : wlen;    }  }
  return 0;}
Print system log directory information, which can be queried through commands.
int system_catalog_all_print(void){  int i = 0;  system_catalog_t catalog;
  printf("System Log Command Information:\r\n");  printf("Query Specifies Log : AT+CATALOG=&lt;LOG_ID&gt;&lt;CR&gt;&lt;LF&gt;\r\n");  printf("Query All Log : AT+CATALOG=&lt;0&gt;&lt;CR&gt;&lt;LF&gt;\r\n\r\n");  printf("Query All System Catalog:\r\n");  printf("LOG_ID    LOG_DATE    LOG_ADDR    LOG_OFFSET  \r\n");  for (i = 0; i &lt; gp_sys_log-&gt;system_log.catalog_num; i++) {    /* Current latest directory information */        if (i == (gp_sys_log-&gt;system_catalog.log_id - 1)) {      catalog = gp_sys_log-&gt;system_catalog; /* Get current latest directory information */    } else {      system_catalog_read(&catalog, i + 1);    }    printf("%d    %04d-%02d-%02d    0x%08X    %d  \r\n",       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;}
Read the specified log directory index information interface, which can specify the log index or read all log data.
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-&gt;system_log_print_enable)     return 1;
  gp_sys_ram-&gt;system_log_print_enable = 0x00;  if (gp_sys_ram-&gt;system_log_print_id == ALL_LOG_PRINT) {    /* log circular write flag, print the entire LOG storage area */    if (0x01 == gp_sys_log-&gt;system_log.log_cyclic_status) {       start_addr = flash_tmp-&gt;start_address;      end_addr = flash_tmp-&gt;end_address;      offset = end_addr - start_addr;    } else {      start_addr = flash_tmp-&gt;start_address;      end_addr = start_addr + gp_sys_log-&gt;system_log.write_pos;      offset = gp_sys_log-&gt;system_log.write_pos;    }  } else { /* Read specified ID log */    if (gp_sys_ram-&gt;system_log_print_id == gp_sys_log-&gt;system_catalog.log_id) {      catalog = gp_sys_log-&gt;system_catalog;    } else {      system_catalog_read(&catalog, gp_sys_ram-&gt;system_log_print_id);    }    start_addr = catalog.log_addr;    offset = catalog.log_offset;  }  
  if (0 == offset)    return 1;
  while (1) {    rlen = (offset &gt; 512) ? 512 : offset;    system_log_read(sector_buf, start_addr, rlen);    HAL_Delay(80);    /* Directory information printed through the debugging serial port */    bsp_debug_send(sector_buf, rlen);    start_addr += rlen;    offset -= rlen;    if (0 == offset)       break;  }  return 0;}
Store system log interface, implement update storage date. When the write position is the sector address, erase a sector as the storage log to avoid erasing once for every write.
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);
  /* Calculate the maximum number of directory items that can be stored in the directory area */  system_catalog_max_id = ((flash_tmp-&gt;end_address - flash_tmp-&gt;start_address) / sizeof(system_catalog_t));  start_addr = flash_tmp-&gt;start_address + gp_sys_log-&gt;system_log.write_pos;  /* Handle when the storage data address exceeds the planned memory address range */  if ((start_addr + wlen) &gt; flash_tmp-&gt;end_address) {     start_addr = flash_tmp-&gt;start_address;    /* Reset write position offset */    gp_sys_log-&gt;system_log.write_pos = 0;    /* LOG circular storage flag set */    gp_sys_log-&gt;system_log.log_cyclic_status = 0x01;   }  /* Write position offset */  gp_sys_log-&gt;system_log.write_pos += wlen; 
  if ((gp_sys_log-&gt;system_log.log_latest_time.Year != gp_sys_log-&gt;system_catalog.log_time.Year) ||    (gp_sys_log-&gt;system_log.log_latest_time.Month != gp_sys_log-&gt;system_catalog.log_time.Month) ||    (gp_sys_log-&gt;system_log.log_latest_time.Day != gp_sys_log-&gt;system_catalog.log_time.Day)) {
    /* Date changed, record directory information; when log_id is 0, do not write */    system_catalog_write(&gp_sys_log-&gt;system_catalog, gp_sys_log-&gt;system_catalog.log_id);    /* Record storage date */    gp_sys_log-&gt;system_catalog.log_time = gp_sys_log-&gt;system_log.log_latest_time;
    if ((gp_sys_log-&gt;system_catalog.log_id + 1) &gt;= system_catalog_max_id) {      gp_sys_log-&gt;system_log.catalog_num = system_catalog_max_id; /* Directory circular write, the number of directories should be maximum */      gp_sys_log-&gt;system_log.catalog_cyclic_status = 1; /* Directory circular write flag */    } else {      if (0 == gp_sys_log-&gt;system_log.catalog_cyclic_status) {        /* Get directory count */        gp_sys_log-&gt;system_log.catalog_num = gp_sys_log-&gt;system_catalog.log_id + 1;       }    }
    /* Store the latest directory item information */    gp_sys_log-&gt;system_catalog.log_id = (gp_sys_log-&gt;system_catalog.log_id + 1) % system_catalog_max_id;    gp_sys_log-&gt;system_catalog.log_addr = start_addr;    gp_sys_log-&gt;system_catalog.log_offset = wlen;   } else {    gp_sys_log-&gt;system_catalog.log_offset += wlen;   }
  /* Write position is the storage starting address and not the sector head address */  if ((flash_tmp-&gt;start_address == start_addr) && (SECTOR_OFFSET(flash_tmp-&gt;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);    /* Write back data from the sector head to the starting address */    flash_write(FLASH_SYSLOG_ZONE, SECTOR_BASE(start_addr), &sector_buf[0], SECTOR_OFFSET(start_addr));   }
  /* Write position is the sector head address, erase a sector storage area */  if (0 == SECTOR_OFFSET(start_addr)) {    flash_erase(FLASH_SYSLOG_ZONE, SECTOR_BASE(start_addr), FLASH_BLOCK_4K);  }
  /* Remaining space size in this sector */  remainbyte = FLASH_SECTOR_SIZE - (start_addr % FLASH_SECTOR_SIZE);  /* If the data length to be written is less than the remaining length of this sector, write directly */  if (remainbyte &gt; 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 &gt; FLASH_SECTOR_SIZE) ? FLASH_SECTOR_SIZE : wlen;      /* If the sector head address, erase the entire sector, this sector data is not saved */      if (0 == SECTOR_OFFSET(start_addr)) {        flash_erase(FLASH_SYSLOG_ZONE, SECTOR_BASE(start_addr), FLASH_BLOCK_4K);      }    }  }
  /* Circular storage parameters */  save_system_log_param();  return 0;}

5. System Debugging Interface

To better record system logs, combine application debugging levels to record error debugging information and key information that needs to be saved. The defined debugging levels are: close debugging level, error debugging level, warning debugging level, critical debugging level, debug debugging level, while LOG_RECORD_LEVEL will actively save logs and output information, LOG_ERROR_LEVEL will store corresponding log information but needs to output information according to the application debugging level. Setting and reading application debugging levels are to be defined by the reader.
#define LOG_CLOSE_LEVEL        0x00 /* Close debugging information */#define LOG_ERROR_LEVEL        0x01 /* Error debugging information */#define LOG_WARN_LEVEL        0x02 /* Warning debugging information */#define LOG_INFO_LEVEL        0x03 /* Critical debugging information */#define LOG_DEBUG_LEVEL        0x04 /* Debug debugging information */#define LOG_RECORD_LEVEL      0x10 /* Save logs and output information */  #define LOG_PRINT_LEVEL        0xff
#define SET_LOG_LEVEL(LEVEL)    (gp_sys_param-&gt;system_print_level = LEVEL)#define GET_LOG_LEVEL()        (gp_sys_param-&gt;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 = "&lt;error&gt;:"},  { .level = LOG_WARN_LEVEL,    .fmt_str = "&lt;warn&gt;:"},  { .level = LOG_INFO_LEVEL,    .fmt_str = "&lt;info&gt;:"},  { .level = LOG_DEBUG_LEVEL,   .fmt_str = "&lt;debug&gt;:"},  { .level = LOG_RECORD_LEVEL,  .fmt_str = "&lt;record&gt;:"},};
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};
  /* For OS system */  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() &lt; level) && (level != LOG_RECORD_LEVEL) && (level != LOG_ERROR_LEVEL))      goto exit_end;
    for (i = 0; i &lt; SYSTEM_PRINT_FMT_LIST_MAX; i++) {      if (level == system_print_fmt_list[i].level) {        fmt_index = i;        break;      }    }
    if (i &gt; 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 &lt;= 0) {    goto exit_end;    }
  if (level != LOG_PRINT_LEVEL) {    num += fmt_str_len;    buf[num + TIME_PREFIX_SIZE] = '\r';    buf[num + TIME_PREFIX_SIZE + 1] = '\n';    num += 2;  }
  if ((GET_LOG_LEVEL() &lt; level) && (level == LOG_ERROR_LEVEL)) {    //do nothing  } else {    ret = bsp_debug_send((uint8_t*)&buf[TIME_PREFIX_SIZE], num);    }
  if ((LOG_ERROR_LEVEL == level) || (LOG_RECORD_LEVEL == level)) {    bsp_rtc_get_time(&time);    sprintf(&buf[0], "[%04d-%02d-%02d %02d:%02d:%02d",      time.Year, time.Month, time.Day,time.Hour, time.Minute, time.Second);    buf[TIME_PREFIX_SIZE - 1] = ']';    gp_sys_log-&gt;system_log.log_latest_time = time;    system_log_write((uint8_t *)buf, num + TIME_PREFIX_SIZE);  }  
exit_end:  xSemaphoreGive(sem);  return ret;}

6. Conclusion

This article provides a simple logging method for embedded devices, with a small amount of code and simple implementation, requiring reasonable memory planning for different devices.
According to the software running status, suitably add debugging information and save corresponding log information, facilitating developers to understand the system or software running status, assisting development in analyzing data resources to better improve the system and enhance the effectiveness of locating and solving problems.
Source:
https://blog.csdn.net/LiaRonBob/article/details/102766871

Declaration: The materials in this article are sourced from the internet, and the copyright belongs to the original author. If there are any copyright issues, please contact me for deletion.


------------ END ------------


● Column "Embedded Tools"
● Column "Embedded Development"
● Column "Keil Tutorial"
● Selected tutorials from the embedded column



Follow the public account and reply "join group" to join the technical exchange group according to the rules, reply "1024" to see more content.




Click "Read the original" to see more sharing.

Leave a Comment

×