news 2026/4/27 1:48:37

STM32F103 学习笔记-21-串口通信(第4节)—串口发送和接收代码讲解(下)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32F103 学习笔记-21-串口通信(第4节)—串口发送和接收代码讲解(下)

本章面向STM32零基础新手,基于STM32F103标准库开发,从USART串口单字节发送的核心原理出发,逐步扩展实现16位数据、数组、字符串发送功能,并讲解C标准库printf/scanf的重定向方法。你可以把USART串口理解为STM32的“有线电话”——芯片通过它和电脑、传感器等外部设备“说话”(发送数据)或“听对方说话”(接收数据),本章核心就是教会STM32如何“说更长的句子、说格式化的话”。


前置基础(新手必看)

1. 核心概念

  • 通用同步/异步收发器(Universal Synchronous/Asynchronous Receiver/Transmitter, USART):STM32用于串行通信的外设,支持异步通信(如和电脑串口助手通信),是嵌入式中最常用的通信方式之一。

  • 发送移位寄存器:USART负责发送数据的核心硬件,单次只能装下8位(1字节)数据——就像快递员的小包裹箱,一次只能装1个8cm见方的包裹,要发更大的包裹,只能拆成小块分次发。

  • TXE标志位(Transmit Data Register Empty):“包裹箱空了”的提示——表示发送数据寄存器已空,可以放入下一个字节的数据。

  • TC标志位(Transmission Complete):“所有包裹都送完了”的提示——表示最后一个字节已完全移出移位寄存器,整组数据发送完成。

2. 芯片架构关联

STM32F103基于ARM Cortex-M3内核,USART外设挂载在APB1/APB2总线上(USART1在APB2,USART2/3在APB1),其发送逻辑由硬件寄存器控制:我们通过操作寄存器(或标准库封装的函数),告诉硬件“要发什么数据”,硬件会自动完成串行移位发送,同时通过标志位反馈“发送状态”。


核心前提:单字节发送函数(所有进阶功能的基础)

USART移位寄存器单次仅能发送8位数据,所有多字节发送功能,都需要基于单字节发送函数循环/分批次实现。

原理

通过库函数USART_SendData将字节写入发送寄存器,然后循环等待TXE标志位为“空”,确保当前字节已进入移位寄存器,再进行下一次发送。

代码实例(可直接编译)

// 串口发送单字节函数 // pUSARTx: 串口外设(USART1/USART2/USART3等),本质是寄存器结构体指针 // ch: 待发送的8位数据(uint8_t对应C语言的无符号字符型,占1字节) void Usart_SendByte(USART_TypeDef * pUSARTx, uint8_t ch) { // 知识点:USART_TypeDef是STM32标准库封装的串口寄存器结构体,pUSARTx是指向该结构体的指针 // 把待发送字节写入串口数据寄存器 USART_SendData(pUSARTx, ch); // 等待TXE标志位为1(寄存器空),RESET表示“未置位”(0),SET表示“已置位”(1) // 知识点:while循环是阻塞式等待——直到条件不满足才退出,确保数据真正送入硬件 while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET); }

关键点解析

  • USART_TypeDef * pUSARTx:C语言指针的典型应用——通过指针传递不同串口外设(如USART1、USART2),让函数支持多串口复用,无需为每个串口写重复代码。

  • 阻塞式等待:新手入门阶段优先保证数据发送的可靠性,阻塞式等待是最简单的方式(后续进阶可改用中断/DMA)。


1. 16位(半字)数据发送函数

概念与原理

16位数据(半字,uint16_t,占2字节)无法单次发送,需拆分为高8位低8位两个字节,分两次调用单字节发送函数,就像把16cm的包裹拆成8cm×2的两个小包裹,先寄大的一半(高8位),再寄小的一半(低8位)。

配置/实现步骤

  1. 提取16位数据的高8位:用& 0xFF00屏蔽低8位,再右移8位;

  2. 提取16位数据的低8位:用& 0x00FF屏蔽高8位;

  3. 依次调用单字节函数发送高低8位。

代码实例

