【STM32】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引脚输出")

  1. 写数据到发送寄存器

    CPU或DMA通过"PWDATA(写操作)",把要发送的并行数据写入发送数据寄存器(TDR)

  2. 数据转移到移位寄存器

    TDR里的并行数据会自动传递到发送移位寄存器

  3. 串行化+时序编码

    发送移位寄存器会根据配置的参数(波特率、数据位、停止位、校验位):

    • 由**USART_BRR(波特率寄存器)**生成的发送时钟控制速率;
    • CR1/CR2(控制寄存器)配置数据格式(8/9位、校验、停止位);
      把并行数据转换成
      串行比特流
  4. 输出到外部

    • 普通串口模式:串行数据直接从TX引脚发送出去;
    • IrDA模式:串行数据先经过IrDA SIR编解码模块 处理,再从IRDA_OUT引脚输出。
  5. 状态/中断反馈

    发送过程中,状态寄存器(SR)会更新状态(比如"TXE"表示发送寄存器空、"TC"表示发送完成);
    如果在
    CR1
    里开启了对应中断(TXEIE/TCIE),会触发USART中断控制模块,通知CPU处理。

二、接收流程(从"RX引脚输入"到"CPU/DMA读数据")

  1. 外部数据输入

    • 普通串口模式:外部串行数据从RX引脚输入;
    • IrDA模式:外部数据从IRDA_IN引脚 输入,先经过IrDA SIR编解码模块处理。
  2. 串行转并行

    串行数据进入接收移位寄存器,移位寄存器会根据配置的波特率、数据格式,把串行比特流还原成并行数据。

  3. 数据存到接收寄存器

    接收移位寄存器的并行数据会传递到接收数据寄存器(RDR)

  4. CPU/DMA读取数据

    CPU或DMA通过"PRDATA(读操作)",从RDR里读取接收到的并行数据。

  5. 状态/中断反馈

    接收过程中,状态寄存器(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)为例:

  1. 空闲位:TX引脚持续高电平;
  2. 起始位:1位低电平(标志帧开始);
  3. 数据位:8位,低位先发→传输顺序为1(b0)→0(b1)→0(b2)→0(b3)→0(b4)→0(b5)→1(b6)→0(b7)
  4. 校验位:无;
  5. 停止位:1位高电平(标志帧结束);
  6. 帧结束后,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)

  1. 波特率(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:

  1. 先算DIV
    DIV = fPCLK / (16 × 波特率) = 72000000 / (16×9600) ≈ 468.75
  2. 拆成整数+小数:
    • 整数部分:468(填到DIV_Mantissa);
    • 小数部分:0.75 = 12/16(所以DIV_Fraction填12);
  3. 最终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协议定义了严格的采样规则(硬件层面实现,属于协议的一部分):

  1. 起始位检测(协议防误判规则)

    总线空闲为高电平,起始位是低电平(高→低下降沿),协议要求:

  2. 检测到"高→低"下降沿后,启动16倍采样时钟;

  3. 对起始位的16个采样点分组验证:每3个采样点为一组,每组至少2个点为低电平;

  4. 连续多组验证通过,才判定为"真起始位"(排除偶然噪声),否则视为无效抖动。

  5. 数据位采样(协议精准采样规则)

    确认起始位后,对后续数据位/校验位/停止位的采样规则:

  6. 采样时机:选择每1位的"中间区域"(第7~9个采样点),避开位边缘的抖动;

  7. 多数表决:对中间3个采样点"投票"(3个点中2个一致则取该值),过滤噪声;

  8. 停止位验证:采样停止位时,要求至少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协议的变体,核心差异是"时钟同步",协议规则简化:

  1. 硬件新增:SCLK引脚(时钟线),由主设备输出时钟,从设备同步采样;
  2. 帧结构变化:无起始位/停止位(时钟已同步),数据位连续传输;
  3. 波特率:时钟频率=波特率(无需16倍采样),传输效率更高;
  4. 适用场景:高速/高精度通信(如与SPI外设兼容通信),需额外接线(SCLK)。

USART协议的错误类型(协议层面的异常处理)

USART硬件会按协议规则检测传输异常,通过状态寄存器(SR)标识错误:

  1. 帧错误(FE):停止位采样为低电平(未检测到高电平停止位),原因:帧格式不匹配/波特率误差过大;
  2. 校验错误(PE):接收端计算的校验位与发送端不一致,原因:数据传输过程中被干扰;
  3. 溢出错误(ORE):接收寄存器(RDR)未读取,新数据已存入,旧数据被覆盖,原因:CPU处理速度慢于接收速率;
  4. 噪声错误(NE):采样数据位时检测到异常抖动,由16倍采样机制识别。

九、数据模式

HEX模式/十六进制模式/二进制模式:以原始数据的形式显示文本模式/字符模式:以原始数据编码后的形式显示

  1. 两种数据模式的区别
  • HEX/十六进制/二进制模式 :显示"原始数据"(就是实际传输的二进制/十六进制数值),不管这个数值对应什么字符。

    比如传输的是0x41,HEX模式下就显示0x41

  • 文本/字符模式 :把"原始数据"按照编码规则(比如ASCII码)转换成可读的字符显示。

    比如0x41对应ASCII表的字符'A',文本模式下就显示A

  1. 结合ASCII表+通信流程理解
    左边的表格是ASCII编码表 :每个字符(比如A)都对应一个唯一的十进制/十六进制数值(A对应65 DEC、0x41 HEX)。

右边的流程是实际通信过程:

  • 发送方想发字符'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);		//串口发送字符数组(字符串)
}

🚩总结

相关推荐
小白学大数据8 小时前
Python 多线程爬取社交媒体品牌反馈数据
开发语言·python·媒体
做一道光8 小时前
电机控制——电流采样(双电阻)
笔记·单片机·嵌入式硬件·电机控制
祝余Eleanor8 小时前
Day 31 类的定义和方法
开发语言·人工智能·python·机器学习
fish_xk8 小时前
c++基础扩展
开发语言·c++
阿沁QWQ8 小时前
C++继承
开发语言·c++
老华带你飞8 小时前
汽车销售|汽车报价|基于Java汽车销售系统(源码+数据库+文档)
java·开发语言·数据库·vue.js·spring boot·后端·汽车
lsx2024069 小时前
SQL LCASE() 函数详解
开发语言
4311媒体网9 小时前
C语言实现简单的二分查找算法
c语言·开发语言·算法
无限进步_9 小时前
C语言实现贪吃蛇游戏详解
c语言·开发语言·数据结构·c++·后端·算法·游戏