news 2026/4/22 10:35:54

STM32串口通信在Keil中的实现:完整示例

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32串口通信在Keil中的实现:完整示例

手把手教你用Keil点亮STM32串口通信:从零开始的实战指南

你有没有遇到过这样的场景?代码烧进STM32后,板子“安静如鸡”,既不报错也不输出,只能靠猜哪里出了问题。这时候,如果能通过串口打印一句Hello, I'm alive!,那简直是黑暗中的一束光。

别急——这篇文章就是来帮你解决这个痛点的。我们将完全基于Keil MDK环境,带你一步步实现STM32的串口通信功能,不仅让你看到数据“飞”起来,还要让你搞懂每一步背后的逻辑。无论你是刚入门的新手,还是想巩固基础的老兵,这篇内容都能直接上手复用。


为什么是USART?它真的比“软件模拟”强那么多吗?

在嵌入式世界里,串口(UART/USART)几乎是每个项目都会用到的功能。它可以用来:

  • 输出调试信息
  • 接收用户命令
  • 与GPS、蓝牙、Wi-Fi模块通信
  • 实现设备间简单协议交互

而STM32自带的硬件USART外设,远非GPIO翻转可比。很多人初学时喜欢用“延时+IO翻转”的方式模拟串口(也就是常说的Bit-Banging),但这种方式存在致命缺陷:

问题具体表现
波特率不准稍有中断干扰就丢帧
占用CPU高发送一个字节就得卡住几毫秒
不支持并发无法同时处理其他任务

相比之下,硬件USART配合DMA或中断机制,能做到近乎“零开销”地完成数据收发。更关键的是:精度高、稳定性好、资源利用率低。

所以,如果你想做真正可靠的产品级开发,必须掌握硬件串口的使用方法。


开发工具选型:为什么我坚持推荐Keil?

市面上STM32的开发工具有很多:STM32CubeIDE、IAR、VSCode + PlatformIO……但如果你追求稳定、高效、调试体验流畅,我还是会首选Keil MDK(μVision)

Keil到底强在哪?

  • ✅ 编译器优化极佳,生成代码紧凑
  • ✅ 调试响应快,尤其在复杂中断场景下不卡顿
  • ✅ 支持ITM/SWO输出,也能轻松重定向printf
  • ✅ 社区资源丰富,90%以上的例程都提供Keil工程
  • ✅ 与ST官方工具链兼容性最好(比如.ST-LINK驱动)

虽然它是收费软件,但免费版(Limited Edition)已经足够用于学习和中小型项目开发。而且对于熟悉ARM架构的人来说,Keil的操作逻辑非常直观。

小贴士:如果你打算长期从事嵌入式开发,建议尽早熟悉Keil。它不仅是工程师的“老朋友”,更是面试常客。


STM32串口核心配置:PA9和PA10怎么用?

我们以最常见的STM32F103C8T6(蓝丸板)为例,使用USART1实现串口通信。

第一步:确定引脚映射

查阅数据手册你会发现:

  • USART1_TX → PA9
  • USART1_RX → PA10

这两个引脚属于GPIOA端口,在默认复用模式下即可作为串口使用。不需要额外配置AFIO重映射。

第二步:时钟使能不能忘!

STM32所有外设工作前都必须开启对应时钟。顺序如下:

  1. 使能GPIOA时钟
  2. 使能USART1时钟

这一步看似简单,却是新手最容易忽略的地方——忘了开时钟,再正确的代码也跑不起来。


HAL库实战:三步搞定串口初始化

ST官方提供的HAL库大大简化了外设配置流程。我们要做的,就是填好一张“配置表”。

核心结构体:UART_HandleTypeDef

这是HAL库中控制串口的核心句柄,包含了所有参数设置:

UART_HandleTypeDef huart1;

我们需要为它赋值以下关键字段:

参数含义
InstanceUSART1使用哪个USART实例
BaudRate115200波特率设为115200bps
WordLengthUART_WORDLENGTH_8B数据位8位
StopBitsUART_STOPBITS_1停止位1位
ParityUART_PARITY_NONE无校验
ModeUART_MODE_TX_RX支持发送和接收
HwFlowCtlUART_HWCONTROL_NONE不启用硬件流控
OverSamplingUART_OVERSAMPLING_16采样倍率16倍

把这些写进初始化函数:

static void MX_USART1_UART_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; if (HAL_UART_Init(&huart1) != HAL_OK) { Error_Handler(); } }

就这么几行代码,就把串口的基本通信能力打开了。

⚠️ 注意:Error_Handler()是你自己定义的错误处理函数,建议点亮LED或死循环,方便定位失败原因。


printf在单片机上跑起来:重定向的艺术

你在PC上习惯了printf("value = %d\n", x);,但在STM32上,默认是没有输出目的地的。怎么办?

答案是:printf重定向到串口!

如何实现?

只需要实现一个标准C库函数:__io_putchar

int __io_putchar(int ch) { HAL_UART_Transmit(&huart1, (uint8_t*)&ch, 1, HAL_MAX_DELAY); return ch; }

这样,每次调用printf,系统就会自动调用这个函数,把字符一个个发出去。

别忘了勾选MicroLIB!

在Keil中,打开Project → Options for Target → Target页面,勾选“Use MicroLIB”

Why?因为标准C库太大,不适合嵌入式系统。MicroLIB是一个轻量级替代版本,专为微控制器设计,且支持printf重定向。


主程序长什么样?来个完整例子

现在我们把所有部分拼起来,看看最终的main.c是什么样子:

#include "main.h" #include <stdio.h> #include <string.h> UART_HandleTypeDef huart1; void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_USART1_UART_Init(void); int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART1_UART_Init(); char msg[64]; int count = 0; while (1) { sprintf(msg, "Hello from STM32! Count: %d\r\n", count++); printf("%s", msg); HAL_Delay(1000); } }

编译、下载、重启——打开你的串口助手(XCOM、SSCOM、PuTTY都可以),设置波特率为115200,你将看到:

Hello from STM32! Count: 0 Hello from STM32! Count: 1 Hello from STM32! Count: 2 ...

每一秒刷新一次,稳得一批。


常见坑点与避坑秘籍

别高兴太早,实际调试中还有很多“隐藏陷阱”。以下是几个高频问题及解决方案:

❌ 问题1:串口助手收不到任何数据

排查方向:
- 检查TX/RX是否接反(TX→RX,RX←TX)
- 查看电平是否匹配(TTL vs RS232)
- 确认波特率一致(两边都要设成115200)
- 检查供电是否正常(尤其是CH340G等转换芯片)

❌ 问题2:收到乱码

最大可能原因:时钟配置错误!

STM32的波特率由APB总线时钟分频而来。若系统时钟没配对(例如该用HSE却用了HSI),会导致实际波特率偏差过大。

经验法则:波特率误差应小于3%。可通过STM32CubeMX辅助计算理想DIV值。

❌ 问题3:printf不输出

除了检查MicroLIB,还要确认:
-__io_putchar函数已正确定义
- 没有被优化掉(可加__attribute__((used))防止删除)
-HAL_UART_Transmit未进入阻塞状态(检查中断是否抢占)

✅ 高阶技巧:使用中断接收命令

目前我们只是单向发送。如果想让MCU接收PC指令怎么办?

可以用中断方式监听接收完成事件:

uint8_t rx_data; void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart->Instance == USART1) { // 回显收到的字符 HAL_UART_Transmit(&huart1, &rx_data, 1, 100); // 重新启动中断接收(单字节模式) HAL_UART_Receive_IT(&huart1, &rx_data, 1); } }

main()中调用一次:

HAL_UART_Receive_IT(&huart1, &rx_data, 1); // 启动中断接收

从此以后,每收到一个字节就会触发回调函数,实现真正的双向通信。


工程实践建议:不只是“能用”

当你把串口跑通之后,下一步该怎么提升?

🔧 加入环形缓冲区(Ring Buffer)

中断接收单字节效率低,容易丢失数据。更好的做法是结合DMA + 环形缓冲区,实现高速、大批量接收。

#define RX_BUFFER_SIZE 128 uint8_t dma_rx_buffer[RX_BUFFER_SIZE]; uint8_t uart_rx_temp; // 用于空闲中断触发 // 启动DMA接收 HAL_UART_Receive_DMA(&huart1, dma_rx_buffer, RX_BUFFER_SIZE); // 开启空闲线检测(IDLE Line Detection) __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);

利用空闲中断判断一帧数据结束,再交给主循环解析,是最高效的串口协议处理方案之一。

📦 构建简易日志系统

有了串口输出,就可以打造自己的嵌入式日志框架:

#define LOG_INFO(fmt, ...) printf("[INFO] " fmt "\r\n", ##__VA_ARGS__) #define LOG_WARN(fmt, ...) printf("[WARN] " fmt "\r\n", ##__VA_ARGS__) #define LOG_ERR(fmt, ...) printf("[ERROR] " fmt "\r\n", ##__VA_ARGS__) // 使用示例 LOG_INFO("System started, version %s", "v1.0"); LOG_WARN("Battery level low: %d%%", 15);

不仅能提高调试效率,还能为后期OTA升级、远程诊断打下基础。


写在最后:串口不止于“打印”

也许你会觉得:“串口不就是用来打log的吗?”但其实它的潜力远不止于此。

  • 它可以是Modbus协议的物理层
  • 可承载AT指令集控制4G模块
  • 能连接指纹识别、RFID读卡器
  • 甚至可以通过自定义协议实现多机协同

掌握了STM32 + Keil下的串口通信,你就拿到了通往更复杂系统的钥匙。

下次当你面对一块新板子时,不要再靠“灯闪几次”来猜状态了。拿起串口线,让MCU亲口告诉你发生了什么。

如果你也曾被串口折磨得夜不能寐,欢迎在评论区分享你的“踩坑史”。我们一起把这条路走得更稳、更快。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/18 10:50:18

Qwen3Guard-Gen-WEB数据标注:构建高质量训练集的方法论

Qwen3Guard-Gen-WEB数据标注&#xff1a;构建高质量训练集的方法论 1. 引言&#xff1a;安全审核模型的演进与挑战 随着大语言模型&#xff08;LLM&#xff09;在各类应用场景中的广泛部署&#xff0c;内容安全问题日益凸显。不当、有害或违规内容的生成不仅影响用户体验&…

作者头像 李华
网站建设 2026/4/18 23:45:41

保护隐私的语音合成|Supertonic完全本地化推理详解

保护隐私的语音合成&#xff5c;Supertonic完全本地化推理详解 1. 引言&#xff1a;为什么需要设备端TTS&#xff1f; 随着人工智能在语音交互场景中的广泛应用&#xff0c;文本转语音&#xff08;Text-to-Speech, TTS&#xff09;技术已成为智能助手、无障碍阅读、有声内容生…

作者头像 李华
网站建设 2026/4/20 11:50:30

图解说明STM32在嘉立创EDA中的差分布线技巧

图解说明STM32在嘉立创EDA中的差分布线技巧从一个USB通信失败说起你有没有遇到过这样的情况&#xff1a;STM32程序烧录正常&#xff0c;外设初始化无误&#xff0c;但USB就是枚举失败&#xff1f;或者以太网偶尔丢包&#xff0c;调试数小时却找不到原因&#xff1f;别急——问题…

作者头像 李华
网站建设 2026/4/18 8:52:42

探索OMRON NJ501 - 1520真实项目程序:编程思维与能力的飞升之旅

OMRON NJ501-1520真实项目程序 欧姆龙NJ做的程序27轴110个气缸1个机械手。 里面的功能块可以会给你种全新的思维。 如果你想扩展编程思维以及提升编程能力&#xff01; 里面写的气缸块 伺服块 转盘块 三轴码垛拆垛位置计算以及触摸屏上伺服和气缸分别用一个页面显示操作。 而…

作者头像 李华
网站建设 2026/4/19 10:37:50

Llama3与cv_unet对比:大模型与视觉模型部署差异实战分析

Llama3与cv_unet对比&#xff1a;大模型与视觉模型部署差异实战分析 1. 引言&#xff1a;大模型与视觉模型的部署场景差异 随着人工智能技术的快速发展&#xff0c;大规模语言模型&#xff08;LLM&#xff09;和计算机视觉模型在实际业务中得到了广泛应用。Llama3作为当前主流…

作者头像 李华
网站建设 2026/4/18 7:27:01

RexUniNLU性能对比:不同硬件环境下的运行效率

RexUniNLU性能对比&#xff1a;不同硬件环境下的运行效率 1. 引言 随着自然语言处理技术的不断演进&#xff0c;通用信息抽取系统在实际业务场景中的需求日益增长。RexUniNLU 是基于 DeBERTa-v2 架构构建的零样本中文通用自然语言理解模型&#xff0c;由 113 小贝团队进行二次…

作者头像 李华