电子产业一站式赋能平台

PCB联盟网

搜索
查看: 115|回复: 0
收起左侧

设计一个嵌入式设备日志记录模块

[复制链接]

514

主题

514

帖子

2472

积分

三级会员

Rank: 3Rank: 3

积分
2472
发表于 2024-12-13 08:00:00 | 显示全部楼层 |阅读模式
我是老温,一名热爱学习的嵌入式工程师- b" g9 [8 S+ R1 r5 r
关注我,一起变得更加优秀! 1在嵌入式设备应用场景中,系统日志时常可以监控设备软件的运行状态,及时记录问题点以及关键信息,方便开发人员后期定位以及解决问题。本文将讲述一种简易的系统日志记录方法,用于保存设备的系统日志,视具体嵌入式设备情况而定,可存储在 MCU 内部Flash、外部 Flash、EEPROM等,本文采用外部 Flash 作为示例展开介绍。- t6 Z) Z- o& I$ H
思路分析对于系统日志可以当成文件系统,可以划分为三个重要部分:目录区、参数区、日志区。6 C5 c* J# L! A* c
目录区:根据日期进行归类,记录当天的日志的存储地址、日志索引、日志大小,通过目录可以获取整个日志文件的概况;
0 k+ g; f! @& y- Q1 D9 g参数区:存储记录日志写位置、目录项个数、写状态等参数;: K* `9 I; [5 g0 W, t
日志区:这是我们主要的存储区,记录系统的日志,支持环写。% a3 f6 X0 J% b2 C" U/ ~: F$ [  w
这三个区域都需要占用部分内存,可以自行分配大小。
$ P) ~  a! a$ O实现的效果如下图所示,设置通过指令可查询到整个日志目录区的概况。查询系统日志目录:AT+CATALOG?
$ k$ d: D  j9 O" wLOG_ID: 存储日志按日期分类,该ID用于查询对应日期日志,从1开始计数;
6 l2 H: \3 t; n1 ~: oLOG_DATE: 系统日志存储日期;' l2 Y7 t4 c- }1 W: I, _/ w
LOG_ADDR: 系统日志存储外部FLASH地址;$ W* @* x  s3 R. U% J3 ?7 _/ U
LOG_OFFSET: 系统日志存储偏移量(各日期日志大小,单位:字节)。: x. t2 G. \. e% V; ~7 w1 {( i

iejtqngseu4640722125.png

iejtqngseu4640722125.png
( Q; i9 ]+ u, L# c$ L- \2 Q
6 i( S* z8 i/ L3 k7 g
查询指定日期系统日志:AT+CATALOG=$ `# x% x$ H& ?
LOG_ID:在查询系统日志目录时获取,当LOG_ID为 0 时,为查询整个系统日志。
  e; V7 y: f* c% H( y

dblt2xzfh2m640722225.png

dblt2xzfh2m640722225.png

$ Z% W5 Z* N" _6 N3 s( g. Y& u6 K0 f* r9 ~
另外提供移除系统日志(清除日志目录)指令:AT+RMLOG,后面将讲述具体实现。& x$ E5 @. \" W' I( a' v

* C$ c5 p0 h( E  n8 OFLASH内存划分7 M  _; x. {" _9 c& c
FLASH内存需要看具体设备进行合理划分,目录区、参数区与日志区实现环形存储,延长擦写寿命。9 G- T* P6 J) f8 y1 c6 l
#define FLASH_SECTOR_SIZE   ((uint32_t)0x001000)
/ \  Y$ w, S5 F& H( g( X#define FLASH_BLOCK_32K_SIZE  ((uint32_t)0x008000)
- o$ |' ~9 g! g- n5 o, l! k# Y#define FLASH_BLOCK_64K_SIZE  ((uint32_t)0x010000)
% @8 \# j! E( V' R8 q0 V9 O#define SECTOR_MASK             (FLASH_SECTOR_SIZE - 1)         /*扇区掩码 ------*/, t+ ^' S1 q9 @, e9 S
#define SECTOR_BASE(addr)       (addr & (~SECTOR_MASK))        /*扇区的基地址 --*/
# P# D' S# D% e, c#define SECTOR_OFFSET(addr)     (addr & SECTOR_MASK)           /*扇区内的偏移 --*/
1 B# ?1 k$ v6 J' Y#define BLOCK_32K_BASE(addr)  (addr & (~(FLASH_BLOCK_32K_SIZE)))
3 H8 B. k  x. B' `/ y#define BLOCK_64K_BASE(addr)  (addr & (~(FLASH_BLOCK_64K_SIZE)))3 x6 ^5 v! l  Y. L
typedef enum {6 w( v  {' I1 c$ Q* c* ]& p8 I
    FLASH_BLOCK_4K  = 0,          /**( `; k; m4 j0 ^( `4 r& R( P8 B
    FLASH_BLOCK_32K = 1,          /**, `* j7 {) ~! ^/ C. X5 r; M. _
    FLASH_BLOCK_64K = 2           /**1 G$ w* [; t7 @' C) x
}flash_block_t;
3 a4 i, d! ~1 K8 V" m( W! V/* flash 空间索引 */
7 _: k5 e( O3 r, y  I5 [typedef enum{
& y# x- O2 o+ O* {    FLASH_CATALOG_ZONE = 0,
  h/ `# D" Q: W+ D, P4 K    FLASH_SYSLOG_PARA_ZONE,
- V# u- p- I/ r- b* g    FLASH_SYSLOG_ZONE,
/ o% l8 N3 T( Z  Q    FLASH_ZONEX,
2 B3 c6 h. {# M; f2 z% X9 A  g- ^/ k}flash_zone_e;+ k# n' m+ L1 Q/ Y
typedef struct{
% c2 F. p5 t5 _9 G: x# C& ]8 Q    flash_zone_e zone;
  P; n  ]9 N. _) }% J    uint32_t start_address;
' e" D1 t+ v" u. T7 O' u- j3 G    uint32_t end_address;
7 h* P, x3 t  A0 z! {# h* [- Z3 T}flash_table_t;( @" |% T; z; Y( F
/* 地址划分 */5 e; i- W+ N- f5 R% r
static const flash_table_t flash_table[] = {
( a7 z8 \! g9 Y% Y3 w8 A  { .zone = FLASH_CATALOG_ZONE,       .start_address = 0x03200000, .end_address = 0x032FFFFF},  
: G9 K; @( i8 V$ C( p  { .zone = FLASH_SYSLOG_PARA_ZONE,   .start_address = 0x03300000, .end_address = 0x033FFFFF},  
- M. S4 k! Z5 V7 V+ f3 n  X2 }  { .zone = FLASH_SYSLOG_ZONE,        .start_address = 0x03400000, .end_address = 0x03FFFFFF},  % ?5 Q& u$ L# y1 Z2 n( u7 b: t- z' ?
};Flash底层实现擦除、读写操作接口,由读者自行实现。! q+ L) F+ R2 c; x0 I
flash_table_t *get_flash_table(flash_zone_e zone): h; c9 u  d- `: l5 h) N8 e) B$ b
{
" }! @: ]3 u2 y5 f  ^  int i = 0;
' ~6 P( |- x1 U" `/ m  for (i = 0; i if (zone == flash_table.zone)
" l6 {2 d7 N8 C  S5 S9 p' r      return (flash_table_t *)&flash_table;
1 l! ~9 ?/ v' ~  }
3 L. ^4 E) {) _) W  : K+ D  g1 i; I# @4 w% g2 J/ R
  return NULL;  1 }+ ~9 u$ H7 S  w! s
}& l- @: L, f' A: {' a
int flash_erase(flash_zone_e zone, uint32_t address, flash_block_t block_type), ~8 \' B0 W! k$ D* z# I! Q
{
; S6 z# P; ]% c7 W  flash_table_t *flash_table_tmp = get_flash_table(zone);# G* E# b6 c9 k# V
  
) X9 h0 g& v: ?; I5 N( C8 a5 k) ^  if (flash_table_tmp == NULL)
* E* e) c8 e/ M; G& |    return -1;) Y4 T! [  v0 [6 X8 k+ W1 |8 C6 J; I: Y
   
% i3 y# c9 i$ V& V7 S  if (address start_address ||address > flash_table_tmp->end_address)
% @& h, m7 H3 a# S# U0 z  S    return -1;
4 m6 q+ Q  K* f  return bsp_spi_flash_erase(address, block_type);
- D* k) u' g3 [0 z}7 N" }' z( Z: m5 ?4 w# d$ g
int flash_write(flash_zone_e zone, uint32_t address, const uint8_t*data, uint32_t length)/ V" r* d3 A+ o5 }0 l! V; t+ p
{
. C$ j* r  W9 h0 L# P, N, z% G  flash_table_t *flash_table_tmp = get_flash_table(zone);
8 d8 C& F% O3 X" q$ ]( P     
/ d1 G0 ~" k6 w/ ]  if (flash_table_tmp == NULL)
+ D1 \+ g2 A' D& P! j: n     return -1;
& W) Q, \7 k. p: o* i/ c# X& _  if ((address start_address) ||((address + length) > flash_table_tmp->end_address))5 f8 N/ s4 R* n# j& w7 e
     return -1;
$ }+ L4 \( ?( B# e( R( D% B  return bsp_spi_flash_buffer_write(address, (uint8_t *)data, length);+ F8 L; G6 \9 b6 e
}
% K2 O+ ^- r. y5 D* ]int flash_read(flash_zone_e zone, uint32_t address, uint8_t*buffer, uint32_t length)8 I& Y4 W5 b0 M  g: [( D
{0 {; p; n- m. e7 g: ^$ ]' J
  flash_table_t *flash_table_tmp = get_flash_table(zone);8 h( D  [! r: u8 Y7 D
  
% H1 ?. Z) @5 L3 _) G) `  if (flash_table_tmp == NULL)  F, E' T1 T. [0 R7 b
    return -1;
5 _! A+ v6 J$ e# i4 c! j  if ((address start_address) ||((address + length) > flash_table_tmp->end_address))
  s4 V  Q9 o: y( t6 j1 ~    return -1;
4 u# W1 K% P9 a& M5 |2 b. F$ l, d    6 k6 o* ]& x/ R" ~
  bsp_spi_flash_buffer_read(buffer, address, length);
6 {! X1 n5 X' ?, k! Q% j  return 0;3 Z- N1 N; a" M# J! n: S' {$ Y. Z% q( t
}
7 x1 N) U- }, `) }& @& H参数与结构体定义: {3 b: e  d  A9 O; ?6 r# j
日志数据存储时间戳,便于问题定位,需要实现RTC接口调用。' V9 b  K! O, q7 s$ m
typedef struct {" y4 m0 i' z% B4 a* @6 w' Y  e
    uint16_t  Year;  /* 年份:YYYY */6 t3 b5 s4 @9 p8 G( {
    uint8_t  Month;  /* 月份:MM */3 A5 \" o# \: K5 H
    uint8_t  Day;  /* 日:DD */- o1 y& H/ V9 |
    uint8_t  Hour;  /* 小时:HH */% @' G- J2 s1 ]9 Z
    uint8_t  Minute;  /* 分钟:MM */
- c% k) J( P: U; K% D2 N7 L    uint8_t  Second;  /* 秒:SS */9 C; _& A  C+ g9 Y" R$ r# @
}time_t;   ) {- F9 N! \7 Y
int bsp_rtc_get_time(time_t *date);参数区应当保证数据的正确性,应加入参数校验存储,定义校验结构体。#define SYSTEM_LOG_MAGIC_PARAM  0x87654321 /* 日志参数标识符 */
5 G2 O# A, m% \0 G) ]typedef struct {' k! V. n5 A! M5 ]8 L! t. e: ^, h
    uint32_t magic;  /* 参数标识符 *// `0 A7 f& Y% ~4 W7 a
    uint16_t crc;    /* 校验值 */
' t, C3 R! m* |) x    uint16_t len;    /* 参数长度 */
+ ?5 H) E6 ~! b' F3 z} single_sav_t;参数区需记录当前日志记录的写位置,以及目录项个数,还有日志区和目录区环写状态,并且存储最新时间等等。, r, ?9 ~! F. i, N; y
/* 日志区参数 */
+ H9 y7 ?  `+ Qtypedef struct {
3 T  V) J) `+ G1 F    uint32_t   write_pos;            /* 写位置 */. E& u7 T0 Z, Z. s9 }
    uint32_t   catalog_num;           /* 目录项个数 */) I) Q" F* Z# H# V- |0 I
    uint8_t    log_cyclic_status;   /* 系统日志环形写状态 */   
1 A& v0 \5 y# f/ |    uint8_t    catalog_cyclic_status; /* 日志目录环形写状态 */
3 o2 D# b" a" R# @5 G9 s    time_t     log_latest_time;    /* 存储最新时间 */
' j4 h/ g5 ^7 S. a" c$ q2 t}system_log_t;  b7 H1 W% d5 G+ y+ m
/* 目录区参数 */2 ]" T) K& ]! U/ f% z
typedef struct {5 Z, a* L# X9 _( {2 n8 v* @
    uint32_t log_id;    /* 日志索引 */
, c  D1 y5 |8 ^& c    uint32_t log_addr;    /* 日志地址 */
. l. j, ^  t9 n3 F    uint32_t log_offset;  /* 日志偏移大小,单位:字节 */$ W6 F0 V; n- u" E  x4 d7 Y% v
    time_t   log_time;    /* 日志存储时间 */
& v9 H' `$ y: M5 R}system_catalog_t;
' y% m& z* Q3 W& E% `# \4 J/* 系统日志参数 */, q1 \) ]7 r. z1 B2 v$ I
typedef struct {
) y6 ~* Q$ u9 X8 r" b! x    single_sav_t crc_val;
! X$ q- [# C2 M, ~8 @5 b6 U! \    system_log_t system_log;
1 L# ~; G/ k) F& w) I( M4 C3 B    system_catalog_t system_catalog;# n9 y7 L  w- v. H6 l" D# t9 b
}sys_log_param_t;
- w& V8 z0 S5 {) o  Mtypedef struct {
" R4 t; g/ M" J1 m) M    uint8_t system_log_print_enable; /* 系统日志打印使能 */) _4 F( h- {7 j4 v4 y
    uint16_t system_log_print_id;    /* 打印指定id系统日志 */
* W2 A& r9 o: [: C5 O7 `( [    uint32_t system_log_param_addr;  /* 当前日志写地址 */) o/ K9 H' a) T: R, v% {
} sys_ram_t;6 V1 `" v4 G9 h3 z! N
sys_ram_t SysRam;! S4 o: k% r) y
sys_log_param_t SysLogParam;, w( h% r5 @5 Z
sys_ram_t *gp_sys_ram = &SysRam;
3 T9 H! _: y4 y8 h( |% {' nsys_log_param_t *gp_sys_log = &SysLogParam;实现接口说明CRC校验接口,可以自定义实现。/* 16位CRC校验高位表 */
! t6 c6 Y4 S7 k9 j- a5 Ostatic const uint8_t auchCRCHi[]={
7 L+ T2 r* }, `0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,: d6 L3 ^8 ]% W1 Y: C8 J: Q- W9 a
0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,: f* q+ n4 @4 p  E% h  m0 E
0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,
1 a, D/ ]/ p1 d6 l0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,8 v6 i- `8 w* i
0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,' L3 I5 }; C# J9 D  k  l8 Y0 ~
0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,
8 N0 l# ?* o0 {0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,/ X8 {" B& s* ?1 N' d( z% h- q7 w
0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,* Q/ t" w! q6 }0 @
0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,
  G5 m$ m2 n# k  n7 `0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,
* M* l: n: F! z2 Y* E% a0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,
3 L5 A0 B! E" Q0 m4 H3 f- W4 i0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,
$ c6 K; L: Y$ n( y0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,' k- j8 M  }/ ~' \# \' Y% Q
0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,3 N* d, m0 A8 I$ A, [8 r* E
0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,
9 Y6 c' G; y  u) N0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40
/ C% |* M/ v4 {$ A1 y6 W; E* p};
8 h0 x9 D' t( ~' K/* 16位CRC校验低位表 */; L' X# X7 L+ S! l; M' p
static const uint8_t auchCRCLo[]={" j6 y+ S7 k9 m
0x00,0xc0,0xc1,0x01,0xc3,0x03,0x02,0xc2,0xc6,0x06,0x07,0xc7,0x05,0xc5,0xc4,0x04,
, _+ e) o/ [# R7 d# S  Q$ e0xcc,0x0c,0x0d,0xcd,0x0f,0xcf,0xce,0x0e,0x0a,0xca,0xcb,0x0b,0xc9,0x09,0x08,0xc8,; ~2 b# @: E3 ]/ J: o2 w' w, v
0xd8,0x18,0x19,0xd9,0x1b,0xdb,0xda,0x1a,0x1e,0xde,0xdf,0x1f,0xdd,0x1d,0x1c,0xdc,  k3 Z' b* G- z0 i# l. a. _9 R6 G
0x14,0xd4,0xd5,0x15,0xd7,0x17,0x16,0xd6,0xd2,0x12,0x13,0xd3,0x11,0xd1,0xd0,0x10,8 E( N/ w" q0 _  `; }
0xf0,0x30,0x31,0xf1,0x33,0xf3,0xf2,0x32,0x36,0xf6,0xf7,0x37,0xf5,0x35,0x34,0xf4,) d3 r3 n* s- O
0x3c,0xfc,0xfd,0x3d,0xff,0x3f,0x3e,0xfe,0xfa,0x3a,0x3b,0xfb,0x39,0xf9,0xf8,0x38,: M4 W2 C. T; r% O6 [
0x28,0xe8,0xe9,0x29,0xeb,0x2b,0x2a,0xea,0xee,0x2e,0x2f,0xef,0x2d,0xed,0xec,0x2c,
2 K! s' S) a0 J. e1 ~% C0xe4,0x24,0x25,0xe5,0x27,0xe7,0xe6,0x26,0x22,0xe2,0xe3,0x23,0xe1,0x21,0x20,0xe0,
2 r9 t3 Y! u9 X; M6 i0xa0,0x60,0x61,0xa1,0x63,0xa3,0xa2,0x62,0x66,0xa6,0xa7,0x67,0xa5,0x65,0x64,0xa4,
' X" h8 V' T/ I1 P. R" v& n+ y0x6c,0xac,0xad,0x6d,0xaf,0x6f,0x6e,0xae,0xaa,0x6a,0x6b,0xab,0x69,0xa9,0xa8,0x68,
" ~) K- L, G& H) ^( p& W& b0x78,0xb8,0xb9,0x79,0xbb,0x7b,0x7a,0xba,0xbe,0x7e,0x7f,0xbf,0x7d,0xbd,0xbc,0x7c,
, Z' b( i5 b& _% m" Z$ H0xb4,0x74,0x75,0xb5,0x77,0xb7,0xb6,0x76,0x72,0xb2,0xb3,0x73,0xb1,0x71,0x70,0xb0,
8 D- t5 q5 \) {! ^+ ?0x50,0x90,0x91,0x51,0x93,0x53,0x52,0x92,0x96,0x56,0x57,0x97,0x55,0x95,0x94,0x54,
. h2 Z0 {0 b( `0x9c,0x5c,0x5d,0x9d,0x5f,0x9f,0x9e,0x5e,0x5a,0x9a,0x9b,0x5b,0x99,0x59,0x58,0x98,# h; N* h: w3 U1 i
0x88,0x48,0x49,0x89,0x4b,0x8b,0x8a,0x4a,0x4e,0x8e,0x8f,0x4f,0x8d,0x4d,0x4c,0x8c,/ |! H/ P  j! n+ Q) z# }: W
0x44,0x84,0x85,0x45,0x87,0x47,0x46,0x86,0x82,0x42,0x43,0x83,0x41,0x81,0x80,0x40
: d- r& j5 n- X};7 B2 J6 \- U7 u) z
/* 实现crc功能函数 */
% G9 k$ a. }# |* U; Vstatic uint16_t CRC16(uint8_t* puchMsg, uint16_t usDataLen)' Y9 ?! z% l+ s9 m
{
- k5 M- F: h& d& b! _# @    uint8_t uchCRCHi=0xff;6 Z: @) y5 B7 n; ^1 Y' R
    uint8_t uchCRCLo=0xff;
, k" t4 E" w) ~* x9 i    uint16_t uIndex;
  i+ d- F2 ^# q    # N: D9 L3 I0 Y6 M$ _) ~
    while(usDataLen--) {- J0 {) J0 B4 z: j: u0 d! R
        uIndex=uchCRCHi^*(puchMsg++);
1 {# g/ t6 G- {, v- A% f# i( N9 M        uchCRCHi=uchCRCLo^auchCRCHi[uIndex];0 k8 r7 `: b% u) u3 S
        uchCRCLo=auchCRCLo[uIndex];9 ]; e) J* q, P9 ]4 r
    }8 r+ Q7 x# O0 Y0 ?+ E' T, u
   
0 |8 L1 o# ]( [( K    return uchCRCHi8|uchCRCLo;, X5 e4 G( P+ i  ?* S( j/ W  k
}保存系统日志参数,每实现写日志操作后都需要保存当前的参数值,防止意外丢失。
6 |8 |0 \8 |" W4 Wvoid save_system_log_param(void)2 U. C. z# K$ W+ ~8 i( n8 [4 H# Y4 r
{
" \& i7 c, [3 T7 m+ {  y# u5 ]1 [3 I    uint32_t i = 0;# v& a) Z! e$ Q) J: E  J6 }5 I0 E
    uint32_t addr = 0;, @9 s- d% b3 x% X
    uint32_t remainbyte = 0;8 P- U2 x$ L, Y% \! u4 C, s- V. e0 E4 |
    uint32_t start_addr;1 U, f8 }( z) k! s! a3 {
    int len = sizeof(sys_log_param_t);/ }* `! a) ]* A9 U! k- i% d. G5 ]
    uint8_t *pdata = (uint8_t *)&SysLogParam;
. h. k0 {1 x. M3 T7 }6 }    flash_table_t *flash_tmp = get_flash_table(FLASH_SYSLOG_PARA_ZONE);0 P8 I) K. v2 T, g
   
4 s; B4 z: r8 h5 z    /* 校验参数 */
* @& I3 z% f% `- m# V4 C" P, |    gp_sys_log->crc_val.magic = SYSTEM_LOG_MAGIC_PARAM;
4 h3 a' B# O+ p+ L8 j# t* ~    gp_sys_log->crc_val.len = sizeof(sys_log_param_t) - sizeof(single_sav_t);
. L/ J7 }) M7 \8 S1 x    gp_sys_log->crc_val.crc = CRC16(&pdata[sizeof(single_sav_t)], gp_sys_log->crc_val.len);0 v8 P) H, W/ e" g/ D
    start_addr = gp_sys_ram->system_log_param_addr;3 L  m* Y0 y* N1 e9 a( A
    /* 剩余内存不够写,则重新从起始地址开始写,实现环形存储功能  */% \7 k( ?6 E+ M0 X, t4 ?8 L
    if ((start_addr + len) > flash_tmp->end_address) { 2 l/ A. N: h* Z$ b- V9 ]1 k8 {
        start_addr = flash_tmp->start_address;6 R1 L% l/ o5 a' s
    }8 K1 U5 [/ e  y2 h
    gp_sys_ram->system_log_param_addr = start_addr + len;7 B6 \3 _1 T0 s% X/ t! T' K
    /* 首地址存储,擦除整个系统日志参数存储区,如果划分的内存较大,可能出现第一次擦写等待时间较长,
8 Z9 i. g9 {8 d7 F" L3 W  S7 R0 @3 Y       但实际应用嵌入式设备应该不会占用太多的内存存储系统日志,只当为辅助使用,有额外应用可自行实现 */: M  M, V0 b" U( [& {7 @" T
    if (flash_tmp->start_address == start_addr) {
0 E  r5 _+ N( r        /*for (i = flash_tmp->start_address; i end_address; i+= FLASH_SECTOR_SIZE)
2 i5 i) h( s1 C3 U3 s6 s            flash_erase(FLASH_SYSLOG_PARA_ZONE, SECTOR_BASE(i), FLASH_BLOCK_4K);
& h) r' `2 F3 F. H1 Z1 m" ?* E/ F        */' z; r* C5 W- n' A7 w+ Q
        addr = flash_tmp->start_address;
/ _1 U' p: q: }        do {
- c" ~5 |. f9 D& S% }3 W3 f, J( l            if ((addr + FLASH_BLOCK_64K_SIZE) end_address) {; t: L, Y/ u* H" ~4 b
                flash_erase(FLASH_SYSLOG_PARA_ZONE, BLOCK_64K_BASE(i), FLASH_BLOCK_64K);" }" T0 x- j) ?& \6 ^* i
                addr += FLASH_BLOCK_64K_SIZE;# K, `+ `( n6 P  q
            } else if ((addr + FLASH_BLOCK_32K_SIZE) end_address) {, J5 `- d0 H6 S
                flash_erase(FLASH_SYSLOG_PARA_ZONE, BLOCK_32K_BASE(i), FLASH_BLOCK_32K);
: u9 p, _+ R6 P4 C0 w$ k$ r/ `/ }$ B0 e                addr += FLASH_BLOCK_32K_SIZE;
5 `/ J( _9 r# S1 f+ @! E$ y            } else if ((addr + FLASH_SECTOR_SIZE) end_address) {7 a  T0 W4 a$ L0 \  b
                flash_erase(FLASH_SYSLOG_PARA_ZONE, SECTOR_BASE(i), FLASH_BLOCK_4K);
( z% U1 P8 G3 I+ [" ^+ s: v/ Z" Z                addr += FLASH_SECTOR_SIZE;
- t, @% t+ V4 T2 Q1 L! _- d; H% O! ?            } else {
* s1 t1 Y! q9 p                break;& [; R; L4 o" F+ E" k0 q
            }, E1 z& C6 A# V6 u
        } while (addr end_address);
7 W  ]. W3 H3 ]+ l    }1 H' n1 E: {% D' m1 Z/ _
    remainbyte = FLASH_SECTOR_SIZE - (start_addr % FLASH_SECTOR_SIZE);
9 @% i. v: z" B* \- u9 t1 S    if (remainbyte > len) {; S0 N3 ?4 ~) C% s) p
        remainbyte = len;- T6 V1 K+ H) i4 C0 D3 B* O$ D8 k7 H! m
    }/ J4 `! P" d# |* v. X" r2 w
    while (1) {" I6 @7 `1 J* U- P
        flash_write(FLASH_SYSLOG_PARA_ZONE, start_addr, pdata, remainbyte);* X, w$ \) t4 \" g( R0 u
        if (remainbyte == len) {7 k' a2 D+ @/ n" i3 Q
            break;; E4 r' W/ W1 a: F# |
        } else {
5 I0 [- [$ L2 O            pdata += remainbyte;
( }. Z5 r7 W- m' o/ u! N% {3 t            start_addr += remainbyte;
5 s9 n: F' [) F8 a            len -= remainbyte;
0 J: j! ]7 X$ Y) c( a# Z" [4 m' X            remainbyte = (len > FLASH_SECTOR_SIZE) ? FLASH_SECTOR_SIZE : len;( w7 D& R9 E/ S  A
        }! h; y* \. }3 I# e2 C
    }
- I6 K6 U: ]% S& V0 Z9 @0 R}导入系统日志默认参数接口,初始化默认参数或者移除日志。9 {2 I. r# y1 w+ v# d' d: }' d
void load_system_log_default_param(void)% K# A$ [/ v0 |4 A0 j3 q
{3 N+ K1 G) B1 D- d  x/ Q; @
    /* 系统日志默认参数 */8 b( S4 t) @0 r+ J
    /* 目录环写状态标志 */. K& b, x8 }+ W0 C3 [8 a
    gp_sys_log->system_log.catalog_cyclic_status = 0x00;
& O/ f" R! S/ S5 r    /* 目录项个数 */
) p# N. P* A) k. z6 |    gp_sys_log->system_log.catalog_num = 0;4 L: J% F8 N$ E; `# x
    /* 日志环写标志 , 1:环写状态 */
( Y2 L9 }5 T5 p& ]- {2 U) `    gp_sys_log->system_log.log_cyclic_status = 0;6 `0 {; n9 ^" H0 g8 E* Z1 m
    /* 设置默认值,实际会重新从RTC获取最新时间 */
0 P0 O  s  E; T: |' z7 ~    gp_sys_log->system_log.log_latest_time.Year = 2019;% I: A3 |, p* b' k: Y# [* I3 H; L" A" @
    gp_sys_log->system_log.log_latest_time.Month = 5;+ @# c2 _) Y  `6 A6 J
    gp_sys_log->system_log.log_latest_time.Day = 8;0 X8 A5 p  q9 D% y5 c
    gp_sys_log->system_log.log_latest_time.Hour = 13;( W, g$ x% g0 H, z
    gp_sys_log->system_log.log_latest_time.Minute = 14;+ i( \0 h8 [2 U( x1 p( D6 o
    gp_sys_log->system_log.log_latest_time.Second = 10;
  Q8 \8 Y2 u0 L7 R+ ?" V8 o    /* 日志写位置从0开始 */# y3 {9 E* h: ]2 X% d+ c' E
    gp_sys_log->system_log.write_pos = 0;
: Z' X) h8 s' N! o' j* J6 e    3 Z5 R; X, E. Z/ A+ w# a
    gp_sys_log->system_catalog.log_addr = 0;
# a- G$ q" D: y! e; s# x    gp_sys_log->system_catalog.log_id = 0;
4 X3 i- g. p" D, |$ R    gp_sys_log->system_catalog.log_offset = 0;
5 ?4 d$ _  X  ]1 u    gp_sys_log->system_catalog.log_time.Year = 2019;# t# i3 j' r+ z6 N
    gp_sys_log->system_catalog.log_time.Month = 5;0 s. U$ v8 n7 U2 W4 o. W) u
    gp_sys_log->system_catalog.log_time.Day = 8;
9 Q; L7 F) `. p    gp_sys_log->system_catalog.log_time.Hour = 12;
3 x1 r9 G' d" ?) h    gp_sys_log->system_catalog.log_time.Minute = 12;
: W+ `! J! \, Y5 T+ }    gp_sys_log->system_catalog.log_time.Second = 14;" ?% E$ J0 |2 z6 M3 b# A) @
   
, e! X0 Q1 ^8 j% T1 Q1 b7 @1 \    gp_sys_log->crc_val.magic = SYSTEM_LOG_MAGIC_PARAM;8 N( O2 Y, X! ^. r8 ~" ~7 ^) e
    /* 导入默认参数后进行保存 */0 C( I2 w6 P0 m; M
    save_system_log_param();
% m# u; n* W. N/ z$ v}
5 i5 }: G7 O$ q) |9 w设备开机或者复位都会进行导入系统日志参数操作,恢复日志读写参数,参数区为频繁读写操作区域,每一次写操作都会进行一次偏移,有效的导入参数方法是从参数区结束地址到起始地址进行扫描,扫描不到合法的参数则会导入默认日志参数。/* 参数初始化,在终端启动时调用 */
/ x2 @2 Q  @) R% R9 ~& s* vint load_system_log_param(void)+ H# I' t/ |# K! H3 W7 A" G" B/ ~3 k
{# d$ X0 A1 T! [/ x& ~
    uint32_t i = 0;8 C+ W$ S9 C3 E/ T( m' }/ F
    single_sav_t psav;- E: b- T2 J  k& s3 {6 c. F6 U) J% ?9 C
    uint32_t end_addr;3 A0 k0 g) c4 s1 A0 V& _  s
    uint32_t interal = sizeof(sys_log_param_t);( i8 W0 b; {* V, ]; {) O
    int data_len = sizeof(sys_log_param_t) - sizeof(single_sav_t);0 s; t# X9 k9 |0 V0 Q8 g
    uint8_t *pram = (uint8_t *)&SysLogParam;
- c8 j, Z6 e' ~; C8 |5 K& e" u& a  W    flash_table_t *flash_tmp = get_flash_table(FLASH_SYSLOG_PARA_ZONE);/ U+ W: G# _3 j& Y: m* z3 t" P5 H2 ~
    . \* P- t+ R5 ?3 m) D8 w# F
    end_addr =flash_tmp->end_address - (flash_tmp->end_address - flash_tmp->start_address) % interal;: q, ]6 ?- j1 M# N- B2 J& ?
    for (i = end_addr - interal; i > flash_tmp->start_address; i -= interal) {
+ U8 E1 [: B' r1 i5 B& c        flash_read(FLASH_SYSLOG_PARA_ZONE, i, (uint8_t *)&psav, sizeof(single_sav_t));6 R5 Y: s* u6 O' e4 v% u" A
        if ((psav.magic == SYSTEM_LOG_MAGIC_PARAM) && (psav.len ==data_len)) {   ! L$ K+ t' Q/ c$ }7 f
            flash_read(FLASH_SYSLOG_PARA_ZONE, i + sizeof(single_sav_t), &pram[sizeof(single_sav_t)], data_len);
3 v2 C5 `0 j+ `# T            if (psav.crc != CRC16(&pram[sizeof(single_sav_t)], data_len))
. m  R7 t2 H  i( d: ^                continue;
6 X; ]8 \% k& W$ A# L" X            gp_sys_ram->system_log_param_addr = i;+ y8 P# \6 Y' s) G) z2 n$ M4 X
            log_info("Load System Log Param Addr[0x%08x]!", gp_sys_ram->system_log_param_addr);
# Z% b% O+ N1 w5 \* I" D. P            return 0;
$ f/ ]) {4 w8 ?* @0 Q; x% l+ N/ o        }, D- d' \* ~4 Q0 c
    }( d8 p$ E+ g- A& y( b- ~# j- i7 J
    ) M; K" L6 k6 N
    /* 扫描不到合法的参数,导入默认系统日志参数 */
1 m( O1 ?7 D4 L; ~: Z$ n    load_system_log_default_param();' @* n* S3 g8 }( `
    /* 获取日志写地址 */( K9 |9 T8 e# a% n# e7 {9 J5 \
    gp_sys_ram->system_log_param_addr = flash_tmp->start_address;. H0 e! a: T( i9 v* o7 {# y
    log_info("Load System Log Param Addr(Default)[0x%08x]!", gp_sys_ram->system_log_param_addr);
/ H- R  _4 L7 `# g3 N9 Z- E, w  K    return 1;, \4 z+ f& L- Y, F
}
7 H" M; w# l- s5 `5 r% B) ~读写系统日志目录接口,读写指定日志索引目录信息。实际实现会定义最新的目录信息存储在日志参数区,当日期发生改变,则表示当前目录信息已经完结,将最新的目录信息录入日志目录区保存,最多每天写入一次目录区。/* 读取日志目录区指定日志索引目录信息 */
. e, U8 b  I+ wint system_catalog_read(system_catalog_t *catalog, uint32_t id)
* L5 ?4 w  C+ R; ]{+ k$ P# m2 i# c  D
    uint32_t addr;
. |/ D: e0 f# ]    int rlen = sizeof(system_catalog_t);
' d% E7 a' U( h    uint8_t *pbuf = (uint8_t *)catalog;
. i; Y! S$ P. s5 S: Q% Q    flash_table_t *flash_tmp = get_flash_table(FLASH_CATALOG_ZONE);9 l; z' V4 s! R- y% O
    if (0 == id)
3 e5 Q/ }# F; x& {+ q- a. Z        return -1;: |3 c: m: m6 D" X4 t* z
    addr = flash_tmp->start_address + (rlen * (id - 1));
8 B6 u) S) S- G9 M8 B7 W    if (addr > flash_tmp->end_address) , s+ a$ B9 V( l' @4 n
        return -1;8 C. Y# F; c0 ?" V3 A* ?
        " m' ~- Q/ h1 H6 r1 I9 X
    return flash_read(FLASH_CATALOG_ZONE, addr, pbuf, rlen);
9 \3 ^1 D& m# t( F7 T/ A) W}
, _" O( Z: X5 T% d' {# ^/* 写日志目录区目录信息 */1 I4 A, q8 ~( {! w$ S
int system_catalog_write(system_catalog_t *catalog, uint32_t id)( E. }1 p  n/ {  \. a9 p
{+ _) [' {, E6 _
    uint32_t start_offset;; G" C) U& c2 g* [% ?
    uint32_t start_addr;1 g1 R5 d- P9 g# B- z  k( a
    uint32_t start_base;
+ ?/ g, r0 I- q) D, ^3 A# ^    uint32_t remainbyte;
: U; A, Q0 z' w8 r5 v0 R* I; Z    int wlen = sizeof(system_catalog_t);3 r* A- \8 f) N) G& S
    uint8_t *pdata = (uint8_t *)catalog;
6 w+ l9 b6 K! _+ L" s; k    flash_table_t *flash_tmp = get_flash_table(FLASH_CATALOG_ZONE);
/ S/ O7 r1 z/ t5 w    9 @+ t; c( ~. f7 s# R
    if (0 == id) return -1;8 N4 J) U6 p7 Y' @6 d0 U: t9 ]
    start_addr = flash_tmp->start_address + wlen * (id - 1);- h9 M% j9 D. {+ U$ d$ ?
    if ((start_addr + wlen) > flash_tmp->end_address) {+ e* e% C( A9 x0 l
        start_addr = flash_tmp->start_address;! a8 @* e  S  ~$ R
    }
' E5 E/ O5 k% o" z& j    4 }# l# w; r* o  B1 I& \$ y3 l
    /* 本扇区剩余空间大小 */
7 D1 R) e4 V  g    remainbyte = FLASH_SECTOR_SIZE - (start_addr % FLASH_SECTOR_SIZE);$ ^1 u4 e! s+ \+ f  R
    /* 写入数据长度小于本扇区剩余长度,直接写入 */
; k  u1 N# A" I6 Z7 J& O# ?5 j) f    if (remainbyte > wlen) {
% f# W& O; ~$ ], x" y4 V. ^        remainbyte = wlen;6 h7 v+ ?( a! h0 ], x
    }2 X3 k6 ]+ X  b. h. X# E* ]  O
    /* 写目录次数不会太频繁,视具体情况改写操作实现 */
: ]+ G, O6 R5 R# p! M  p! h  S    while (1) {0 F7 \8 A1 s' f1 R& N
        start_base = SECTOR_BASE(start_addr);/ o! b0 }* a, u8 d
        start_offset = SECTOR_OFFSET(start_addr);
& ~9 p* g% U, u1 D        flash_read(FLASH_CATALOG_ZONE, start_base, sector_buf, FLASH_SECTOR_SIZE);0 ~/ P1 k' @/ {0 Z' M+ r
        flash_erase(FLASH_CATALOG_ZONE, start_base, FLASH_BLOCK_4K);3 ?2 [$ H& z. g
        memcpy((char *)&sector_buf[start_offset], pdata, remainbyte);
( Z( [; ~# W4 m$ x        flash_write(FLASH_CATALOG_ZONE, start_base, sector_buf, FLASH_SECTOR_SIZE);7 n& X6 M7 t2 `
        if (remainbyte == wlen) {" n) t& B7 j0 o( k# D+ _2 q1 X
            break;( s7 _, Q! }8 {* \9 |
        } else {
6 ?0 }; O; M3 g' f2 ?$ j3 q- L; @            pdata += remainbyte;8 e$ H: p4 m/ ^
            start_addr += remainbyte;
9 E3 I; K; k+ P9 X) s+ u            wlen -= remainbyte;
: A0 v2 m; G, B1 Q" p! T; ]; X2 G            remainbyte = (wlen > FLASH_SECTOR_SIZE) ? FLASH_SECTOR_SIZE : wlen;
- h7 _: [! n& I) ]! x" y' k# `        }
- N7 P' e0 U- T- s2 l    }
/ J  N" @# P5 c2 `! \7 F. b) E1 o2 w   
( b+ B5 L! v5 \2 Z: n% ]# r    return 0;
& U, S/ w6 t% A" @}
& y  ?. E* b+ t7 ^% W打印系统日志目录区信息,可实现通过指令查询到目录区信息。int system_catalog_all_print(void)0 o2 I9 |; m! o* V0 K
{( `4 X- p4 U" s2 d8 x
    int i = 0;
, o9 Q: ~; d% x# q    system_catalog_t catalog;
+ ?% B0 ]; u* W- w( [    printf("System Log Command Information:\r" F& s) X8 E; y" K4 m# \' w4 i
");  B! }5 T/ h: K
    printf("Query Specifies Log : AT+CATALOG=\r
, j1 r. `0 ?( T) e6 f$ X# Z");
* ~. q! p7 H) Y* `5 _/ A0 Z    printf("Query All Log : AT+CATALOG=\r
" d% S8 T; ~1 n  b, m\r! m( I- |) j7 P+ ~
");  K8 }+ ]1 G' _2 v4 t6 R! _9 O
    printf("Query All System Catalog:\r" f( a1 p1 W2 b/ a( G- u! K
");
  q3 G0 B1 v# O    printf("LOG_ID  LOG_DATE  LOG_ADDR  LOG_OFFSET \r
: }% A# y: Z. _( u* [6 P");# E/ ?3 E4 E* S. a! X& Q& t* i' k; V
    for (i = 0; i system_log.catalog_num; i++) {* ^0 t% [( s' P. V
        /* 当前最新目录信息 */  
6 s# U, \' d8 z6 y) W+ B8 O: @; m; D        if (i == (gp_sys_log->system_catalog.log_id - 1)) {
% E1 h  m1 I7 s/ {            catalog = gp_sys_log->system_catalog; /* 获取当前最新目录信息 */) ?4 a, M  W0 h4 U  t% Z: n. _
        } else {
/ R+ {4 a: F, q& D8 V1 B) B            system_catalog_read(&catalog, i + 1);: C1 I6 q) Y; \  G/ r) Y
        }: {# h. R+ B' c8 G
        printf("%d  %04d-%02d-%02d  0x%08X  %d \r
/ n& N- V# B8 ?% o5 R", ) N( ~' @9 @) {' |+ D
            catalog.log_id, catalog.log_time.Year, catalog.log_time.Month, catalog.log_time.Day,
: s6 u; d" X$ [& v            catalog.log_addr, catalog.log_offset);4 ~4 i4 p1 s, m& g6 g
        memset((char *)&catalog, 0, sizeof(system_catalog_t));
+ i# g4 o- o( L( H( n1 x& ~5 J    }
! n: F; b! X) `1 `" m. R  T! R    return 0;$ ]! [5 p- |  y5 E# L5 P
}- n8 P. v/ d% o. U- F% E
读取指定日志目录索引信息接口,可指定日志索引或者读取全部日志数据。int system_log_task(int argc)
( v- t3 x0 H; |+ j8 L  J% r{/ e* U6 N6 T- A
    int rlen = 0;; [3 W; w, M) k5 \- W+ }8 Z1 ^
    uint32_t offset, start_addr, end_addr;% g& p; a7 m0 B8 o
    system_catalog_t catalog;
" D3 o3 _7 N( V& \0 [& V* t    flash_table_t *flash_tmp =get_flash_table(FLASH_SYSLOG_ZONE);
5 E) z7 S& Y" O8 z* G' a   
1 B9 J# _6 v: O3 R    if (0 == gp_sys_ram->system_log_print_enable) / {( Y- i) S! m& P* W" z
        return 1;6 W9 H7 R. ~; j; \$ i1 s
    gp_sys_ram->system_log_print_enable = 0x00;
/ h' \, v! p6 D    if (gp_sys_ram->system_log_print_id == ALL_LOG_PRINT) {7 ]6 ]: s0 ~6 _. j# @6 I  O
        /* log回环写标志,打印整个LOG存储区 */
. _/ r+ [8 l; Y, m  r) M        if (0x01 == gp_sys_log->system_log.log_cyclic_status) {
" u/ h" `! V% z2 L0 i! v            start_addr = flash_tmp->start_address;
; a# g4 N9 H8 Z# u* @6 O            end_addr = flash_tmp->end_address;) k9 O& s- s0 v/ {$ C
            offset = end_addr - start_addr;
. N2 d; ^: N. {% `        } else {
/ n! s+ w- N2 V* E% M, H            start_addr = flash_tmp->start_address;" Y# w+ u! t" o: t
            end_addr = start_addr + gp_sys_log->system_log.write_pos;& J  q  S" E- h9 J: F% I
            offset = gp_sys_log->system_log.write_pos;/ U7 N: G: K. P( t" l) u
        }) H& W( j" }/ X/ E' G' \
    } else { /* 读取指定ID日志 */% A9 \: J- l" R; M- ^( G
        if (gp_sys_ram->system_log_print_id == gp_sys_log->system_catalog.log_id) {
/ j5 ?5 ?5 R; m            catalog = gp_sys_log->system_catalog;+ C, B, u$ V+ A' w1 h: ]9 V! T
        } else {
, B, [5 f* H6 L* Z; e0 \            system_catalog_read(&catalog, gp_sys_ram->system_log_print_id);9 `5 R6 s$ i; f4 E7 S
        }
) i9 d. g0 R* `: A+ T* u* ]' |; d        start_addr = catalog.log_addr;6 s/ F) B$ |" z
        offset = catalog.log_offset;/ \9 @- W) y7 p! d
    } / _1 I- ~, D6 g6 r; q
    if (0 == offset)
* G: E# v* w  k        return 1;# ]  c. U" w. u8 c
    while (1) {* |  t  ~& q! X2 b: s; s
        rlen = (offset > 512) ? 512 : offset;
9 k) u0 z5 a% L) N  V        system_log_read(sector_buf, start_addr, rlen);
4 c! y+ ?3 l2 i; A% O        HAL_Delay(80);
7 j3 s! p4 u+ e' n6 I        /* 目录信息通过调式串口打印 */$ \) a5 X( j( u! W/ w, N! A& ~" s
        bsp_debug_send(sector_buf, rlen);: H7 D9 S! ?9 B& k4 l% z' {
        start_addr += rlen;; @1 Z: b$ o( U+ ?
        offset -= rlen;1 h: C5 P) D- ?( c- W  q) ]5 }
        if (0 == offset)
6 I9 s6 u% A- q7 A8 V& A' Y            break;0 P! P& Y# u/ B5 f
    }
, c+ Q' E/ D; q% u- K    return 0;# t* }+ k4 v$ ?* M
}
& F- S3 j6 E+ x% M存储系统日志接口,实现更新存储日期,当写位置为扇区地址,则擦除一个扇区作为存储日志,这样避免每写一次就擦除一次。int system_log_write(uint8_t *wbuf, int wlen)/ o, B$ w( Z! H  j8 Q$ P7 z
{; `7 p: u; S/ e$ X1 l+ B4 F7 O
    uint32_t start_addr;' [) [1 k: Z+ A# J, u8 n: h$ y
    uint8_t *pdata = wbuf;+ N6 W! ^* |! N; G! v, D4 j  L
    uint32_t remainbyte;
2 k8 V+ [- D6 B& k, [1 X    int system_catalog_max_id;
; E2 h6 l" k4 {0 h& w- f/ t" H3 q    flash_table_t *flash_tmp =get_flash_table(FLASH_SYSLOG_ZONE);9 J5 W4 S# D( i& K
    . C7 K$ n+ G1 S; B! B) j
    /* 计算目录区的最大存储目录项个数 */
& q' l6 m3 u' H2 o; @+ C    system_catalog_max_id = ((flash_tmp->end_address - flash_tmp->start_address) / sizeof(system_catalog_t));& a" F; r( s; E. P/ L
    start_addr = flash_tmp->start_address + gp_sys_log->system_log.write_pos;
1 o& `" [: a7 Q    /* 存储数据地址大于规划内存地址范围处理 */, P4 y9 _( j( f. A9 r( Y- Q. d/ p
    if ((start_addr + wlen) > flash_tmp->end_address) {
6 w( C0 S# I8 J9 Q        start_addr = flash_tmp->start_address;
9 V& W) v# e2 J" l, C& ^/ Y+ x/ m: N        /* 写位置偏移量重置 */: n' Q2 M& E* a# y" l- W1 \
        gp_sys_log->system_log.write_pos = 0;$ ~7 V+ J: V! D6 ^9 Q2 H
        /* LOG回环存储标志置位 */" q. t* N* R+ V/ n' D4 G4 S  m
        gp_sys_log->system_log.log_cyclic_status = 0x01;
- L% ]: o& E- j. A# ^" L    }
6 C1 Y4 R  Q: N8 L- `    /* 写位置偏移 */. V+ j1 i( J% z; Y0 I) X
    gp_sys_log->system_log.write_pos += wlen; ; g8 z. y4 s1 {4 s0 f7 Z" t$ d0 g
    if ((gp_sys_log->system_log.log_latest_time.Year != gp_sys_log->system_catalog.log_time.Year) ||7 c, |  x$ D, G! R6 k- Y' [3 w
        (gp_sys_log->system_log.log_latest_time.Month != gp_sys_log->system_catalog.log_time.Month) ||
' i2 G& ~0 t1 p$ ]7 d        (gp_sys_log->system_log.log_latest_time.Day != gp_sys_log->system_catalog.log_time.Day)) {
% K  @' a9 @- W* ?- w% [9 b6 o( Z        /* 日期改变,记录目录信息,当log_id为0,则不写入 */0 Q, j! x! {, l) ~( l! H
        system_catalog_write(&gp_sys_log->system_catalog, gp_sys_log->system_catalog.log_id);
, v1 ?6 x% [7 W0 w1 C& z        /* 记录存储日期 */
. G& W# z3 [: c+ n5 ~        gp_sys_log->system_catalog.log_time = gp_sys_log->system_log.log_latest_time;
; f0 O& Q2 l8 L$ h, E; L2 [        3 K) O0 t9 j9 U% |2 E+ k
        if ((gp_sys_log->system_catalog.log_id + 1) >= system_catalog_max_id) {+ A  ?/ [1 }% N% R$ A
            gp_sys_log->system_log.catalog_num = system_catalog_max_id; /* 目录循环写,目录数应为最大 */
. U, M) E! I& u$ y6 D# z            gp_sys_log->system_log.catalog_cyclic_status = 1; /* 目录回环写标志 */
3 U6 L8 Y$ o# f+ S( K        } else {
4 w% f) `1 V0 a' o5 w( ]7 I! q            if (0 == gp_sys_log->system_log.catalog_cyclic_status) {
- {$ l; x. b; r# A& A8 x# f4 y                /* 获取目录数 */
4 _3 A+ S  N( L                gp_sys_log->system_log.catalog_num = gp_sys_log->system_catalog.log_id + 1;
0 v9 r/ ^' y* U. B            }3 c/ ^+ N1 J* A3 q: k4 n
        }" d( e& g* l6 X% n$ a3 G7 f; X6 Z' j
        
( p# b0 `0 L) Y/ \  L4 Y        /* 存储最新目录项信息 */( _4 M% t" I- r1 Q5 i
        gp_sys_log->system_catalog.log_id = (gp_sys_log->system_catalog.log_id + 1) % system_catalog_max_id;: K) V4 R8 \5 t+ Y2 p
        gp_sys_log->system_catalog.log_addr = start_addr;
- x9 D) U0 `* G        gp_sys_log->system_catalog.log_offset = wlen;
% o) _$ ]6 z$ ]4 ^    } else {
8 a% F8 X" h1 q, t  h# A. X        gp_sys_log->system_catalog.log_offset += wlen; 6 T& l& T' |! _
    }
6 d: n  u. c" o) o    " p8 L; {% ^& P4 R# \
    /* 写位置为存储起始地址并且不为扇区首地址 */% s1 }# s' N2 ~+ k6 V8 @9 i
    if ((flash_tmp->start_address == start_addr) && (SECTOR_OFFSET(flash_tmp->start_address))){1 I8 X# s# b! n0 \+ M, p
        flash_read(FLASH_SYSLOG_ZONE, SECTOR_BASE(start_addr), sector_buf, FLASH_SECTOR_SIZE);8 J" R! b3 `# v
        flash_erase(FLASH_SYSLOG_ZONE, SECTOR_BASE(start_addr), FLASH_BLOCK_4K);
9 E. W. N+ E: F        /* 将扇区头部至起始地址区间的数据回写 */
+ g! l0 Z6 \7 S7 M$ e6 P3 E        flash_write(FLASH_SYSLOG_ZONE, SECTOR_BASE(start_addr), &sector_buf[0], SECTOR_OFFSET(start_addr));
" O+ b9 X" B2 d    }1 u. k  g/ [5 H1 O/ g
    /* 写位置为扇区首地址,则擦除一个扇区的存储区    */  |1 r7 m$ A6 s: A* o1 N) H  C
    if (0 == SECTOR_OFFSET(start_addr)) {# [' ]3 x% v0 }8 f+ n( z
        flash_erase(FLASH_SYSLOG_ZONE, SECTOR_BASE(start_addr), FLASH_BLOCK_4K);# k; m4 {: A) X* u4 w
    }1 V  |5 k% T/ v
    /* 本扇区剩余空间大小 */
+ `$ C4 g5 k/ d  c' [3 }    remainbyte = FLASH_SECTOR_SIZE - (start_addr % FLASH_SECTOR_SIZE);
6 T; V9 _: E- U) N* ^2 ~    /* 写入数据长度小于本扇区剩余长度,直接写入 */% |" s/ w2 y5 g  g% W
    if (remainbyte > wlen) {
0 v  @* z9 N' }+ K$ n: h4 g        remainbyte = wlen;
2 W% \5 `* V1 b( D8 d; f    }% T/ c0 \' k- c+ w* L
    while (1) {
; c* h# A1 m: N) ?( u        flash_write(FLASH_SYSLOG_ZONE, start_addr, pdata, remainbyte);) @5 ]' D, d/ Q
        if (remainbyte == wlen) {! ]3 n9 }& V7 Y6 _0 F! G
            break;
1 B; U+ e+ [* z! L$ P  H- [" x7 ]  Q        } else {
( J/ x' Q7 {* _" c' m  t. s7 g            pdata += remainbyte;
) e) h% s3 x2 Y  a            start_addr += remainbyte;  W3 R# R9 _' ~, I) ?
            wlen -= remainbyte;) x  q, n  l6 N- n0 u# z7 H
            remainbyte = (wlen > FLASH_SECTOR_SIZE) ? FLASH_SECTOR_SIZE : wlen;
( H. Y% b4 ?/ d" b% r8 M            /* 扇区首地址则擦除整个扇区,该扇区数据不保存 */: o' c3 l, |- c+ @2 [$ _2 b
            if (0 == SECTOR_OFFSET(start_addr)) {8 o+ ?+ {6 F! x) E4 n, a
                flash_erase(FLASH_SYSLOG_ZONE, SECTOR_BASE(start_addr), FLASH_BLOCK_4K);0 o2 e3 m# P; d) h0 ^" {8 {
            }
$ i% o- r$ O( T, [" c) h        }) b7 f6 ^/ J' s: ^& ~) V
    }0 U2 f, V% t& {6 ~1 C! Y/ G  }
    /* 环形存储参数 */
6 b0 j/ h9 U; S! u- w+ Y1 w    save_system_log_param();
! g* I2 N8 q0 C    return 0;4 c0 X8 g" b  [
} 系统调试对接
" }- t( g4 N# _) @为了更好记录系统日志,将应用调试等级结合一块,实现记录错误调试信息以及需要保存的关键信息。定义的调试等级有:关闭调试等级、错误调试等级、警告调试等级、关键调试等级、debug调试等级。而 LOG_RECORD_LEVEL 将主动保存日志并输出信息,LOG_ERROR_LEVEL 会存储对应的日志信息,但需要根据应用调试等级输出信息。设置与读取应用调试等级由读者自行定义。: ~( d. k; P% V8 x4 K
#define LOG_CLOSE_LEVEL    0x00 /* 关闭调试信息 */
* e0 u- c# F. l# z+ Q: a#define LOG_ERROR_LEVEL    0x01 /* 错误调试信息 */
$ G. v1 U6 O: N, c3 P#define LOG_WARN_LEVEL    0x02 /* 警告调试信息 *// p9 p) G, Z# M- w4 B: y
#define LOG_INFO_LEVEL    0x03 /* 关键调试信息 */& H; V! U; b( C/ K) j4 _
#define LOG_DEBUG_LEVEL    0x04 /* debug调试信息 */; B: i# m. \0 g6 M( z
#define LOG_RECORD_LEVEL   0x10 /* 保存日志并输出信息 */ + J7 N8 o8 t' G- v
#define LOG_PRINT_LEVEL    0xff
% m: _& z. [5 |) p, O#define SET_LOG_LEVEL(LEVEL)  (gp_sys_param->system_print_level = LEVEL): ]' b9 f, g+ E+ I5 D
#define GET_LOG_LEVEL()    (gp_sys_param->system_print_level)
8 z7 R2 a" H- V# `+ u9 D6 e#define log_debug(fmt, args...)  log_format(LOG_DEBUG_LEVEL, fmt, ##args)
; `* g# o" p0 b) j4 b#define log_info(fmt, args...)  log_format(LOG_INFO_LEVEL, fmt, ##args)
  r1 D( p' X6 C3 m) W( T9 I$ B' l#define log_warn(fmt, args...)  log_format(LOG_WARN_LEVEL, fmt, ##args)- [& K: d! F" F) \1 x+ T- X4 u7 i" c
#define log_error(fmt, args...)  log_format(LOG_ERROR_LEVEL, fmt, ##args)3 h. C0 }  g8 |& f
#define log_record(fmt, args...) log_format(LOG_RECORD_LEVEL, fmt, ##args)0 y, t2 `5 M1 k1 r
#define printf(fmt, args...)  log_format(LOG_PRINT_LEVEL, fmt, ##args)  `7 D% s+ J  a/ o. b" Q% f- G
typedef struct {) d! h: M  g1 R$ g2 E
    int level;  A/ U2 U5 b8 t5 n, n
    char *fmt_str;
# G9 c9 Q, _$ q9 ]  ]5 }, Z' ]}system_print_fmt_t;; a  O  Y% K  k, G4 k
system_print_fmt_t system_print_fmt_list[] = {
) R  F; V& ~8 a1 k5 H- ^    { .level = LOG_ERROR_LEVEL,   .fmt_str = ":"},/ V3 r7 e; x3 B: V: p. n8 ~( D& E
    { .level = LOG_WARN_LEVEL,    .fmt_str = ":"},- O; f. \; A. J. h0 e7 w& V7 Y
    { .level = LOG_INFO_LEVEL,    .fmt_str = ":"},
2 H8 x& b. ^  ]    { .level = LOG_DEBUG_LEVEL,   .fmt_str = ":"},
' A" B: i" i, r, E    { .level = LOG_RECORD_LEVEL,  .fmt_str = ":"},
& |2 S( f" k) K8 F};: ~# X; i. {* U# B  k4 {6 ~) r
int log_format(uint8_t level, const char *fmt, ...)( u) o- E: T' v1 F7 k
{
# T  A! ]( X0 z' k* J    #define TIME_PREFIX_SIZE (21): v! ^1 i8 g1 d. ?- Y
    #define PRINT_MAX_SIZE  (1024 + TIME_PREFIX_SIZE)
# r3 ~7 }9 ^5 g+ s' W$ b    " g: |. I; J$ R9 c, e; c
    va_list args;
4 b1 _8 Y& ]! t: B    int num = 0, i = 0, fmt_index = 0;
- v1 a/ J) M0 |0 m4 P9 r    int fmt_str_len = 0, ret = -1;6 F, S* x0 g) H: e& G
    int file_str_len = 0, line_str_len = 0;* m; u" X: t& x0 I
    char line_buf[20] = {0};( y4 I3 V( p8 K: [( \
    static char buf[PRINT_MAX_SIZE];/ V# W2 ^  ^* }, R
    static QueueHandle_t sem = NULL;; ~% A3 C$ `! ~& \( t! E9 _1 y
    time_t time = {0};( r: `1 j6 o; P! s& {2 U4 r- u6 Q
   
9 i; H5 `1 a" g    /* 针对os系统 */% o; R  i9 d; @; Q! A9 `7 f& J
    if (NULL == sem) {
9 s0 k; u! Q( U  E. I6 S; [          sem = xSemaphoreCreateCounting(1, 1); /* always think of success */- A# ^. F6 `6 K* J  g3 p0 v6 r& _
    }
! q* g) i/ V2 ^; ~    ! a8 h% P5 @' X) G
    xSemaphoreTake(sem, portMAX_DELAY);4 e5 \, a& ~, K2 K
    ret = -1;
9 N0 M8 n' l0 c    fmt_str_len = 0;8 v" T+ C  s# [6 W* F: |
    if (level != LOG_PRINT_LEVEL) {# Y! Q& L1 `8 D5 ~
        if ((GET_LOG_LEVEL() goto exit_end;
+ h0 H) }+ D8 |( G, M) z4 A        for (i = 0; i if (level == system_print_fmt_list.level) {
" H' r4 _; x6 @/ m" p                fmt_index = i;( m- M& N* x9 s% B2 _$ z
                break;% @0 |( \, B! e4 R/ c( D' n: k
            }( D! A, N/ M* v& ]. D0 ~5 N
        }/ ]( ?& V0 ?0 W' e5 j
        if (i > SYSTEM_PRINT_FMT_LIST_MAX) {4 R  V- T0 y0 i% A+ O' d7 X- j# o$ S" q
            goto exit_end;
& L  t3 M) R3 _% C: M% `3 s        }
6 m9 @0 e% _1 j/ q7 l        fmt_str_len = strlen(system_print_fmt_list[fmt_index].fmt_str);
' A7 k! l- F! y) d2 n0 M        strncpy((char *)&buf[TIME_PREFIX_SIZE], system_print_fmt_list[fmt_index].fmt_str, fmt_str_len);2 E+ n. C, k9 Z  E+ D
    }6 v9 \$ ?* q, c% F" j) Q! _/ q
    va_start(args, fmt);
5 p: z! B3 T7 r4 {/ D% C( c5 R    num = vsnprintf((char *)&buf[fmt_str_len + TIME_PREFIX_SIZE], PRINT_MAX_SIZE - fmt_str_len - TIME_PREFIX_SIZE - 2, fmt, args);
. R+ M! u* X4 }1 t- l5 O    va_end(args);
& B8 Z+ g4 G; e6 q) H    if (num 0) {
: W3 _  j+ u" W        goto exit_end;- ]& G, c: \/ F3 X9 e# `
    }
. N3 m" {! ~/ E1 i: v    if (level != LOG_PRINT_LEVEL) {
- ]' h- j9 q$ N" k& b. |: g! N        num += fmt_str_len;
- K; T+ N8 o. f1 ~5 Y9 l        buf[num + TIME_PREFIX_SIZE] = '\r';
; Q$ n, P% V! i        buf[num + TIME_PREFIX_SIZE + 1] = '
/ V" f1 f2 H/ \/ W  M';2 S7 m1 ^, g* v! g
        num += 2;
: B9 J% v) `; M+ e% C/ S' o. G    }
: e8 d$ e" I% w& c" C' d9 T1 c    if ((GET_LOG_LEVEL() //do nothing( K% N- l% P$ l+ [$ ?* l/ |
    } else {# w+ l7 Q2 d& U9 S4 Q1 y: ^( d5 }
        ret = bsp_debug_send((uint8_t*)&buf[TIME_PREFIX_SIZE], num);
, t: N  i* ~5 Z2 u$ g    }+ H8 O% f8 O; a, e+ o
    if ((LOG_ERROR_LEVEL == level) || (LOG_RECORD_LEVEL == level)) {+ K! T2 K) E# C3 X+ r" e
        bsp_rtc_get_time(&time);
. W4 K/ r1 D% Z7 `0 Q, [) Q        sprintf(&buf[0], "[%04d-%02d-%02d %02d:%02d:%02d",
' ~& i, h3 j9 L% J0 w            time.Year, time.Month, time.Day,time.Hour, time.Minute, time.Second);
+ `6 [) b. P6 ^2 N! s        buf[TIME_PREFIX_SIZE - 1] = ']';. b7 F+ }7 a' {
        gp_sys_log->system_log.log_latest_time = time;# l/ [+ D# Q% M9 ?1 s4 p& a- a
        system_log_write((uint8_t *)buf, num + TIME_PREFIX_SIZE);, Y# R& V5 W3 h5 x0 V9 M# K
    }
1 h& S9 n0 }1 D- K. P* y8 g# `exit_end:( P0 \2 R  Y" @; a5 d/ ?: ]
    xSemaphoreGive(sem);1 J5 t" h. u) D1 W6 z+ z2 K
    return ret;: w  R" D3 T1 m& E/ D" j0 u
}结语
" J5 F4 y( T5 o! ^+ Z1 z本文提供的一种简易嵌入式设备系统日志记录方法,代码量不多,实现简单,针对不同的设备需要合理规划内存使用。根据软件运行状态,加入调试信息并保存对应的日志信息,方便开发人员了解系统或软件运行状况,协助开发分析数据资源从而更好完善系统,提高定位以及解决问题的效果。文章来源于网络,排版:一起学嵌入式' i- P( G0 J5 N) o' z

" z. A2 X  h. k+ h$ m7 H+ E* A/ o-END-" F  J  P% [1 x, W# z9 E. U
往期推荐:点击图片即可跳转阅读% @! C& h( m/ V+ I( i, U! U0 y
: |. U" L: G( _4 J+ ~: y: e
                                                       
4 P4 p) S5 `; \4 Q                                                               
) L& y8 a7 @& M# Q                                                                       
8 w: ^! k) Q& u8 k, O; O6 V% L1 w                                                                               
  u! J5 r: Y" }+ {$ {. S( P% J2 K" H1 r

5tt1joomon0640722325.jpg

5tt1joomon0640722325.jpg

/ R0 @( V5 [( ]1 C+ ]                                                                               
7 q/ c% T+ e3 H: q' O1 m                                                                                        掌握这些嵌入式 C/C++ 开发神器,迅速提升开发效率!
" R) Q+ c' d  u( E                                                                                5 O0 i8 p2 r( f4 Z' m& U
                                                                        7 M& u3 H3 S' A$ z0 Q5 F
                                                                5 q9 B% u2 w; p/ j7 E$ J* F7 ~
                                                       
2 H) L* y# a( r9 j                                               
, A2 F# C$ g3 `" |. Q6 S5 S# k( i/ k& U: ]8 q/ L
                                                        5 n1 K6 V( [! z5 n
                                                               
" G" e- T! d0 T                                                                       
0 j) b( L6 _$ K" x7 v6 Z                                                                                ) U$ M9 h4 h: R- N2 `  h/ E5 k

go1ab3lg5db640722425.jpg

go1ab3lg5db640722425.jpg

; ]! o8 N- H7 N/ F9 z, L5 s# u% D9 ~                                                                                " A3 H$ v, k! E+ g+ ?2 b& S
                                                                                        用嵌入式 C 语言,设计一种垃圾内存回收机制。+ Y2 X" `2 x' F/ R9 S
                                                                                " M: U$ u* G; f3 w+ ~* D  b
                                                                       
, \# r& S  t4 _* a; `                                                               
  X  U! a' M& V0 D                                                       
% U  d9 Y* G. ], Z, w' }: T% j                                               
3 z1 u/ A/ ^& X$ B+ f
( n! B' y5 L& ~! w- Q                                                       
! f+ J5 @) p9 p4 X                                                                3 _9 c+ A, B: R" y
                                                                        % X! v) U8 ]3 z% T2 H
                                                                               
. x* a# D* b: V' l

sqm0kevbkt4640722525.jpg

sqm0kevbkt4640722525.jpg
2 j0 t3 r- C$ v
                                                                               
2 W2 b0 e+ B: z; M! a" `6 @" L                                                                                        工程师的艺术,完美治愈强迫症!
4 t" n4 k& n% @" Z6 ]2 {" A7 x% q                                                                               
3 w+ L0 h0 P& Y) F- y' [                                                                       
$ w  j4 n+ D" G/ ~                                                                0 z5 Z  O9 k' @
                                                       
+ `; D1 \3 Z! U" H                                               
3 M3 V/ {" Q9 u6 [: V我是老温,一名热爱学习的嵌入式工程师
+ k, T& b9 ]8 z8 R5 {关注我,一起变得更加优秀!. d5 M5 K! H1 L

6 ?1 R6 \! _% `+ {! Z- J% d  X6 E

dihtz2qydzp640722625.png

dihtz2qydzp640722625.png
回复

使用道具 举报

发表回复

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则


联系客服 关注微信 下载APP 返回顶部 返回列表