?在嵌入式設備中,很多場景都需要記錄日志,特別是單片機這種存儲資源有限的環境下,就需要一種輕量級的存儲方法。
1、系統日志??
在嵌入式設備應用場景中,系統日志時常可以監控設備軟件的運行狀態,及時記錄問題點以及關鍵信息,方便開發人員后期定位以及解決問題。 本文將講述一種簡易的系統日志記錄方法,用于保存設備的系統日志,視具體嵌入式設備情況而定,可存儲在MCU內部Flash、外部Flash、EEPROM等,本文采用外部Flash作為示例展開介紹。
2、思路分析??
對于系統日志可以當成文件系統,可以劃分為三個重要部分:目錄區、參數區、日志區。
目錄區:根據日期進行歸類,記錄當天的日志的存儲地址、日志索引、日志大小,通過目錄可以獲取整個日志文件的概況;
參數區:存儲記錄日志寫位置、目錄項個數、寫狀態等參數;
日志區:這是我們主要的存儲區,記錄系統的日志,支持環寫。這三個區域都需要占用部分內存,可以自行分配大小。
? 實現的效果如下圖所示,設置通過指令可查詢到整個日志目錄區的概況。 查詢系統日志目錄:AT+CATALOG? LOG_ID:存儲日志按日期分類,該ID用于查詢對應日期日志,從1開始計數; LOG_DATE:系統日志存儲日期; LOG_ADDR:系統日志存儲外部FLASH地址; LOG_OFFSET:系統日志存儲偏移量(各日期日志大小,單位:字節)。
? ?
查詢指定日期系統日志:AT+CATALOG=
?
另外提供移除系統日志(清除日志目錄)指令:AT+RMLOG,后面將講述具體實現。
3、Flash內存劃分?
FLASH內存需要看具體設備進行合理劃分,目錄區、參數區與日志區實現環形存儲,延長擦寫壽命。
#define FLASH_SECTOR_SIZE ((uint32_t)0x001000) #define FLASH_BLOCK_32K_SIZE ((uint32_t)0x008000) #define FLASH_BLOCK_64K_SIZE ((uint32_t)0x010000) #define SECTOR_MASK (FLASH_SECTOR_SIZE - 1) /*扇區掩碼 ------*/ #define SECTOR_BASE(addr) (addr & (~SECTOR_MASK)) /*扇區的基地址 --*/ #define SECTOR_OFFSET(addr) (addr & SECTOR_MASK) /*扇區內的偏移 --*/ #define BLOCK_32K_BASE(addr) (addr & (~(FLASH_BLOCK_32K_SIZE))) #define BLOCK_64K_BASE(addr) (addr & (~(FLASH_BLOCK_64K_SIZE))) typedef enum { FLASH_BLOCK_4K = 0, /**< flash erase block size 4k */ FLASH_BLOCK_32K = 1, /**< flash erase block size 32k */ FLASH_BLOCK_64K = 2 /**< flash erase block size 64k */ }flash_block_t; /* flash 空間索引 */ typedef enum{ FLASH_CATALOG_ZONE = 0, FLASH_SYSLOG_PARA_ZONE, FLASH_SYSLOG_ZONE, FLASH_ZONEX, }flash_zone_e; typedef struct{ flash_zone_e zone; uint32_t start_address; uint32_t end_address; }flash_table_t; /* 地址劃分 */ static const flash_table_t flash_table[] = { { .zone = FLASH_CATALOG_ZONE, .start_address = 0x03200000, .end_address = 0x032FFFFF}, { .zone = FLASH_SYSLOG_PARA_ZONE, .start_address = 0x03300000, .end_address = 0x033FFFFF}, { .zone = FLASH_SYSLOG_ZONE, .start_address = 0x03400000, .end_address = 0x03FFFFFF}, };? Flash底層實現擦除、讀寫操作接口,由讀者自行實現。
flash_table_t *get_flash_table(flash_zone_e zone) { int i = 0; for (i = 0; i < flash_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; }? 參數與結構體定義 日志數據存儲時間戳,便于問題定位,需要實現RTC接口調用。
typedef struct { uint16_t Year; /* 年份:YYYY */ uint8_t Month; /* 月份:MM */ uint8_t Day; /* 日:DD */ uint8_t Hour; /* 小時:HH */ uint8_t Minute; /* 分鐘:MM */ uint8_t Second; /* 秒:SS */ }time_t; int?bsp_rtc_get_time(time_t?*date);? 參數區應當保證數據的正確性,應加入參數校驗存儲,定義校驗結構體。
#define SYSTEM_LOG_MAGIC_PARAM 0x87654321 /* 日志參數標識符 */ typedef struct { uint32_t magic; /* 參數標識符 */ uint16_t crc; /* 校驗值 */ uint16_t len; /* 參數長度 */ }?single_sav_t;? 參數區需記錄當前日志記錄的寫位置,以及目錄項個數,還有日志區和目錄區環寫狀態,并且存儲最新時間等等。
/* 日志區參數 */ typedef struct { uint32_t write_pos; /* 寫位置 */ uint32_t catalog_num; /* 目錄項個數 */ uint8_t log_cyclic_status; /* 系統日志環形寫狀態 */ uint8_t catalog_cyclic_status; /* 日志目錄環形寫狀態 */ time_t log_latest_time; /* 存儲最新時間 */ }system_log_t; /* 目錄區參數 */ typedef struct { uint32_t log_id; /* 日志索引 */ uint32_t log_addr; /* 日志地址 */ uint32_t log_offset; /* 日志偏移大小,單位:字節 */ time_t log_time; /* 日志存儲時間 */ }system_catalog_t; /* 系統日志參數 */ typedef struct { single_sav_t crc_val; system_log_t system_log; system_catalog_t system_catalog; }sys_log_param_t; typedef struct { uint8_t system_log_print_enable; /* 系統日志打印使能 */ uint16_t system_log_print_id; /* 打印指定id系統日志 */ uint32_t system_log_param_addr; /* 當前日志寫地址 */ } sys_ram_t; sys_ram_t SysRam; sys_log_param_t SysLogParam; sys_ram_t *gp_sys_ram = &SysRam; sys_log_param_t?*gp_sys_log?=?&SysLogParam;?
?
4、實現接口說明
CRC校驗接口,可以自定義實現。
/* 16位CRC校驗高位表 */ static const uint8_t auchCRCHi[]={ 0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40, 0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41, 0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41, 0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40, 0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41, 0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40, 0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40, 0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41, 0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41, 0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40, 0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40, 0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41, 0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40, 0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41, 0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41, 0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40 }; /* 16位CRC校驗低位表 */ static const uint8_t auchCRCLo[]={ 0x00,0xc0,0xc1,0x01,0xc3,0x03,0x02,0xc2,0xc6,0x06,0x07,0xc7,0x05,0xc5,0xc4,0x04, 0xcc,0x0c,0x0d,0xcd,0x0f,0xcf,0xce,0x0e,0x0a,0xca,0xcb,0x0b,0xc9,0x09,0x08,0xc8, 0xd8,0x18,0x19,0xd9,0x1b,0xdb,0xda,0x1a,0x1e,0xde,0xdf,0x1f,0xdd,0x1d,0x1c,0xdc, 0x14,0xd4,0xd5,0x15,0xd7,0x17,0x16,0xd6,0xd2,0x12,0x13,0xd3,0x11,0xd1,0xd0,0x10, 0xf0,0x30,0x31,0xf1,0x33,0xf3,0xf2,0x32,0x36,0xf6,0xf7,0x37,0xf5,0x35,0x34,0xf4, 0x3c,0xfc,0xfd,0x3d,0xff,0x3f,0x3e,0xfe,0xfa,0x3a,0x3b,0xfb,0x39,0xf9,0xf8,0x38, 0x28,0xe8,0xe9,0x29,0xeb,0x2b,0x2a,0xea,0xee,0x2e,0x2f,0xef,0x2d,0xed,0xec,0x2c, 0xe4,0x24,0x25,0xe5,0x27,0xe7,0xe6,0x26,0x22,0xe2,0xe3,0x23,0xe1,0x21,0x20,0xe0, 0xa0,0x60,0x61,0xa1,0x63,0xa3,0xa2,0x62,0x66,0xa6,0xa7,0x67,0xa5,0x65,0x64,0xa4, 0x6c,0xac,0xad,0x6d,0xaf,0x6f,0x6e,0xae,0xaa,0x6a,0x6b,0xab,0x69,0xa9,0xa8,0x68, 0x78,0xb8,0xb9,0x79,0xbb,0x7b,0x7a,0xba,0xbe,0x7e,0x7f,0xbf,0x7d,0xbd,0xbc,0x7c, 0xb4,0x74,0x75,0xb5,0x77,0xb7,0xb6,0x76,0x72,0xb2,0xb3,0x73,0xb1,0x71,0x70,0xb0, 0x50,0x90,0x91,0x51,0x93,0x53,0x52,0x92,0x96,0x56,0x57,0x97,0x55,0x95,0x94,0x54, 0x9c,0x5c,0x5d,0x9d,0x5f,0x9f,0x9e,0x5e,0x5a,0x9a,0x9b,0x5b,0x99,0x59,0x58,0x98, 0x88,0x48,0x49,0x89,0x4b,0x8b,0x8a,0x4a,0x4e,0x8e,0x8f,0x4f,0x8d,0x4d,0x4c,0x8c, 0x44,0x84,0x85,0x45,0x87,0x47,0x46,0x86,0x82,0x42,0x43,0x83,0x41,0x81,0x80,0x40 }; /* 實現crc功能函數 */ static uint16_t CRC16(uint8_t* puchMsg, uint16_t usDataLen) { uint8_t uchCRCHi=0xff; uint8_t uchCRCLo=0xff; uint16_t uIndex; while(usDataLen--) { uIndex=uchCRCHi^*(puchMsg++); uchCRCHi=uchCRCLo^auchCRCHi[uIndex]; uchCRCLo=auchCRCLo[uIndex]; } return uchCRCHi<<8|uchCRCLo; }? 保存系統日志參數,每實現寫日志操作后都需要保存當前的參數值,防止意外丟失。
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); /* 校驗參數 */ 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; /* 剩余內存不夠寫,則重新從起始地址開始寫,實現環形存儲功能 */ if ((start_addr + len) > flash_tmp->end_address) { start_addr = flash_tmp->start_address; } gp_sys_ram->system_log_param_addr = start_addr + len; /* 首地址存儲,擦除整個系統日志參數存儲區,如果劃分的內存較大,可能出現第一次擦寫等待時間較長, 但實際應用嵌入式設備應該不會占用太多的內存存儲系統日志,只當為輔助使用,有額外應用可自行實現 */ if (flash_tmp->start_address == start_addr) { /*for (i = flash_tmp->start_address; i < 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; } } }? 導入系統日志默認參數接口,初始化默認參數或者移除日志。
void load_system_log_default_param(void) { /* 系統日志默認參數 */ /* 目錄環寫狀態標志 */ gp_sys_log->system_log.catalog_cyclic_status = 0x00; /* 目錄項個數 */ gp_sys_log->system_log.catalog_num = 0; /* 日志環寫標志 , 1:環寫狀態 */ gp_sys_log->system_log.log_cyclic_status = 0; /* 設置默認值,實際會重新從RTC獲取最新時間 */ gp_sys_log->system_log.log_latest_time.Year = 2019; gp_sys_log->system_log.log_latest_time.Month = 5; gp_sys_log->system_log.log_latest_time.Day = 8; gp_sys_log->system_log.log_latest_time.Hour = 13; gp_sys_log->system_log.log_latest_time.Minute = 14; gp_sys_log->system_log.log_latest_time.Second = 10; /* 日志寫位置從0開始 */ gp_sys_log->system_log.write_pos = 0; gp_sys_log->system_catalog.log_addr = 0; gp_sys_log->system_catalog.log_id = 0; gp_sys_log->system_catalog.log_offset = 0; gp_sys_log->system_catalog.log_time.Year = 2019; gp_sys_log->system_catalog.log_time.Month = 5; gp_sys_log->system_catalog.log_time.Day = 8; gp_sys_log->system_catalog.log_time.Hour = 12; gp_sys_log->system_catalog.log_time.Minute = 12; gp_sys_log->system_catalog.log_time.Second = 14; gp_sys_log->crc_val.magic = SYSTEM_LOG_MAGIC_PARAM; /* 導入默認參數后進行保存 */ save_system_log_param(); }? 設備開機或者復位都會進行導入系統日志參數操作,恢復日志讀寫參數,參數區為頻繁讀寫操作區域,每一次寫操作都會進行一次偏移,有效的導入參數方法是從參數區結束地址到起始地址進行掃描,掃描不到合法的參數則會導入默認日志參數。
/* 參數初始化,在終端啟動時調用 */ int load_system_log_param(void) { uint32_t i = 0; single_sav_t psav; uint32_t end_addr; uint32_t interal = sizeof(sys_log_param_t); int data_len = sizeof(sys_log_param_t) - sizeof(single_sav_t); uint8_t *pram = (uint8_t *)&SysLogParam; flash_table_t *flash_tmp = get_flash_table(FLASH_SYSLOG_PARA_ZONE); end_addr =flash_tmp->end_address - (flash_tmp->end_address - flash_tmp->start_address) % interal; for (i = end_addr - interal; i > flash_tmp->start_address; i -= interal) { flash_read(FLASH_SYSLOG_PARA_ZONE, i, (uint8_t *)&psav, sizeof(single_sav_t)); if ((psav.magic == SYSTEM_LOG_MAGIC_PARAM) && (psav.len ==data_len)) { flash_read(FLASH_SYSLOG_PARA_ZONE, i + sizeof(single_sav_t), &pram[sizeof(single_sav_t)], data_len); if (psav.crc != CRC16(&pram[sizeof(single_sav_t)], data_len)) continue; gp_sys_ram->system_log_param_addr = i; log_info("Load System Log Param Addr[0x%08x]!", gp_sys_ram->system_log_param_addr); return 0; } } /* 掃描不到合法的參數,導入默認系統日志參數 */ load_system_log_default_param(); /* 獲取日志寫地址 */ gp_sys_ram->system_log_param_addr = flash_tmp->start_address; log_info("Load System Log Param Addr(Default)[0x%08x]!", gp_sys_ram->system_log_param_addr); return 1; }? 讀寫系統日志目錄接口,讀寫指定日志索引目錄信息。實際實現會定義最新的目錄信息存儲在日志參數區,當日期發生改變,則表示當前目錄信息已經完結,將最新的目錄信息錄入日志目錄區保存,最多每天寫入一次目錄區。
/* 讀取日志目錄區指定日志索引目錄信息 */ int system_catalog_read(system_catalog_t *catalog, uint32_t id) { uint32_t addr; int rlen = sizeof(system_catalog_t); uint8_t *pbuf = (uint8_t *)catalog; flash_table_t *flash_tmp = get_flash_table(FLASH_CATALOG_ZONE); if (0 == id) return -1; addr = flash_tmp->start_address + (rlen * (id - 1)); if (addr > flash_tmp->end_address) return -1; return flash_read(FLASH_CATALOG_ZONE, addr, pbuf, rlen); } /* 寫日志目錄區目錄信息 */ int system_catalog_write(system_catalog_t *catalog, uint32_t id) { uint32_t start_offset; uint32_t start_addr; uint32_t start_base; uint32_t remainbyte; int wlen = sizeof(system_catalog_t); uint8_t *pdata = (uint8_t *)catalog; flash_table_t *flash_tmp = get_flash_table(FLASH_CATALOG_ZONE); if (0 == id) return -1; start_addr = flash_tmp->start_address + wlen * (id - 1); if ((start_addr + wlen) > flash_tmp->end_address) { start_addr = flash_tmp->start_address; } /* 本扇區剩余空間大小 */ remainbyte = FLASH_SECTOR_SIZE - (start_addr % FLASH_SECTOR_SIZE); /* 寫入數據長度小于本扇區剩余長度,直接寫入 */ if (remainbyte > wlen) { remainbyte = wlen; } /* 寫目錄次數不會太頻繁,視具體情況改寫操作實現 */ while (1) { start_base = SECTOR_BASE(start_addr); start_offset = SECTOR_OFFSET(start_addr); flash_read(FLASH_CATALOG_ZONE, start_base, sector_buf, FLASH_SECTOR_SIZE); flash_erase(FLASH_CATALOG_ZONE, start_base, FLASH_BLOCK_4K); memcpy((char *)§or_buf[start_offset], pdata, remainbyte); flash_write(FLASH_CATALOG_ZONE, start_base, sector_buf, FLASH_SECTOR_SIZE); if (remainbyte == wlen) { break; } else { pdata += remainbyte; start_addr += remainbyte; wlen -= remainbyte; remainbyte = (wlen > FLASH_SECTOR_SIZE) ? FLASH_SECTOR_SIZE : wlen; } } return 0; }? 打印系統日志目錄區信息,可實現通過指令查詢到目錄區信息。
int system_catalog_all_print(void) { int i = 0; system_catalog_t catalog; printf("System Log Command Information: "); printf("Query Specifies Log : AT+CATALOG=? 讀取指定日志目錄索引信息接口,可指定日志索引或者讀取全部日志數據。"); printf("Query All Log : AT+CATALOG=<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++) { /* 當前最新目錄信息 */ if (i == (gp_sys_log->system_catalog.log_id - 1)) { catalog = gp_sys_log->system_catalog; /* 獲取當前最新目錄信息 */ } else { system_catalog_read(&catalog, i + 1); } printf("%d %04d-%02d-%02d 0x%08X %d ", catalog.log_id, catalog.log_time.Year, catalog.log_time.Month, catalog.log_time.Day, catalog.log_addr, catalog.log_offset); memset((char *)&catalog, 0, sizeof(system_catalog_t)); } return 0; }
int system_log_task(int argc) { int rlen = 0; uint32_t offset, start_addr, end_addr; system_catalog_t catalog; flash_table_t *flash_tmp =get_flash_table(FLASH_SYSLOG_ZONE); if (0 == gp_sys_ram->system_log_print_enable) return 1; gp_sys_ram->system_log_print_enable = 0x00; if (gp_sys_ram->system_log_print_id == ALL_LOG_PRINT) { /* log回環寫標志,打印整個LOG存儲區 */ if (0x01 == gp_sys_log->system_log.log_cyclic_status) { start_addr = flash_tmp->start_address; end_addr = flash_tmp->end_address; offset = end_addr - start_addr; } else { start_addr = flash_tmp->start_address; end_addr = start_addr + gp_sys_log->system_log.write_pos; offset = gp_sys_log->system_log.write_pos; } } else { /* 讀取指定ID日志 */ if (gp_sys_ram->system_log_print_id == gp_sys_log->system_catalog.log_id) { catalog = gp_sys_log->system_catalog; } else { system_catalog_read(&catalog, gp_sys_ram->system_log_print_id); } start_addr = catalog.log_addr; offset = catalog.log_offset; } if (0 == offset) return 1; while (1) { rlen = (offset > 512) ? 512 : offset; system_log_read(sector_buf, start_addr, rlen); HAL_Delay(80); /* 目錄信息通過調式串口打印 */ bsp_debug_send(sector_buf, rlen); start_addr += rlen; offset -= rlen; if (0 == offset) break; } return 0; }? 存儲系統日志接口,實現更新存儲日期,當寫位置為扇區地址,則擦除一個扇區作為存儲日志,這樣避免每寫一次就擦除一次。
int system_log_write(uint8_t *wbuf, int wlen) { uint32_t start_addr; uint8_t *pdata = wbuf; uint32_t remainbyte; int system_catalog_max_id; flash_table_t *flash_tmp =get_flash_table(FLASH_SYSLOG_ZONE); /* 計算目錄區的最大存儲目錄項個數 */ system_catalog_max_id = ((flash_tmp->end_address - flash_tmp->start_address) / sizeof(system_catalog_t)); start_addr = flash_tmp->start_address + gp_sys_log->system_log.write_pos; /* 存儲數據地址大于規劃內存地址范圍處理 */ if ((start_addr + wlen) > flash_tmp->end_address) { start_addr = flash_tmp->start_address; /* 寫位置偏移量重置 */ gp_sys_log->system_log.write_pos = 0; /* LOG回環存儲標志置位 */ gp_sys_log->system_log.log_cyclic_status = 0x01; } /* 寫位置偏移 */ gp_sys_log->system_log.write_pos += wlen; if ((gp_sys_log->system_log.log_latest_time.Year != gp_sys_log->system_catalog.log_time.Year) || (gp_sys_log->system_log.log_latest_time.Month != gp_sys_log->system_catalog.log_time.Month) || (gp_sys_log->system_log.log_latest_time.Day != gp_sys_log->system_catalog.log_time.Day)) { /* 日期改變,記錄目錄信息,當log_id為0,則不寫入 */ system_catalog_write(&gp_sys_log->system_catalog, gp_sys_log->system_catalog.log_id); /* 記錄存儲日期 */ gp_sys_log->system_catalog.log_time = gp_sys_log->system_log.log_latest_time; if ((gp_sys_log->system_catalog.log_id + 1) >= system_catalog_max_id) { gp_sys_log->system_log.catalog_num = system_catalog_max_id; /* 目錄循環寫,目錄數應為最大 */ gp_sys_log->system_log.catalog_cyclic_status = 1; /* 目錄回環寫標志 */ } else { if (0 == gp_sys_log->system_log.catalog_cyclic_status) { /* 獲取目錄數 */ gp_sys_log->system_log.catalog_num = gp_sys_log->system_catalog.log_id + 1; } } /* 存儲最新目錄項信息 */ gp_sys_log->system_catalog.log_id = (gp_sys_log->system_catalog.log_id + 1) % system_catalog_max_id; gp_sys_log->system_catalog.log_addr = start_addr; gp_sys_log->system_catalog.log_offset = wlen; } else { gp_sys_log->system_catalog.log_offset += wlen; } /* 寫位置為存儲起始地址并且不為扇區首地址 */ if ((flash_tmp->start_address == start_addr) && (SECTOR_OFFSET(flash_tmp->start_address))){ flash_read(FLASH_SYSLOG_ZONE, SECTOR_BASE(start_addr), sector_buf, FLASH_SECTOR_SIZE); flash_erase(FLASH_SYSLOG_ZONE, SECTOR_BASE(start_addr), FLASH_BLOCK_4K); /* 將扇區頭部至起始地址區間的數據回寫 */ flash_write(FLASH_SYSLOG_ZONE, SECTOR_BASE(start_addr), §or_buf[0], SECTOR_OFFSET(start_addr)); } /* 寫位置為扇區首地址,則擦除一個扇區的存儲區 */ if (0 == SECTOR_OFFSET(start_addr)) { flash_erase(FLASH_SYSLOG_ZONE, SECTOR_BASE(start_addr), FLASH_BLOCK_4K); } /* 本扇區剩余空間大小 */ remainbyte = FLASH_SECTOR_SIZE - (start_addr % FLASH_SECTOR_SIZE); /* 寫入數據長度小于本扇區剩余長度,直接寫入 */ if (remainbyte > wlen) { remainbyte = wlen; } while (1) { flash_write(FLASH_SYSLOG_ZONE, start_addr, pdata, remainbyte); if (remainbyte == wlen) { break; } else { pdata += remainbyte; start_addr += remainbyte; wlen -= remainbyte; remainbyte = (wlen > FLASH_SECTOR_SIZE) ? FLASH_SECTOR_SIZE : wlen; /* 扇區首地址則擦除整個扇區,該扇區數據不保存 */ if (0 == SECTOR_OFFSET(start_addr)) { flash_erase(FLASH_SYSLOG_ZONE, SECTOR_BASE(start_addr), FLASH_BLOCK_4K); } } } /* 環形存儲參數 */ save_system_log_param(); return 0; }?
?
5、系統調試對接
為了更好記錄系統日志,將應用調試等級結合一塊,實現記錄錯誤調試信息以及需要保存的關鍵信息。定義的調試等級有:關閉調試等級、錯誤調試等級、警告調試等級、關鍵調試等級、debug調試等級,而LOG_RECORD_LEVEL將主動保存日志并輸出信息,LOG_ERROR_LEVEL會存儲對應的日志信息,但需要根據應用調試等級輸出信息。設置與讀取應用調試等級由讀者自行定義。
#define LOG_CLOSE_LEVEL 0x00 /* 關閉調試信息 */ #define LOG_ERROR_LEVEL 0x01 /* 錯誤調試信息 */ #define LOG_WARN_LEVEL 0x02 /* 警告調試信息 */ #define LOG_INFO_LEVEL 0x03 /* 關鍵調試信息 */ #define LOG_DEBUG_LEVEL 0x04 /* debug調試信息 */ #define LOG_RECORD_LEVEL 0x10 /* 保存日志并輸出信息 */ #define LOG_PRINT_LEVEL 0xff #define SET_LOG_LEVEL(LEVEL) (gp_sys_param->system_print_level = LEVEL) #define GET_LOG_LEVEL() (gp_sys_param->system_print_level) #define log_debug(fmt, args...) log_format(LOG_DEBUG_LEVEL, fmt, ##args) #define log_info(fmt, args...) log_format(LOG_INFO_LEVEL, fmt, ##args) #define log_warn(fmt, args...) log_format(LOG_WARN_LEVEL, fmt, ##args) #define log_error(fmt, args...) log_format(LOG_ERROR_LEVEL, fmt, ##args) #define log_record(fmt, args...) log_format(LOG_RECORD_LEVEL, fmt, ##args) #define printf(fmt, args...) log_format(LOG_PRINT_LEVEL, fmt, ##args) typedef struct { int level; char *fmt_str; }system_print_fmt_t; system_print_fmt_t system_print_fmt_list[] = { { .level = LOG_ERROR_LEVEL, .fmt_str = "?:"}, { .level = LOG_WARN_LEVEL, .fmt_str = " :"}, { .level = LOG_INFO_LEVEL, .fmt_str = " :"}, { .level = LOG_DEBUG_LEVEL, .fmt_str = " :"}, { .level = LOG_RECORD_LEVEL, .fmt_str = " :"}, }; int log_format(uint8_t level, const char *fmt, ...) { #define TIME_PREFIX_SIZE (21) #define PRINT_MAX_SIZE (1024 + TIME_PREFIX_SIZE) va_list args; int num = 0, i = 0, fmt_index = 0; int fmt_str_len = 0, ret = -1; int file_str_len = 0, line_str_len = 0; char line_buf[20] = {0}; static char buf[PRINT_MAX_SIZE]; static QueueHandle_t sem = NULL; time_t time = {0}; /* 針對os系統 */ if (NULL == sem) { sem = xSemaphoreCreateCounting(1, 1); /* always think of success */ } xSemaphoreTake(sem, portMAX_DELAY); ret = -1; fmt_str_len = 0; if (level != LOG_PRINT_LEVEL) { if ((GET_LOG_LEVEL() < level) && (level != LOG_RECORD_LEVEL) && (level != LOG_ERROR_LEVEL)) goto exit_end; for (i = 0; i < SYSTEM_PRINT_FMT_LIST_MAX; 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、結語
本文提供的一種簡易嵌入式設備系統日志記錄方法,代碼量不多,實現簡單,針對不同的設備需要合理規劃內存使用。 ? 根據軟件運行狀態,合適加入調試信息并保存對應的日志信息,方便開發人員了解系統或軟件運行狀況,協助開發分析數據資源從而更好完善系統,提高定位以及解決問題的效果。 ?
審核編輯:湯梓紅
評論
查看更多