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 记住这三句话

  1. 串口配置:波特率、数据位、停止位、校验位,最常用8N1

  2. 接收方式:轮询(简单)、中断(常用)、DMA(高效)

  3. 调试技巧:printf重定向、分级日志、数据分析

6.2 实践建议

对于新手

  • 学习串口配置
  • 实践中断接收
  • 使用调试工具

对于有经验工程师

  • 建立串口驱动模板
  • 实现环形缓冲区
  • 优化调试输出

6.3 下讲预告

第31讲将深入讲解:实战:SPI屏驱动快速试错、点亮屏幕

SPI常用于驱动屏幕,如何快速试错点亮屏幕?下一讲将详细讲解。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