声明
文中内容为观看 BiliBili 视频【STM32入门教程-2023版 细致讲解 中文字幕】后学习并扩展总结。
本文章为个人学习使用,版面观感若有不适请谅解,文中知识仅代表个人观点,若出现错误,欢迎各位批评指正。
一、USART 串口协议
1.1 通信接口
在嵌入式系统与电子设备互联领域,通信的核心目的是实现不同设备间的数据传输与交互,进而扩展硬件系统的功能边界、提升系统集成度,使单一设备能够通过数据交互协同完成复杂任务。为保障通信过程的有序性、准确性与兼容性,通信双方需遵循统一的通信协议,该协议作为数据收发的标准化规则,明确了数据传输的格式、时序、电平标准及交互逻辑,是实现设备间可靠通信的基础。
目前嵌入式系统中常用的通信方式各具特色,其核心引脚配置、双工模式、时钟机制、电平类型及设备互联方式如下:通用同步异步收发传输器 (USART)采用TX、RX引脚,支持全双工异步通信,为单端电平、点对点设备互联;集成电路总线 (I2C)以SCL、SDA为核心引脚,半双工同步传输,单端电平且支持多设备互联;串行外设接口 (SPI)包含SCLK、MOSI、MISO、CS引脚,全双工同步传输,单端电平可实现多设备互联;控制器局域网 (CAN)通过CAN_H、CAN_L差分引脚,半双工异步通信,支持多设备互联;通用串行总线(USB)以DP、DM为差分引脚,半双工异步传输,适用于点对点设备互联,详见下附表格。
| 名称 | 引脚 | 双工 | 时钟 | 电平 | 设备 |
|---|---|---|---|---|---|
| USART | TX、RX | 全双工 | 异步 | 单端 | 点对点 |
| I2C | SCL、SDA | 半双工 | 同步 | 单端 | 多设备 |
| SPI | SCLK、MOSI、MISO、CS | 全双工 | 同步 | 单端 | 多设备 |
| CAN | CAN_H、CAN_L | 半双工 | 异步 | 差分 | 多设备 |
| USB | DP、DM | 半双工 | 异步 | 差分 | 点对点 |
综上,不同通信方式基于其引脚配置、双工模式、时钟机制、电平类型及设备互联能力的差异,形成了各自的应用优势与适用场景,在实际系统设计中,需结合数据传输速率、传输距离、设备数量及抗干扰需求,选择合适的通信方式与对应协议,以实现硬件系统的高效、可靠互联。
1.2 串口通信
在嵌入式系统与电子设备互联领域,串口作为应用最为广泛的通信接口之一,具备成本低廉、操作便捷、通信线路简洁的核心优势,其仅需少量通信线路即可实现两个设备间的双向数据交互,无需复杂的硬件配置,有效降低了系统设计与实现成本。
在单片机应用系统中,串口是核心通信接口之一,其可实现单片机与单片机、单片机与上位机(如计算机)、单片机与各类功能模块(如传感器模块、显示模块、通信模块等)之间的可靠通信。这一功能有效打破了单一单片机的功能局限,极大地拓展了单片机系统的应用范围,增强了单片机系统的硬件扩展能力与数据交互能力,为单片机系统实现复杂功能、接入多设备互联网络提供了重要支撑。此外,串口的广泛应用,进一步推动了嵌入式系统向小型化、低成本、高兼容性方向发展,在智能控制、数据采集、工业自动化等诸多领域发挥着不可替代的作用。

1.3 硬件电路
在嵌入式系统与外设互联场景中,UART(通用异步收发传输器) 作为典型的串行通信接口,其物理层连接与通信模式设计直接影响数据传输的可靠性与效率。针对设备间的简单双向串口通信,其核心连接规范可归纳为:通信链路包含发送端(Transmit, TX) 与接收端(Receive, RX) 两根核心信号线,为实现双向数据交互,需遵循 TX 与 RX 交叉连接 的原则 ------ 即设备 1 的 TX 引脚需与设备 2 的 RX 引脚直连,设备 1 的 RX 引脚则与设备 2 的 TX 引脚直连,以此构建完整的双向传输通道;同时,为保障电平参考一致,两设备的接地端(Ground, GND) 必须共地连接,若需由一方为另一方供电,可额外接入电源端(VCC)。
在通信模式层面,UART 支持灵活的传输配置:当应用场景仅需单向数据传输 (如传感器向主控节点上报数据)时,可仅保留单根通信线(仅连接发送端与对应接收端),以简化硬件布线并降低功耗;而在双向交互场景(如主控与外设的指令应答)中,则需完整保留 TX/RX 交叉链路,实现全双工数据交换。此外,不同设备间的电平标准差异(如 3.3V CMOS 与 5V TTL、RS-232 与 TTL 电平)会直接导致信号识别失效,因此在电平标准不兼容的场景下,必须引入电平转换芯片 (如 MAX3232、SP3232 等)进行电平匹配,以保障通信的稳定性与兼容性。

