GD32单片机裸机ezlogger日志系统移植详解
github代码源地址:GitHub – armink/EasyLogger: An ultra-lightweight(ROM<1.6K, RAM<0.3k), high-performance C/C++ log library. | 一款超轻量级(ROM<1.6K, RAM<0.3k)、高性能的 C/C++ 日志库
一、移植文件
在User目录下新建easylogger文件夹,将demon目录下elog_cfg.h和elog_port.c,以及demon文件夹外部的easylogger内的elog_utils.c、elog.c、elog.h拷贝到自己新建的easylogger文件夹

makefile添加easylogger目录
![]()
![]()
在elog_port.c中,引用的是ST的固件库配置文件,需要改为GD的固件库文件,根据自己的芯片型号更改
#include <gd32f30x_libopt.h>
二、使用RTC获取当前时间
在例程的ezlogger系统中,获取当前时间的函数是elog_port_get_time(),此函数在裸机demon中没有实际定义,需要我们自己编写

这里我们使用RTC进行计时。在gd官方例程库中有RTC配置方法。其中需要根据硬件情况选择时钟源,GD例程为RCU_LXTAL外部低速时钟源。本代码外接8M晶振,所以使用RCU_HXTAL外部高速时钟源。
rtc_prescaler_set()函数是设置预分频值,根据实际需要设置。本程序是基于外部8M晶振128分频秒中断设置
//RTC初始化
void rtc_configuration(void)
{
//设置中断优先级
nvic_priority_group_set(NVIC_PRIGROUP_PRE1_SUB3);
nvic_irq_enable(RTC_IRQn,1,0);
/* enable PMU and BKPI clocks */
rcu_periph_clock_enable(RCU_BKPI);
rcu_periph_clock_enable(RCU_PMU);
/* allow access to BKP domain */
pmu_backup_write_enable();
/* reset backup domain */
bkp_deinit();
/* enable LXTAL */
rcu_osci_on(RCU_HXTAL);
/* wait till LXTAL is ready */
rcu_osci_stab_wait(RCU_HXTAL);
/* select RCU_LXTAL as RTC clock source */
rcu_rtc_clock_config(RCU_RTCSRC_HXTAL_DIV_128);
/* enable RTC Clock */
rcu_periph_clock_enable(RCU_RTC);
/* wait for RTC registers synchronization */
rtc_register_sync_wait();
/* wait until last write operation on RTC registers has finished */
rtc_lwoff_wait();
/* enable the RTC second interrupt*/
rtc_interrupt_enable(RTC_INT_SECOND);
/* wait until last write operation on RTC registers has finished */
rtc_lwoff_wait();
/* set RTC prescaler: set RTC period to 1s */
rtc_prescaler_set(62500 - 1);
/* wait until last write operation on RTC registers has finished */
rtc_lwoff_wait();
}
注意编写RTC中断服务函数,没有清除中断标志位程序会跑飞
//RTC中断服务函数
void RTC_IRQHandler(void)
{
if (rtc_flag_get(RTC_FLAG_SECOND) != RESET)
{
/* clear the RTC second interrupt flag*/
rtc_flag_clear(RTC_FLAG_SECOND);
/* wait until last write operation on RTC registers has finished */
rtc_lwoff_wait();
}
}
完成后可以使用串口打印进行验证
RTC主要用到两个库函数:
rtc_counter_set()设置当前时间戳
rtc_counter_get()获取当前时间戳
注意在设置RTC时间时,在操作前后需要使用函数rtc_lwoff_wait等待写操作完成
在elog_port.c文件中修改函数elog_port_get_time
const char *elog_port_get_time(void)
{
time_t timestamp = rtc_counter_get() + 28800;
struct tm *time_info;
char datetime[20]; // 存储结果,格式:YYYY-MM-DD HH:MM:SS
// 将时间戳转换为时间结构体
time_info = localtime(×tamp);
// 格式化时间到 char 数组
snprintf(datetime, 20, "%04d-%02d-%02d %02d:%02d:%02d",
time_info->tm_year + 1900, // tm_year 从 1900 开始
time_info->tm_mon + 1, // tm_mon 从 0 开始(0-11)
time_info->tm_day,
time_info->tm_hour,
time_info->tm_min,
time_info->tm_sec);
return datetime;
}
因为格里尼治时间与北京时间相差28800秒,所以在程序中做补偿
使用localtime函数进行格式转换,将时间戳转换为char型
定义时间结构体
//时间结构体
struct tm{
int tm_sec; /* seconds after the minute - [0,59] */
int tm_min; /* minutes after the hour - [0,59] */
int tm_hour; /* hours since midnight - [0,23] */
int tm_mday; /* day of the month - [1,31] */
int tm_mon; /* months since January - [0,11] */
int tm_year; /* years since 1900 */
int tm_wday; /* days since Sunday - [0,6] */
int tm_yday; /* days since January 1 - [0,365] */
int tm_isdst; /* daylight savings time flag */
};
三、完善FLASH功能
这里使用为5Q128外部FLASH
使用GD例程和正点例程。正点例程会检测写入地址是否有数据,有数据会先擦除
driver_flash.c
#include "driver_gd25qxx.h"
#include "gd32f30x.h"
#include <string.h>
#define WRITE 0x02 /* write to memory instruction */
#define WRSR 0x01 /* write status register instruction */
#define WREN 0x06 /* write enable instruction */
#define READ 0x03 /* read from memory instruction */
#define RDSR 0x05 /* read status register instruction */
#define RDID 0x9F /* read identification */
#define SE 0x20 /* sector erase instruction */
#define BE 0xC7 /* bulk erase instruction */
#define WIP_FLAG 0x01 /* write in progress(wip)flag */
#define DUMMY_BYTE 0xFF
/*!
\brief initialize SPI0 GPIO and parameter
\param[in] none
\param[out] none
\retval none
*/
void driver_spi_flash_init(void)
{
spi_parameter_struct spi_init_struct;
rcu_periph_clock_enable(RCU_GPIOB);
rcu_periph_clock_enable(RCU_GPIOD);
rcu_periph_clock_enable(RCU_SPI1);
/* SPI1_SCK(PB13), SPI1_MISO(PB14) and SPI1_MOSI(PB15) GPIO pin configuration */
gpio_init(SPI1_MOSI_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, SPI1_MOSI_PIN | SPI1_SCK_PIN);
gpio_init(SPI1_MISO_PORT,GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, SPI1_MISO_PIN);
/* CS */
gpio_init(SPI1_CS_PORT, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, SPI1_CS_PIN);
//spi_disable(SPI1);
spi_i2s_deinit(SPI1);
spi_init_struct.trans_mode = SPI_TRANSMODE_FULLDUPLEX;
spi_init_struct.device_mode = SPI_MASTER;
spi_init_struct.frame_size = SPI_FRAMESIZE_8BIT;
spi_init_struct.clock_polarity_phase = SPI_CK_PL_LOW_PH_1EDGE;
spi_init_struct.nss = SPI_NSS_SOFT;
spi_init_struct.prescale = SPI_PSC_16;
spi_init_struct.endian = SPI_ENDIAN_MSB;
spi_init(SPI1, &spi_init_struct);
/* enable SPI1 */
spi_enable(SPI1);
}
/*!
\brief erase the specified flash sector
\param[in] sector_addr: address of the sector to erase
\param[out] none
\retval none
*/
void driver_spi_flash_sector_erase(uint32_t sector_addr)
{
/* send write enable instruction */
driver_spi_flash_write_enable();
/* sector erase */
/* select the flash: chip select low */
SPI_FLASH_CS_LOW();
/* send sector erase instruction */
driver_spi_flash_send_byte(SE);
/* send sector_addr high nibble address byte */
driver_spi_flash_send_byte((sector_addr & 0xFF0000) >> 16);
/* send sector_addr medium nibble address byte */
driver_spi_flash_send_byte((sector_addr & 0xFF00) >> 8);
/* send sector_addr low nibble address byte */
driver_spi_flash_send_byte(sector_addr & 0xFF);
/* deselect the flash: chip select high */
SPI_FLASH_CS_HIGH();
/* wait the end of flash writing */
driver_spi_flash_wait_for_write_end();
}
/*!
\brief erase the entire flash
\param[in] none
\param[out] none
\retval none
*/
void driver_spi_flash_bulk_erase(void)
{
/* send write enable instruction */
driver_spi_flash_write_enable();
/* bulk erase */
/* select the flash: chip select low */
SPI_FLASH_CS_LOW();
/* send bulk erase instruction */
driver_spi_flash_send_byte(BE);
/* deselect the flash: chip select high */
SPI_FLASH_CS_HIGH();
/* wait the end of flash writing */
driver_spi_flash_wait_for_write_end();
}
/*!
\brief write more than one byte to the flash
\param[in] pbuffer: pointer to the buffer
\param[in] write_addr: flash's internal address to write
\param[in] num_byte_to_write: number of bytes to write to the flash
\param[out] none
\retval none
*/
void driver_spi_flash_page_write(uint8_t* pbuffer, uint32_t write_addr, uint16_t num_byte_to_write)
{
/* enable the write access to the flash */
driver_spi_flash_write_enable();
/* select the flash: chip select low */
SPI_FLASH_CS_LOW();
/* send "write to memory" instruction */
driver_spi_flash_send_byte(WRITE);
/* send write_addr high nibble address byte to write to */
driver_spi_flash_send_byte((write_addr & 0xFF0000) >> 16);
/* send write_addr medium nibble address byte to write to */
driver_spi_flash_send_byte((write_addr & 0xFF00) >> 8);
/* send write_addr low nibble address byte to write to */
driver_spi_flash_send_byte(write_addr & 0xFF);
/* while there is data to be written on the flash */
while(num_byte_to_write--)
{
/* send the current byte */
driver_spi_flash_send_byte(*pbuffer);
/* point on the next byte to be written */
pbuffer++;
}
/* deselect the flash: chip select high */
SPI_FLASH_CS_HIGH();
/* wait the end of flash writing */
driver_spi_flash_wait_for_write_end();
}
static void norflash_write_nocheck(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
uint16_t pageremain;
pageremain = 256 - addr % 256; /* 单页剩余的字节数 */
if (datalen <= pageremain) /* 不大于256个字节 */
{
pageremain = datalen;
}
while (1)
{
/* 当写入字节比页内剩余地址还少的时候, 一次性写完
* 当写入直接比页内剩余地址还多的时候, 先写完整个页内剩余地址, 然后根据剩余长度进行不同处理
*/
driver_spi_flash_page_write(pbuf, addr, pageremain);
if (datalen == pageremain) /* 写入结束了 */
{
break;
}
else /* datalen > pageremain */
{
pbuf += pageremain; /* pbuf指针地址偏移,前面已经写了pageremain字节 */
addr += pageremain; /* 写地址偏移,前面已经写了pageremain字节 */
datalen -= pageremain; /* 写入总长度减去已经写入了的字节数 */
if (datalen > 256) /* 剩余数据还大于一页,可以一次写一页 */
{
pageremain = 256; /* 一次可以写入256个字节 */
}
else /* 剩余数据小于一页,可以一次写完 */
{
pageremain = datalen; /* 不够256个字节了 */
}
}
}
}
uint8_t g_norflash_buf[4096]; /* 扇区缓存 */
void norflash_write(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
uint32_t secpos;
uint16_t secoff;
uint16_t secremain;
uint16_t i;
uint8_t *norflash_buf;
norflash_buf = g_norflash_buf;
secpos = addr / 4096; /* 扇区地址 */
secoff = addr % 4096; /* 在扇区内的偏移 */
secremain = 4096 - secoff; /* 扇区剩余空间大小 */
//printf("ad:%X,nb:%X\r\n", addr, datalen); /* 测试用 */
if (datalen <= secremain)
{
secremain = datalen; /* 不大于4096个字节 */
}
while (1)
{
norflash_read(norflash_buf, secpos * 4096, 4096); /* 读出整个扇区的内容 */
for (i = 0; i < secremain; i++) /* 校验数据 */
{
if (norflash_buf[secoff + i] != 0XFF)
{
break; /* 需要擦除, 直接退出for循环 */
}
}
if (i < secremain) /* 需要擦除 */
{
driver_spi_flash_sector_erase(secpos); /* 擦除这个扇区 */
for (i = 0; i < secremain; i++) /* 复制 */
{
norflash_buf[i + secoff] = pbuf[i];
}
norflash_write_nocheck(norflash_buf, secpos * 4096, 4096); /* 写入整个扇区 */
}
else /* 写已经擦除了的,直接写入扇区剩余区间. */
{
norflash_write_nocheck(pbuf, addr, secremain); /* 直接写扇区 */
}
if (datalen == secremain)
{
break; /* 写入结束了 */
}
else /* 写入未结束 */
{
secpos++; /* 扇区地址增1 */
secoff = 0; /* 偏移位置为0 */
pbuf += secremain; /* 指针偏移 */
addr += secremain; /* 写地址偏移 */
datalen -= secremain; /* 字节数递减 */
if (datalen > 4096)
{
secremain = 4096; /* 下一个扇区还是写不完 */
}
else
{
secremain = datalen;/* 下一个扇区可以写完了 */
}
}
}
}
/*!
\brief read a block of data from the flash
\param[in] pbuffer: pointer to the buffer that receives the data read from the flash
\param[in] read_addr: flash's internal address to read from
\param[in] num_byte_to_read: number of bytes to read from the flash
\param[out] none
\retval none
*/
void norflash_read(uint8_t* pbuffer, uint32_t read_addr, uint16_t num_byte_to_read)
{
/* select the flash: chip slect low */
SPI_FLASH_CS_LOW();
/* send "read from memory " instruction */
driver_spi_flash_send_byte(READ);
/* send read_addr high nibble address byte to read from */
driver_spi_flash_send_byte((read_addr & 0xFF0000) >> 16);
/* send read_addr medium nibble address byte to read from */
driver_spi_flash_send_byte((read_addr& 0xFF00) >> 8);
/* send read_addr low nibble address byte to read from */
driver_spi_flash_send_byte(read_addr & 0xFF);
/* while there is data to be read */
while(num_byte_to_read--){
/* read a byte from the flash */
*pbuffer = driver_spi_flash_send_byte(DUMMY_BYTE);
/* point to the next location where the byte read will be saved */
pbuffer++;
}
/* deselect the flash: chip select high */
SPI_FLASH_CS_HIGH();
}
/*!
\brief read flash identification
\param[in] none
\param[out] none
\retval flash identification
*/
uint32_t driver_spi_flash_read_id(void)
{
uint32_t temp = 0, temp0 = 0, temp1 = 0, temp2 = 0;
/* select the flash: chip select low */
SPI_FLASH_CS_LOW();
/* send "RDID " instruction */
driver_spi_flash_send_byte(0x90);
driver_spi_flash_send_byte(0x0);
driver_spi_flash_send_byte(0x0);
driver_spi_flash_send_byte(0x0);
temp0 = driver_spi_flash_send_byte(0xFF);
temp1 = driver_spi_flash_send_byte(0xFF);
// /* read a byte from the flash */
// temp0 = driver_spi_flash_send_byte(DUMMY_BYTE);
// /* read a byte from the flash */
// temp1 = driver_spi_flash_send_byte(DUMMY_BYTE);
// /* read a byte from the flash */
// temp2 = driver_spi_flash_send_byte(DUMMY_BYTE);
/* deselect the flash: chip select high */
SPI_FLASH_CS_HIGH();
//temp = (temp0 << 16) | (temp1 << 8) | temp2;
temp = (temp0 << 8) | temp1 ;
return temp;
}
/*!
\brief initiate a read data byte (read) sequence from the flash
\param[in] read_addr: flash's internal address to read from
\param[out] none
\retval none
*/
void driver_spi_flash_start_read_sequence(uint32_t read_addr)
{
/* select the flash: chip select low */
SPI_FLASH_CS_LOW();
/* send "read from memory " instruction */
driver_spi_flash_send_byte(READ);
/* send the 24-bit address of the address to read from */
/* send read_addr high nibble address byte */
driver_spi_flash_send_byte((read_addr & 0xFF0000) >> 16);
/* send read_addr medium nibble address byte */
driver_spi_flash_send_byte((read_addr& 0xFF00) >> 8);
/* send read_addr low nibble address byte */
driver_spi_flash_send_byte(read_addr & 0xFF);
}
/*!
\brief read a byte from the SPI flash
\param[in] none
\param[out] none
\retval byte read from the SPI flash
*/
uint8_t driver_spi_flash_read_byte(void)
{
return(driver_spi_flash_send_byte(DUMMY_BYTE));
}
/*!
\brief send a byte through the SPI interface and return the byte received from the SPI bus
\param[in] byte: byte to send
\param[out] none
\retval the value of the received byte
*/
uint8_t driver_spi_flash_send_byte(uint8_t byte)
{
/* loop while data register in not emplty */
while (RESET == spi_i2s_flag_get(SPI1,SPI_FLAG_TBE));
/* send byte through the SPI0 peripheral */
spi_i2s_data_transmit(SPI1,byte);
/* wait to receive a byte */
while(RESET == spi_i2s_flag_get(SPI1,SPI_FLAG_RBNE));
/* return the byte read from the SPI bus */
return(spi_i2s_data_receive(SPI1));
}
/*!
\brief send a half word through the SPI interface and return the half word received from the SPI bus
\param[in] half_word: half word to send
\param[out] none
\retval the value of the received byte
*/
uint16_t driver_spi_flash_send_halfword(uint16_t half_word)
{
/* loop while data register in not emplty */
while(RESET == spi_i2s_flag_get(SPI1,SPI_FLAG_TBE));
/* send half word through the SPI0 peripheral */
spi_i2s_data_transmit(SPI1,half_word);
/* wait to receive a half word */
while(RESET == spi_i2s_flag_get(SPI1,SPI_FLAG_RBNE));
/* return the half word read from the SPI bus */
return spi_i2s_data_receive(SPI1);
}
/*!
\brief enable the write access to the flash
\param[in] none
\param[out] none
\retval none
*/
void driver_spi_flash_write_enable(void)
{
/* select the flash: chip select low */
SPI_FLASH_CS_LOW();
/* send "write enable" instruction */
driver_spi_flash_send_byte(WREN);
/* deselect the flash: chip select high */
SPI_FLASH_CS_HIGH();
}
/*!
\brief poll the status of the write in progress(wip) flag in the flash's status register
\param[in] none
\param[out] none
\retval none
*/
void driver_spi_flash_wait_for_write_end(void)
{
uint8_t flash_status = 0;
/* select the flash: chip select low */
SPI_FLASH_CS_LOW();
/* send "read status register" instruction */
driver_spi_flash_send_byte(RDSR);
/* loop as long as the memory is busy with a write cycle */
do{
/* send a dummy byte to generate the clock needed by the flash
and put the value of the status register in flash_status variable */
flash_status = driver_spi_flash_send_byte(DUMMY_BYTE);
}while((flash_status & WIP_FLAG) == SET);
/* deselect the flash: chip select high */
SPI_FLASH_CS_HIGH();
}
driver_flash.h
#ifndef DRIVER_GD25QXX_H
#define DRIVER_GD25QXX_H
#include "gd32f30x.h"
#include "systick.h"
#define SPI_FLASH_PAGE_SIZE (4096)
#define SPI_FLASH_CS_LOW() gpio_bit_reset(GPIOD, GPIO_PIN_8)
#define SPI_FLASH_CS_HIGH() gpio_bit_set(GPIOD, GPIO_PIN_8)
#define SPI1_MOSI_PORT (GPIOB)
#define SPI1_MOSI_PIN (GPIO_PIN_15)
#define SPI1_MISO_PORT (GPIOB)
#define SPI1_MISO_PIN (GPIO_PIN_14)
#define SPI1_SCK_PORT (GPIOB)
#define SPI1_SCK_PIN (GPIO_PIN_13)
#define SPI1_CS_PORT (GPIOD)
#define SPI1_CS_PIN (GPIO_PIN_8)
/* initialize SPI0 GPIO and parameter */
void driver_spi_flash_init(void);
/* erase the specified flash sector */
void driver_spi_flash_sector_erase(uint32_t sector_addr);
/* erase the entire flash */
void driver_spi_flash_bulk_erase(void);
/* write more than one byte to the flash */
void norflash_read(uint8_t* pbuffer,uint32_t write_addr,uint16_t num_byte_to_write);
/* write block of data to the flash */
void norflash_write(uint8_t* pbuffer,uint32_t write_addr,uint16_t num_byte_to_write);
/* read a block of data from the flash */
void driver_spi_flash_buffer_read(uint8_t* pbuffer,uint32_t read_addr,uint16_t num_byte_to_read);
/* read flash identification */
uint32_t driver_spi_flash_read_id(void);
/* initiate a read data byte (read) sequence from the flash */
void driver_spi_flash_start_read_sequence(uint32_t read_addr);
/* read a byte from the SPI flash */
uint8_t driver_spi_flash_read_byte(void);
/* send a byte through the SPI interface and return the byte received from the SPI bus */
uint8_t driver_spi_flash_send_byte(uint8_t byte);
/* send a half word through the SPI interface and return the half word received from the SPI bus */
uint16_t driver_spi_flash_send_halfword(uint16_t half_word);
/* enable the write access to the flash */
void driver_spi_flash_write_enable(void);
/* poll the status of the write in progress (wip) flag in the flash's status register */
void driver_spi_flash_wait_for_write_end(void);
#endif /* GD25QXX_H */
同样在elog_port.c文件中函数elog_port_output需要自己编写
这里的变量ul_lendth是全局变量,有需要可以将该变量更新到单片机内置flash,这里不再赘述
//将日志写入flash
void flash_log(uint8_t *pbuffer, uint32_t ul_lendth)
{
norflash_write(pbuffer, gul_flash_addr, ul_lendth);
gul_flash_addr += ul_lendth;
}
elog_port.c中
void elog_port_output(const char *log, size_t size)
{
flash_log(log, size);
}
四、导入接口
最后需要导入ezlogger系统几个重要函数,即关于日志系统的初始化、配置、启动和使用
//初始化spiflash,rtc
void mylog_init(void)
{
elog_init();
//设置日志格式
elog_set_fmt(ELOG_LVL_ASSERT, ELOG_FMT_ALL);
elog_set_fmt(ELOG_LVL_ERROR, ELOG_FMT_LVL | ELOG_FMT_TAG | ELOG_FMT_TIME);
elog_set_fmt(ELOG_LVL_WARN, ELOG_FMT_LVL | ELOG_FMT_TAG | ELOG_FMT_TIME);
elog_set_fmt(ELOG_LVL_INFO, ELOG_FMT_LVL | ELOG_FMT_TAG | ELOG_FMT_TIME);
elog_set_fmt(ELOG_LVL_DEBUG, ELOG_FMT_ALL & ~ELOG_FMT_FUNC);
elog_set_fmt(ELOG_LVL_VERBOSE, ELOG_FMT_ALL & ~ELOG_FMT_FUNC);
elog_start();
uint32_t RTCSRC_FLAG = 0;
//flash初始化
driver_spi_flash_init();
//RTC初始化
rtc_configuration();
//ezlogger日志系统初始化
elog_start();
}
1.elog_init:elog初始化函数
2.elog_set_fmt:日志设置函数,第一个参数是日志等级,然后是日志需要显示哪些信息
3.flash和RTC初始化函数
4.elog_start:启动elog函数
elog_output(ELOG_LVL_VERBOSE, "test", __FILE__, __func__, __LINE__, "mian running");
函数elog_output就是我们要用的写日志的函数,他的参数分别是:
日志等级-标签-文件名-函数名-行数-日志内容
实际运行见下图
![]()
ezlogger一些设置项:
1.设置可打印等级:
在接口函数elog_init中有一条函数elog_set_filter_lvl(ELOG_LVL_VERBOSE);
默认函数的效果是设置ELOG_LVL_VERBOSE等级及更高等级的日志可被打印
2.设置日志输出格式:
函数elog_set_fmt可以设置指定等级的日志输出何种格式,如输出tag、func、line、time等,第一个参数是对应的日志等级,第二个参数是输出内容。
在默认设置中,ELOG_LVL_ASSERT等级的日志会打印全部信息,因为裸机系统没有线程和进程的信息,所以在这去掉,修改代码如下
/* macro definition for all formats */
#define ELOG_FMT_ALL (ELOG_FMT_LVL|ELOG_FMT_TAG|ELOG_FMT_TIME| \
ELOG_FMT_DIR|ELOG_FMT_FUNC|ELOG_FMT_LINE)
作者:姜饼人GO