
文章目录
- 一、通信接口
- 二、串口通信
- 三、USART通信协议
- 四、硬件电路
- 五、电平标准
- 六、串口参数及时序
- 七、USART协议核心定位
-
- [7.1 异步USART协议的核心:帧结构](#7.1 异步USART协议的核心:帧结构)
- [7.2 USART协议的时序规则(波特率与同步)](#7.2 USART协议的时序规则(波特率与同步))
- [7.3 USART协议的接收采样机制(抗干扰规则)](#7.3 USART协议的接收采样机制(抗干扰规则))
- [7.4 USART协议的关键可配置参数(协议变体)](#7.4 USART协议的关键可配置参数(协议变体))
- 八、同步USART协议(补充)
- 九、数据模式
- 十、USART协议的完整通信代码(发送)
- 🚩总结
一、通信接口
通信的目的:将一个设备的数据传送到另一个设备,扩展硬件系统通信协议︰制定通信的规则,通信双方按照协议规则进行数据收发
二、串口通信

串口是一种应用十分广泛的通讯接口,串口成本低、容易使用、通信线路简单,可实现两个设备的互相通信
单片机的串口可以使单片机与单片机、单片机与电脑、单片机与各式各样的模块互相通信,极大地扩展了单片机的应用范围,增强了单片机系统的硬件实力
三、USART通信协议
USART(通用同步/异步收发器)的通信协议是实现串口数据可靠传输的核心规则,异步模式 (无时钟线)是嵌入式开发中最常用的场景(同步模式仅在高精度/高速场景使用),下文将以异步USART协议为核心,从帧结构、时序、采样机制、参数配置、错误处理等维度全面解析,同时补充同步模式的协议差异。
四、硬件电路
简单双向串口通信有两根通信线(发送端TX和接收端RX)TX与RX要交叉连接
当只需单向的数据传输时,可以只接一根通信线当电平标准不一致时,需要加电平转换芯片

五、电平标准
电平标准是数据1和数据0的表达方式,是传输线缆中人为规定的电压与数据的对应关系,串口常用的电平标准有如下三种:
TTL电平:+3.3V或+5V表示1,OV表示0
RS232电平:-3~-15V表示1,
+ 3~+ 15V表示0RS485电平:两线压差+2~+6V表示1,
2~-6V表示0(差分信号)
六、串口参数及时序
波特率:串口通信的速率
起始位: 标志一个数据帧的开始,固定为低电平
数据位: 数据帧的有效载荷,1为高电平,0为低电平,低位先行
校验位: 用于数据验证,根据数据位计算得来
停止位: 用于数据帧间隔,固定为高电平
串口时序

七、USART协议核心定位
USART协议是一种串行、异步/同步、全双工的通信协议,核心目标是:在无时钟同步(异步)或有时钟同步(同步)的前提下,让收发双方对"数据的起始/结束、数据内容、传输速率"达成一致,实现二进制数据的可靠传输。
- 异步模式(Asynchronous):无专用时钟线,靠"波特率预约定"+"帧格式规则"同步,是串口调试、上位机通信的主流方式;
- 同步模式(Synchronous):新增时钟线(SCLK),由主设备提供时钟,收发严格同步,协议复杂度更低,但需额外接线。
下文重点解析异步USART协议(日常说的"串口协议"本质就是异步USART协议)。



理解过程
一、发送流程(从"CPU/DMA发数据"到"TX引脚输出")
-
写数据到发送寄存器
CPU或DMA通过"PWDATA(写操作)",把要发送的并行数据写入发送数据寄存器(TDR)。
-
数据转移到移位寄存器
TDR里的并行数据会自动传递到发送移位寄存器。
-
串行化+时序编码
发送移位寄存器会根据配置的参数(波特率、数据位、停止位、校验位):
- 由**USART_BRR(波特率寄存器)**生成的发送时钟控制速率;
- 由CR1/CR2(控制寄存器)配置数据格式(8/9位、校验、停止位);
把并行数据转换成串行比特流。
-
输出到外部
- 普通串口模式:串行数据直接从TX引脚发送出去;
- IrDA模式:串行数据先经过IrDA SIR编解码模块 处理,再从IRDA_OUT引脚输出。
-
状态/中断反馈
发送过程中,状态寄存器(SR)会更新状态(比如"TXE"表示发送寄存器空、"TC"表示发送完成);
如果在CR1 里开启了对应中断(TXEIE/TCIE),会触发USART中断控制模块,通知CPU处理。
二、接收流程(从"RX引脚输入"到"CPU/DMA读数据")
-
外部数据输入
- 普通串口模式:外部串行数据从RX引脚输入;
- IrDA模式:外部数据从IRDA_IN引脚 输入,先经过IrDA SIR编解码模块处理。
-
串行转并行
串行数据进入接收移位寄存器,移位寄存器会根据配置的波特率、数据格式,把串行比特流还原成并行数据。
-
数据存到接收寄存器
接收移位寄存器的并行数据会传递到接收数据寄存器(RDR)。
-
CPU/DMA读取数据
CPU或DMA通过"PRDATA(读操作)",从RDR里读取接收到的并行数据。
-
状态/中断反馈
接收过程中,状态寄存器(SR)会更新状态(比如"RXNE"表示接收寄存器非空、"ORE"表示溢出错误);
如果在CR1 里开启了对应中断(RXNEIE),会触发USART中断控制模块,通知CPU处理。
三、辅助模块的作用(框图里的其他部分)
- 波特率控制(USART_BRR) :通过
DIV_Mantissa(整数部分)和DIV_Fraction(小数部分)配置波特率,生成发送/接收时钟。 - 控制寄存器(CR1/CR2/CR3):配置工作模式(同步/异步)、中断使能、DMA使能、硬件流控等。
- 硬件流控(nRTS/nCTS):通过nRTS(自己是否准备好接收)、nCTS(对方是否准备好接收)引脚,实现发送/接收的流量控制。
7.1 异步USART协议的核心:帧结构
USART协议以"帧(Frame)"为基本传输单元,所有数据都封装在帧中传输,空闲时总线为高电平,每帧数据的电平变化严格遵循固定格式。完整的异步USART帧结构如下(从TX引脚输出的电平时序):



| 帧组成部分 | 电平状态 | 位数/时长 | 核心作用 | 细节说明 |
|---|---|---|---|---|
| 空闲位 | 高电平(1) | 任意时长 | 标识总线空闲 | 无数据传输时,TX/RX引脚持续高电平;帧传输开始时,空闲位被起始位打断 |
| 起始位 | 低电平(0) | 1位 | 标识帧开始 | 必须为1位低电平,是帧的"启动信号";USART通过检测"高→低"的下降沿识别起始位 |
| 数据位 | 0/1电平 | 8位或9位 | 传输有效数据 | 低位(LSB)先发送(如数据0x41=01000001,先传最低位1,最后传最高位0);9位模式下,第9位可作为"地址/数据标识位"(多机通信) |
| 校验位 | 0/1电平 | 0位(无校验)/1位(奇/偶/固定值) | 检错 | 仅当配置校验时存在;用于验证数据位传输是否出错,计算规则见下文 |
| 停止位 | 高电平(1) | 0.5/1/1.5/2位 | 标识帧结束 | 必须为高电平,是帧的"结束信号";常用1位停止位,2位停止位用于低速/高干扰场景 |
帧结构示例(最常用配置:8N1)
"8N1"是USART最经典的配置(8位数据位+无校验+1位停止位),以发送字符A(ASCII码0x41=二进制01000001)为例:
- 空闲位:TX引脚持续高电平;
- 起始位:1位低电平(标志帧开始);
- 数据位:8位,低位先发→传输顺序为
1(b0)→0(b1)→0(b2)→0(b3)→0(b4)→0(b5)→1(b6)→0(b7); - 校验位:无;
- 停止位:1位高电平(标志帧结束);
- 帧结束后,TX引脚回到高电平(空闲位),等待下一次帧传输。
校验位的计算规则(协议检错核心)
若配置校验位(奇/偶),硬件会自动计算数据位的"1的个数"并生成校验位:
- 偶校验(Even Parity) :数据位+校验位的总"1的个数"为偶数;
例:数据位01000001(1的个数=2,偶数)→ 校验位=0; - 奇校验(Odd Parity) :数据位+校验位的总"1的个数"为奇数;
例:数据位01000001(1的个数=2)→ 校验位=1(总个数=3,奇数); - 固定值校验(Space/Mark):校验位固定为0(Space)或1(Mark),仅用于特定场景。
7.2 USART协议的时序规则(波特率与同步)
异步USART无时钟线,收发双方靠"波特率"同步时序,这是协议的核心约束。
发送器和接收器的波特率由波特率寄存器BRR里的DIV确定计算公式:波特率= fecLK2/1 / (16 + DIV)
- 波特率(Baud Rate)
- 定义 :每秒传输的"位(bit)数"(异步USART中,波特率=比特率);
常用值:9600、19200、38400、115200 bps(bit per second); - 时序对应 :1位的传输时长 = 1 / 波特率;
例:9600波特率 → 1位时长≈104.17μs;115200波特率 → 1位时长≈8.68μs; - 误差要求 :收发双方波特率误差需≤3%(否则采样出错),STM32通过
USART_BRR寄存器精准配置波特率,避免误差超限。

图解:
一、波特率发生器的核心作用
USART收发需要时钟(比如之前说的"16倍采样时钟"),波特率发生器的任务就是根据你想要的波特率,把芯片的系统时钟(fPCLK)分频成USART需要的时钟。
二、波特率公式怎么理解?
公式:波特率 = fPCLK / (16 × DIV)
fPCLK:USART挂在的系统时钟(比如STM32中USART1挂在PCLK2,USART2/3挂在PCLK1,频率是已知的,比如72MHz);16:对应之前说的"16倍过采样"(USART接收要16倍波特率的采样时钟,所以这里要除以16);DIV:分频系数(是个带小数的数,由寄存器里的"整数+小数"组成)。
三、USART_BRR寄存器怎么填?
这个寄存器是用来存DIV的,把DIV拆成整数部分 和小数部分:
- 位15-4(DIV_Mantissa) :存
DIV的整数部分(12位,能存0~4095); - 位3-0(DIV_Fraction) :存
DIV的小数部分(4位,范围0~15,代表"小数部分=数值/16",比如数值是8,小数部分就是8/16=0.5)。
所以 DIV = DIV_Mantissa + (DIV_Fraction / 16)
四、举个例子
比如你要用STM32的USART1,fPCLK2=72MHz,想要波特率=9600:
- 先算
DIV:
DIV = fPCLK / (16 × 波特率) = 72000000 / (16×9600) ≈ 468.75 - 拆成整数+小数:
- 整数部分:468(填到DIV_Mantissa);
- 小数部分:0.75 = 12/16(所以DIV_Fraction填12);
- 最终USART_BRR寄存器的值就是:
0x1D4C(468的十六进制是0x1D4,12是0xC,拼起来是0x1D4C)。
这样USART就能生成"16×9600"的采样时钟
16倍过采样时钟(协议层面的同步保障)
USART硬件会生成"16倍波特率"的采样时钟(协议规定的同步机制),用于精准识别帧的起始和数据:
- 例:9600波特率 → 采样时钟频率=16×9600=153600Hz → 采样周期≈6.51μs;
- 作用:将1位的传输时长拆分为16个采样点,通过"多采样点表决"抗干扰、提精度(下文采样机制详解)。
7.3 USART协议的接收采样机制(抗干扰规则)
为避免噪声/抖动导致的帧识别错误,USART协议定义了严格的采样规则(硬件层面实现,属于协议的一部分):

-
起始位检测(协议防误判规则)
总线空闲为高电平,起始位是低电平(高→低下降沿),协议要求:
-
检测到"高→低"下降沿后,启动16倍采样时钟;
-
对起始位的16个采样点分组验证:每3个采样点为一组,每组至少2个点为低电平;
-
连续多组验证通过,才判定为"真起始位"(排除偶然噪声),否则视为无效抖动。

-
数据位采样(协议精准采样规则)
确认起始位后,对后续数据位/校验位/停止位的采样规则:
-
采样时机:选择每1位的"中间区域"(第7~9个采样点),避开位边缘的抖动;
-
多数表决:对中间3个采样点"投票"(3个点中2个一致则取该值),过滤噪声;
-
停止位验证:采样停止位时,要求至少1个采样点为高电平,否则判定为"帧错误"。
7.4 USART协议的关键可配置参数(协议变体)
USART协议支持灵活配置帧参数,适配不同场景,各参数对应STM32的USART_InitTypeDef配置项:
| 协议参数 | 可选值 | STM32配置宏示例 | 适用场景 |
|---|---|---|---|
| 数据位 | 8位/9位 | USART_WordLength_8b/9b | 8位:通用场景(ASCII字符);9位:多机通信(第9位为地址位) |
| 停止位 | 0.5/1/1.5/2位 | USART_StopBits_1/2 | 1位:通用场景;2位:低速/高干扰(如工业现场) |
| 校验位 | 无/奇/偶/固定0/固定1 | USART_Parity_No/Odd/Even | 无校验:调试/低干扰;奇/偶校验:高可靠性场景(如工业通信) |
| 波特率 | 1200~115200(常用) | USART_BaudRate = 115200 | 9600:兼容/抗干扰;115200:高速传输 |
常见协议配置组合
- 8N1:8位数据+无校验+1位停止位(最通用,如串口调试助手默认);
- 8E1:8位数据+偶校验+1位停止位(工业通信);
- 9O1:9位数据+奇校验+1位停止位(多机通信)。
八、同步USART协议(补充)
同步模式是USART协议的变体,核心差异是"时钟同步",协议规则简化:
- 硬件新增:SCLK引脚(时钟线),由主设备输出时钟,从设备同步采样;
- 帧结构变化:无起始位/停止位(时钟已同步),数据位连续传输;
- 波特率:时钟频率=波特率(无需16倍采样),传输效率更高;
- 适用场景:高速/高精度通信(如与SPI外设兼容通信),需额外接线(SCLK)。
USART协议的错误类型(协议层面的异常处理)
USART硬件会按协议规则检测传输异常,通过状态寄存器(SR)标识错误:
- 帧错误(FE):停止位采样为低电平(未检测到高电平停止位),原因:帧格式不匹配/波特率误差过大;
- 校验错误(PE):接收端计算的校验位与发送端不一致,原因:数据传输过程中被干扰;
- 溢出错误(ORE):接收寄存器(RDR)未读取,新数据已存入,旧数据被覆盖,原因:CPU处理速度慢于接收速率;
- 噪声错误(NE):采样数据位时检测到异常抖动,由16倍采样机制识别。
九、数据模式
HEX模式/十六进制模式/二进制模式:以原始数据的形式显示文本模式/字符模式:以原始数据编码后的形式显示
- 两种数据模式的区别
-
HEX/十六进制/二进制模式 :显示"原始数据"(就是实际传输的二进制/十六进制数值),不管这个数值对应什么字符。
比如传输的是
0x41,HEX模式下就显示0x41。 -
文本/字符模式 :把"原始数据"按照编码规则(比如ASCII码)转换成可读的字符显示。
比如
0x41对应ASCII表的字符'A',文本模式下就显示A。
- 结合ASCII表+通信流程理解
左边的表格是ASCII编码表 :每个字符(比如A)都对应一个唯一的十进制/十六进制数值(A对应65 DEC、0x41HEX)。
右边的流程是实际通信过程:
- 发送方想发字符
'A'→ 先按ASCII编码成原始数据0x41; - 串口传输的是
0x41这个原始数值; - 接收方收到
0x41→ 再按ASCII解码成字符'A'显示。
十、USART协议的完整通信代码(发送)

USART协议常用库函数
cpp
// 恢复USART缺省配置
void USART_DeInit(USART_TypeDef* USARTx);
// USART初始化
void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct);
// USART配置结构体初始化
void USART_StructInit(USART_InitTypeDef* USART_InitStruct);
// USART时钟初始化
void USART_ClockInit(USART_TypeDef* USARTx, USART_ClockInitTypeDef* USART_ClockInitStruct);
// USART时钟配置结构体初始化
void USART_ClockStructInit(USART_ClockInitTypeDef* USART_ClockInitStruct);
// 使能/失能USART外设
void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState);
// 配置USART中断
void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT, FunctionalState NewState);
// 配置USART DMA请求
void USART_DMACmd(USART_TypeDef* USARTx, uint16_t USART_DMAReq, FunctionalState NewState);
// 设置USART多机通信地址
void USART_SetAddress(USART_TypeDef* USARTx, uint8_t USART_Address);
// 配置USART唤醒方式
void USART_WakeUpConfig(USART_TypeDef* USARTx, uint16_t USART_WakeUp);
// 使能/失能USART接收唤醒
void USART_ReceiverWakeUpCmd(USART_TypeDef* USARTx, FunctionalState NewState);
// 配置USART LIN中断检测长度
void USART_LINBreakDetectLengthConfig(USART_TypeDef* USARTx, uint16_t USART_LINBreakDetectLength);
// 使能/失能USART LIN模式
void USART_LINCmd(USART_TypeDef* USARTx, FunctionalState NewState);
// USART发送数据
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);
// USART接收数据
uint16_t USART_ReceiveData(USART_TypeDef* USARTx);
// USART发送Break帧
void USART_SendBreak(USART_TypeDef* USARTx);
// 设置USART智能卡保护时间
void USART_SetGuardTime(USART_TypeDef* USARTx, uint8_t USART_GuardTime);
// 设置USART预分频器
void USART_SetPrescaler(USART_TypeDef* USARTx, uint8_t USART_Prescaler);
// 使能/失能USART智能卡模式
void USART_SmartCardCmd(USART_TypeDef* USARTx, FunctionalState NewState);
// 使能/失能USART智能卡NACK
void USART_SmartCardNACKCmd(USART_TypeDef* USARTx, FunctionalState NewState);
// 使能/失能USART半双工模式
void USART_HalfDuplexCmd(USART_TypeDef* USARTx, FunctionalState NewState);
// 使能/失能USART8倍过采样
void USART_OverSampling8Cmd(USART_TypeDef* USARTx, FunctionalState NewState);
// 使能/失能USART单比特采样
void USART_OneBitMethodCmd(USART_TypeDef* USARTx, FunctionalState NewState);
// 配置USART IrDA模式
void USART_IrDAConfig(USART_TypeDef* USARTx, uint16_t USART_IrDAMode);
// 使能/失能USART IrDA模式
void USART_IrDACmd(USART_TypeDef* USARTx, FunctionalState NewState);
// 获取USART标志位状态
FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG);
// 清除USART标志位
void USART_ClearFlag(USART_TypeDef* USARTx, uint16_t USART_FLAG);
// 获取USART中断状态
ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT);
// 清除USART中断挂起位
void USART_ClearITPendingBit(USART_TypeDef* USARTx, uint16_t USART_IT);
main.c
cpp
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
int main(void)
{
/*模块初始化*/
OLED_Init(); //OLED初始化
Serial_Init(); //串口初始化
/*串口基本函数*/
Serial_SendByte(0x41); //串口发送一个字节数据0x41
uint8_t MyArray[] = {0x42, 0x43, 0x44, 0x45}; //定义数组
Serial_SendArray(MyArray, 4); //串口发送一个数组
Serial_SendString("\r\nNum1="); //串口发送字符串
Serial_SendNumber(111, 3); //串口发送数字
/*下述3种方法可实现printf的效果*/
/*方法1:直接重定向printf,但printf函数只有一个,此方法不能在多处使用*/
printf("\r\nNum2=%d", 222); //串口发送printf打印的格式化字符串
//需要重定向fputc函数,并在工程选项里勾选Use MicroLIB
/*方法2:使用sprintf打印到字符数组,再用串口发送字符数组,此方法打印到字符数组,之后想怎么处理都可以,可在多处使用*/
char String[100]; //定义字符数组
sprintf(String, "\r\nNum3=%d", 333);//使用sprintf,把格式化字符串打印到字符数组
Serial_SendString(String); //串口发送字符数组(字符串)
/*方法3:将sprintf函数封装起来,实现专用的printf,此方法就是把方法2封装起来,更加简洁实用,可在多处使用*/
Serial_Printf("\r\nNum4=%d", 444); //串口打印字符串,使用自己封装的函数实现printf的效果
Serial_Printf("\r\n");
while (1)
{
}
}
Serial.h
cpp
#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);
void Serial_Printf(char *format, ...);
#endif
cpp
Serial.c
#include "stm32f10x.h" // Device header
#include <stdio.h>
#include <stdarg.h>
/**
* 函 数:串口初始化
* 参 数:无
* 返 回 值:无
*/
void Serial_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //开启USART1的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA9引脚初始化为复用推挽输出
/*USART初始化*/
USART_InitTypeDef USART_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; //停止位,选择1位
USART_InitStructure.USART_WordLength = USART_WordLength_8b; //字长,选择8位
USART_Init(USART1, &USART_InitStructure); //将结构体变量交给USART_Init,配置USART1
/*USART使能*/
USART_Cmd(USART1, ENABLE); //使能USART1,串口开始运行
}
/**
* 函 数:串口发送一个字节
* 参 数:Byte 要发送的一个字节
* 返 回 值:无
*/
void Serial_SendByte(uint8_t Byte)
{
USART_SendData(USART1, Byte); //将字节数据写入数据寄存器,写入后USART自动生成时序波形
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); //等待发送完成
/*下次写入数据寄存器会自动清除发送完成标志位,故此循环后,无需清除标志位*/
}
/**
* 函 数:串口发送一个数组
* 参 数:Array 要发送数组的首地址
* 参 数:Length 要发送数组的长度
* 返 回 值:无
*/
void Serial_SendArray(uint8_t *Array, uint16_t Length)
{
uint16_t i;
for (i = 0; i < Length; i ++) //遍历数组
{
Serial_SendByte(Array[i]); //依次调用Serial_SendByte发送每个字节数据
}
}
/**
* 函 数:串口发送一个字符串
* 参 数:String 要发送字符串的首地址
* 返 回 值:无
*/
void Serial_SendString(char *String)
{
uint8_t i;
for (i = 0; String[i] != '\0'; i ++)//遍历字符数组(字符串),遇到字符串结束标志位后停止
{
Serial_SendByte(String[i]); //依次调用Serial_SendByte发送每个字节数据
}
}
/**
* 函 数:次方函数(内部使用)
* 返 回 值:返回值等于X的Y次方
*/
uint32_t Serial_Pow(uint32_t X, uint32_t Y)
{
uint32_t Result = 1; //设置结果初值为1
while (Y --) //执行Y次
{
Result *= X; //将X累乘到结果
}
return Result;
}
/**
* 函 数:串口发送数字
* 参 数:Number 要发送的数字,范围:0~4294967295
* 参 数:Length 要发送数字的长度,范围:0~10
* 返 回 值:无
*/
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 + '0'); //依次调用Serial_SendByte发送每位数字
}
}
/**
* 函 数:使用printf需要重定向的底层函数
* 参 数:保持原始格式即可,无需变动
* 返 回 值:保持原始格式即可,无需变动
*/
int fputc(int ch, FILE *f)
{
Serial_SendByte(ch); //将printf的底层重定向到自己的发送字节函数
return ch;
}
/**
* 函 数:自己封装的prinf函数
* 参 数:format 格式化字符串
* 参 数:... 可变的参数列表
* 返 回 值:无
*/
void Serial_Printf(char *format, ...)
{
char String[100]; //定义字符数组
va_list arg; //定义可变参数列表数据类型的变量arg
va_start(arg, format); //从format开始,接收参数列表到arg变量
vsprintf(String, format, arg); //使用vsprintf打印格式化字符串和参数列表到字符数组中
va_end(arg); //结束变量arg
Serial_SendString(String); //串口发送字符数组(字符串)
}
🚩总结