1.4 电平标准
在串行通信体系中,电平标准是实现数字逻辑与物理电信号映射的核心规范,它明确定义了二进制数据 "1" 和 "0" 在传输线缆上的电压表示方式,是保障设备间信号可靠识别与传输的基础。不同电平标准通过差异化的电压区间、信号形式及抗干扰设计,适配了多样化的通信场景与距离需求。
串口通信领域常用的电平标准主要包括三类:
1、TTL 电平(Transistor-Transistor Logic): 作为嵌入式系统与板级通信的主流电平标准,其逻辑定义为:+3.3 V 或 +5 V 对应逻辑 "1",0 V 对应逻辑 "0"。该标准具有功耗低、响应速度快、电路结构简单等优势,广泛应用于微控制器、传感器等短距离板级设备间的通信,但抗共模干扰能力较弱,传输距离通常限制在 1 米以内。
2、RS-232 电平: 作为早期计算机与外设通信的经典标准,采用负逻辑定义:-3 V ~ -15 V 表示逻辑 "1",+3 V ~ +15 V 表示逻辑 "0"。通过正负电压的极性差异提升了抗干扰能力,传输距离可达 15 米,但由于单端信号传输的特性,其抗共模干扰能力仍有限,且电平幅值较高,需专用芯片实现与 TTL 电平的转换。
3、RS-485 电平: 基于差分信号传输的工业级电平标准,其逻辑由两线间的电压差决定:+2 V ~ +6 V 压差对应逻辑 "1",-2 V ~ -6 V 压差对应逻辑 "0"。差分结构使其具备极强的抗共模干扰能力,可支持最长 1200 米的远距离传输,同时允许多节点总线连接,因此成为工业现场、楼宇自动化等长距离、强干扰环境下的首选串口通信电平标准。
不同电平标准的电气特性差异决定了其应用边界:TTL 电平适用于板级高密度互联,RS-232 适用于中短距离点对点通信,RS-485 则面向长距离、多节点的工业级场景。当设备间电平标准不兼容时,需通过专用电平转换芯片(如 MAX3232、MAX485)实现信号电平的匹配,以保障通信链路的稳定性与兼容性。
1.5 串口通信核心参数定义
串口通信(UART/USART)是一种异步、点对点的串行数据传输协议,其通信行为由一组关键参数定义,直接决定数据帧的格式与传输速率:
- 波特率(Baud Rate): 波特率是衡量串口通信速率的核心指标,定义为单位时间内传输的码元数量(单位:bit/s),它决定了每位数据的持续时间 T b =1/B(B 为波特率)。常见标准波特率包括 9600、19200、38400、115200 bit/s 等,通信双方必须配置为相同波特率以实现同步采样。
- 起始位(Start Bit): 起始位是数据帧的同步标志位,固定为低电平(逻辑 0),用于通知接收方新数据帧的开始,触发接收端的采样时序逻辑。
- 数据位(Data Bits): 数据位是帧的有效载荷部分,通常为 8 位(或 9 位,支持多字节 / 地址模式),遵循低位先行(LSB First) 原则,即最低有效位(D0)最先传输,最高有效位(D7/D8)最后传输。逻辑 1 对应高电平,逻辑 0 对应低电平。
- 校验位(Parity Bit): 校验位是可选的差错检测位,根据数据位计算生成(奇校验 / 偶校验),用于验证传输过程中数据的完整性。在 9 位数据帧模式下,第 9 位可复用为校验位(如 RB8/TB8),实现多机通信中的地址 / 数据区分。
- 停止位(Stop Bit): 停止位是数据帧的结束标志位,固定为高电平(逻辑 1),用于帧间间隔与电平复位,常见长度为 1 位或 2 位,为接收端提供足够的时序恢复时间。
1.6 串口数据帧时序结构
串口数据帧的时序结构 1.5 部分所述参数按固定顺序组合而成,典型帧格式如下:
-
8 位数据位帧(10 位帧结构)
空闲态 → 起始位(1位) → 数据位(D0~D7, 8位) → 停止位(1位) → 空闲态
总长度: 1(起始) + 8(数据) + 1(停止) = 10 位
时序特征: 起始位触发接收,数据位按 LSB 到 MSB 顺序传输,停止位结束帧并恢复空闲电平。

