?在嵌入式設(shè)備中,很多場(chǎng)景都需要記錄日志,特別是單片機(jī)這種存儲(chǔ)資源有限的環(huán)境下,就需要一種輕量級(jí)的存儲(chǔ)方法。
1、系統(tǒng)日志??
在嵌入式設(shè)備應(yīng)用場(chǎng)景中,系統(tǒng)日志時(shí)??梢员O(jiān)控設(shè)備軟件的運(yùn)行狀態(tài),及時(shí)記錄問題點(diǎn)以及關(guān)鍵信息,方便開發(fā)人員后期定位以及解決問題。 本文將講述一種簡(jiǎn)易的系統(tǒng)日志記錄方法,用于保存設(shè)備的系統(tǒng)日志,視具體嵌入式設(shè)備情況而定,可存儲(chǔ)在MCU內(nèi)部Flash、外部Flash、EEPROM等,本文采用外部Flash作為示例展開介紹。
2、思路分析??
對(duì)于系統(tǒng)日志可以當(dāng)成文件系統(tǒng),可以劃分為三個(gè)重要部分:目錄區(qū)、參數(shù)區(qū)、日志區(qū)。
目錄區(qū):根據(jù)日期進(jìn)行歸類,記錄當(dāng)天的日志的存儲(chǔ)地址、日志索引、日志大小,通過目錄可以獲取整個(gè)日志文件的概況;
參數(shù)區(qū):存儲(chǔ)記錄日志寫位置、目錄項(xiàng)個(gè)數(shù)、寫狀態(tài)等參數(shù);
日志區(qū):這是我們主要的存儲(chǔ)區(qū),記錄系統(tǒng)的日志,支持環(huán)寫。這三個(gè)區(qū)域都需要占用部分內(nèi)存,可以自行分配大小。
	? 實(shí)現(xiàn)的效果如下圖所示,設(shè)置通過指令可查詢到整個(gè)日志目錄區(qū)的概況。 查詢系統(tǒng)日志目錄:AT+CATALOG? LOG_ID:存儲(chǔ)日志按日期分類,該ID用于查詢對(duì)應(yīng)日期日志,從1開始計(jì)數(shù); LOG_DATE:系統(tǒng)日志存儲(chǔ)日期; LOG_ADDR:系統(tǒng)日志存儲(chǔ)外部FLASH地址; LOG_OFFSET:系統(tǒng)日志存儲(chǔ)偏移量(各日期日志大小,單位:字節(jié))。
	? 
 ?
	查詢指定日期系統(tǒng)日志:AT+CATALOG=
 ?
	另外提供移除系統(tǒng)日志(清除日志目錄)指令:AT+RMLOG,后面將講述具體實(shí)現(xiàn)。
