哈密市网站建设_网站建设公司_CSS_seo优化
2025/12/31 2:04:22 网站建设 项目流程

手把手教你用 Keil 玩转 STM32 串口通信:从零到“Hello World”的完整实战


当你的 STM32 不说话?可能是 UART 没调通

你有没有遇到过这样的场景:
代码烧进去了,开发板也上电了,但串口助手却一片漆黑——没有一个字输出。
或者更糟,屏幕上全是乱码,像外星人发来的密文。

别急,这几乎是每个嵌入式新手都会踩的坑。而问题的核心,往往就出在UART 配置上。

今天我们就来彻底解决这个问题。不讲虚的,只说干货——带你用Keil MDK + STM32F103C8T6实现稳定可靠的串口收发,做到“我说你听,你说我回”,真正打通 MCU 和 PC 之间的第一条数据通道。

整个过程将涵盖:硬件连接、工程搭建、时钟配置、GPIO映射、波特率计算、数据收发逻辑,以及常见问题排查。全程基于标准外设库(StdPeriph),让你看懂每一行代码背后的含义。

准备好了吗?我们开始。


为什么是 UART?它凭什么成为嵌入式的“普通话”

在五花八门的通信协议中,UART 虽然古老,却是最实用的一种。它不需要同步时钟线,只需要两根线(TX 和 RX)就能完成全双工通信,适合点对点、低速到中速的数据传输。

STM32 几乎每款芯片都集成了多个 USART/UART 外设。以经典的STM32F103C8T6(俗称“蓝丸子”)为例:

  • USART1:挂载在 APB2 总线,最高时钟 72MHz,支持高速通信
  • USART2:挂载在 APB1 总线,最高时钟 36MHz
  • UART3:同样在 APB1,功能略少

它们都能实现异步串行通信,支持可编程波特率(如 9600、115200)、8位或9位数据位、奇偶校验、1~2位停止位等参数。

更重要的是,所有调试日志、AT指令交互、Bootloader升级,几乎都依赖 UART。可以说,不会 UART,等于不会嵌入式开发。


先搞明白:UART 是怎么传数据的?

UART 的通信是“异步”的,意味着发送方和接收方没有共享时钟信号。那它是如何保持同步的呢?

答案是:双方提前约定好波特率

比如我们都设为 115200 bps,即每秒传送 115200 个比特。然后靠这个节奏来采样每一位数据。

一个典型的数据帧结构如下:

字段内容说明
起始位1 bit,低电平,表示帧开始
数据位8 bit(常用),低位先发
校验位(可选)1 bit,用于简单错误检测
停止位1 或 2 bit,高电平,帧结束标志

举个例子,你要发字符'A'(ASCII = 0x41 =0b01000001),实际在线上传输的顺序是:

[起始位] 1 0 0 0 0 0 1 0 [停止位] ↑ LSB ↑ MSB

注意:低位在前!

STM32 的 UART 模块通过几个关键寄存器控制这一切:

  • USART_DR:写入要发送的数据,或读取接收到的数据
  • USART_SR:查看状态,比如 TXE(发送缓冲空)、RXNE(接收非空)
  • USART_BRR:设置波特率分频系数
  • USART_CR1~CR3:启用发送/接收、中断、DMA 等功能

只要把这些寄存器配对了,UART 就能跑起来。


引脚怎么接?别让 GPIO 成了拦路虎

再好的配置,如果引脚没接对,也是白搭。

对于 STM32F103C8T6 来说:

  • USART1_TX→ PA9
  • USART1_RX→ PA10

这两个引脚必须配置为复用功能模式:

  • PA9(TX):复用推挽输出(AF_PP)
  • PA10(RX):浮空输入(IN_FLOATING)

为什么这么配?

  • TX 是输出,要驱动外部电路,所以用推挽;
  • RX 是输入,等待对方送信号进来,不用上下拉,避免干扰原始电平。

同时别忘了:开启对应外设时钟!

这是很多初学者忽略的关键一步。STM32 默认关闭所有外设时钟以省电,所以我们得手动打开:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);

APB2?因为 USART1 属于高速总线 APB2,而 GPIOA 也在同一组,必须一起开。


波特率怎么算?别让误差毁了一切

波特率不准,通信必崩。理想情况下,接收端应在每位中间采样,但如果频率偏差太大(>±3%),就会采样错位,导致乱码。

