0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

嵌入式设备系统日志记录方法

jf_BxU6dNQb 来源:CSDN-JustCasper 2023-03-28 15:20 次阅读

嵌入式设备应用场景中,系统日志时常可以监控设备软件的运行状态,及时记录问题点以及关键信息,方便开发人员后期定位以及解决问题。本文将讲述一种简易的系统日志记录方法,用于保存设备的系统日志,视具体嵌入式设备情况而定,可存储在MCU内部Flash、外部Flash、EEPROM等,本文采用外部Flash作为示例展开介绍。

思路分析 对于系统日志可以当成文件系统,可以划分为三个重要部分:目录区、参数区、日志区。目录区:根据日期进行归类,记录当天的日志的存储地址、日志索引、日志大小,通过目录可以获取整个日志文件的概况;参数区:存储记录日志写位置、目录项个数、写状态等参数;日志区:这是我们主要的存储区,记录系统的日志,支持环写。这三个区域都需要占用部分内存,可以自行分配大小。

实现的效果如下图所示,设置通过指令可查询到整个日志目录区的概况。

查询系统日志目录:AT+CATALOG? LOG_ID: 存储日志按日期分类,该ID用于查询对应日期日志,从1开始计数; LOG_DATE: 系统日志存储日期; LOG_ADDR: 系统日志存储外部FLASH地址; LOG_OFFSET: 系统日志存储偏移量(各日期日志大小,单位:字节)。

6c8fde44-cd38-11ed-bfe3-dac502259ad0.png

查询指定日期系统日志:AT+CATALOG=

LOG_ID:在查询系统日志目录时获取,当LOG_ID为0时,为查询整个系统日志。

6c9f6fbc-cd38-11ed-bfe3-dac502259ad0.png

另外提供移除系统日志(清除日志目录)指令:AT+RMLOG,后面将讲述具体实现。

FLASH内存划分 FLASH内存需要看具体设备进行合理划分,目录区、参数区与日志区实现环形存储,延长擦写寿命。

#defineFLASH_SECTOR_SIZE((uint32_t)0x001000)
#defineFLASH_BLOCK_32K_SIZE((uint32_t)0x008000)
#defineFLASH_BLOCK_64K_SIZE((uint32_t)0x010000)
#defineSECTOR_MASK(FLASH_SECTOR_SIZE-1)/*扇区掩码------*/
#defineSECTOR_BASE(addr)(addr&(~SECTOR_MASK))/*扇区的基地址--*/
#defineSECTOR_OFFSET(addr)(addr&SECTOR_MASK)/*扇区内的偏移--*/

#defineBLOCK_32K_BASE(addr)(addr&(~(FLASH_BLOCK_32K_SIZE)))
#defineBLOCK_64K_BASE(addr)(addr&(~(FLASH_BLOCK_64K_SIZE)))

typedefenum{
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_ezone)
{
inti=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;

returnbsp_spi_flash_erase(address,block_type);
}

intflash_write(flash_zone_ezone,uint32_taddress,constuint8_t*data,uint32_tlength)
{
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;

returnbsp_spi_flash_buffer_write(address,(uint8_t*)data,length);
}

intflash_read(flash_zone_ezone,uint32_taddress,uint8_t*buffer,uint32_tlength)
{
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);
return0;
}

参数与结构体定义 日志数据存储时间戳,便于问题定位,需要实现RTC接口调用。

typedefstruct{
uint16_tYear;/*年份:YYYY*/
uint8_tMonth;/*月份:MM*/
uint8_tDay;/*日:DD*/
uint8_tHour;/*小时:HH*/
uint8_tMinute;/*分钟:MM*/
uint8_tSecond;/*秒:SS*/
}time_t;

intbsp_rtc_get_time(time_t*date);

参数区应当保证数据的正确性,应加入参数校验存储,定义校验结构体。

#defineSYSTEM_LOG_MAGIC_PARAM0x87654321/*日志参数标识符*/
typedefstruct{
uint32_tmagic;/*参数标识符*/
uint16_tcrc;/*校验值*/
uint16_tlen;/*参数长度*/
}single_sav_t;

参数区需记录当前日志记录的写位置,以及目录项个数,还有日志区和目录区环写状态,并且存储最新时间等等。

