第30讲:实战——USART串口收发原型快速搭建+调试
·
CSDN专栏:
第30讲:实战——USART串口收发原型快速搭建+调试
一、USART基础
USART(通用同步/异步收发器)是嵌入式最常用的通信接口,用于与PC、其他MCU、模块通信。
1.1 USART配置要点
基本参数:
- 波特率:9600、19200、38400、57600、115200等
- 数据位:8位(最常用)
- 停止位:1位(最常用)
- 校验位:无(最常用)
- 流控:无(最常用)
引脚配置:
USART1:PA9(TX)、PA10(RX)
USART2:PA2(TX)、PA3(RX)
USART3:PB10(TX)、PB11(RX)
1.2 通信模式
轮询模式:
特点:阻塞等待
适用:简单应用、调试
中断模式:
特点:非阻塞、事件驱动
适用:实际应用
DMA模式:
特点:高效、CPU占用少
适用:高速、大数据量
二、串口发送实战
2.1 基础发送
需求:
串口发送"Hello World"
Prompt:
生成STM32F103 USART发送代码:
- USART1:PA9(TX)、PA10(RX)
- 波特率:115200
- 功能:发送"Hello World"
- 使用HAL库
AI生成:
#include "stm32f1xx_hal.h"
UART_HandleTypeDef huart1;
void USART1_Init(void) {
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
HAL_UART_Init(&huart1);
}
int main(void) {
HAL_Init();
SystemClock_Config();
USART1_Init();
char *msg = "Hello World\r\n";
HAL_UART_Transmit(&huart1, (uint8_t*)msg, strlen(msg), 100);
while(1);
}
2.2 printf重定向
需求:
使用printf输出到串口
代码:
// 重定向fputc
int fputc(int ch, FILE *f) {
HAL_UART_Transmit(&huart1, (uint8_t*)&ch, 1, 100);
return ch;
}
// 使用
int main(void) {
HAL_Init();
SystemClock_Config();
USART1_Init();
printf("System Start\r\n");
printf("Version: 1.0\r\n");
while(1) {
printf("Tick: %d\r\n", HAL_GetTick());
HAL_Delay(1000);
}
}
Keil配置:
Options → Target → Use MicroLIB(勾选)
2.3 格式化输出
需求:
输出各种格式的数据
代码:
void Print_Data(void) {
uint8_t data = 0x55;
uint16_t value = 1234;
float temperature = 25.6f;
// 十进制
printf("Value: %d\r\n", value);
// 十六进制
printf("Hex: 0x%02X\r\n", data);
// 浮点数
printf("Temperature: %.2f\r\n", temperature);
// 二进制
printf("Binary: ");
for(int i = 7; i >= 0; i--) {
printf("%d", (data >> i) & 1);
}
printf("\r\n");
}
三、串口接收实战
3.1 轮询接收
需求:
接收数据并回显
代码:
int main(void) {
HAL_Init();
SystemClock_Config();
USART1_Init();
uint8_t rxData;
while(1) {
// 接收数据
if(HAL_UART_Receive(&huart1, &rxData, 1, 100) == HAL_OK) {
// 回显
HAL_UART_Transmit(&huart1, &rxData, 1, 100);
}
}
}
3.2 中断接收
需求:
使用中断接收数据
Prompt:
生成STM32F103 USART中断接收代码:
- USART1:PA9(TX)、PA10(RX)
- 波特率:115200
- 功能:中断接收并回显
- 使用HAL库
AI生成:
uint8_t rxData;
void USART1_Init(void) {
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
HAL_UART_Init(&huart1);
// 启动接收中断
HAL_UART_Receive_IT(&huart1, &rxData, 1);
}
void USART1_IRQHandler(void) {
HAL_UART_IRQHandler(&huart1);
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if(huart->Instance == USART1) {
// 回显
HAL_UART_Transmit(&huart1, &rxData, 1, 100);
// 继续接收
HAL_UART_Receive_IT(&huart1, &rxData, 1);
}
}
int main(void) {
HAL_Init();
SystemClock_Config();
USART1_Init();
while(1) {
// 主循环可以做其他事情
}
}
3.3 环形缓冲区接收
需求:
使用环形缓冲区接收数据,避免数据丢失
代码:
#define BUFFER_SIZE 256
typedef struct {
uint8_t buffer[BUFFER_SIZE];
uint16_t head;
uint16_t tail;
} RingBuffer_t;
RingBuffer_t rxBuffer = {0};
void RingBuffer_Write(uint8_t data) {
uint16_t nextHead = (rxBuffer.head + 1) % BUFFER_SIZE;
if(nextHead != rxBuffer.tail) { // 缓冲区未满
rxBuffer.buffer[rxBuffer.head] = data;
rxBuffer.head = nextHead;
}
}
uint8_t RingBuffer_Read(uint8_t *data) {
if(rxBuffer.head == rxBuffer.tail) { // 缓冲区空
return 0;
}
*data = rxBuffer.buffer[rxBuffer.tail];
rxBuffer.tail = (rxBuffer.tail + 1) % BUFFER_SIZE;
return 1;
}
uint16_t RingBuffer_Available(void) {
return (rxBuffer.head - rxBuffer.tail + BUFFER_SIZE) % BUFFER_SIZE;
}
// 中断回调
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if(huart->Instance == USART1) {
RingBuffer_Write(rxData);
HAL_UART_Receive_IT(&huart1, &rxData, 1);
}
}
// 主循环处理
void Process_UartData(void) {
uint8_t data;
while(RingBuffer_Read(&data)) {
// 处理数据
printf("Received: 0x%02X\r\n", data);
}
}
四、串口调试技巧
4.1 串口工具
常用工具:
- 串口调试助手:SSCOM、XCOM、串口精灵
- 终端工具:PuTTY、Tera Term、SecureCRT
- 分析工具:逻辑分析仪、协议分析仪
配置:
波特率:115200
数据位:8
停止位:1
校验位:无
流控:无
4.2 调试输出
分级日志:
#define LOG_LEVEL 3 // 0:无 1:错误 2:警告 3:信息 4:调试
#define LOG_ERROR(fmt, ...) if(LOG_LEVEL >= 1) printf("[ERROR] " fmt "\r\n", ##__VA_ARGS__)
#define LOG_WARN(fmt, ...) if(LOG_LEVEL >= 2) printf("[WARN] " fmt "\r\n", ##__VA_ARGS__)
#define LOG_INFO(fmt, ...) if(LOG_LEVEL >= 3) printf("[INFO] " fmt "\r\n", ##__VA_ARGS__)
#define LOG_DEBUG(fmt, ...) if(LOG_LEVEL >= 4) printf("[DEBUG] " fmt "\r\n", ##__VA_ARGS__)
// 使用
LOG_ERROR("UART initialization failed");
LOG_WARN("Buffer nearly full: %d", usage);
LOG_INFO("System started");
LOG_DEBUG("Received byte: 0x%02X", data);
4.3 数据分析
十六进制输出:
void Print_Hex(uint8_t *data, uint16_t len) {
printf("Hex: ");
for(uint16_t i = 0; i < len; i++) {
printf("%02X ", data[i]);
}
printf("\r\n");
}
ASCII输出:
void Print_ASCII(uint8_t *data, uint16_t len) {
printf("ASCII: ");
for(uint16_t i = 0; i < len; i++) {
if(data[i] >= 32 && data[i] <= 126) { // 可打印字符
printf("%c", data[i]);
} else {
printf(".");
}
}
printf("\r\n");
}
混合输出:
void Print_Data_Mixed(uint8_t *data, uint16_t len) {
printf("Data[%d]:\r\n", len);
for(uint16_t i = 0; i < len; i++) {
printf("%02X ", data[i]);
if((i + 1) % 16 == 0) { // 每16字节换行
printf(" ");
for(uint16_t j = i - 15; j <= i; j++) {
if(data[j] >= 32 && data[j] <= 126) {
printf("%c", data[j]);
} else {
printf(".");
}
}
printf("\r\n");
}
}
// 处理剩余字节
if(len % 16 != 0) {
for(uint16_t i = len % 16; i < 16; i++) {
printf(" ");
}
printf(" ");
for(uint16_t j = len - (len % 16); j < len; j++) {
if(data[j] >= 32 && data[j] <= 126) {
printf("%c", data[j]);
} else {
printf(".");
}
}
printf("\r\n");
}
}
五、常见问题与解决
5.1 乱码问题
现象:串口输出乱码
原因:
1. 波特率不匹配
2. 系统时钟配置错误
3. 数据格式不匹配
解决:
// 验证波特率
void Verify_Baudrate(void) {
printf("SystemCoreClock: %d\r\n", SystemCoreClock);
printf("UART BRR: 0x%04X\r\n", USART1->BRR);
uint32_t actualBaud = SystemCoreClock / (USART1->BRR & 0xFFFF);
printf("Actual baudrate: %d\r\n", actualBaud);
}
5.2 数据丢失
现象:接收数据丢失
原因:
1. 接收缓冲区溢出
2. 处理速度慢
3. 中断被阻塞
解决:
// 使用环形缓冲区
// 提高处理速度
// 优化中断优先级
5.3 发送卡死
现象:发送函数卡死
原因:
1. 超时时间过短
2. UART未初始化
3. 引脚配置错误
解决:
// 增加超时时间
HAL_UART_Transmit(&huart1, data, len, 1000); // 1秒超时
// 检查初始化
if(huart1.Instance != USART1) {
printf("UART not initialized\r\n");
}
六、本讲核心要点
6.1 记住这三句话
-
串口配置:波特率、数据位、停止位、校验位,最常用8N1
-
接收方式:轮询(简单)、中断(常用)、DMA(高效)
-
调试技巧:printf重定向、分级日志、数据分析
6.2 实践建议
对于新手:
- 学习串口配置
- 实践中断接收
- 使用调试工具
对于有经验工程师:
- 建立串口驱动模板
- 实现环形缓冲区
- 优化调试输出
6.3 下讲预告
第31讲将深入讲解:实战:SPI屏驱动快速试错、点亮屏幕
SPI常用于驱动屏幕,如何快速试错点亮屏幕?下一讲将详细讲解。
更多推荐

所有评论(0)