-
9 位数据位帧(11 位帧结构,支持多机通信)
空闲态 → 起始位(1位) → 数据位(D0~D8, 9位) → 停止位(1位) → 空闲态
总长度: 1(起始) + 9(数据 / 校验) + 1(停止) = 11 位
时序特征: 第 9 位(D8)可作为校验位(RB8/TB8)或地址标记位,在多机通信中用于区分地址帧与数据帧,增强协议扩展性。

1.7 USART 简介
USART(Universal Synchronous/Asynchronous Receiver/Transmitter,通用同步 / 异步收发器) 是嵌入式微控制器中广泛应用的串行通信外设,作为实现设备间全双工串行数据交互的核心硬件模块,其兼具同步通信与异步通信的双重特性,能够适配不同场景下的串行数据传输需求,是嵌入式系统中数据通信的基础核心组件之一。
在硬件实现层面 ,USART 作为 STM32 系列微控制器(如 STM32F103C8T6)内部集成的专用外设,具备独立的硬件时序生成与解析能力,无需占用 CPU 核心的大量运算资源:发送数据时,CPU 仅需将待传输的字节数据写入数据寄存器,USART 外设即可自动按照预设的通信协议生成标准数据帧时序,并通过 TX(发送)引脚完成数据输出;接收数据时,外设可自动检测 RX(接收)引脚的电平变化,解析串行数据帧时序,将逐位传输的串行数据拼接为完整的字节数据后存入数据寄存器,等待 CPU 读取,这一硬件化的处理方式大幅提升了串行通信的效率,降低了 CPU 的负载。
从性能与配置特性来看 ,STM32 内置的 USART 外设搭载了独立的波特率发生器,可灵活配置通信速率,最高传输速率可达 4.5 Mbits/s,能够满足中高速串行通信场景的需求;在数据帧格式方面,USART 支持高度可配置化设计:数据位长度可选择 8 位或 9 位,停止位长度可配置为 0.5 位、1 位、1.5 位或 2 位,校验位支持无校验、奇校验与偶校验三种模式,可适配不同通信协议对帧格式的差异化要求。
除基础的异步串行通信功能外,STM32 的 USART 外设还具备丰富的扩展特性 :支持同步通信模式,可通过同步时钟引脚(CLK)实现主从设备间的同步数据传输;集成硬件流控制功能(CTS/RTS),能够有效避免数据传输过程中的溢出或丢失;支持 DMA(直接存储器访问)传输,可实现数据在 USART 数据寄存器与内存间的无 CPU 干预传输,进一步提升通信效率;此外,还兼容智能卡协议、IrDA(红外数据协会)红外通信、LIN(本地互联网络)等专用通信标准,拓展了其在工业控制、汽车电子等领域的应用场景。
针对 STM32F103C8T6 这款主流微控制器 ,其集成的 USART 资源包含 USART1、USART2、USART3 三路独立外设,各路 USART 均可独立配置通信参数,支持多通道并行通信,满足多设备互联的应用需求。USART 凭借其高可靠性、灵活配置性与低 CPU 占用率的特性,成为 STM32 系列微控制器实现串口通信、外设互联、上位机交互等功能的核心外设,广泛应用于工业自动化、物联网、智能硬件等领域。