/*日志区参数*/
typedefstruct{
uint32_twrite_pos;/*写位置*/
uint32_tcatalog_num;/*目录项个数*/
uint8_tlog_cyclic_status;/*系统日志环形写状态*/
uint8_tcatalog_cyclic_status;/*日志目录环形写状态*/
time_tlog_latest_time;/*存储最新时间*/
}system_log_t;

/*目录区参数*/
typedefstruct{
uint32_tlog_id;/*日志索引*/
uint32_tlog_addr;/*日志地址*/
uint32_tlog_offset;/*日志偏移大小,单位:字节*/
time_tlog_time;/*日志存储时间*/
}system_catalog_t;

/*系统日志参数*/
typedefstruct{
single_sav_tcrc_val;
system_log_tsystem_log;
system_catalog_tsystem_catalog;
}sys_log_param_t;

typedefstruct{
uint8_tsystem_log_print_enable;/*系统日志打印使能*/
uint16_tsystem_log_print_id;/*打印指定id系统日志*/
uint32_tsystem_log_param_addr;/*当前日志写地址*/
}sys_ram_t;

sys_ram_tSysRam;
sys_log_param_tSysLogParam;

sys_ram_t*gp_sys_ram=&SysRam;
sys_log_param_t*gp_sys_log=&SysLogParam;

实现接口说明 CRC校验接口,可以自定义实现。

/*16位CRC校验高位表*/
staticconstuint8_tauchCRCHi[]={
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校验低位表*/
staticconstuint8_tauchCRCLo[]={
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功能函数*/
staticuint16_tCRC16(uint8_t*puchMsg,uint16_tusDataLen)
{
uint8_tuchCRCHi=0xff;
uint8_tuchCRCLo=0xff;
uint16_tuIndex;

while(usDataLen--){
uIndex=uchCRCHi^*(puchMsg++);
uchCRCHi=uchCRCLo^auchCRCHi[uIndex];
uchCRCLo=auchCRCLo[uIndex];
}

returnuchCRCHi<<8|uchCRCLo;
}

保存系统日志参数,每实现写日志操作后都需要保存当前的参数值,防止意外丢失。

voidsave_system_log_param(void)
{
uint32_ti=0;
uint32_taddr=0;
uint32_tremainbyte=0;
uint32_tstart_addr;
intlen=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;
}elseif((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;
}elseif((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;
}
}
}

导入系统日志默认参数接口,初始化默认参数或者移除日志。

voidload_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();
}

设备开机或者复位都会进行导入系统日志参数操作,恢复日志读写参数,参数区为频繁读写操作区域,每一次写操作都会进行一次偏移,有效的导入参数方法是从参数区结束地址到起始地址进行扫描,扫描不到合法的参数则会导入默认日志参数。

/*参数初始化,在终端启动时调用*/
intload_system_log_param(void)
{
uint32_ti=0;
single_sav_tpsav;
uint32_tend_addr;
uint32_tinteral=sizeof(sys_log_param_t);
intdata_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("LoadSystemLogParamAddr[0x%08x]!",gp_sys_ram->system_log_param_addr);
return0;
}
}

/*扫描不到合法的参数,导入默认系统日志参数*/
load_system_log_default_param();
/*获取日志写地址*/
gp_sys_ram->system_log_param_addr=flash_tmp->start_address;
log_info("LoadSystemLogParamAddr(Default)[0x%08x]!",gp_sys_ram->system_log_param_addr);
return1;
}

读写系统日志目录接口,读写指定日志索引目录信息。实际实现会定义最新的目录信息存储在日志参数区,当日期发生改变,则表示当前目录信息已经完结,将最新的目录信息录入日志目录区保存,最多每天写入一次目录区。

/*读取日志目录区指定日志索引目录信息*/
intsystem_catalog_read(system_catalog_t*catalog,uint32_tid)
{
uint32_taddr;
intrlen=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;

returnflash_read(FLASH_CATALOG_ZONE,addr,pbuf,rlen);
}

/*写日志目录区目录信息*/
intsystem_catalog_write(system_catalog_t*catalog,uint32_tid)
{
uint32_tstart_offset;
uint32_tstart_addr;
uint32_tstart_base;
uint32_tremainbyte;
intwlen=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;
}
}

return0;
}

打印系统日志目录区信息,可实现通过指令查询到目录区信息。

