3. 添加启动日志输出功能
任务目标
在Bootloader启动流程中初始化UART外设,通过串口输出带时间戳和日志级别的启动信息,用于调试与状态监控。本案例基于STM32F407平台,实现最小可复用的日志模块,不依赖RTOS。
硬件依赖
- USART2:PA2(TX)、PA3(RX),波特率115200,8N1
- 时钟:APB1时钟42MHz(系统时钟168MHz时)
实现代码
// boot_log.h
#ifndef BOOT_LOG_H
#define BOOT_LOG_H
#include <stdint.h>
#define LOG_LEVEL_DEBUG 0
#define LOG_LEVEL_INFO 1
#define LOG_LEVEL_WARN 2
#define LOG_LEVEL_ERROR 3
#define LOG_LEVEL LOG_LEVEL_DEBUG
#define LOG_DEBUG(fmt, ...) \
log_output(LOG_LEVEL_DEBUG, "DBG", fmt, ##__VA_ARGS__)
#define LOG_INFO(fmt, ...) \
log_output(LOG_LEVEL_INFO, "INF", fmt, ##__VA_ARGS__)
#define LOG_WARN(fmt, ...) \
log_output(LOG_LEVEL_WARN, "WRN", fmt, ##__VA_ARGS__)
#define LOG_ERROR(fmt, ...) \
log_output(LOG_LEVEL_ERROR, "ERR", fmt, ##__VA_ARGS__)
void log_init(void);
void log_output(uint8_t level, const char *tag, const char *fmt, ...);
#endif // BOOT_LOG_H
// boot_log.c
#include "boot_log.h"
#include <stdarg.h>
#include <stdio.h>
#include "stm32f4xx.h"
static uint32_t tick_ms;
static void uart2_init(void) {
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
RCC->APB1ENR |= RCC_APB1ENR_USART2EN;
GPIOA->AFR[0] |= (7 << 4) | (7 << 8); // PA2=AF7, PA3=AF7
GPIOA->MODER |= (2 << 4) | (2 << 6); // PA2=AF, PA3=AF
USART2->BRR = 42000000 / 115200; // 42MHz / 115200
USART2->CR1 = USART_CR1_TE | USART_CR1_RE | USART_CR1_UE;
}
static void uart2_putc(char c) {
while (!(USART2->SR & USART_SR_TXE));
USART2->DR = c;
}
static void uart2_puts(const char *s) {
while (*s) uart2_putc(*s++);
}
static void put_hex(uint32_t val) {
char buf[11];
buf[0] = '0'; buf[1] = 'x';
for (int i = 0; i < 8; i++) {
uint8_t nib = (val >> (28 - i * 4)) & 0xF;
buf[2 + i] = nib < 10 ? '0' + nib : 'A' + nib - 10;
}
buf[10] = '\0';
uart2_puts(buf);
}
static void put_dec(uint32_t val) {
char buf[12];
int i = 11;
buf[i--] = '\0';
if (val == 0) buf[i--] = '0';
while (val > 0 && i >= 0) {
buf[i--] = '0' + (val % 10);
val /= 10;
}
uart2_puts(&buf[i + 1]);
}
void log_init(void) {
uart2_init();
tick_ms = 0;
SysTick->LOAD = 168000 - 1; // 1ms @168MHz
SysTick->VAL = 0;
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_ENABLE_Msk;
}
void log_output(uint8_t level, const char *tag, const char *fmt, ...) {
if (level < LOG_LEVEL) return;
// 时间戳 [ms]
uart2_putc('[');
put_dec(tick_ms);
uart2_puts("ms][");
uart2_puts(tag);
uart2_puts("] ");
char buf[128];
va_list args;
va_start(args, fmt);
vsnprintf(buf, sizeof(buf), fmt, args);
va_end(args);
uart2_puts(buf);
uart2_putc('\r');
uart2_putc('\n');
}
// 在SysTick_Handler中调用
void SysTick_Handler(void) {
tick_ms++;
}
// main.c 调用示例
#include "boot_log.h"
int main(void) {
log_init();
LOG_INFO("Bootloader started");
LOG_DEBUG("Reset cause: 0x%08X", RCC->CSR & 0x1F);
LOG_WARN("Flash size: %d KB", *(uint16_t *)0x1FFF7A22);
LOG_ERROR("Invalid app magic");
while (1);
}
编译与运行
- 将三个代码块分别保存为
boot_log.h、boot_log.c,并加入工程编译 - 在
stm32f4xx_it.c中确认SysTick_Handler已调用tick_ms++(或将上述Handler实现加入工程) - 使用USB转TTL连接PA2(TX)到RX,GND共地
- 编译烧录后打开串口助手,波特率115200
验证方法
复位开发板后,串口助手应收到以下格式输出(数值因芯片实际值而异):
[0ms][INF] Bootloader started
[0ms][DBG] Reset cause: 0x00000000
[0ms][WRN] Flash size: 1024 KB
[0ms][ERR] Invalid app magic
判定通过条件:
- 四条日志均按级别正确输出,时间戳随SysTick递增(若在日志间插入延时,时间戳应相应增加)
- 十六进制与十进制数值格式正确,无乱码
- 改变
LOG_LEVEL为LOG_LEVEL_WARN后重新编译,DEBUG和INFO日志不再输出
常见问题
- 无输出:检查PA2/PA3复用配置是否正确,确认USART2时钟源为APB1(42MHz),若时钟不同需重新计算BRR
- 乱码:波特率不匹配或BRR计算错误,核对系统时钟频率
- 时间戳不更新:确认SysTick_Handler被正确调用,且中断优先级未屏蔽