1.8 USART基本结构
通用同步异步收发器(Universal Synchronous Asynchronous Receiver Transmitter, USART) 是嵌入式系统中实现串行数据通信的核心外设,其硬件架构可划分为时钟与波特率生成模块、发送控制通路、接收控制通路、数据缓冲与移位寄存器及接口控制模块五大部分,各模块协同完成同步 / 异步串行数据的帧编码、传输与解码。
(1)时钟与波特率生成模块
该模块以外设时钟(PCLK2/PCLK1)为输入,通过可编程分频与计数逻辑生成精确的波特率时钟,为发送与接收通路提供同步时序基准。
核心功能: 根据用户配置的波特率参数,将系统外设时钟分频为符合通信标准的比特率时钟,确保收发双方时序对齐。
控制关系: 生成的波特率时钟分别送入发送控制器与接收控制器,作为数据移位与采样的时间基准。
(2)发送控制通路
发送通路负责将并行数据转换为符合通信协议的串行比特流,由发送控制器、发送数据寄存器(Transmit Data Register, TDR)与发送移位寄存器构成。
发送数据寄存器(TDR): 作为 CPU 与发送硬件之间的并行数据缓冲,CPU 通过写操作将待发送数据载入 TDR。
发送移位寄存器: 在发送控制器的时序驱动下,将 TDR 中的并行数据逐位转换为串行比特流,并添加起始位、校验位与停止位等协议帧头帧尾。
发送控制器: 依据波特率时钟与通信模式(同步 / 异步),控制数据加载、移位输出与帧格式生成,同时管理发送完成标志与中断请求。
(3)接收控制通路
接收通路实现串行比特流到并行数据的解码与缓冲,由接收控制器、接收移位寄存器与接收数据寄存器(Receive Data Register, RDR) 构成。
接收移位寄存器: 在接收控制器的采样时序控制下,对 RX 引脚输入的串行比特流进行过采样与同步,逐位接收并拼接为完整并行数据。
接收数据寄存器(RDR): 作为接收硬件与 CPU 之间的并行数据缓冲,存储已完成拼接的有效数据,等待 CPU 读取。
接收控制器: 负责检测起始位、同步时钟、校验位验证与停止位检测,控制移位寄存器的数据加载与向 RDR 的转移,同时管理接收完成标志与错误状态(如帧错误、溢出错误、校验错误)。
(4)接口与控制模块
GPIO 接口: 实现 USART 硬件引脚与芯片外部物理链路的连接,TX 引脚用于发送串行数据,RX 引脚用于接收串行数据,GPIO 的复用功能配置决定引脚的通信角色。
开关控制模块: 作为全局控制逻辑,负责 USART 外设的使能 / 禁用、收发通路的单独开关、中断使能与 DMA 请求控制,是系统软件配置与硬件行为的交互接口。
(5)模块协同工作机制
发送流程: CPU 写入 TDR → 发送控制器将 TDR 数据载入发送移位寄存器 → 波特率时钟驱动移位寄存器逐位输出至 TX 引脚 → 完成发送后触发状态标志。
接收流程: RX 引脚检测到起始位 → 接收控制器启动采样与移位 → 接收移位寄存器拼接为并行数据 → 数据载入 RDR → 触发接收完成标志,等待 CPU 读取。
该架构通过双缓冲机制(数据寄存器 + 移位寄存器)实现了全双工通信,有效提升了数据传输的吞吐量与可靠性。

1.9 字长配置与帧基本结构
数据帧以起始位 作为传输唤醒与同步标识,通常为低电平逻辑,用于通知接收方后续数据的到来。数据位段长度可配置为 8 位或 9 位:当未设置 M 位时,数据位为 8 位(位 0 至位 7),对应标准字节传输;当设置 M 位时,数据位扩展为 9 位(位 0 至位 8),可用于多模态数据或地址 / 数据复用传输。在数据位之后,可选择性插入奇偶校验位 ,通过奇校验或偶校验机制实现传输差错检测,提升通信可靠性。帧尾以停止位 结束,停止位为高电平逻辑,用于标识一帧数据的结束,并为下一次传输提供空闲间隔。此外,通信链路中存在空闲帧 与断开帧 两种特殊状态:空闲帧表现为持续高电平,代表无数据传输的链路空闲状态;断开帧则以持续低电平后跟随停止位的形式,标识物理链路的断开或异常状态。

