欢迎光临
我们一直在努力

011、UART实战:串口打印调试、AT指令解析、自定义通信协议设计

UART实战:串口打印调试、AT指令解析、自定义通信协议设计

一、从一次“死机”说起

凌晨两点,板子上的LED灯规律闪烁,串口助手却一片死寂。我盯着逻辑分析仪上的波形——TX引脚有信号,但全是0x00。拔掉杜邦线,用示波器直接量芯片引脚,波形正常。问题出在USB转串口模块上,CH340G的RTS引脚被拉低,导致模块进入了硬件流控的“待机”状态。

那次之后,我养成了一个习惯:任何串口调试,第一件事就是量TX引脚的静态电平。正常空闲状态应该是高电平(3.3V或5V),如果量出来是0V,先别怀疑代码,检查硬件连接和流控引脚。

二、串口打印调试的“坑”与“道”

2.1 初始化顺序的玄学

很多教程告诉你这样初始化串口:

void uart_init(uint32_t baud) {


// 使能时钟
RCC->APB2ENR |= RCC_APB2ENR_USART1EN;
// 配置GPIO
GPIOA->CRH &= ~0xFF; // 清空PA9,PA10配置
GPIOA->CRH |= 0x4B; // PA9推挽复用输出, PA10浮空输入
// 配置波特率
USART1->BRR = 72000000 / baud;
// 使能USART
USART1->CR1 |= USART_CR1_UE | USART_CR1_TE | USART_CR1_RE;
}

别这样写。 这里踩过坑:GPIO配置必须在USART使能之前完成,否则第一个字节会丢失。更隐蔽的问题是,PA10作为RX输入,必须配置为浮空输入或上拉输入,千万别配成推挽输出——我见过有人把RX配成推挽输出,结果自己发数据把自己接收引脚拉死了。

正确的顺序应该是:

void uart_init_safe(uint32_t baud) {


// 1. 先开GPIO时钟,再开USART时钟(顺序反了可能卡死)
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
__NOP(); __NOP(); // 等待时钟稳定,别省这两条
RCC->APB2ENR |= RCC_APB2ENR_USART1EN;

// 2. 配置GPIO,先配TX,再配RX
GPIOA->CRH &= ~(0xF << 4); // PA9: 清空
GPIOA->CRH |= (0xB << 4); // 推挽复用输出,50MHz
GPIOA->CRH &= ~(0xF << 8); // PA10: 清空
GPIOA->CRH |= (0x4 << 8); // 浮空输入(外部有上拉)或0x8(内部上拉)

// 3. 复位USART(防止之前配置残留)
USART1->CR1 = 0;
USART1->CR2 = 0;
USART1->CR3 = 0;

// 4. 配置波特率
USART1->BRR = 72000000 / baud;

// 5. 使能发送和接收,最后才使能USART
USART1->CR1 |= USART_CR1_TE | USART_CR1_RE;
USART1->CR1 |= USART_CR1_UE;
}

2.2 printf重定向的“脏活”

嵌入式里用printf打印调试信息,本质是把标准库的fputc映射到串口发送函数。但标准库的printf是阻塞式的,而且会占用大量栈空间。我见过一个项目,在中断里调用printf,直接导致栈溢出复位。

我的做法是:裸机调试用自定义的tiny_printf,RTOS下用带缓冲的打印任务。

// 轻量级打印,不依赖标准库
void uart_send_string(const char *str) {


while (*str) {


// 等待发送寄存器空,超时保护不能少
uint32_t timeout = 100000;
while (!(USART1->SR & USART_SR_TXE)) {


if (timeout == 0) {


// 这里可以加个错误标志,别死等
break;
}
}
USART1->DR = *str++;
}
}

// 格式化打印,只支持%d %x %s,够用了
void debug_printf(const char *fmt, ..

赞(0)
未经允许不得转载:171主机测评 » 011、UART实战:串口打印调试、AT指令解析、自定义通信协议设计
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址