在嵌入式設備應用場景中,系統日志時常可以監控設備軟件的運行狀態,及時記錄問題點以及關鍵信息,方便開發人員后期定位以及解決問題。
系統日志
本文將講述一種簡易的系統日志記錄方法,用于保存設備的系統日志,視具體嵌入式設備情況而定,可存儲在MCU內部Flash、外部Flash、EEPROM等,本文采用外部Flash作為示例展開介紹。
思路分析 ?
對于系統日志可以當成文件系統,可以劃分為三個重要部分:目錄區、參數區、日志區。目錄區:根據日期進行歸類,記錄當天的日志的存儲地址、日志索引、日志大小,通過目錄可以獲取整個日志文件的概況;參數區:存儲記錄日志寫位置、目錄項個數、寫狀態等參數;日志區:這是我們主要的存儲區,記錄系統的日志,支持環寫。這三個區域都需要占用部分內存,可以自行分配大小。
實現的效果如下圖所示,設置通過指令可查詢到整個日志目錄區的概況。
查詢系統日志目錄:
AT+CATALOG?
LOG_ID:存儲日志按日期分類,該ID用于查詢對應日期日志,從1開始計數;
LOG_DATE:系統日志存儲日期;
LOG_ADDR:系統日志存儲外部FLASH地址;
LOG_OFFSET:系統日志存儲偏移量(各日期日志大小,單位:字節)。
另外提供移除系統日志(清除日志目錄)指令:AT+RMLOG,后面將講述具體實現。
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底層實現擦除、讀寫操作接口,由讀者自行實現。
?
flash_table_t?*get_flash_table(flash_zone_e?zone) { ????int?i?=?0; ????for?(i?=?0;?i?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?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?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;? 實現接口說明 ?
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?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?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?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; }? 系統調試對接 ?
為了更好記錄系統日志,將應用調試等級結合一塊,實現記錄錯誤調試信息以及需要保存的關鍵信息。定義的調試等級有:關閉調試等級、錯誤調試等級、警告調試等級、關鍵調試等級、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()??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()?system_log.log_latest_time?=?time; ????????system_log_write((uint8_t?*)buf,?num?+?TIME_PREFIX_SIZE); ????}? exit_end: ????xSemaphoreGive(sem); ????return?ret; } ?
結語 ?
本文提供的一種簡易嵌入式設備系統日志記錄方法,代碼量不多,實現簡單,針對不同的設備需要合理規劃內存使用,根據軟件運行狀態,合適加入調試信息并保存對應的日志信息,方便開發人員了解系統或軟件運行狀況,協助開發分析數據資源從而更好完善系統,提高定位以及解決問題的效果。審核編輯:湯梓紅
評論
查看更多