intsystem_catalog_all_print(void)
{
inti=0;
system_catalog_tcatalog;

printf("SystemLogCommandInformation:
");
printf("QuerySpecifiesLog:AT+CATALOG=
");
printf("QueryAllLog:AT+CATALOG=<0>

");
printf("QueryAllSystemCatalog:
");
printf("LOG_IDLOG_DATELOG_ADDRLOG_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-%02d0x%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));
}
return0;
}

读取指定日志目录索引信息接口,可指定日志索引或者读取全部日志数据。

intsystem_log_task(intargc)
{
intrlen=0;
uint32_toffset,start_addr,end_addr;
system_catalog_tcatalog;
flash_table_t*flash_tmp=get_flash_table(FLASH_SYSLOG_ZONE);

if(0==gp_sys_ram->system_log_print_enable)
return1;

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)
return1;

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;
}
return0;
}

存储系统日志接口,实现更新存储日期,当写位置为扇区地址,则擦除一个扇区作为存储日志,这样避免每写一次就擦除一次。

intsystem_log_write(uint8_t*wbuf,intwlen)
{
uint32_tstart_addr;
uint8_t*pdata=wbuf;
uint32_tremainbyte;
intsystem_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();
return0;
}
系统调试对接 为了更好记录系统日志,将应用调试等级结合一块,实现记录错误调试信息以及需要保存的关键信息。定义的调试等级有:关闭调试等级、错误调试等级、警告调试等级、关键调试等级、debug调试等级,而LOG_RECORD_LEVEL将主动保存日志并输出信息,LOG_ERROR_LEVEL会存储对应的日志信息,但需要根据应用调试等级输出信息。设置与读取应用调试等级由读者自行定义。
#defineLOG_CLOSE_LEVEL0x00/*关闭调试信息*/
#defineLOG_ERROR_LEVEL0x01/*错误调试信息*/
#defineLOG_WARN_LEVEL0x02/*警告调试信息*/
#defineLOG_INFO_LEVEL0x03/*关键调试信息*/
#defineLOG_DEBUG_LEVEL0x04/*debug调试信息*/
#defineLOG_RECORD_LEVEL0x10/*保存日志并输出信息*/
#defineLOG_PRINT_LEVEL0xff

#defineSET_LOG_LEVEL(LEVEL)(gp_sys_param->system_print_level=LEVEL)
#defineGET_LOG_LEVEL()(gp_sys_param->system_print_level)

#definelog_debug(fmt,args...)log_format(LOG_DEBUG_LEVEL,fmt,##args)
#definelog_info(fmt,args...)log_format(LOG_INFO_LEVEL,fmt,##args)
#definelog_warn(fmt,args...)log_format(LOG_WARN_LEVEL,fmt,##args)
#definelog_error(fmt,args...)log_format(LOG_ERROR_LEVEL,fmt,##args)
#definelog_record(fmt,args...)log_format(LOG_RECORD_LEVEL,fmt,##args)
#defineprintf(fmt,args...)log_format(LOG_PRINT_LEVEL,fmt,##args)

typedefstruct{
intlevel;
char*fmt_str;
}system_print_fmt_t;

system_print_fmt_tsystem_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=":"},
};

intlog_format(uint8_tlevel,constchar*fmt,...)
{
#defineTIME_PREFIX_SIZE(21)
#definePRINT_MAX_SIZE(1024+TIME_PREFIX_SIZE)

va_listargs;
intnum=0,i=0,fmt_index=0;
intfmt_str_len=0,ret=-1;
intfile_str_len=0,line_str_len=0;
charline_buf[20]={0};
staticcharbuf[PRINT_MAX_SIZE];
staticQueueHandle_tsem=NULL;
time_ttime={0};

/*针对os系统*/
if(NULL==sem){
sem=xSemaphoreCreateCounting(1,1);/*alwaysthinkofsuccess*/
}

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){
gotoexit_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);
returnret;
}

结语 本文提供的一种简易嵌入式设备系统日志记录方法,代码量不多,实现简单,针对不同的设备需要合理规划内存使用,根据软件运行状态,合适加入调试信息并保存对应的日志信息,方便开发人员了解系统或软件运行状况,协助开发分析数据资源从而更好完善系统,提高定位以及解决问题的效果。

审核编辑:汤梓红

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 嵌入式
    +关注

    关注

    4983

    文章

    18286

    浏览量

    288494
  • FlaSh
    +关注

    关注

    10

    文章

    1551

    浏览量

    146652
  • 存储
    +关注

    关注

    12

    文章

    3859

    浏览量

    84667
  • 嵌入式设备
    +关注

    关注

    0

    文章

    104

    浏览量

    16830
  • 日志
    +关注

    关注

    0

    文章

    126

    浏览量

    10526