1.10 停止位配置与时序适配
停止位作为帧结束的标识,其长度可配置为 0.5 位、1 位、1.5 位或 2 位,以适配不同时钟频率与收发设备时序差异:
- 1 位停止位: 为最基础配置,提供最小的帧间隔,适用于高速、同步精度较高的通信场景;
- 1.5 位与 2 位停止位: 通过延长帧尾高电平时长,为低速设备或时钟偏差较大的收发双方提供更充裕的时序恢复窗口,降低帧同步丢失风险;
- 0.5 位停止位: 为极简配置,可最小化帧间开销,适用于极高速、时序高度同步的专用通信场景。
停止位长度的配置由 LBCL(Last Bit Clock Length)位控制,该位直接决定最后一位数据位之后的时钟脉冲数量,从而精准定义停止位的时序宽度。这种可配置的帧结构设计,使异步串行通信能够在传输效率与时序容错性之间实现动态平衡,满足多样化嵌入式与工业通信场景的需求。

1.11 起始位侦测机制
在异步串行通信系统中,起始位侦测 是实现帧同步的核心环节,其本质是通过多采样点的统计判决,在噪声与时钟偏差干扰下可靠识别数据帧的起始标志。
系统在空闲状态下持续监测接收信号线的电平变化,当检测到下降沿跳变 (由高电平至低电平)时,即触发起始位侦测流程,进入 "起始位" 接收状态。为应对收发端时钟频率偏差与信号噪声,系统采用过采样策略 :以 16 倍于波特率的采样时钟对接收信号进行逐位采样,每一位数据对应 16 个采样点。
在起始位判定阶段,系统将 16 个采样点划分为若干组进行统计判决:首先在下降沿后对前若干采样点进行初步电平确认,随后将后续采样点分为多组(每组 3 个采样点),要求每组中至少出现 2 个低电平采样值,以此作为判定当前低电平为有效起始位的核心条件。这种多采样点的多数表决机制 ,能够有效滤除瞬时噪声干扰,并容忍约 6/16 至 7/16 的时钟相位偏差,确保在波特率误差范围内仍可稳定识别起始位,为后续数据位、校验位与停止位的可靠接收奠定同步基础。

1.12 数据采样机制
在高速串行数据传输的噪声检测场景中,数据采样 是实现信号可靠判决的核心环节,其采样时序与位周期的匹配直接决定了噪声容限与数据恢复精度。如下图所示,系统采用16 倍于数据位速率的过采样时钟 ,将单个数据位的时间长度划分为 16 个等间隔采样点。为规避信号跳变沿的瞬时噪声干扰,采样窗口被精确限定在数据位的稳定区间内:以第 1 个数据位为例,其有效采样区间为第 8 至第 10 个采样时钟沿,覆盖了数据位中部的稳定电平阶段;而数据位的前后沿区域(分别对应前 7 个与后 6 个采样点)则被排除在有效采样之外,以避免跳变噪声引入的判决误差。
这种居中过采样策略 通过将采样窗口锚定在数据位的稳态平台区,最大化了对信号抖动与信道噪声的容忍能力:一方面,16 倍过采样率提供了精细的时间分辨率,可捕捉信号电平的微小波动;另一方面,限定采样窗口于稳态区间,规避了上升 / 下降沿的亚稳态风险,确保采样结果能够真实反映数据位的逻辑电平。从时序分配来看,单个数据位的有效采样区间占比为 3 16 \frac{3}{16} 163,剩余 13 16 \frac{13}{16} 1613 的时间窗口用于隔离跳变噪声,这种设计在噪声抑制与采样效率之间实现了优化平衡,为后续的噪声分析与数据恢复提供了可靠的原始样本。