// 串口发送16位半字函数 void Usart_SendHalfWord(USART_TypeDef * pUSARTx, uint16_t ch) { uint8_t temp_h, temp_l; // 知识点:位运算——&是按位与,>>是右移,嵌入式中常用位运算拆分/组合数据 // 提取高8位:0xFF00是16进制掩码,屏蔽低8位后右移8位,得到纯高8位 temp_h = (ch & 0xFF00) >> 8; // 提取低8位:0x00FF屏蔽高8位,直接得到低8位 temp_l = ch & 0x00FF; // 先发送高8位 Usart_SendByte(pUSARTx, temp_h); // 后发送低8位 Usart_SendByte(pUSARTx, temp_l); }

实验验证(新手必做)

  1. 主函数调用示例:

int main(void) { // 串口初始化(需提前实现,配置115200波特率、8位数据位、1位停止位、无校验) USART_Config(); // 发送16位数据0xFF56 Usart_SendHalfWord(DEBUG_USARTx, 0xFF56); while(1); // 死循环,防止程序退出 }
  1. 现象说明:

    • 串口调试助手勾选「十六进制显示」:接收到FF 56(对应拆分的高低8位);

    • 不勾选十六进制:显示乱码(因为0xFF、0x56不是可打印ASCII字符),属于正常现象。

关键点解析

  • 位运算:嵌入式开发中最常用的操作之一,用于拆分/组合数据、配置寄存器位,比算术运算更高效(硬件直接支持)。

  • 数据类型:uint16_t(无符号16位整数)、uint8_t(无符号8位整数)是嵌入式标准类型(定义在stdint.h),比int/char更明确,避免不同编译器的位数差异。


2. 8位数据数组批量发送函数

概念与原理

数组是连续存储的多个8位数据,就像一整箱8cm的小包裹,通过for循环逐个取出包裹,调用单字节函数发送;全部发完后,需等待TC标志位,确保最后一个“包裹”真正送到对方手里。

配置/实现步骤

  1. 传入数组首地址和元素个数;

  2. 循环遍历数组,逐个发送元素;

  3. 等待TC标志位,确认整组数据发送完成。

代码实例

// 串口发送8位数组函数 // array: 数组首地址(C语言中数组名本质是首元素指针) // number: 数组元素个数(最大255,因为uint8_t范围0~255) void Usart_SendArray(USART_TypeDef * pUSARTx, uint8_t *array, uint8_t number) { uint8_t i; // 知识点:for循环遍历数组,嵌入式中常用遍历方式 for(i = 0; i < number; i++) { // 数组元素访问:*(array + i) 等价于 array[i] Usart_SendByte(pUSARTx, array[i]); } // 等待整组数据发送完成(TC标志位),区别于单字节的TXE while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TC) == RESET); }
实验验证
  1. 主函数调用示例:

int main(void) { // 定义并初始化数组,10个8位数据 uint8_t send_array[10] = {1,2,3,4,5,6,7,8,9,10}; USART_Config(); // 串口初始化 // 发送数组:串口1、数组首地址、10个元素 Usart_SendArray(USART1, send_array, 10); while(1); }
  1. 调试要点:

    • 勾选「十六进制显示」:接收到01 02 03 04 05 06 07 08 09 0A

    • 不勾选:无可见字符(1~10是不可打印ASCII码),并非代码错误;

    • 若数组元素为'a'(97)、'b'(98),不勾选可显示ab

关键点解析

  • 数组与指针:uint8_t *array接收数组首地址,array[i]等价于*(array + i),嵌入式中常通过指针操作硬件寄存器/数组,节省内存。

  • TC vs TXE:单字节发送等TXE(寄存器空),整组发送等TC(全部发完),混淆会导致最后一个字节发送不完整。


3. 字符串发送函数

概念与原理

C语言中字符串是以'\0'(空字符,ASCII码0)为结束标志的字符数组,就像一串有“终止符”的包裹,我们只需循环发送字符,直到遇到'\0'停止,无需提前知道字符串长度。

配置/实现步骤

  1. 传入字符串首地址;

  2. do-while循环逐个发送字符;

  3. 循环结束后等待TC标志位;

  4. 处理常见问题(如换行、变量初始化)。

代码实例

// 串口发送字符串函数 void Usart_SendString(USART_TypeDef * pUSARTx, char *str) { // 知识点:变量必须显式初始化!未初始化的i是随机值,会导致数组越界 uint8_t i = 0; // do-while循环:至少执行一次(避免空字符串) do { Usart_SendByte(pUSARTx, *(str + i)); i++; } while(*(str + i) != '\0'); // 直到遇到结束符'\0' // 等待字符串全部发送完成 while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TC) == RESET); }

实验验证与常见问题

  1. 主函数调用示例:

int main(void) { USART_Config(); // 字符串末尾加\r\n(回车+换行),解决数据粘连问题 Usart_SendString(USART1, "STM32串口字符串测试\r\n"); Usart_SendString(USART1, "零基础也能学会!\r\n"); while(1); }
  1. 常见问题解决:

    • 问题1:串口无输出→循环变量i未初始化(随机值导致访问越界)→必须uint8_t i = 0;

    • 问题2:字符串无换行→在字符串末尾加\r\n(如"测试\r\n");

    • 问题3:乱码→串口波特率/数据位配置不匹配(如初始化是115200,助手设为9600)。

关键点解析

  • 字符串结束符:'\0'是C语言字符串的核心标志,缺失会导致循环“跑飞”(访问内存中无关数据),嵌入式中内存越界可能导致程序崩溃。

  • do-while循环:区别于while循环,先执行后判断,确保空字符串也会进入循环(但发送0字节),更适配字符串发送场景。


4. C标准库输入输出函数重定向

STM32默认无法使用printf/scanf(这些函数默认向“电脑屏幕/键盘”读写),需重定向其底层函数,将读写逻辑绑定到串口——相当于把printf的“输出屏幕”改成“串口”,scanf的“输入键盘”改成“串口”。

4.1 printf/putchar重定向

原理

printf/putchar底层都会调用fputc函数(标准库函数,负责输出单个字符),只需重写fputc,将字符输出逻辑替换为串口单字节发送,即可让printf通过串口打印。

配置步骤
  1. 代码实现(添加到串口驱动.c文件):

// 必须包含标准库头文件,否则无法识别FILE、fputc #include "stdio.h" // 知识点:函数重写——自定义fputc覆盖标准库默认实现 // ch: 待输出的字符;f: 文件指针(printf默认忽略,仅兼容标准库格式) int fputc(int ch, FILE *f) { // 将int型ch转为uint8_t(仅保留低8位,符合串口发送要求) USART_SendData(DEBUG_USARTx, (uint8_t) ch); // 阻塞等待TXE标志位 while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_TXE) == RESET); return (ch); // 返回字符,兼容标准库调用逻辑 }
  1. Keil MDK工程配置(关键!):

    • 点击Options for TargetTarget→ 勾选Use MicroLIB(微库);

    • 微库是精简版C标准库,适配嵌入式场景,不勾选则重定向失效。

实验验证
#include "stdio.h" // 使用printf必须包含 int main(void) { uint16_t num = 1234; float temp = 25.68f; USART_Config(); // 知识点:printf格式化输出——嵌入式中常用作调试信息打印 printf("STM32 printf重定向测试\r\n"); printf("数字:%d,十六进制:0x%X\r\n", num, num); printf("温度:%.2f℃\r\n", temp); // putchar同步生效,发送单个字符 putchar('!'); while(1); }

现象:串口助手(取消十六进制显示)显示:

STM32 printf重定向测试 数字:1234,十六进制:0x4D2 温度:25.68℃ !

4.2 scanf/getchar重定向

原理

scanf/getchar底层调用fgetc函数(负责读取单个字符),重写fgetc,将字符读取逻辑替换为串口接收,即可通过串口输入数据。

代码实现
#include "stdio.h" // 重写fgetc,绑定串口接收 int fgetc(FILE *f) { // 阻塞等待串口接收数据(RXNE标志位:接收寄存器非空) while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_RXNE) == RESET); // 读取接收寄存器数据并返回 return (int)USART_ReceiveData(DEBUG_USARTx); }
实验验证
#include "stdio.h" int main(void) { char ch; int num; USART_Config(); printf("请输入一个字符:"); // getchar读取串口输入的字符 ch = getchar(); printf("你输入的字符是:%c\r\n", ch); printf("请输入一个数字:"); // scanf格式化读取串口输入的数字 scanf("%d", &num); printf("你输入的数字是:%d\r\n", num); while(1); }

注:该函数是阻塞式接收——程序会卡在while处,直到串口接收到数据,适合简单的指令交互场景。

关键点解析

  • 函数重写:嵌入式中常用的技巧,通过重写标准库/底层函数,适配硬件场景,无需修改上层代码(如直接用printf)。

  • MicroLIB:Keil专为嵌入式优化的C标准库,体积小、适配裸机开发,默认标准库不支持重定向fputc/fgetc


5. 关键调试与兼容性

5.1 串口调试助手配置规则(新手必记)

发送数据类型

串口助手显示配置

示例现象

16位数据、数组(原始数值)

勾选「十六进制显示」

发送0xFF56 → 显示FF 56

字符串、printf格式化输出

取消「十六进制显示」

发送"测试" → 显示测试

5.2 常见避坑点

  1. 循环变量未初始化→数组/字符串访问越界→串口无输出;

  2. TXE/TC标志位混淆→最后一个字节发送不完整;

  3. 未勾选MicroLIB→printf重定向失效;

  4. 字符串无'\0'→发送乱码/程序崩溃;

  5. 串口初始化参数(波特率、数据位)与助手不匹配→乱码。

5.3 跨平台兼容性

  • 跨串口:函数通过pUSARTx形参指定串口,只需修改初始化代码,即可从USART1移植到USART2/3;

  • 跨芯片:核心逻辑兼容Cortex-M3/M4/M7内核的STM32(如F4/F7系列),仅需调整库函数名(如HAL库改为HAL_UART_Transmit)。


小结

  1. USART串口单次仅能发送8位数据,多字节发送需基于单字节函数循环/拆分实现;

  2. 16位数据拆分为高低8位发送,数组通过for循环遍历发送,字符串通过'\0'判断结束;

  3. printf/scanf重定向的核心是重写fputc/fgetc,并配置MicroLIB;

  4. 嵌入式开发中,位运算、指针、变量初始化、标志位判断是核心基础,需熟练掌握。

思考

  1. 阻塞式发送/接收会占用CPU资源,如何通过中断实现非阻塞的串口发送/接收?

  2. 除了标准库,STM32HAL库中如何实现串口多字节发送和printf重定向?

  3. 若要发送浮点型数据(如3.1415),如何基于现有函数实现?

  4. 串口接收数据时,如何避免因数据丢失导致的程序异常?(提示:缓冲区)

建议查阅STM32F103官方参考手册(RM0008)的USART章节,进一步理解寄存器工作原理和标志位时序。

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

AIGC检测太贵怎么办?盘点10个主流工具,查AI率少花钱!

2026年答辩季临近&#xff0c;AIGC检测已经成为大多数高校论文审核的标配流程。不管你有没有用过A论文&#xff0c;学校都可能会查一遍AI率。很多同学的第一反应就是&#xff1a;ai率查重要多少钱&#xff1f;有没有能免费查AI率的工具&#xff1f; 有免费的aigc检测工具&…

作者头像 李华
网站建设 2026/4/27 1:38:19

DocsGPT 二次开发:打造面向国内用户的私有 AI 知识库平台

基于 GitHub 17.8K Star 的开源 RAG 项目&#xff0c;我们做了什么&#xff0c;为什么做&#xff0c;接下来要做什么前言 大家好&#xff0c;我是张大鹏。 最近在研究 RAG&#xff08;检索增强生成&#xff09;相关的开源项目&#xff0c;想找到一个适合做二次开发的底座&#…

作者头像 李华
网站建设 2026/4/27 1:36:20

AI智能体文件感知规划:让AI在行动前先读懂你的文件

1. 项目概述&#xff1a;当AI规划器学会“读文件”最近在折腾AI智能体&#xff08;Agent&#xff09;和自动化工作流&#xff0c;我发现一个挺有意思的痛点&#xff1a;很多规划任务&#xff0c;比如写周报、整理会议纪要、分析数据&#xff0c;其实都离不开对现有文件的处理。…

作者头像 李华
网站建设 2026/4/27 1:33:43

汽修门店 POS 机断网?映翰通 IR615 工业路由器搞定稳定联网

一、门店痛点&#xff1a;收银断网&#xff0c;生意白跑汽车维修门店的 POS 机&#xff0c;是日常运营的核心。有线宽带不稳、信号差&#xff0c;付款高峰期频繁断网&#xff0c;订单卡单、失败普通家用路由器扛不住门店复杂环境&#xff0c;用不久就宕机交易数据传输没保障&am…

作者头像 李华