原文标题:嵌入式设备系统日志记录方法

文章出处:【微信号:混说Linux,微信公众号:混说Linux】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    简易的嵌入式系统日志记录方法

    嵌入式设备中,很多场景都需要记录日志,特别是单片机这种存储资源有限的环境下,就需要一种轻量级的存储方法
    发表于 10-31 14:17 811次阅读

    一种简易的嵌入式设备系统日志记录方法

    嵌入式设备应用场景中,系统日志时常可以监控设备软件的运行状态,及时记录问题点以及关键信息,方便
    发表于 11-10 11:43 513次阅读

    嵌入式基础教程之嵌入式系统组成

      本文创客学院嵌入式开发培训讲师为读者介绍嵌入式基础教程之嵌入式系统组成,嵌入式系统通常由
    发表于 06-28 13:59

    如何设计嵌入式数据记录设备

    蓝牙方式下载数据,与传统的数据下载方式相比,下载数据时既不需要拆卸设备连接线缆或插拔存储卡,又减少了连线和插拔存储卡可能导致的接触故障。那么,我们具体该如何设计嵌入式数据记录设备呢?
    发表于 07-31 07:50

    嵌入式文件系统µC/FS的日志使用

    尽管在PC领域NTFS已经取代了FAT,但FAT文件系统仍然是嵌入式开发的首选。除了为嵌入式应用程序提供与PC(因为Windows继续支持FAT)的无缝交互,对于电源不稳定的设备开发者
    发表于 09-19 16:41

    什么是嵌入式系统 嵌入式系统定义

    Institution of Electrical Engineer)的定义,嵌入式系统为控制、监视或辅助设备、机器或用于工厂运作的设备。与个人计算机这样的通用计算机
    发表于 09-23 15:57

    嵌入式系统日志相关资料分享

    嵌入式系统日志void wdbg_printf(const char * format, ... ) { va_list args; s32 len; if(g_trace_handle
    发表于 10-27 09:21

    如何去实现嵌入式linux设备中应用运行日志

    嵌入式linux设备中应用运行日志的实现最近在做一个项目时,需要记录设备运行中情况,以方便对故障进行跟踪定位,完善.所以决定采用
    发表于 11-04 08:24

    嵌入式系统设计方法变化

    嵌入式系统设计方法变化的背景嵌入式系统设计方法的演化总的来说是因为应用需求的牵引和IT技术的推动
    发表于 11-08 07:27

    如何学习嵌入式系统

    装置或设备的一部分。有了大概的了解就聊一聊学习嵌入式该学习哪些相关的知识呢?嵌入式系统,嵌入式系统学习方
    发表于 12-22 06:52

    什么是嵌入式系统嵌入式系统的特点

    目录一、什么是嵌入式系统1、嵌入式系统的特点:2、嵌入式系统的软件组成:软件(含驱动、OS、应用
    发表于 12-22 07:32

    简单记录下最近设计的一种日志打印方法

    前言:相信大多数嵌入式开发者都会碰到对程序进行调试或者查找BUG的情况,常见的两种方法都是仿真和通过日志进行的。有条件仿真的是更好,单是在编写嵌入式软件程序过程中,并不是所有的硬件都支
    发表于 01-11 06:09

    基于安卓移动设备嵌入式监控系统设计方法_齐继阳

    基于安卓移动设备嵌入式监控系统设计方法_齐继阳
    发表于 02-07 15:05 0次下载

    嵌入式系统日志

    嵌入式系统日志void wdbg_printf(const char * format, ... ) { va_list args; s32 len
    发表于 10-20 16:21 8次下载
    <b class='flag-5'>嵌入式</b><b class='flag-5'>系统</b><b class='flag-5'>日志</b>

    嵌入式linux设备中应用运行日志的实现

    嵌入式linux设备中应用运行日志的实现      最近在做一个项目时,需要记录设备运行中情况,以方便对故障进行跟踪定位,完善.所以决定采用
    发表于 11-01 17:22 8次下载
    <b class='flag-5'>嵌入式</b>linux<b class='flag-5'>设备</b>中应用运行<b class='flag-5'>日志</b>的实现