1.13 波特比率寄存器
USART_BRR(波特比率寄存器) 是通用同步 / 异步收发器(USART)中负责波特率配置的核心寄存器,其地址偏移为 0x08,复位值为 0x0000。该寄存器的 32 位结构中,位 31 至 16 为硬件强制置 0 的保留位,位 15 至 4 为 DIV_Mantissa [11:0],用于定义 USART 分频器除法因子(USARTDIV)的整数部分,位 3 至 0 为 DIV_Fraction [3:0],用于定义 USARTDIV 的小数部分,且所有有效位域均支持读写操作。在模块运行逻辑中,发送器(TE)与接收器(RE)的使能状态直接影响波特率计数器:当 TE 或 RE 任一被使能时,计数器正常计数以驱动波特率生成,若二者均被禁止,则计数器停止计数,以降低功耗并避免无效时序干扰。波特率的生成严格遵循公式:
波特率 = f P C L K 2 / 1 16 × DIV \text{波特率} = \frac{f_{PCLK2/1}}{16 \times \text{DIV}} 波特率=16×DIVfPCLK2/1
其中 fPCLK2 为 APB2 总线时钟频率,DIV 由 USART_BRR 中 DIV_Mantissa 与 DIV_Fraction 共同构成,通过对该寄存器的精准配置,可实现 USART 收发链路波特率的精确调控,以适配不同串行通信场景的速率需求。

二、串口发送数据 及 串口发送+接收数据
2.1 串口发送数据的实现
-
首先,按下图接线方式,搭建面包板电路连接 OLED 显示屏,并将 USB 转串口的 TXD 和 RXD 分别与 PA2 和 PA3 连接,然后将 DAP-Link / ST-Link 连接到 STM32 最小系统板上,为使 OLED 显示屏的 VCC 和 GND 正确连接正负极,请先连接对应正负极跳线(或直接使用 GPIO 口进行供电)。

-
直接复制先前演示的已有文件目录,重命名并双击后缀名为 .uvprojx 的文件打开工程文件,并对 main.c 进行修改,工程中所使用的全部头文件其详细内容已放于文末。
#include "stm32f10x.h" // Device header
#include "Serial.h"
#include "OLED.h"int main(void)
{
OLED_Init();
Serial_Init();Serial_SendByte(0x41); uint8_t MyArray[] = {0x42, 0x43, 0x44, 0x45}; Serial_SendArray(MyArray, 4); Serial_SendString("\r\nNum1="); Serial_SendNumber(111, 3); printf("\r\nNum2=%d", 222); char String[100]; sprintf(String, "\r\nNum3=%d", 333); Serial_SendString(String); Serial_printf("\r\nNum4=%d", 444); Serial_printf("\r\n"); while (1) { }}
-
打开串口助手(串口助手软件 请在【江协科技】视频下方下载) ,按下 STM32 开发板上的复位按键后,系统将发送数据,可在串口助手软件中查看数据输出结果。
注: 波特率、数据位、停止位、校验位等信息可根据自身需求更改,但务必与代码中设置保持一致 。

2.2 串口发送 + 接收数据的实现
-
首先,按下图接线方式,搭建面包板电路连接 OLED 显示屏,并将 USB 转串口的 TXD 和 RXD 分别与 PA2 和 PA3 连接,然后将 DAP-Link / ST-Link 连接到 STM32 最小系统板上,为使 OLED 显示屏的 VCC 和 GND 正确连接正负极,请先连接对应正负极跳线(或直接使用 GPIO 口进行供电)。