STM32 的波特率由以下公式决定:

$$
\text{BaudRate} = \frac{f_{PCLK}}{16 \times (\text{DIV_Mantissa} + \frac{\text{DIV_Fraction}}{16})}
$$

其中:
- 若是 USART1(APB2),$ f_{PCLK} = 72MHz $
- 若是 USART2/3(APB1),$ f_{PCLK} = 36MHz $

我们想要115200 bps,代入计算:

$$
\frac{72000000}{16 \times 115200} ≈ 39.0625
$$

所以整数部分DIV_Mantissa = 39,小数部分DIV_Fraction = 1(0.0625 × 16 = 1)

幸运的是,标准库已经帮我们封装好了:

USART_InitStructure.USART_BaudRate = 115200;

内部会自动计算并写入 BRR 寄存器。

但建议你在设计时优先选择能让分频结果为整数的波特率,例如 72MHz 下 115200 刚好接近整除,误差仅 0.15%,非常安全。


上手写代码:从初始化到回环测试

下面这段代码,是你实现 UART 通信的“最小可行系统”。

#include "stm32f10x.h" #define BUFFER_SIZE 64 void USART1_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; // 1. 开启时钟:GPIOA 和 USART1 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE); // 2. 配置 PA9 为复用推挽输出(TX) GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); // 3. 配置 PA10 为浮空输入(RX) GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); // 4. 配置 USART1 参数 USART_InitStructure.USART_BaudRate = 115200; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // 收发模式 USART_Init(USART1, &USART_InitStructure); // 5. 启动 USART1 USART_Cmd(USART1, ENABLE); } // 发送单字节 void USART_SendChar(USART_TypeDef* USARTx, uint8_t ch) { while (!USART_GetFlagStatus(USARTx, USART_FLAG_TXE)); // 等待发送缓冲空 USART_SendData(USARTx, ch); } // 发送字符串 void USART_SendString(USART_TypeDef* USARTx, char *str) { while (*str) { USART_SendChar(USARTx, *str++); } } int main(void) { char rxBuffer[BUFFER_SIZE]; int len = 0; USART1_Config(); USART_SendString(USART1, "STM32 UART通信已启动!\r\n"); while (1) { if (USART_GetFlagStatus(USART1, USART_FLAG_RXNE)) { // 收到数据? rxBuffer[len++] = USART_ReceiveData(USART1); // 判断是否收到换行符或缓冲满 if (len >= BUFFER_SIZE - 1 || rxBuffer[len-1] == '\n') { rxBuffer[len] = '\0'; // 添加字符串结束符 USART_SendString(USART1, "你输入的是: "); USART_SendString(USART1, rxBuffer); len = 0; // 清空缓冲区 } } } }

关键点解读:

  • RCC_APB2PeriphClockCmd():必须先开时钟,否则后面配置无效。
  • GPIO_Mode_AF_PP:复用推挽,TX 才能正常输出高/低电平。
  • USART_FLAG_RXNE:表示接收数据寄存器非空,可以读取。
  • 主循环采用轮询方式监听数据,适合入门学习;后续可升级为中断或 DMA。

运行效果:你在串口助手输入任意内容,MCU 会原样回显,并加上前缀"你输入的是: "


Keil 工程怎么建?一步步带你走通流程

现在回到 Keil uVision,教你从零创建一个可用的工程。

第一步:新建项目

  1. 打开 Keil uVision
  2. Project → New uVision Project
  3. 保存路径不要有中文
  4. 选择芯片型号:STM32F103C8

✅ 提示:Keil 会自动加载对应的启动文件(startup_stm32f10x_md.s)

第二步:添加源文件

你需要把以下文件加入项目:

  • system_stm32f10x.c:系统时钟初始化
  • startup_stm32f10x_md.s:启动汇编(Keil 自动加)
  • stm32f10x_usart.c,stm32f10x_gpio.c,stm32f10x_rcc.c:标准外设库源码
  • 你的主程序main.c

建议建立如下分组:

Project ├── User ← main.c ├── Drivers ← stm32f10x_xx.c ├── CMSIS ← core_cm3.c, startup file └── Config ← system_stm32f10x.c

第三步:配置编译选项

右键“Target” → Options for Target:

【Output】标签页
  • ✔ Generate HEX File:方便后期使用下载工具
