|

我是老温,一名热爱学习的嵌入式工程师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
& 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
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 *)§or_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), §or_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
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
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
! 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
|
|