-
直接复制先前演示的已有文件目录,重命名并双击后缀名为 .uvprojx 的文件打开工程文件,并对 main.c 进行修改,工程中所使用的全部头文件其详细内容已放于文末。
#include "stm32f10x.h" // Device header
#include "Serial_Receive.h"
#include "OLED.h"uint8_t RxData;
int main(void)
{
OLED_Init();
Serial_Init();OLED_ShowString(1, 1, "RxData:"); while (1) { // 查询方法// if(USART_GetFlagStatus(USART2, USART_FLAG_RXNE) == SET){
// RxData = USART_ReceiveData(USART2);
// OLED_ShowHexNum(1, 1, RxData, 2);
// }// 中断方法 if(Serial_GetRxFlag() == 1){ RxData = Serial_GetRxData(); Serial_SendByte(RxData); OLED_ShowHexNum(1, 8, RxData, 2); } }}

三、演示代码关联的头文件与源文件说明
-
OLED 相关头文件请从 STM32 学习 ------ 个人学习笔记4(OLED 显示屏及调试工具) 文末查看,此处不重复展示。
-
Serial.c
#include "stm32f10x.h" // Device header
#include <stdio.h>
#include <stdarg.h>void Serial_Init(void){
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); USART_InitStructure.USART_BaudRate = 9600; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Tx; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_Init(USART2, &USART_InitStructure); USART_Cmd(USART2, ENABLE);}
void Serial_SendByte(uint8_t Byte){
USART_SendData(USART2, Byte);
while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
}void Serial_SendArray(uint8_t *Array, uint16_t Length){
uint16_t i;
for(i=0; i<Length; i++){
Serial_SendByte(Array[i]);
}
}void Serial_SendString(char *String){
uint8_t i;
for(i=0; String[i] != '\0'; i++){
Serial_SendByte(String[i]);
}
}uint32_t Serial_Pow(uint32_t X, uint32_t Y){
uint32_t Result = 1;
while(Y--){
Result *=X;
}
return Result;
}void Serial_SendNumber(uint32_t Number, uint8_t Length){
uint8_t i;
for(i=0; i<Length; i++){
Serial_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 + 0x30);
}
}int fputc(int ch, FILE *f){
Serial_SendByte(ch);
return ch;
}void Serial_printf(char *format, ...){
char String[100];
va_list arg;
va_start(arg, format);
vsprintf(String, format, arg);
va_end(arg);
Serial_SendString(String);
} -
Serial.h
#ifndef __SERIAL_H
#define __SERIAL_H#include <stdio.h>
void Serial_Init(void);
void Serial_SendByte(uint8_t Byte);
void Serial_SendArray(uint8_t *Array, uint16_t Length);
void Serial_SendString(char *String);
void Serial_SendNumber(uint32_t Number, uint8_t Length);
int fputc(int ch, FILE *f);
void Serial_printf(char *format, ...);#endif
-
Serial_Receive.c
#include "stm32f10x.h" // Device header
#include <stdio.h>
#include <stdarg.h>uint8_t Serial_RxData;
uint8_t Serial_RxFlag;void Serial_Init(void){
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); USART_InitStructure.USART_BaudRate = 9600; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_Init(USART2, &USART_InitStructure); USART_ITConfig(USART2, USART_IT_RXNE, ENABLE); NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_Init(&NVIC_InitStructure); USART_Cmd(USART2, ENABLE);}
void Serial_SendByte(uint8_t Byte){
USART_SendData(USART2, Byte);
while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
}void Serial_SendArray(uint8_t *Array, uint16_t Length){
uint16_t i;
for(i=0; i<Length; i++){
Serial_SendByte(Array[i]);
}
}void Serial_SendString(char *String){
uint8_t i;
for(i=0; String[i] != '\0'; i++){
Serial_SendByte(String[i]);
}
}uint32_t Serial_Pow(uint32_t X, uint32_t Y){
uint32_t Result = 1;
while(Y--){
Result *=X;
}
return Result;
}void Serial_SendNumber(uint32_t Number, uint8_t Length){
uint8_t i;
for(i=0; i<Length; i++){
Serial_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 + 0x30);
}
}int fputc(int ch, FILE *f){
Serial_SendByte(ch);
return ch;
}void Serial_printf(char *format, ...){
char String[100];
va_list arg;
va_start(arg, format);
vsprintf(String, format, arg);
va_end(arg);
Serial_SendString(String);
}uint8_t Serial_GetRxFlag(void){
if(Serial_RxFlag==1){
Serial_RxFlag = 0;
return 1;
}
return 0;
}uint8_t Serial_GetRxData(void){
return Serial_RxData;
}void USART2_IRQHandler(void){
if(USART_GetITStatus(USART2, USART_IT_RXNE) == SET){
Serial_RxData = USART_ReceiveData(USART2);
Serial_RxFlag = 1;
USART_ClearITPendingBit(USART2, USART_IT_RXNE);
}
} -
Serial_Receive.h
#ifndef __SERIAL_RECEIVE_H
#define __SERIAL_RECEIVE_H#include <stdio.h>
void Serial_Init(void);
void Serial_SendByte(uint8_t Byte);
void Serial_SendArray(uint8_t *Array, uint16_t Length);
void Serial_SendString(char *String);
void Serial_SendNumber(uint32_t Number, uint8_t Length);
int fputc(int ch, FILE *f);
void Serial_printf(char *format, ...);
uint8_t Serial_GetRxFlag(void);
uint8_t Serial_GetRxData(void);#endif
文中部分知识参考:B 站 ------ 江协科技;百度百科