Easy Method for Logging in Embedded Systems

Follow+Star Public Account, don’t miss exciting content

Easy Method for Logging in Embedded Systems

Source | My Last Name is Liang

Many scenarios require logging. In embedded systems, especially in environments with limited storage resources like microcontrollers, a lightweight storage method is necessary.

System Log

In embedded device applications, system logs can often monitor the running status of device software, timely record problem points and key information, making it easier for developers to locate and solve problems later.
This article will discuss a simple method for logging system logs to save the device’s system logs. Depending on the specific embedded device, the logs can be stored in MCU internal Flash, external Flash, EEPROM, etc. This article uses external Flash as an example for detailed introduction.
Thought Analysis
The system log 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, recording the storage address, log index, and log size of the logs for that day. The directory provides an overview of the entire log file;

  • Parameter Area: Stores parameters such as the log write position, number of directory entries, write status, etc.;

  • Log Area: This is the main storage area for recording system logs, supporting circular writing. These three areas need to occupy some memory, and their sizes can be allocated as needed.

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 for storing system logs;
LOG_OFFSET: Offset of system log storage (size of logs for each date, in bytes).
Easy Method for Logging in Embedded Systems
Query Specified Date System Log: AT+CATALOG=<LOG_ID>
LOG_ID: Obtained when querying the system log directory; when LOG_ID is 0, it queries the entire system log.
Easy Method for Logging in Embedded Systems
Additionally, a command to remove system logs (clear log directory) is provided: AT+RMLOG, and the specific implementation will be discussed later.
FLASH Memory Division
The FLASH memory needs to be reasonably divided based on the specific device, implementing circular storage for the directory area, parameter area, and log area to extend the 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 division */
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},
};
The Flash low-level implementation of erase, read, and write operation interfaces is 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 *)&amp;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 definitions
Log data storage timestamp for easy problem localization, requires implementing RTC interface calls.
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 correctness, and parameter verification storage should be added, defining a 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 entries, and the circular write status of the log area and directory area, as well as store the latest time, etc.
/* Log area parameters */
typedef struct {
  uint32_t   write_pos;             /* Write position */
  uint32_t   catalog_num;            /* Number of directory entries */
  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 = &amp;SysRam;
sys_log_param_t *gp_sys_log = &amp;SysLogParam;
Implementation Interface Description
CRC verification interface can be implemented as needed.
/* 16-bit CRC high byte 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,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,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,0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40};
/* 16-bit CRC low byte 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 functionality */
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 *)&amp;SysLogParam;
  flash_table_t *flash_tmp = get_flash_table(FLASH_SYSLOG_PARA_ZONE);
  /* Check 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(&amp;pdata[sizeof(single_sav_t)], gp_sys_log-&gt;crc_val.len);
  start_addr = gp_sys_ram-&gt;system_log_param_addr;
  /* If remaining memory is insufficient for writing, 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 at the first address, erase the entire system log parameter storage area. If the allocated memory is large, the first erase might take longer, but in practical applications, embedded devices should not occupy too much memory for system logs, only as auxiliary use, additional applications can be implemented as needed */
  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 default system log parameters interface to initialize default parameters or remove logs.