【Debug】标签页
  • Use ST-Link Debugger:选择仿真器
  • Settings → Connect: SWD → Max Clock 设为 4MHz(稳定优先)
【C/C++】标签页
  • Define:USE_STDPERIPH_DRIVER, STM32F10X_MD
  • Include Paths:
    .\Inc .\Libraries\CMSIS .\Libraries\StdPeriph_Driver\inc
【User】标签页(可选)
  • 在 After Build/Rebuild 中勾选 “Run #1”
  • 输入:"C:\Program Files\STMicroelectronics\Software\Flash Loader Demonstrator\FlashLoader.exe" $L@H

这样编译后可自动调用 STM32 Flash Loader 工具下载程序。


硬件怎么连?一根 USB-TTL 搞定一切

你需要一块USB 转 TTL 模块(常用 CH340G 或 CP2102 芯片):

模块引脚接 STM32
GNDGND
TXDPA10 (RX)
RXDPA9 (TX)
VCC3.3V(慎接5V!)

⚠️ 注意事项:
-不要同时接 ST-Link 和 USB-TTL 的 VCC,可能导致电源冲突
- 如果使用独立供电,确保共地(GND 连在一起)
- PA9/PA10 不要接其他负载,避免影响通信电平

PC 端使用串口助手(如 XCOM、SSCOM、PuTTY)打开对应 COM 口,波特率设为 115200,8-N-1。

上电后你应该立刻看到:

STM32 UART通信已启动!

接着输入任何内容,比如hello+ 回车,会收到:

你输入的是: hello

恭喜,你已经打通了第一道通信关卡!


遇到问题怎么办?这些“坑”我都替你踩过了

别慌,下面是高频故障排查清单:

现象可能原因解决方法
完全无输出未开启 RCC 时钟检查RCC_APB2PeriphClockCmd是否包含 USART1 和 GPIOA
输出乱码波特率不一致双方确认都是 115200,且主频配置正确
下载失败ST-Link 驱动未装 / 接线松更新驱动,检查 SWCLK/SWDIO 是否接触良好
收不到数据RX 引脚配置错误确保 PA10 是IN_FLOATING,不是模拟输入或其他模式
程序跑飞堆栈溢出或中断未处理增大栈大小,添加HardFault_Handler打印异常
串口助手收不到回车缺少\r\nWindows 串口通常需要\r\n才能换行显示

还有一个隐藏陷阱:串口助手默认发送的是 ASCII 字符,但可能带 CR/LF。如果你只判断\n,记得在助手里勾选“发送新行”。


进阶思路:下一步你可以做什么?

你现在掌握的是“轮询 + 缓冲区”的基础模型。接下来可以尝试:

1. 改用中断接收

减少 CPU 占用,提升响应速度:

USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); NVIC_EnableIRQ(USART1_IRQn);

USART1_IRQHandler()中处理接收事件。

2. 重定向printf

让 C 标准库的printf直接输出到串口:

int fputc(int ch, FILE *f) { USART_SendChar(USART1, ch); return ch; }

之后就可以直接printf("ADC Value: %d\r\n", value);调试了。

3. 结合传感器做数据上报

比如读取 DHT11 温湿度,通过串口传给 PC 显示。

4. 对接 ESP8266/WiFi 模块

用 AT 指令控制模块联网,实现远程监控。


写在最后:UART 是起点,不是终点

当你第一次看到自己的 STM32 在串口助手中说出“Hello World”,那种成就感,就像第一次点亮 LED 一样纯粹。

但请记住:UART 不是用来炫技的,而是解决问题的工具

它是你调试系统的耳朵和嘴巴,是你与设备对话的语言。掌握了它,你就拿到了进入嵌入式世界的第一把钥匙。

未来你可以用 HAL 库、CubeMX 快速生成代码,也可以玩 FreeRTOS 多任务调度,甚至用 DMA 实现零拷贝通信。

但无论技术如何演进,理解底层原理的人,永远拥有更强的掌控力。


如果你正在学习 STM32,不妨现在就打开 Keil,照着这篇文章动手试一遍。
只有亲手让代码跑起来,才算真正学会。

有任何问题,欢迎留言讨论。
我们一起,把每一个“不说话”的板子,变成会思考的智能终端。

🔧热词索引:Keil、STM32、UART、USART、串口通信、波特率、GPIO、RCC、中断、DMA、Hex文件、SWD、标准外设库、嵌入式系统、调试器

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询