@TOC


一、通信接口

通信的目的:將一個設備的數據傳送到另一個設備,擴展硬件系統通信協議︰制定通信的規則,通信雙方按照協議規則進行數據收發

二、串口通信

STM32的USART串口通信_串口

串口是一種應用十分廣泛的通訊接口,串口成本低、容易使用、通信線路簡單,可實現兩個設備的互相通信 單片機的串口可以使單片機與單片機、單片機與電腦、單片機與各式各樣的模塊互相通信,極大地擴展了單片機的應用範圍,增強了單片機系統的硬件實力

三、USART通信協議

USART(通用同步/異步收發器)的通信協議是實現串口數據可靠傳輸的核心規則,異步模式(無時鐘線)是嵌入式開發中最常用的場景(同步模式僅在高精度/高速場景使用),下文將以異步USART協議為核心,從幀結構、時序、採樣機制、參數配置、錯誤處理等維度全面解析,同時補充同步模式的協議差異。

四、硬件電路

簡單雙向串口通信有兩根通信線(發送端TX和接收端RX)TX與RX要交叉連接當只需單向的數據傳輸時,可以只接一根通信線當電平標準不一致時,需要加電平轉換芯片

STM32的USART串口通信_串口_02

五、電平標準

電平標準是數據1和數據0的表達方式,是傳輸線纜中人為規定的電壓與數據的對應關係,串口常用的電平標準有如下三種:

TTL電平:+3.3V或+5V表示1,OV表示0RS232電平:-3~-15V表示1,+ 3~+ 15V表示0RS485電平:兩線壓差+2~+6V表示1,2~-6V表示0(差分信號)

六、串口參數及時序

波特率:串口通信的速率起始位: 標誌一個數據幀的開始,固定為低電平 數據位: 數據幀的有效載荷,1為高電平,0為低電平,低位先行 校驗位: 用於數據驗證,根據數據位計算得來 停止位: 用於數據幀間隔,固定為高電平

STM32的USART串口通信_數據_03

串口時序

STM32的USART串口通信_寄存器_04

七、USART協議核心定位

USART協議是一種串行、異步/同步、全雙工的通信協議,核心目標是:在無時鐘同步(異步)或有時鐘同步(同步)的前提下,讓收發雙方對“數據的起始/結束、數據內容、傳輸速率”達成一致,實現二進制數據的可靠傳輸。

  • 異步模式(Asynchronous):無專用時鐘線,靠“波特率預約定”+“幀格式規則”同步,是串口調試、上位機通信的主流方式;
  • 同步模式(Synchronous):新增時鐘線(SCLK),由主設備提供時鐘,收發嚴格同步,協議複雜度更低,但需額外接線。

下文重點解析異步USART協議(日常説的“串口協議”本質就是異步USART協議)。

STM32的USART串口通信_數據_05

STM32的USART串口通信_串口_06

STM32的USART串口通信_串口_07

理解過程

一、發送流程(從“CPU/DMA發數據”到“TX引腳輸出”)

  1. 寫數據到發送寄存器 CPU或DMA通過“PWDATA(寫操作)”,把要發送的並行數據寫入發送數據寄存器(TDR)
  2. 數據轉移到移位寄存器 TDR裏的並行數據會自動傳遞到發送移位寄存器
  3. 串行化+時序編碼 發送移位寄存器會根據配置的參數(波特率、數據位、停止位、校驗位):
  • 由**USART_BRR(波特率寄存器)**生成的發送時鐘控制速率;
  • CR1/CR2(控制寄存器)配置數據格式(8/9位、校驗、停止位);把並行數據轉換成串行比特流
  1. 輸出到外部
  • 普通串口模式:串行數據直接從TX引腳發送出去;
  • IrDA模式:串行數據先經過IrDA SIR編解碼模塊處理,再從IRDA_OUT引腳輸出。
  1. 狀態/中斷反饋 發送過程中,狀態寄存器(SR)會更新狀態(比如“TXE”表示發送寄存器空、“TC”表示發送完成);如果在CR1裏開啓了對應中斷(TXEIE/TCIE),會觸發USART中斷控制模塊,通知CPU處理。

二、接收流程(從“RX引腳輸入”到“CPU/DMA讀數據”)

  1. 外部數據輸入
  • 普通串口模式:外部串行數據從RX引腳輸入;
  • IrDA模式:外部數據從IRDA_IN引腳輸入,先經過IrDA SIR編解碼模塊處理。
  1. 串行轉並行 串行數據進入接收移位寄存器,移位寄存器會根據配置的波特率、數據格式,把串行比特流還原成並行數據。
  2. 數據存到接收寄存器 接收移位寄存器的並行數據會傳遞到接收數據寄存器(RDR)
  3. CPU/DMA讀取數據 CPU或DMA通過“PRDATA(讀操作)”,從RDR裏讀取接收到的並行數據。
  4. 狀態/中斷反饋 接收過程中,狀態寄存器(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引腳輸出的電平時序):

STM32的USART串口通信_數據_08

STM32的USART串口通信_數據_09

STM32的USART串口通信_寄存器_10

幀組成部分

電平狀態

位數/時長

核心作用

細節説明

空閒位

高電平(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)

STM32的USART串口通信_串口_11

  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. 先算DIVDIV = fPCLK / (16 × 波特率) = 72000000 / (16×9600) ≈ 468.75
  2. 拆成整數+小數:
  • 整數部分:468(填到DIV_Mantissa);
  • 小數部分:0.75 = 12/16(所以DIV_Fraction填12);
  1. 最終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協議定義了嚴格的採樣規則(硬件層面實現,屬於協議的一部分):

STM32的USART串口通信_數據_12

  1. 起始位檢測(協議防誤判規則)總線空閒為高電平,起始位是低電平(高→低下降沿),協議要求:
  2. 檢測到“高→低”下降沿後,啓動16倍採樣時鐘;
  3. 對起始位的16個採樣點分組驗證:每3個採樣點為一組,每組至少2個點為低電平;

  4. 連續多組驗證通過,才判定為“真起始位”(排除偶然噪聲),否則視為無效抖動。
  5. STM32的USART串口通信_串口_13

  6. 數據位採樣(協議精準採樣規則)確認起始位後,對後續數據位/校驗位/停止位的採樣規則:
  7. 採樣時機:選擇每1位的“中間區域”(第7~9個採樣點),避開位邊緣的抖動;
  8. 多數表決:對中間3個採樣點“投票”(3個點中2個一致則取該值),過濾噪聲;
  9. 停止位驗證:採樣停止位時,要求至少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模式/十六進制模式/二進制模式:以原始數據的形式顯示文本模式/字符模式:以原始數據編碼後的形式顯示

STM32的USART串口通信_寄存器_14


STM32的USART串口通信_寄存器_15

  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協議常用庫函數

// 恢復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

#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

#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
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);		//串口發送字符數組(字符串)
}

🚩總結

STM32的USART串口通信_寄存器_16