04-UART串口.md 5.4 KB

04 UART 串口通信(HAL)

目标:串口发送 + 中断接收控制 LED 闪烁速度

帧格式

USART 使用异步串行通信,无时钟线,双方约定相同波特率:

空闲状态:TX 保持高电平

传输 1 字节 (115200-8-N-1):
  ┌──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┐
  │ 空闲 │START │  D0  │  D1  │  D2  │  D3  │  D4  │  D5  │  D6  │  D7  │ STOP │ 空闲
  │  (H) │  (L) │      │      │      │      │      │      │      │      │  (H) │  (H)
  └──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┘
  • 起始位:1 位,拉低(标志数据开始)
  • 数据位:8 位,LSB 先发(低字节在前)
  • 校验位:None(本例),也可选 Even/Odd
  • 停止位:1 位,拉高(标志数据结束)

TTL 电平连线

STM32 (PA9 TX) ────────→ USB-TTL (RX)
STM32 (PA10 RX) ←────── USB-TTL (TX)
STM32 (GND)     ──────── USB-TTL (GND)     ← 共地必须!

数据发送流程详解

HAL_UART_Transmit() 内部做三件事:

  1. 把数据写入发送数据寄存器 (TDR)
  2. 等待 TXE 标志(Transmit Data Register Empty,TDR 已空,可以写下一个字节)
  3. 等待 TC 标志(Transmission Complete,最后一位停止位已发完)

初学者容易混淆:TXE 表示"发送寄存器空了(可以填下一个)";TC 表示"整个字节包括停止位发完了"。HAL_UART_Transmit 内部两种标志都处理了,不需要手动轮询。

CubeMX 配置

  • USART1 → 模式 Asynchronous
  • 参数:
    • Baud Rate:115200
    • Word Length:8 Bit (including Parity) — Parity=None 时就是纯 8 位数据
    • Parity:None
    • Stop Bits:1
  • 引脚自动分配:PA9(TX)、PA10(RX)
  • NVIC Settings → USART1 global interrupt → Enable(中断接收需要)

关键 HAL API

函数 说明
HAL_UART_Transmit(&huart, pData, Size, Timeout) 阻塞发送(等待 TXE+TC)
HAL_UART_Receive(&huart, pData, Size, Timeout) 阻塞接收(等待 RXNE)
HAL_UART_Receive_IT(&huart, pData, Size) 中断接收(推荐,不阻塞)
HAL_UART_RxCpltCallback(huart) 接收完成回调(用户重写)

参数说明:

  • huart:UART 句柄(如 huart1
  • pData:数据缓冲区指针(注意类型是 uint8_t *
  • Size:期望收发字节数
  • Timeout:超时 ms(HAL_MAX_DELAY = 无限等待)

完整代码:阻塞发送

#include <string.h>

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_USART1_UART_Init();

    while (1)
    {
        char msg[] = "Hello STM32!\r\n";
        HAL_UART_Transmit(&huart1, (uint8_t *)msg, strlen(msg), HAL_MAX_DELAY);
        HAL_Delay(1000);
    }
}

完整代码:中断接收控制 LED 速度

中断接收的核心模式:先调用 HAL_UART_Receive_IT 注册接收 → 硬件收到 1 字节触发中断 → HAL 自动调用回调 → 回调里处理完数据后再调用一次 HAL_UART_Receive_IT 重新注册

/* 全局变量 */
static uint32_t blinkInterval = 1000;
static uint8_t dataRcvd;

/* 回调函数 —— 每次接收完成自动调用(由 HAL 在中断上下文调用) */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if (huart == &huart1)
    {
        // 根据接收到的字符修改闪烁间隔
        if (dataRcvd == '1')      blinkInterval = 1000;
        else if (dataRcvd == '2') blinkInterval = 300;
        else if (dataRcvd == '3') blinkInterval = 50;

        HAL_UART_Receive_IT(&huart1, &dataRcvd, 1);  // 重新开启接收(否则只收一次)
    }
}

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_USART1_UART_Init();

    /* 第一次启动中断接收 —— 注册回调 */
    HAL_UART_Receive_IT(&huart1, &dataRcvd, 1);

    while (1)
    {
        HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
        HAL_Delay(blinkInterval);
    }
}

重定向 printf 到串口

Keil 中使用 MicroLIB 时,重写 fputc 即可让 printf 输出到串口:

#include <stdio.h>

/* 在 uart.c 或 main.c 中重写 fputc */
int fputc(int ch, FILE *f)
{
    HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
    return ch;
}

使用前提:

  1. Keil Options → Target → 勾选 Use MicroLIB
  2. 包含 <stdio.h>
  3. 之后可直接写:printf("ADC=%d, V=%.2f\r\n", val, voltage);

常见坑点

  • ⚠️ HAL_UART_TransmitpData 类型是 uint8_t *,传 char * 字符串时必须强转
  • ⚠️ 中断接收每次回调后必须重新调用 HAL_UART_Receive_IT,否则只收一次就停了
  • ⚠️ 串口助手与 MCU 的波特率、数据位、校验位、停止位完全一致才不出乱码
  • ⚠️ MCU 是 3.3V TTL 电平,接 5V 的 USB-TTL 模块可能损坏引脚,确认电压匹配
  • ⚠️ HAL_UART_Receive(阻塞版)会一直等到收到指定字节数或超时,不要在中断中调用