3、Flash內(nèi)存劃分?
FLASH內(nèi)存需要看具體設(shè)備進(jìn)行合理劃分,目錄區(qū)、參數(shù)區(qū)與日志區(qū)實(shí)現(xiàn)環(huán)形存儲(chǔ),延長(zhǎng)擦寫壽命。
#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)         /*扇區(qū)掩碼 ------*/
#define SECTOR_BASE(addr)         (addr & (~SECTOR_MASK))        /*扇區(qū)的基地址 --*/
#define SECTOR_OFFSET(addr)       (addr & SECTOR_MASK)           /*扇區(qū)內(nèi)的偏移 --*/
#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底層實(shí)現(xiàn)擦除、讀寫操作接口,由讀者自行實(shí)現(xiàn)。
flash_table_t *get_flash_table(flash_zone_e zone)
{
  int i = 0;
  for (i = 0; i < 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 < flash_table_tmp->start_address ||address > 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 < flash_table_tmp->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 < flash_table_tmp->start_address) ||((address + length) > flash_table_tmp->end_address))
    return -1;
  bsp_spi_flash_buffer_read(buffer, address, length);
  return 0;
}
? 參數(shù)與結(jié)構(gòu)體定義 日志數(shù)據(jù)存儲(chǔ)時(shí)間戳,便于問題定位,需要實(shí)現(xiàn)RTC接口調(diào)用。
typedef struct {
  uint16_t   Year;    /* 年份:YYYY */
  uint8_t    Month;    /* 月份:MM */
  uint8_t    Day;    /* 日:DD */
  uint8_t     Hour;    /* 小時(shí):HH */
  uint8_t     Minute;    /* 分鐘:MM */
  uint8_t   Second;    /* 秒:SS */
}time_t;   
int?bsp_rtc_get_time(time_t?*date);
? 參數(shù)區(qū)應(yīng)當(dāng)保證數(shù)據(jù)的正確性,應(yīng)加入?yún)?shù)校驗(yàn)存儲(chǔ),定義校驗(yàn)結(jié)構(gòu)體。
#define SYSTEM_LOG_MAGIC_PARAM 0x87654321 /* 日志參數(shù)標(biāo)識(shí)符 */ typedef struct { uint32_t magic; /* 參數(shù)標(biāo)識(shí)符 */ uint16_t crc; /* 校驗(yàn)值 */ uint16_t len; /* 參數(shù)長(zhǎng)度 */ }?single_sav_t;? 參數(shù)區(qū)需記錄當(dāng)前日志記錄的寫位置,以及目錄項(xiàng)個(gè)數(shù),還有日志區(qū)和目錄區(qū)環(huán)寫狀態(tài),并且存儲(chǔ)最新時(shí)間等等。
/* 日志區(qū)參數(shù) */
typedef struct {
  uint32_t   write_pos;             /* 寫位置 */
  uint32_t   catalog_num;            /* 目錄項(xiàng)個(gè)數(shù) */
  uint8_t    log_cyclic_status;    /* 系統(tǒng)日志環(huán)形寫狀態(tài) */   
  uint8_t    catalog_cyclic_status; /* 日志目錄環(huán)形寫狀態(tài) */
  time_t     log_latest_time;     /* 存儲(chǔ)最新時(shí)間 */
}system_log_t;
/* 目錄區(qū)參數(shù) */
typedef struct {
  uint32_t log_id;     /* 日志索引 */  
  uint32_t log_addr;    /* 日志地址 */
  uint32_t log_offset;  /* 日志偏移大小,單位:字節(jié) */
  time_t   log_time;    /* 日志存儲(chǔ)時(shí)間 */
}system_catalog_t;
/* 系統(tǒng)日志參數(shù) */
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; /* 系統(tǒng)日志打印使能 */
  uint16_t system_log_print_id;    /* 打印指定id系統(tǒng)日志 */
  uint32_t system_log_param_addr;  /* 當(dāng)前日志寫地址 */
} 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、實(shí)現(xiàn)接口說明
CRC校驗(yàn)接口,可以自定義實(shí)現(xiàn)。
/* 16位CRC校驗(yàn)高位表 */
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校驗(yàn)低位表 */
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
};
/* 實(shí)現(xiàn)crc功能函數(shù) */
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<<8|uchCRCLo;
}
? 保存系統(tǒng)日志參數(shù),每實(shí)現(xiàn)寫日志操作后都需要保存當(dāng)前的參數(shù)值,防止意外丟失。
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);
  /* 校驗(yàn)參數(shù) */
  gp_sys_log->crc_val.magic = SYSTEM_LOG_MAGIC_PARAM;
  gp_sys_log->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;
  /* 剩余內(nèi)存不夠?qū)?,則重新從起始地址開始寫,實(shí)現(xiàn)環(huán)形存儲(chǔ)功能  */
  if ((start_addr + len) > flash_tmp->end_address) { 
    start_addr = flash_tmp->start_address;
  }
  gp_sys_ram->system_log_param_addr = start_addr + len;
  /* 首地址存儲(chǔ),擦除整個(gè)系統(tǒng)日志參數(shù)存儲(chǔ)區(qū),如果劃分的內(nèi)存較大,可能出現(xiàn)第一次擦寫等待時(shí)間較長(zhǎng),
     但實(shí)際應(yīng)用嵌入式設(shè)備應(yīng)該不會(huì)占用太多的內(nèi)存存儲(chǔ)系統(tǒng)日志,只當(dāng)為輔助使用,有額外應(yīng)用可自行實(shí)現(xiàn) */
  if (flash_tmp->start_address == start_addr) {
    /*for (i = flash_tmp->start_address; i < flash_tmp->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) <= flash_tmp->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) <= flash_tmp->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) <= flash_tmp->end_address) {
        flash_erase(FLASH_SYSLOG_PARA_ZONE, SECTOR_BASE(i), FLASH_BLOCK_4K);
        addr += FLASH_SECTOR_SIZE;
      } else {
        break;
      }
    } while (addr < flash_tmp->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;
    }
  }
}
? 導(dǎo)入系統(tǒng)日志默認(rèn)參數(shù)接口,初始化默認(rèn)參數(shù)或者移除日志。
void load_system_log_default_param(void)
{
  /* 系統(tǒng)日志默認(rèn)參數(shù) */
  /* 目錄環(huán)寫狀態(tài)標(biāo)志 */
  gp_sys_log->system_log.catalog_cyclic_status = 0x00;
  /* 目錄項(xiàng)個(gè)數(shù) */
  gp_sys_log->system_log.catalog_num = 0;
  /* 日志環(huán)寫標(biāo)志 , 1:環(huán)寫狀態(tài) */
  gp_sys_log->system_log.log_cyclic_status = 0;
  /* 設(shè)置默認(rèn)值,實(shí)際會(huì)重新從RTC獲取最新時(shí)間 */
  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;
  /* 導(dǎo)入默認(rèn)參數(shù)后進(jìn)行保存 */
  save_system_log_param();
}
? 設(shè)備開機(jī)或者復(fù)位都會(huì)進(jìn)行導(dǎo)入系統(tǒng)日志參數(shù)操作,恢復(fù)日志讀寫參數(shù),參數(shù)區(qū)為頻繁讀寫操作區(qū)域,每一次寫操作都會(huì)進(jìn)行一次偏移,有效的導(dǎo)入?yún)?shù)方法是從參數(shù)區(qū)結(jié)束地址到起始地址進(jìn)行掃描,掃描不到合法的參數(shù)則會(huì)導(dǎo)入默認(rèn)日志參數(shù)。
/* 參數(shù)初始化,在終端啟動(dòng)時(shí)調(diào)用 */
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;
    }
  }
  /* 掃描不到合法的參數(shù),導(dǎo)入默認(rèn)系統(tǒng)日志參數(shù) */
  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;
}
? 讀寫系統(tǒng)日志目錄接口,讀寫指定日志索引目錄信息。實(shí)際實(shí)現(xiàn)會(huì)定義最新的目錄信息存儲(chǔ)在日志參數(shù)區(qū),當(dāng)日期發(fā)生改變,則表示當(dāng)前目錄信息已經(jīng)完結(jié),將最新的目錄信息錄入日志目錄區(qū)保存,最多每天寫入一次目錄區(qū)。
/* 讀取日志目錄區(qū)指定日志索引目錄信息 */
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);
}
/* 寫日志目錄區(qū)目錄信息 */
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;
  }
  /* 本扇區(qū)剩余空間大小 */
  remainbyte = FLASH_SECTOR_SIZE - (start_addr % FLASH_SECTOR_SIZE);
  /* 寫入數(shù)據(jù)長(zhǎng)度小于本扇區(qū)剩余長(zhǎng)度,直接寫入 */
  if (remainbyte > wlen) {
    remainbyte = wlen;
  }
  /* 寫目錄次數(shù)不會(huì)太頻繁,視具體情況改寫操作實(shí)現(xiàn) */
  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;
}
? 打印系統(tǒng)日志目錄區(qū)信息,可實(shí)現(xiàn)通過指令查詢到目錄區(qū)信息。
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=<0>
");
  printf("Query All System Catalog:
");
  printf("LOG_ID    LOG_DATE    LOG_ADDR    LOG_OFFSET  
");
  for (i = 0; i < gp_sys_log->system_log.catalog_num; i++) {
    /* 當(dāng)前最新目錄信息 */    
    if (i == (gp_sys_log->system_catalog.log_id - 1)) {
      catalog = gp_sys_log->system_catalog; /* 獲取當(dāng)前最新目錄信息 */
    } 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;
}  
? 讀取指定日志目錄索引信息接口,可指定日志索引或者讀取全部日志數(shù)據(jù)。
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回環(huán)寫標(biāo)志,打印整個(gè)LOG存儲(chǔ)區(qū) */
    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);
    /* 目錄信息通過調(diào)式串口打印 */
    bsp_debug_send(sector_buf, rlen);
    start_addr += rlen;
    offset -= rlen;
    if (0 == offset) 
      break;
  }
  return 0;
}
? 存儲(chǔ)系統(tǒng)日志接口,實(shí)現(xiàn)更新存儲(chǔ)日期,當(dāng)寫位置為扇區(qū)地址,則擦除一個(gè)扇區(qū)作為存儲(chǔ)日志,這樣避免每寫一次就擦除一次。
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);
  /* 計(jì)算目錄區(qū)的最大存儲(chǔ)目錄項(xiàng)個(gè)數(shù) */
  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;
  /* 存儲(chǔ)數(shù)據(jù)地址大于規(guī)劃內(nèi)存地址范圍處理 */
  if ((start_addr + wlen) > flash_tmp->end_address) { 
    start_addr = flash_tmp->start_address;
    /* 寫位置偏移量重置 */
    gp_sys_log->system_log.write_pos = 0;
    /* LOG回環(huán)存儲(chǔ)標(biāo)志置位 */
    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)) {
    /* 日期改變,記錄目錄信息,當(dāng)log_id為0,則不寫入 */
    system_catalog_write(&gp_sys_log->system_catalog, gp_sys_log->system_catalog.log_id);
    /* 記錄存儲(chǔ)日期 */
    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; /* 目錄循環(huán)寫,目錄數(shù)應(yīng)為最大 */
      gp_sys_log->system_log.catalog_cyclic_status = 1; /* 目錄回環(huán)寫標(biāo)志 */
    } else {
      if (0 == gp_sys_log->system_log.catalog_cyclic_status) {
        /* 獲取目錄數(shù) */
        gp_sys_log->system_log.catalog_num = gp_sys_log->system_catalog.log_id + 1; 
      }
    }
    /* 存儲(chǔ)最新目錄項(xiàng)信息 */
    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; 
  }
  /* 寫位置為存儲(chǔ)起始地址并且不為扇區(qū)首地址 */
  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);
    /* 將扇區(qū)頭部至起始地址區(qū)間的數(shù)據(jù)回寫 */
    flash_write(FLASH_SYSLOG_ZONE, SECTOR_BASE(start_addr), §or_buf[0], SECTOR_OFFSET(start_addr)); 
  }
  /* 寫位置為扇區(qū)首地址,則擦除一個(gè)扇區(qū)的存儲(chǔ)區(qū)    */
  if (0 == SECTOR_OFFSET(start_addr)) {
    flash_erase(FLASH_SYSLOG_ZONE, SECTOR_BASE(start_addr), FLASH_BLOCK_4K);
  }
  /* 本扇區(qū)剩余空間大小 */
  remainbyte = FLASH_SECTOR_SIZE - (start_addr % FLASH_SECTOR_SIZE);
  /* 寫入數(shù)據(jù)長(zhǎng)度小于本扇區(qū)剩余長(zhǎng)度,直接寫入 */
  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;
      /* 扇區(qū)首地址則擦除整個(gè)扇區(qū),該扇區(qū)數(shù)據(jù)不保存 */
      if (0 == SECTOR_OFFSET(start_addr)) {
        flash_erase(FLASH_SYSLOG_ZONE, SECTOR_BASE(start_addr), FLASH_BLOCK_4K);
      }
    }
  }
  /* 環(huán)形存儲(chǔ)參數(shù) */
  save_system_log_param();
  return 0;
}
?
?
5、系統(tǒng)調(diào)試對(duì)接
為了更好記錄系統(tǒng)日志,將應(yīng)用調(diào)試等級(jí)結(jié)合一塊,實(shí)現(xiàn)記錄錯(cuò)誤調(diào)試信息以及需要保存的關(guān)鍵信息。定義的調(diào)試等級(jí)有:關(guān)閉調(diào)試等級(jí)、錯(cuò)誤調(diào)試等級(jí)、警告調(diào)試等級(jí)、關(guān)鍵調(diào)試等級(jí)、debug調(diào)試等級(jí),而LOG_RECORD_LEVEL將主動(dòng)保存日志并輸出信息,LOG_ERROR_LEVEL會(huì)存儲(chǔ)對(duì)應(yīng)的日志信息,但需要根據(jù)應(yīng)用調(diào)試等級(jí)輸出信息。設(shè)置與讀取應(yīng)用調(diào)試等級(jí)由讀者自行定義。
#define LOG_CLOSE_LEVEL        0x00 /* 關(guān)閉調(diào)試信息 */
#define LOG_ERROR_LEVEL        0x01 /* 錯(cuò)誤調(diào)試信息 */
#define LOG_WARN_LEVEL        0x02 /* 警告調(diào)試信息 */
#define LOG_INFO_LEVEL        0x03 /* 關(guān)鍵調(diào)試信息 */
#define LOG_DEBUG_LEVEL        0x04 /* debug調(diào)試信息 */
#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};
  /* 針對(duì)os系統(tǒng) */
  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; i++) {
      if (level == system_print_fmt_list[i].level) {
        fmt_index = i;
        break;
      }
    }
    if (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() < 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->system_log.log_latest_time = time;
    system_log_write((uint8_t *)buf, num + TIME_PREFIX_SIZE);
  }  
exit_end:
  xSemaphoreGive(sem);
  return ret;
}     
?
?
6、結(jié)語
本文提供的一種簡(jiǎn)易嵌入式設(shè)備系統(tǒng)日志記錄方法,代碼量不多,實(shí)現(xiàn)簡(jiǎn)單,針對(duì)不同的設(shè)備需要合理規(guī)劃內(nèi)存使用。 ? 根據(jù)軟件運(yùn)行狀態(tài),合適加入調(diào)試信息并保存對(duì)應(yīng)的日志信息,方便開發(fā)人員了解系統(tǒng)或軟件運(yùn)行狀況,協(xié)助開發(fā)分析數(shù)據(jù)資源從而更好完善系統(tǒng),提高定位以及解決問題的效果。 ?
審核編輯:湯梓紅
                        電子發(fā)燒友App
                    
                
                
          
        
        



           
            
            
                
            
評(píng)論