资讯动态

Arduino驱动STM32F103指南者串口通信实战指南

发布时间:2026/10/9 12:07:38 来源:尧图企业网站定制
1. 项目缘起与整体设计思路1.1 为什么要在 Arduino 环境下折腾 STM32F103很多人第一次接触 STM32F103 指南者这块板子都是被它“国产经典入门板”的名头吸引来的。板载 STM32F103VET672MHz 主频512KB Flash64KB SRAM外设齐全价格又便宜拿来学串口通信再合适不过。但问题也来了官方推荐的工具链是 Keil MDK 或者 STM32CubeIDE注册、配置、烧录一套流程下来对刚入门的朋友并不友好。Arduino 生态的好处在于“开箱即用”。它的 API 高度封装Serial.begin()、Serial.print()这种写法几乎零学习成本特别适合快速验证串口通信逻辑。所以这个项目的核心思路就是用 Arduino 的编程体验去驱动 STM32F103 的硬件串口既保留 STM32 的性能和外设优势又降低开发门槛。具体来说这个项目要解决三个问题第一让 Arduino IDE 能够识别并编译 STM32F103 指南者第二配置好硬件串口USART1的引脚和波特率第三实现稳定的收发通信包括与电脑串口助手、与其他单片机、甚至与 Unity 等上位机软件的交互。适合谁来参考如果你手头正好有一块 STM32F103 指南者又不想被 Keil 的复杂配置劝退或者你想用 Arduino 快速做一个串口通信的小项目那这篇内容就是为你准备的。哪怕你之前只玩过 Arduino UNO跟着走也能跑通。1.2 方案选型为什么不用 HAL 库直接写这里得说清楚一个关键选择。STM32F103 在 Arduino 环境下有两种主流玩法一种是直接用 STM32duino 官方核心基于 HAL 库封装另一种是用 Roger Clark 的 Arduino_STM32 核心基于标准外设库。我实测下来对于指南者这块板子STM32duino 官方核心更稳因为它对 F103 系列的支持已经非常成熟串口、定时器、DMA 都有现成封装。那为什么不直接用 STM32CubeMX 生成 HAL 库代码因为那样就脱离了 Arduino 的语境。Arduino 的核心价值在于“快速迭代”你改一行代码点一下上传几秒钟就能看到结果。而 CubeMX Keil 的流程光是改个波特率就要重新生成代码、重新编译、重新烧录效率差太多。当然如果你要做复杂的 DMA 接收或者定时器触发串口发送那确实需要回到 HAL 库层面去精细控制这个后面会讲到。还有一个细节指南者的串口引脚。STM32F103VET6 的 USART1 默认引脚是 PA9TX和 PA10RX。但指南者板子上PA9 和 PA10 可能被其他外设占用或者引到了特定的排针位置。我踩过的坑是直接按默认引脚接线结果串口助手收不到任何数据。后来查原理图才发现板子上有跳线帽或者复用设计需要确认实际引出的引脚。所以第一步一定是确认硬件连接而不是急着写代码。1.3 整体架构与数据流整个项目的架构可以拆成三层硬件层、固件层、上位机层。硬件层就是 STM32F103 指南者、USB 转 TTL 模块比如 CH340 或 CP2102、杜邦线。注意指南者板载了 USB 转串口芯片但那个串口通常连的是 USART1而且经过了电平转换。如果你要用其他串口比如 USART2就得外接 USB 转 TTL 模块。固件层就是 Arduino sketch。核心是HardwareSerial对象STM32duino 核心默认把Serial映射到 USART1。你可以用Serial1、Serial2等访问其他串口。数据流是STM32 通过 USART1 发送数据 - 板载 USB 转串口芯片 - USB 线 - 电脑。接收则反过来。上位机层可以是 Arduino IDE 自带的串口监视器也可以是专门的串口助手如 SSCOM、XCOM甚至是用 C# 写的 Unity 串口通信程序。这里的关键是波特率、数据位、停止位、校验位必须完全一致。我习惯用 115200 波特率8 数据位1 停止位无校验这是最通用的配置。注意STM32F103 的 USART1 挂在 APB2 总线上最高时钟 72MHz。波特率的计算是72MHz / (16 * 波特率)所以 115200 对应的分频系数是 39.0625实际会取 39误差约 0.16%完全在容忍范围内。但如果你用 9600 这种低波特率误差会更小通信更稳。2. 核心细节解析与实操要点2.1 环境搭建从零配置 Arduino IDE 支持 STM32F103第一步安装 Arduino IDE。版本建议用 1.8.x 或者 2.x 都行但 2.x 的界面变化较大有些第三方核心的兼容性还在完善中。我目前用的是 1.8.19稳定。第二步添加 STM32duino 的板管理器网址。打开“文件 - 首选项”在“附加开发板管理器网址”里填入https://github.com/stm32duino/BoardManagerFiles/raw/main/package_stmicroelectronics_index.json然后打开“工具 - 开发板 - 开发板管理器”搜索“STM32”找到“STM32 MCU based boards”点击安装。这个过程会下载几百 MB 的编译工具链网速慢的话耐心等。第三步安装完成后在开发板列表里选择“Generic STM32F1 series”然后根据指南者的具体型号选择“STM32F103VE”。注意指南者用的是 VET6所以选 VE 没错。如果选错了型号编译能过但烧录会失败。第四步烧录方式。指南者板载了 ST-Link 或者串口下载电路我手头这块是串口下载所以要在“Upload method”里选“Serial”。但串口下载需要手动进入 Bootloader 模式把 BOOT0 跳线帽接到 1BOOT1 接到 0按一下复位键然后点上传。上传完成后再把 BOOT0 接回 0再复位程序才会正常运行。这个步骤很繁琐但没办法串口下载就是这样。如果你有 ST-Link 调试器那就简单多了。在“Upload method”里选“STLink”直接点上传自动复位运行。我后来买了一个 ST-Link V2几十块钱效率提升巨大。所以强烈建议如果打算长期玩 STM32一定要配一个 ST-Link。2.2 串口初始化波特率、引脚与模式配置在 Arduino 环境下串口初始化就一行代码Serial.begin(115200);但这行代码背后做了很多事情。STM32duino 核心会自动配置 USART1 的时钟、引脚复用、波特率寄存器。默认引脚是 PA9 和 PA10。如果你要用其他引脚比如 PA2 和 PA3USART2那就用Serial2.begin(115200)。这里有个细节STM32F103 的串口引脚是 5V 容忍的吗PA9 和 PA10 是 5V 容忍的但其他一些引脚不是。如果你外接 5V 的 USB 转 TTL 模块一定要确认引脚是否支持 5V。指南者板载的 USB 转串口芯片输出的是 3.3V 电平所以直接连没问题。但如果你用外部模块最好选 3.3V 电平的或者加电平转换电路。提示STM32F103 的 USART1 是挂在 APB2 上的而 USART2、USART3 挂在 APB1 上。APB2 的最高时钟是 72MHzAPB1 是 36MHz。所以 USART1 的波特率上限更高可以到 4.5Mbps而 USART2/3 最高 2.25Mbps。一般用 115200 就够了。2.3 数据收发print、write 与 read 的区别Arduino 的串口 API 里Serial.print()和Serial.write()是两个最容易混淆的函数。print()会把数据转换成 ASCII 字符串发送比如Serial.print(65)发送的是字符 6 和 5也就是 0x36 和 0x35。而Serial.write(65)直接发送一个字节 0x41也就是字符 A。这个区别在跟其他设备通信时特别重要。比如你跟一个只认二进制协议的模块通信用print()就会出错。我刚开始玩的时候用Serial.print()发送一个十六进制命令结果对方完全没反应后来才发现发送的是 ASCII 码。接收方面Serial.read()返回一个字节Serial.available()返回缓冲区里的字节数。典型的接收循环void loop() { if (Serial.available() 0) { char c Serial.read(); // 处理字符 } }但要注意Serial.read()一次只读一个字节如果数据量大最好用Serial.readBytes()或者Serial.readString()。不过readString()会阻塞不推荐在实时性要求高的场景用。2.4 电平转换与硬件连接要点STM32F103 的 IO 电平是 3.3V而很多外设是 5V 的。如果你直接把 5V 的信号接到 STM32 的 RX 引脚可能会烧坏芯片。所以电平转换是必须的。最简单的电平转换方案是电阻分压。比如 5V 信号经过一个 1kΩ 和一个 2kΩ 的电阻分压得到 3.3V。但这种方法只适合低速信号高速下波形会变差。更好的方案是用专用的电平转换芯片比如 TXS0108E 或者 74LVC245。指南者板子上已经集成了 USB 转串口芯片而且做了电平转换所以直接用板载 USB 口就行。但如果你要用其他串口比如 USART2 接一个 5V 的 GPS 模块那就得自己加电平转换。注意STM32F103 的 BOOT0 和 BOOT1 引脚在启动时决定从哪启动。BOOT00 从 Flash 启动BOOT01 从系统存储器启动用于串口下载。所以正常运行时 BOOT0 必须接地。3. 实操过程与核心环节实现3.1 硬件连接清单与接线步骤先列一下我用的硬件STM32F103 指南者开发板一块USB 转 TTL 模块一个CH340G3.3V 电平杜邦线若干电脑一台接线步骤确认指南者的 BOOT0 跳线帽接在 0 位置GND。用杜邦线连接 USB 转 TTL 模块的 TXD 到指南者的 PA10RXRXD 到 PA9TXGND 到 GND。注意 TX 和 RX 要交叉连接。如果指南者板载了 USB 转串口可以直接用 USB 线连接电脑不需要外接模块。但板载的串口通常只连了 USART1而且可能占用了 PA9 和 PA10。检查电源指南者可以通过 USB 供电也可以用外部 5V 或 3.3V 供电。如果同时接了两个电源可能会冲突。我实测下来板载 USB 口最方便一根线搞定供电和通信。但如果你要同时用 USART1 和 USART2那就得外接模块。3.2 第一个串口程序Hello World 与回环测试先写一个最简单的发送程序void setup() { Serial.begin(115200); while (!Serial) { // 等待串口初始化STM32 上其实不需要 } Serial.println(Hello from STM32F103!); } void loop() { Serial.println(Tick); delay(1000); }上传后打开串口监视器波特率选 115200应该能看到每秒打印一次 Tick。如果乱码检查波特率是否一致。如果没反应检查接线和 BOOT0 跳线。接下来做回环测试也就是把收到的数据原样发回去void loop() { if (Serial.available() 0) { char c Serial.read(); Serial.print(Received: ); Serial.println(c); } }在串口监视器里输入一个字符应该能看到 Received: x。这个测试能验证收发都正常。3.3 与上位机通信C# 串口助手与 Unity 对接如果你要用 C# 写上位机核心是System.IO.Ports.SerialPort类。初始化SerialPort port new SerialPort(COM3, 115200, Parity.None, 8, StopBits.One); port.Open(); port.DataReceived (sender, e) { string data port.ReadLine(); Console.WriteLine(data); };注意C# 的ReadLine()会阻塞直到收到换行符所以 STM32 那边要用println()发送末尾带\n。Unity 串口通信稍微麻烦一点因为 Unity 的 Mono 运行时对串口的支持有限。我试过用System.IO.Ports在 Windows 上能跑但在某些版本会报错。后来改用了一个开源插件或者直接用 C# 的异步读取。关键点是不要在 Unity 主线程里做阻塞读取否则画面会卡死。用Thread或者Task在后台读然后通过消息队列把数据传给主线程。提示Unity 串口通信的波特率不要设太高115200 就够了。太高的话Unity 的帧率会受影响。另外记得在OnDestroy()里关闭串口否则下次运行会报“端口被占用”。3.4 进阶DMA 接收与首帧丢失问题STM32F103 的串口 DMA 接收是个好东西可以大大减轻 CPU 负担。但在 Arduino 环境下DMA 的配置比较麻烦因为 STM32duino 核心没有直接暴露 DMA API。你需要直接操作寄存器或者用 HAL 库的函数。我遇到的一个典型问题是DMA 接收首帧丢失。原因是 DMA 通道没有及时使能或者串口接收中断没有正确配置。解决方法是在初始化时先清空接收缓冲区再使能 DMA。具体代码// 伪代码需要根据 HAL 库调整 HAL_UART_Receive_DMA(huart1, rxBuffer, BUFFER_SIZE); __HAL_UART_ENABLE_IT(huart1, UART_IT_IDLE);然后在空闲中断里处理数据。这个方案比较复杂如果你只是做简单的串口通信不建议一开始就上 DMA。先用中断接收稳定了再考虑 DMA。3.5 参数计算波特率误差与时钟配置前面提到波特率的计算这里详细说一下。STM32F103 的 USART1 时钟是 72MHz波特率寄存器USART_BRR的值是72MHz / (16 * 波特率)。比如 11520072,000,000 / (16 * 115200) 39.0625取整数部分 39小数部分 0.0625 * 16 1所以 BRR 0x271。实际波特率是72MHz / (16 * 39.0625) 115200误差为 0。但如果取 39实际波特率是72MHz / (16 * 39) 115384.6误差 0.16%。这个误差在 10 位数据帧下累积误差不到 2%完全没问题。如果你用 9600 波特率72,000,000 / (16 * 9600) 468.75取 468实际波特率72MHz / (16 * 468) 9615.4误差 0.16%。同样很小。所以 STM32F103 的串口波特率精度很高不用担心。4. 常见问题与排查技巧实录4.1 串口无输出或乱码的排查流程这是最常见的问题。我整理了一个排查表现象可能原因解决方法完全无输出接线错误检查 TX/RX 是否交叉GND 是否共地完全无输出BOOT0 跳线错误确保 BOOT0 接 GND复位后运行完全无输出串口被占用关闭其他串口软件重新插拔 USB乱码波特率不匹配检查两边波特率是否一致乱码时钟配置错误检查 STM32duino 的时钟源设置部分乱码电平不匹配检查是否用了 5V 电平直接接 3.3V 引脚数据丢失缓冲区溢出降低发送频率或增大缓冲区我踩过最坑的一次是串口助手能收到数据但全是乱码。查了半天发现是 STM32duino 核心的时钟配置默认用了内部 RC 振荡器而不是外部晶振。指南者板子上有 8MHz 晶振但核心默认没启用。解决方法是在variant文件里修改时钟配置或者用Serial.begin()之前手动配置 RCC。后来我换了一个核心版本问题就消失了。4.2 上传失败与 Bootloader 问题串口下载时经常遇到“上传失败”或者“无法连接到 Bootloader”。原因通常是BOOT0 没有接高电平复位键没有按对时机串口被其他程序占用USB 线质量差数据线不是数据线我的经验是先拔掉 USB把 BOOT0 接 1插上 USB按一下复位然后立刻点上传。上传成功后拔掉 USB把 BOOT0 接回 0再插上 USB。这个流程虽然繁琐但成功率很高。如果你有 ST-Link那就完全不用管 BOOT0直接点上传就行。所以再次强调ST-Link 是必备工具。4.3 与 Unity/C# 通信时的典型故障Unity 串口通信最常见的问题是“端口被占用”。因为 Unity 编辑器在停止运行后有时候不会释放串口。解决方法是在OnApplicationQuit()里强制关闭串口或者用try-catch包裹打开操作。另一个问题是数据解析错误。比如 STM32 发送的是二进制数据而 C# 用ReadLine()读取就会一直阻塞。这时候要用ReadExisting()或者ReadByte()。我一般会在协议里加一个帧头和帧尾比如0xAA 0x55开头0x0D 0x0A结尾这样解析起来更可靠。提示如果 Unity 里串口读取导致卡顿可以把读取操作放在单独的线程里然后用ConcurrentQueue把数据传给主线程。这样既不影响帧率又能实时处理数据。4.4 电源与干扰问题STM32F103 指南者用 USB 供电时如果同时驱动多个外设可能会供电不足。表现是串口数据断断续续或者芯片频繁复位。解决方法是用外部 5V 电源供电或者加一个大的滤波电容。另外如果串口线太长或者周围有电机、继电器等干扰源数据也会出错。我试过用屏蔽线效果明显好很多。如果条件允许尽量缩短串口线长度或者用光耦隔离。4.5 常见问题速查表问题排查方向快速解决编译报错找不到板子板管理器网址错误重新添加 STM32duino 网址上传时提示“无法打开端口”串口被占用关闭串口监视器和其他软件程序运行一次就死机看门狗未喂狗检查是否启用了看门狗串口数据偶尔丢失中断优先级冲突调整 NVIC 优先级DMA 接收首帧丢失DMA 使能时机不对先清空缓冲区再使能 DMA波特率高于 115200 出错时钟精度不够降低波特率或改用外部晶振5. 个人实操心得与扩展思路5.1 几个让我少走弯路的习惯第一个习惯每次改代码前先备份。Arduino 的 sketch 没有版本控制改错了想回退很麻烦。我后来用 Git 管理每个功能点提交一次清晰很多。第二个习惯串口打印调试信息。STM32 没有屏幕串口就是唯一的调试窗口。我会在关键代码处加Serial.println(Step 1)这样一旦卡住立刻知道在哪一步。第三个习惯用逻辑分析仪抓波形。如果串口通信不稳定用逻辑分析仪看一下 TX 和 RX 的波形能快速判断是硬件问题还是软件问题。一个几十块钱的 8 通道逻辑分析仪比万用表好用多了。5.2 这个项目还能怎么扩展串口通信只是基础往上可以做很多事情。比如用串口接收 GPS 模块的 NMEA 数据解析经纬度用串口控制舵机、电机驱动器用串口和 ESP8266 通信实现物联网功能用串口和 OpenMV 通信做视觉识别用串口和 PLC 通信做工业控制我最近在做一个项目用 STM32F103 的串口接收微波炉控制面板的按键数据然后通过另一个串口发送给显示屏。整个系统用 Arduino 框架开发两天就跑通了。如果换成 Keil HAL 库估计得一周。5.3 关于 STM32F103 最小系统的选型建议如果你还没有板子想买一个 STM32F103 最小系统我建议注意几点确认晶振是 8MHz有些便宜板子用的是 12MHz时钟配置不一样确认有 BOOT0 和 BOOT1 跳线方便串口下载确认 USB 转串口芯片是 CH340 或 CP2102驱动好装确认引出了所有 IO 口方便扩展指南者这块板子在这些方面做得不错但价格比普通最小系统贵一点。如果预算有限买个普通的最小系统板也行就是接线麻烦点。5.4 最后分享一个串口调试小技巧如果你在调试串口时不确定对方发的是什么数据可以用一个“十六进制显示”的串口助手。比如 SSCOM 就有这个功能。把显示模式切成 HEX就能看到原始字节。我经常用这个功能来抓协议帧比看 ASCII 码直观多了。另外如果你要发送十六进制数据在 Arduino 里可以这样写byte cmd[] {0xAA, 0x55, 0x01, 0x02, 0x0D, 0x0A}; Serial.write(cmd, sizeof(cmd));这样发送的就是纯二进制不会经过 ASCII 转换。接收端用 HEX 模式看一目了然。这个项目虽然简单但涉及的知识点不少硬件接线、电平转换、时钟配置、波特率计算、中断处理、上位机通信。每一个环节都有坑但踩过之后你对串口通信的理解会深很多。我到现在还在用这套方案做各种小项目稳定、快速、够用。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