
UART实战:串口打印调试、AT指令解析、自定义通信协议设计一、从一次“死机”说起凌晨两点,板子上的LED灯规律闪烁,串口助手却一片死寂。我盯着逻辑分析仪上的波形——TX引脚有信号,但全是0x00。拔掉杜邦线,用示波器直接量芯片引脚,波形正常。问题出在USB转串口模块上,CH340G的RTS引脚被拉低,导致模块进入了硬件流控的“待机”状态。那次之后,我养成了一个习惯:任何串口调试,第一件事就是量TX引脚的静态电平。正常空闲状态应该是高电平(3.3V或5V),如果量出来是0V,先别怀疑代码,检查硬件连接和流控引脚。二、串口打印调试的“坑”与“道”2.1 初始化顺序的玄学很多教程告诉你这样初始化串口:voiduart_init(uint32_tbaud){// 使能时钟RCC-APB2ENR|=RCC_APB2ENR_USART1EN;// 配置GPIOGPIOA-CRH=~0xFF;// 清空PA9,PA10配置GPIOA-CRH|=0x4B;// PA9推挽复用输出, PA10浮空输入// 配置波特率USART1-BRR=72000000/baud;// 使能USARTUSART1-CR1|=USART_CR1_UE|USART_CR1_TE|USART_CR1_RE;}别这样写。这里踩过坑:GPIO配置必须在USART使能之前完成,否则第一个字节会丢失。更隐蔽的问题是,PA10作为RX输入,必须配置为浮空输入或上拉输入,千万别配成推挽输出——我见过有人把RX配成推挽输出,结果自己发数据把自己接收引脚拉死了。正确的顺序应该是:voiduart_init_safe(uint32_tbaud){// 1. 先开GPIO时钟,再开USART时钟(顺序反了可能卡死)RCC-APB2ENR|=RCC_APB2ENR_IOPAEN;__NOP();__NOP();// 等待时钟稳定,别省这两条RCC-APB2ENR|=RCC_APB2ENR_USART1EN;// 2. 配置GPIO,先配TX,再配RXGPIOA-CRH=~(0xF4);// PA9: 清空GPIOA-CRH|=(0xB4);// 推挽复用输出,50MHzGPIOA-CRH=~(0xF8);// PA10: 清空GPIOA-CRH|=(0x48);// 浮空输入(外部有上拉)或0x8(内部上拉)// 3. 复位USART(防止之前配置残留)USART1-CR1=0;USART1-CR2=0;USART1-CR3=0;// 4. 配置波特率USART1-BRR=72000000/baud;// 5. 使能发送和接收,最后才使能USARTUSART1-CR1|=USART_CR1_TE|USART_CR1_RE;USART1-CR1|=USART_CR1_UE;}2.2 printf重定向的“脏活”嵌入式里用printf打印调试信息,本质是把标准库的fputc映射到串口发送函数。但标准库的printf是阻塞式的,而且会占用大量栈空间。我见过一个项目,在中断里调用printf,直接导致栈溢出复位。我的做法是:裸机调试用自定义的tiny_printf,RTOS下用带缓冲的打印任务。// 轻量级打印,不依赖标准库voiduart_send_string(constchar*str){while(*str){// 等待发送寄存器空,超时保护不能少uint32_ttimeout=100000;while(!(USART1-SRUSART_SR_TXE)){if(--timeout==0){// 这里可以加个错误标志,别死等break;}}USART1-DR=*str++;}}// 格式化打印,只支持%d %x %s,够用了voiddebug_printf(constchar*fmt,..