void load_system_log_default_param(void){
  /* Default system log parameters */
  /* Directory circular write status flag */
  gp_sys_log-&gt;system_log.catalog_cyclic_status = 0x00;
  /* Number of directory entries */
  gp_sys_log-&gt;system_log.catalog_num = 0;
  /* Log circular write flag, 1: circular write status */
  gp_sys_log-&gt;system_log.log_cyclic_status = 0;
  /* Set default values, will actually re-fetch 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();
}
Device startup or reset will perform the operation of importing system log parameters, 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 *)&amp;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 *)&amp;psav, sizeof(single_sav_t));
    if ((psav.magic == SYSTEM_LOG_MAGIC_PARAM) &amp;&amp; (psav.len ==data_len)) {
            flash_read(FLASH_SYSLOG_PARA_ZONE, i + sizeof(single_sav_t), &amp;pram[sizeof(single_sav_t)], data_len);
      if (psav.crc != CRC16(&amp;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 entry written to the directory area per day.
/* Read specified log index directory information from the log directory */
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 */
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 in this sector */
  remainbyte = FLASH_SECTOR_SIZE - (start_addr % FLASH_SECTOR_SIZE);
  /* If the length of data to be written is less than the remaining length of this sector, write directly */
  if (remainbyte &gt; wlen) {
    remainbyte = wlen;
  }
  /* The number of directory writes will not be too frequent, modify the rewrite operation implementation as needed */
  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 *)&amp;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(&amp;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 *)&amp;catalog, 0, sizeof(system_catalog_t));
  }
  return 0;
}
Read specified log directory index information interface, can specify 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(&amp;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);
    /* Print directory information via 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 at the sector address, erase one sector as the storage log, thus avoiding erasing each time a write is performed.
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 storage directory entries 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 storage data address exceeds allocated 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_latest_time.Day)) {
    /* Date changed, record directory information, when log_id is 0, do not write */
    system_catalog_write(&amp;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; /* Maximum number of directory entries should be the 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 the number of directories */
        gp_sys_log-&gt;system_log.catalog_num = gp_sys_log-&gt;system_catalog.log_id + 1;
      }
    }
    /* Store latest directory 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 starting address of storage and not the sector head address */
  if ((flash_tmp-&gt;start_address == start_addr) &amp;&amp; (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 the data from the sector head to the starting address */
    flash_write(FLASH_SYSLOG_ZONE, SECTOR_BASE(start_addr), &amp;sector_buf[0], SECTOR_OFFSET(start_addr));
  }
  /* If the write position is the sector head address, erase one sector of the storage area */
  if (0 == SECTOR_OFFSET(start_addr)) {
    flash_erase(FLASH_SYSLOG_ZONE, SECTOR_BASE(start_addr), FLASH_BLOCK_4K);
  }
  /* Remaining space in this sector */
  remainbyte = FLASH_SECTOR_SIZE - (start_addr % FLASH_SECTOR_SIZE);
  /* If the length of data 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 it is the sector head address, erase the entire sector, the data in this sector will not be 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;
}
System Debugging Integration
To better record system logs, integrate 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, where LOG_RECORD_LEVEL will actively save logs and output information, and LOG_ERROR_LEVEL will store corresponding log information but need to output information based on the application debugging level. The setting and reading of 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) &amp;&amp; (level != LOG_RECORD_LEVEL) &amp;&amp; (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 *)&amp;buf[TIME_PREFIX_SIZE], system_print_fmt_list[fmt_index].fmt_str, fmt_str_len);
  }
    va_start(args, fmt);
    num = vsnprintf((char *)&amp;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*)&amp;buf[TIME_PREFIX_SIZE], num);
  }
  if ((LOG_ERROR_LEVEL == level) || (LOG_RECORD_LEVEL == level)) {
    bsp_rtc_get_time(&amp;time);
    sprintf(&amp;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;
}
Conclusion
This article provides a simple method for logging system logs in embedded devices. The code is not extensive and the implementation is simple, requiring reasonable memory planning based on different devices.
According to the software running status, appropriate debugging information can be added and the corresponding log information saved, which helps developers understand the system or software running status, assists in data resource analysis, and thus better improves the system, enhancing the effectiveness of localization and problem-solving.

Source address:

https://blog.csdn.net/LiaRonBob/article/details/102766871

Statement:This article’s materials come from the internet, and the copyright belongs to the original author. If there are any copyright issues, please contact me for deletion.

———— END ————

Easy Method for Logging in Embedded Systems

●Column “Embedded Tools

●Column “Embedded Development”

●Column “Keil Tutorial”

●Selected Tutorials from Embedded Column

Follow the public account reply “Join Group” to join the technical exchange group according to the rules, reply “1024” to see more content.

Easy Method for Logging in Embedded Systems

Easy Method for Logging in Embedded Systems

Click “Read the Original” to see more shares.

Leave a Comment