电子产业一站式赋能平台

PCB联盟网

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

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

[复制链接]

567

主题

567

帖子

4209

积分

四级会员

Rank: 4

积分
4209
发表于 2024-12-13 08:00:00 | 显示全部楼层 |阅读模式
我是老温,一名热爱学习的嵌入式工程师5 ~/ x- c5 O+ x8 F% y
关注我,一起变得更加优秀! 1在嵌入式设备应用场景中,系统日志时常可以监控设备软件的运行状态,及时记录问题点以及关键信息,方便开发人员后期定位以及解决问题。本文将讲述一种简易的系统日志记录方法,用于保存设备的系统日志,视具体嵌入式设备情况而定,可存储在 MCU 内部Flash、外部 Flash、EEPROM等,本文采用外部 Flash 作为示例展开介绍。
+ N/ ]# i4 o" V3 [思路分析对于系统日志可以当成文件系统,可以划分为三个重要部分:目录区、参数区、日志区。* q4 g; Q; ?% l3 q& S9 N/ Z( J2 D
目录区:根据日期进行归类,记录当天的日志的存储地址、日志索引、日志大小,通过目录可以获取整个日志文件的概况;/ u" C2 ~3 S. y* c# p, r$ [6 t" M/ R; }
参数区:存储记录日志写位置、目录项个数、写状态等参数;7 {. a  l  W/ d6 [/ b/ x
日志区:这是我们主要的存储区,记录系统的日志,支持环写。
% c* l) A! d+ i* J这三个区域都需要占用部分内存,可以自行分配大小。3 s, u' M" y; i" ], C
实现的效果如下图所示,设置通过指令可查询到整个日志目录区的概况。查询系统日志目录:AT+CATALOG?
1 S6 g; g) e* v- G9 U7 `, e! RLOG_ID: 存储日志按日期分类,该ID用于查询对应日期日志,从1开始计数;
1 d) q' o2 G  n' i* S) a3 u% X8 g. X( |LOG_DATE: 系统日志存储日期;- @5 G8 c5 Y! _- j* @1 L
LOG_ADDR: 系统日志存储外部FLASH地址;) B) T" m, Q! P7 K3 B
LOG_OFFSET: 系统日志存储偏移量(各日期日志大小,单位:字节)。
; \* {# w+ {# L2 t" f3 q

iejtqngseu4640722125.png

iejtqngseu4640722125.png

& Q* G4 W3 ~9 w2 v! }2 d# m3 ?3 P( h3 _
查询指定日期系统日志:AT+CATALOG=. {3 i3 [) B3 I, A) g6 p8 _! d
LOG_ID:在查询系统日志目录时获取,当LOG_ID为 0 时,为查询整个系统日志。' E4 h" U6 Z: |2 f. ]

dblt2xzfh2m640722225.png

dblt2xzfh2m640722225.png
6 @7 h/ y4 {, A2 P& F! n9 ~0 s

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

4 I3 V+ ^; t1 U  x                                                       
+ I0 I" T0 b4 J9 S% n+ x3 J                                                                3 V6 H% o9 G! [7 q, Z8 x
                                                                        " K' H! }" \2 }% J7 G
                                                                                + ^4 A8 X6 B7 j" L

5tt1joomon0640722325.jpg

5tt1joomon0640722325.jpg

2 z" }3 c$ \! x4 \5 p' {0 p' r                                                                                . T  s( i! z0 ^7 `- F; ~3 C9 v
                                                                                        掌握这些嵌入式 C/C++ 开发神器,迅速提升开发效率!
- x$ h' `% R2 q0 Z- k, M                                                                               
7 x1 U: P$ V' M( U                                                                        & S& R( w2 I: l3 o) ]
                                                               
4 p( Z  P. _; I" |0 ~                                                       
% c' j% u. t6 p$ A                                                ' H0 H" R( C# l' H4 T  S# l- G; ]1 m1 }
& I' R9 C  ^$ F0 P5 w: D
                                                        & |9 e/ [9 L* u0 k
                                                               
7 [$ P: K6 ?; g5 |7 f                                                                        , l  a' |, K( H# l8 ^
                                                                               
$ H/ e& ]' {- R

go1ab3lg5db640722425.jpg

go1ab3lg5db640722425.jpg

4 c: N) v, @1 U& |- x                                                                               
8 K: f+ d; j/ i. ~7 ~( ^5 f8 S( W                                                                                        用嵌入式 C 语言,设计一种垃圾内存回收机制。
2 f6 H6 U# b8 I. X$ M                                                                               
- G& _6 v% l8 F$ p, {3 p                                                                        # S! R' g$ A0 v3 Y/ S
                                                               
# D6 x  d6 p0 z; A, r7 f, {" a4 I                                                        5 C8 i3 h, C' c6 p, U; O' O) z
                                               
8 z( j% ]$ v8 O7 N: T! o, ^4 q5 U3 q8 E' m2 A! r
                                                       
( ?# k9 J  D# H  N8 F                                                               
$ A- U& b/ f) N0 `! \( U/ H& A/ W- Y                                                                       
, L( a8 }" s" t/ f/ A$ q                                                                               
6 A2 o! B- L" j3 C* Z

sqm0kevbkt4640722525.jpg

sqm0kevbkt4640722525.jpg
! H5 \5 ^; A; k$ T7 N, t; C0 q7 [
                                                                                4 ~$ O3 {; G( R& x, h7 |$ y* L
                                                                                        工程师的艺术,完美治愈强迫症!
% ]$ U5 K, e/ V' d' k/ z                                                                                8 S+ L7 J( E- |; _* e5 d. {$ Q; R
                                                                        0 q% f, g: a  ]3 t( }$ q) w
                                                                ) }0 K7 i1 p0 S& _5 g; A/ J$ {* H
                                                       
# W2 p/ }7 [% M1 V5 {                                               
; R3 ?+ p& Z  \我是老温,一名热爱学习的嵌入式工程师
6 [% k7 q% l& l5 ^- n; v0 C; ?1 w' k关注我,一起变得更加优秀!/ o! O( s0 x# g. D" J6 X* d% T9 b1 {

% U- P7 y6 ?6 e" N8 W1 _- a

dihtz2qydzp640722625.png

dihtz2qydzp640722625.png
回复

使用道具 举报

发表回复

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

本版积分规则


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