@TOC
一、通信接口
通信的目的:將一個設備的數據傳送到另一個設備,擴展硬件系統通信協議︰制定通信的規則,通信雙方按照協議規則進行數據收發
二、串口通信
串口是一種應用十分廣泛的通訊接口,串口成本低、容易使用、通信線路簡單,可實現兩個設備的互相通信 單片機的串口可以使單片機與單片機、單片機與電腦、單片機與各式各樣的模塊互相通信,極大地擴展了單片機的應用範圍,增強了單片機系統的硬件實力
三、USART通信協議
USART(通用同步/異步收發器)的通信協議是實現串口數據可靠傳輸的核心規則,異步模式(無時鐘線)是嵌入式開發中最常用的場景(同步模式僅在高精度/高速場景使用),下文將以異步USART協議為核心,從幀結構、時序、採樣機制、參數配置、錯誤處理等維度全面解析,同時補充同步模式的協議差異。
四、硬件電路
簡單雙向串口通信有兩根通信線(發送端TX和接收端RX)TX與RX要交叉連接當只需單向的數據傳輸時,可以只接一根通信線當電平標準不一致時,需要加電平轉換芯片
五、電平標準
電平標準是數據1和數據0的表達方式,是傳輸線纜中人為規定的電壓與數據的對應關係,串口常用的電平標準有如下三種:
TTL電平:+3.3V或+5V表示1,OV表示0RS232電平:-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協議常用庫函數
// 恢復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); //串口發送字符數組(字符串)
}