【STM32学习笔记】(9)——串口通讯(USART)详解
????????本文主要參考了野火的零死角玩轉STM32和正點原子的STM32F1 開發指南 V1.1(精英板-庫函數版本),文章中大部分知識都是從兩本書中提取出來,串口通信協議的知識主要參考野火的書籍,而串口實驗部分是根據正點原子的開發板和例程進行講解。
串口通訊協議簡介
????????串口通訊 (Serial Communication) 是一種設備間極為常用的串行通訊方式,目前多存在于工控機及部分通信設備中。
? ? ? ? 對于通訊協議,以分層的方式來理解,可以把它分為物理層和協議層。
????????物理層規定通訊系統中具有機械、電子功能部分的特性,確保原始數據在物理媒體的傳輸。
????????協議層主要規定通訊邏輯,統一收發雙方的數據打包、解包標準。簡單來說物理層規定我們用嘴巴還是用肢體來交流,協議層則規定我們用中文還是英文來交流。
串口通訊的物理層
????????串口通訊的物理層有很多標準及變種,串口通訊的物理層的主要標準是RS-232標準,其規定了信號的用途、通訊接口及信號的電平標準,其通訊結構如下:
????????在上面的通訊方式中,兩個通訊設備的“DB9接口”之間通過串口信號線建立起連接,串口信號線中使用“RS-232標準”傳輸數據信號。由于RS-232電平標準的信號不能直接被控制器直接識別,所以這些信號會經過一個“電平轉換芯片”轉換成控制器能識別的“TTL 標準”的電平信號,才能實現通訊。
1、信號的電平標準
????????在設備內部信號是以TTL電平標準傳輸的,設備之間是通過RS-232電平標準傳輸的,而且TTL電平需要經過電平轉換芯片才能轉化為RS-232電平,RS-232電平轉TTL電平也是如此。如圖是TTL電平標準與RS-232電平標準。
?
?
????????電子電路中常使用 TTL 的電平標準,理想狀態下,使用 5V 表示二進制邏輯 1, 使用 0V 表示邏輯 0;
????????為了增加串口通訊的遠距離傳輸及抗干擾能力,所以RS-232信號線,理想狀態下,使用-15V 表示邏輯 1, +15V 表示邏輯 0。
2、RS-232 信號線
????????在最初的應用中,RS-232 串口標準常用于計算機、路由與調制調解器 (MODEN,俗稱“貓”) 之間的通訊,在這種通訊系統中,設備被分為數據終端設備 DTE(計算機、路由) 和數據通訊設備DCE(調制調解器)。在舊式的臺式計算機中一般會有 RS-232 標準的 COM 口 (也稱 DB9 接口),下圖就是電腦主板上的 COM 口及串口線 。
?
?
????????其中接線口以針式引出信號線的稱為公頭,以孔式引出信號線的稱為母頭。在計算機中一般引出公頭接口,而在調制調解器設備中引出的一般為母頭,使用上圖中的串口線即可把它與計算機連接起來。通訊時,串口線中傳輸的信號就是使用前面講解的 RS-232 標準調制的。
????????DB9 接口中的公頭及母頭的各個引腳的標準信號線接法如下圖。?
???????
?
????????上表中的是計算機端的 DB9 公頭標準接法,由于兩個通訊設備之間的收發信號 (RXD 與 TXD) 應交叉相連,所以調制調解器端的 DB9 母頭的收發信號接法一般與公頭的相反。?
?
????????串口線中的 RTS、CTS、DSR、DTR 及 DCD 信號,使用邏輯 1 表示信號有效,邏輯 0 表示信號無效。
????????例如,當計算機端控制 DTR 信號線表示為邏輯 1 時,它是為了告知遠端的調制調解器,本機已準備好接收數據,0 則表示還沒準備就緒。
????????在目前的其它工業控制使用的串口通訊中,一般只使用 RXD、TXD 以及 GND 三條信號線,直接傳輸數據信號,而 RTS、CTS、DSR、DTR 及 DCD 信號都被裁剪掉了。
串口通訊的協議層
1、數據包
????????串口通訊的數據包由發送設備通過自身的 TXD 接口傳輸到接收設備的 RXD 接口。
????????在串口通訊的協議層中,規定了數據包的內容,它由啟始位、主體數據、校驗位以及停止位組成,通訊雙方的數據包格式要約定一致才能正常收發數據。
?
2、波特率
????????由于異步通信中沒有時鐘信號,所以接收雙方要約定好波特率,即每秒傳輸的碼元個數,以便對信號進行解碼,常見的波特率有4800、9600、115200等。STM32中波特率的設置通過串口初始化結構體來實現。
3、起始和停止信號
????????數據包的首尾分別是起始位和停止位,數據包的起始信號由一個邏輯0的數據位表示,停止位信號可由0.5、1、1.5、2個邏輯1的數據位表示,雙方需約定一致。STM32中起始和停止信號的設置也是通過串口初始化結構體來實現。
4、有效數據
????????在數據包的起始位之后緊接著的就是要傳輸的主體數據內容,也稱為有效數據,有效數據的長度常被約定為 5、6、7 或 8 位長。
5、數據校驗
????????在有效數據之后,有一個可選的數據校驗位。由于數據通信相對更容易受到外部干擾導致傳輸數據出現偏差,可以在傳輸過程加上校驗位來解決這個問題。
????????校驗方法有奇校驗 (odd)、偶校驗 (even)、0 校驗 (space)、1 校驗 (mark) 以及無校驗 (noparity)。
????????奇校驗要求有效數據和校驗位中“1”之和的個數為奇數,比如一個 8 位長的有效數據為:01101001, 此時總共有 4 個“1”,為達到奇校驗效果,校驗位為“1”,最后傳輸的數據將是 8 位的有效數據加上 1 位的校驗位總共 9 位。
????????偶校驗與奇校驗要求剛好相反,要求幀數據和校驗位中“1”之和的個數為偶數,比如數據幀:11001010, 此時數據幀“1”的個數為 4 個,所以偶校驗位為“0”。
????????0 校驗是不管有效數據中的內容是什么,校驗位總為“0”。
????????1 校驗是不管有效數據中的內容是什么,校驗位總為“1”。
STM32 的 USART 簡介
????????通用同步異步收發器 (Universal Synchronous Asynchronous Receiver and Transmitter) 是一個串行通信設備,可以靈活地與外部設備進行全雙工數據交換。
????????UART(Universal Asynchronous Receiver and Transmitter)跟 USART不一樣的是:它是在 USART 基礎上裁剪掉了同步通信功能,只有異步通信。簡單區分同步和異步就是看通信時需不需要對外提供時鐘輸出,我們平時用的串口通信基本都是UART。
????????串行通信一般是以幀格式傳輸數據,即是一幀一幀的傳輸,每幀包含有起始信號、數據信息、停止信號,可能還有校驗信息。USART 就是對這些傳輸參數有具體規定,當然也不是只有唯一一 個參數值,很多參數值都可以自定義設置,只是增強它的兼容性。
????????USART 滿足外部設備對工業標準 NRZ 異步串行數據格式的要求,并且使用了小數波特率發生器,可以提供多種波特率,使得它的應用更加廣泛。
????????USART不僅支持同步單向通信和半雙工單線通信,也支持LIN(局部互連網),智能卡協議和IrDA(紅外數據組織)SIR ENDEC規范,以及調制解調器(CTS/RTS)操作。它還允許多處理器通信。 USART 支持使用 DMA,可實現高速數據通信。
????????STM32在硬件設計時一般都會預留一個 USART 通信接口連接電腦,用于在調試程序是可以把一些調試信息“打印”在電腦端的串口調試助手工具上,從而來用串口調試助手來驗證自己的程序是否出了問題。
USART功能概述
????????接口通過三個引腳與其他設備連接在一起USART框圖。任何USART雙向通信至少需要兩個腳:接收數據輸入(RX)和發送數據輸出(TX)。
? ? ? ? ?RX:接收數據串行輸入。通過過采樣技術來區別數據和噪音,從而恢復數據。
????????TX:發送數據輸出。當發送器被禁止時,輸出引腳恢復到它的I/O端口配置。當發送器被激活,并且不發送數據時,TX引腳處于高電平。在單線和智能卡模式里,此I/O口被同時用于數據的發送和接收。
????????● 總線在發送或接收前應處于空閑狀態
????????● 一個起始位
????????● 一個數據字(8或9位),最低有效位在前
????????● 0.5,1.5,2個的停止位,由此表明數據幀的結束
????????● 使用分數波特率發生器 —— 12位整數和4位小數的表示方法。
????????● 一個狀態寄存器(USART_SR)
????????● 數據寄存器(USART_DR)
????????● 一個波特率寄存器(USART_BRR),12位的整數和4位小數
????????● 一個智能卡模式下的保護時間寄存器(USART_GTPR)
USART框圖
?
功能引腳
????????TX:發送數據輸出引腳。
????????RX:接收數據輸入引腳。
????????SW_RX:數據接收引腳,只用于單線和智能卡模式,屬于內部引腳,沒有具體外部引腳。
????????nRTS:請求以發送 (Request To Send),n 表示低電平有效。如果使能 RTS 流控制,當 USART 接收器準備好接收新數據時就會將 nRTS 變成低電平;當接收寄存器已滿時,nRTS 將被設置為高電平。該引腳只適用于硬件流控制。
????????nCTS:清除以發送 (Clear To Send),n 表示低電平有效。如果使能 CTS 流控制,發送器在發送下一幀數據之前會檢測 nCTS 引腳,如果為低電平,表示可以發送數據,如果為高電平則在發送完當前數據幀之后停止發送。該引腳只適用于硬件流控制。
????????SCLK:發送器時鐘輸出引腳。這個引腳僅適用于同步模式。
????????STM32F103ZET6 系統控制器有三個 USART 和兩個 UART,其中 USART1 和時鐘來源于 APB2 總線時鐘,其最大頻率為 72MHz,其他四個的時鐘來源于 APB1 總線時鐘,其最大頻率為 36MHz。 UART 只是異步傳輸功能,所以沒有 SCLK、nCTS 和 nRTS 功能引腳。?
?與USART有關的寄存器
USART寄存器地址映像
?
USART寄存器描述?
????????1、USART狀態控制器(USART_SR)
?
????????2、數據寄存器(USART_DR)
?
? ??????3、波特比率寄存器(USART_BRR)
????????4、控制寄存器 1(USART_CR1)
?
????????5、控制寄存器 2(USART_CR2)
?
????????6、控制寄存器 3(USART_CR3)
?
使用寄存器來配置USART?
數據寄存器
????????USART 數據寄存器 (USART_DR) 只有低 9 位有效,并且第 9 位數據是否有效要取決于 USART 控制寄存器 1(USART_CR1) 的 M 位設置,當 M 位為 0 時表示 8 位數據字長,當 M 位為 1 表示 9 位數據字長,我們一般使用 8 位數據字長。 USART_DR 包含了已發送的數據或者接收到的數據。USART_DR 實際是包含了兩個寄存器,一 個是專門用于發送的可寫 TDR,另一個是專門用于接收的可讀 RDR。當進行發送操作時,往USART_DR 寫入數據會自動存儲在 TDR 內;當進行讀取操作時,向 USART_DR 讀取數據會自動提取 RDR 數據。
????????TDR 和 RDR 都是介于系統總線和移位寄存器之間。串行通信是一個位緊接著一個位傳輸的。
????????發送時把 TDR 內容轉移到發送移位寄存器,然后把移位寄存器數據每一位發送出去;接收時把接收到的 每一位順序保存在接收移位寄存器內然后才轉移到 RDR。
????????USART 支持 DMA 傳輸,可以實現高速數據傳輸。
控制器
????????USART 有專門控制發送的發送器、控制接收的接收器,還有喚醒單元、中斷控制等等。使用 USART 之前需要向 USART_CR1 寄存器的 UE 位置 1 使能 USART,UE 位用來開啟供給給串口 的時鐘。
????????發送或者接收數據字長可選 8 位或 9 位,由 USART_CR1 的 M 位控制。
發送器
????????當 USART_CR1 寄存器的發送使能位 TE 置 1 時,啟動數據發送,發送移位寄存器的數據會在 TX 引腳輸出,低位在前,高位在后。如果是同步模式 SCLK 也輸出時鐘信號。
????????一個字符幀發送需要三個部分:起始位 + 數據幀 + 停止位。起始位是一個位周期的低電平,位周期就是每一位占用的時間;數據幀就是我們要發送的 8 位或 9 位數據,數據是從最低位開始傳輸的;停止位是一定時間周期的高電平。
????????停止位時間長短是可以通過 USART 控制寄存器 2(USART_CR2) 的 STOP[1:0] 位控制,可選 0.5 個、1 個、1.5 個和 2 個停止位。默認使用 1 個停止位。2 個停止位適用于正常 USART 模式、單線模式和調制解調器模式。0.5 個和 1.5 個停止位用于智能卡模式。 當選擇 8 位字長,使用 1 個停止位時,具體發送字符時序圖見圖字符發送時序圖 。
????????例如:當選擇 8 位字長,使用 1 個停止位時,具體發送字符時序圖見圖字符發送時序圖 。
????????當發送使能位 TE 置 1 之后,發送器開始會先發送一個空閑幀 (一個數據幀長度的高電平),接下來就可以往 USART_DR 寄存器寫入要發送的數據。在寫入最后一個數據后,需要等待 USART 狀態寄存器 (USART_SR) 的 TC 位為 1,表示數據傳輸完成,如果 USART_CR1 寄存器的 TCIE 位置 1,將產生中斷。
????????在發送數據時,編程的時候有幾個比較重要的標志位我們來總結下。
?
接收器
????????如果將 USART_CR1 寄存器的 RE 位置 1,使能 USART 接收,使得接收器在 RX 線開始搜索起始位。在確定到起始位后就根據 RX 線電平狀態把數據存放在接收移位寄存器內。接收完成后就把接收移位寄存器數據移到 RDR 內,并把 USART_SR 寄存器的 RXNE 位置 1,同時如果 USART_CR2 寄存器的 RXNEIE 置 1 的話可以產生中斷。
????????在接收數據時,編程的時候有幾個比較重要的標志位我們來總結下。
?
?
USARTDIV的計算
????????波特率指數據信號對載波的調制速率,它用單位時間內載波調制狀態改變次數來表示,單位為比特。比特率指單位時間內傳輸的比特數,單位 bit/s(bps)。對于 USART 波特率與比特率相等,所以可以將波特率和比特率作為同一個概念。波特率越大,傳輸速率越快。 USART 的發送器和接收器需要使用相同的波特率。
????????計算公式如下:
????????其中,fPLCK為 USART 時鐘,USARTDIV 是一個存放在波特率寄存器 (USART_BRR) 的一個無符號定點數。其中 DIV_Mantissa[11:0] 位定義 USARTDIV 的整數部分,DIV_Fraction[3:0] 位定義USARTDIV 的小數部分。
????????例如:DIV_Mantissa=24(0x18),DIV_Fraction=10(0x0A),此時 USART_BRR 值為 0x18A;那么 USARTDIV 的小數位 10/16=0.625;整數位 24,最終 USARTDIV 的值為 24.625。
????????如果知道 USARTDIV 值為 27.68,那么 DIV_Fraction=16*0.68=10.88,最接近的正整數為 11,所以 DIV_Fraction[3:0] 為 0xB;DIV_Mantissa= 整數 (27.68)=27,即為 0x1B。
????????波特率的常用值有 2400、9600、19200、115200。下面以實例講解如何設定寄存器值得到波特率的值。
????????我們知道 USART1 使用 APB2 總線時鐘,最高可達 72MHz,其他 USART 的最高頻率為 36MHz。
????????我們選取 USART1 作為實例講解,即fPLCK=72MHz。為得到 115200bps 的波特率,此時:
? ? ? ????????????????????????????????????????????????????115200?=? ?
?
? 解得USARTDIV=39.0625,可算得 DIV_Fraction=0.0625*16=1=0x01,DIV_Mantissa=39=0x27,即應該設置 USART_BRR 的值為 0x271。
USART校驗控制
????????STM32F103 系列控制器 USART 支持奇偶校驗。當使用校驗位時,串口傳輸的長度將是 8 位的數據幀加上 1 位的校驗位總共 9 位,此時 USART_CR1 寄存器的 M 位需要設置為 1,即 9 數據位。
????????將 USART_CR1 寄存器的 PCE 位置 1 就可以啟動奇偶校驗控制,奇偶校驗由硬件自動完成。啟動了奇偶校驗控制之后,在發送數據幀時會自動添加校驗位,接收數據時自動驗證校驗位。接收數據時如果出現奇偶校驗位驗證失敗,會見 USART_SR 寄存器的 PE 位置 1,并可以產生奇偶校驗中斷。
????????使能了奇偶校驗控制后,每個字符幀的格式將變成:起始位 + 數據幀 + 校驗位 + 停止位。
?
中斷控制
與USART配置有關的固件庫函數
USART 初始化結構體
????????標準庫函數對每個外設都建立了一個初始化結構體,比如 USART_InitTypeDef,結構體成員用于 設置外設工作參數,并由外設初始化配置函數,比如 USART_Init() 調用,這些設定參數將會設置 外設相應的寄存器,達到配置外設工作環境的目的。
????????初始化結構體定義在 stm32f10x_usart.h 文件中,初始化庫函數定義在 stm32f10x_usart.c 文件中。
typedef struct {uint32_t USART_BaudRate; // 波特率uint16_t USART_WordLength; // 字長uint16_t USART_StopBits; // 停止位uint16_t USART_Parity; // 校驗位uint16_t USART_Mode; // USART 模式uint16_t USART_HardwareFlowControl; // 硬件流控制 } USART_InitTypeDef;????????●USART_BaudRate:波特率設置。
????????一般設置為 2400、9600、19200、115200。標準庫函數會根據設定值計算得到 USARTDIV 值,從而設置 USART_BRR 寄存器值。
????????●USART_WordLength:數據幀字長,可選 8 位或 9 位。
????????它設定 USART_CR1 寄存器的 M 位的值。如果沒有使能奇偶校驗控制,一般使用 8 數據位;如果使能了奇偶校驗則一般設置為 9 數據位。
#define USART_WordLength_8b ((uint16_t)0x0000) #define USART_WordLength_9b ((uint16_t)0x1000)#define IS_USART_WORD_LENGTH(LENGTH) (((LENGTH) == USART_WordLength_8b) || \((LENGTH) == USART_WordLength_9b))????????● USART_StopBits:停止位設置。
????????停止位可選 0.5 個、1 個、1.5 個和 2 個停止位,它設定 USART_CR2寄存器的 STOP[1:0] 位的值,一般我們選擇 1 個停止位。
#define USART_StopBits_1 ((uint16_t)0x0000) #define USART_StopBits_0_5 ((uint16_t)0x1000) #define USART_StopBits_2 ((uint16_t)0x2000) #define USART_StopBits_1_5 ((uint16_t)0x3000) #define IS_USART_STOPBITS(STOPBITS) (((STOPBITS) == USART_StopBits_1) || \((STOPBITS) == USART_StopBits_0_5) || \((STOPBITS) == USART_StopBits_2) || \((STOPBITS) == USART_StopBits_1_5))????????● USART_Parity:奇偶校驗控制選擇。
????奇偶校驗位可選 USART_Parity_No(無校驗)、USART_Parity_Even(偶校驗) 以及 USART_Parity_Odd(奇校驗),它設定 USART_CR1 寄存器的 PCE 位和 PS 位的值。
#define USART_Parity_No ((uint16_t)0x0000) #define USART_Parity_Even ((uint16_t)0x0400) #define USART_Parity_Odd ((uint16_t)0x0600) #define IS_USART_PARITY(PARITY) (((PARITY) == USART_Parity_No) || \((PARITY) == USART_Parity_Even) || \((PARITY) == USART_Parity_Odd))????????● USART_Mode:USART 模式選擇。
????????USART的模式有 USART_Mode_Rx 和 USART_Mode_Tx,允許使用邏輯或運算選擇兩個,它設定 USART_CR1 寄存器的 RE 位和 TE 位。
#define USART_Mode_Rx ((uint16_t)0x0004) #define USART_Mode_Tx ((uint16_t)0x0008) #define IS_USART_MODE(MODE) ((((MODE) & (uint16_t)0xFFF3) == 0x00) && ((MODE) != (uint16_t)0x00))????????●USART_HardwareFlowControl:硬件流控制選擇。
#define USART_HardwareFlowControl_None ((uint16_t)0x0000) #define USART_HardwareFlowControl_RTS ((uint16_t)0x0100) #define USART_HardwareFlowControl_CTS ((uint16_t)0x0200) #define USART_HardwareFlowControl_RTS_CTS ((uint16_t)0x0300) #define IS_USART_HARDWARE_FLOW_CONTROL(CONTROL)\(((CONTROL) == USART_HardwareFlowControl_None) || \((CONTROL) == USART_HardwareFlowControl_RTS) || \((CONTROL) == USART_HardwareFlowControl_CTS) || \((CONTROL) == USART_HardwareFlowControl_RTS_CTS))?
USART 時鐘初始化結構體
?
typedef struct {uint16_t USART_Clock; // 時鐘使能控制uint16_t USART_CPOL; // 時鐘極性uint16_t USART_CPHA; // 時鐘相位uint16_t USART_LastBit; // 最尾位時鐘脈沖 } USART_ClockInitTypeDef;????????● USART_Clock:同步模式下 SCLK 引腳上時鐘輸出使能控制。
????????可選禁止時鐘輸出 (USART_Clock_Disable) 或開啟時鐘輸出 (USART_Clock_Enable);如果使用同步模式發送,一般都需要開啟時鐘。它設定 USART_CR2 寄存器的 CLKEN 位的值。
#define USART_Clock_Disable ((uint16_t)0x0000) #define USART_Clock_Enable ((uint16_t)0x0800) #define IS_USART_CLOCK(CLOCK) (((CLOCK) == USART_Clock_Disable) || \((CLOCK) == USART_Clock_Enable))????????● USART_CPOL:同步模式下 SCLK 引腳上輸出時鐘極性設置。
? ? ? 可設置在空閑時 SCLK 引腳為低電平 (USART_CPOL_Low) 或高電平 (USART_CPOL_High)。它設定 USART_CR2 寄存器的 CPOL位的值。
#define USART_CPOL_Low ((uint16_t)0x0000) #define USART_CPOL_High ((uint16_t)0x0400) #define IS_USART_CPOL(CPOL) (((CPOL) == USART_CPOL_Low) || ((CPOL) == USART_CPOL_High))????????● USART_CPHA:同步模式下 SCLK 引腳上輸出時鐘相位設置。
????????可設置在時鐘第一個變化沿捕獲數據 (USART_CPHA_1Edge) 或在時鐘第二個變化沿捕獲數據。它設定 USART_CR2 寄存器的CPHA 位的值。USART_CPHA 與 USART_CPOL 配合使用可以獲得多種模式時鐘關系。
#define USART_CPHA_1Edge ((uint16_t)0x0000) #define USART_CPHA_2Edge ((uint16_t)0x0200) #define IS_USART_CPHA(CPHA) (((CPHA) == USART_CPHA_1Edge) || ((CPHA) == USART_CPHA_2Edge))????????● USART_LastBit:選擇在發送最后一個數據位的時候時鐘脈沖是否在 SCLK 引腳輸出。
????????可以是不輸出脈沖 (USART_LastBit_Disable)、輸出脈沖 (USART_LastBit_Enable)。它設定 USART_CR2 寄存器的 LBCL 位的值。
#define USART_LastBit_Disable ((uint16_t)0x0000) #define USART_LastBit_Enable ((uint16_t)0x0100) #define IS_USART_LASTBIT(LASTBIT) (((LASTBIT) == USART_LastBit_Disable) || \((LASTBIT) == USART_LastBit_Enable))?
USART文件夾介紹
?
? ? ? ? ? ? usart 文件夾內包含了 usart.c 和 usart.h 兩個文件。這兩個文件用于串口的初始化和中斷接收。這里只是針對串口 1,比如你要用串口 2 或者其他的串口,只要對代碼稍作修改就可以了。
????????usart.c里面包含了2個函數一個是void USART1_IRQHandler(void);另外一個是void uart_init(u32 bound);里面還有一段對串口 printf 的支持代碼,如果去掉,則會導致 printf 無法使用,雖然軟件編譯不會報錯,但是硬件上 STM32 是無法啟動的,這段代碼不要去修改。
printf 函數
????????這段引入 printf 函數支持的代碼在 usart.h 頭文件的最上方,這段代碼加入之后便可以通過 printf 函數向串口發送我們需要的內容,方便開發過程中查看代碼執行情況以及一些變量值。這 段代碼不需要修改,引入到 usart.h 即可。
????????這段代碼為:
// //加入以下代碼,支持printf函數,而不需要選擇use MicroLIB #if 1 #pragma import(__use_no_semihosting) //標準庫需要的支持函數 struct __FILE { int handle; }; FILE __stdout; //定義_sys_exit()以避免使用半主機模式 _sys_exit(int x) { x = x; } //重定義fputc函數 int fputc(int ch, FILE *f) { while((USART1->SR&0X40)==0);//循環發送,直到發送完畢 USART1->DR = (u8) ch; return ch; } #endif?
uart_init函數
????????串口設置的一般步驟可以總結為如下幾個步驟:
????????1) 串口時鐘使能,GPIO 時鐘使能
????????2) 串口復位
????????3) GPIO 端口模式設置
????????4) 串口參數初始化
????????5) 開啟中斷并且初始化 NVIC(如果需要開啟中斷才需要這個步驟)
????????6) 使能串口
????????7) 編寫中斷處理函數
????????下面,我們就簡單介紹下這幾個與串口基本配置直接相關的幾個固件庫函數。
????????這些函數和定義主要分布在 stm32f10x_usart.h 和 stm32f10x_usart.c 文件中。
1.串口時鐘使能。
????????串口是掛載在 APB2 下面的外設,所以使能函數為:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1);?
2.串口復位。
????????當外設出現異常的時候可以通過復位設置,實現該外設的復位,然后重新配置這個外設達到讓其重新工作的目的。一般在系統剛開始配置外設的時候,都會先執行復位該外設的操作。
????????復位的是在函數 USART_DeInit()中完成:
void USART_DeInit(USART_TypeDef* USARTx);//串口復位????????比如我們要復位串口 1,方法為:
USART_DeInit(USART1); //復位串口 1?
3.串口參數初始化。
????????串口初始化是通過 USART_Init()函數實現的:
void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct);????????這個函數的第一個入口參數是指定初始化的串口標號,這里選擇 USART1。
????????第二個入口參數是一個 USART_InitTypeDef 類型的結構體指針,這個結構體指針的成員變量用來設置串口的一些參數。一般的實現格式為:
USART_InitStructure.USART_BaudRate = bound; //波特率設置; USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字長為 8 位數據格式 USART_InitStructure.USART_StopBits = USART_StopBits_1; //一個停止位 USART_InitStructure.USART_Parity = USART_Parity_No; //無奇偶校驗位 USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //無硬件數據流控制 USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收發模式 USART_Init(USART1, &USART_InitStructure); //初始化串口????????從上面的初始化格式可以看出初始化需要設置的參數為:波特率,字長,停止位,奇偶校驗位,硬件數據流控制,模式(收,發)。我們可以根據需要設置這些參數。
4.數據發送與接收。
????????STM32 的發送與接收是通過數據寄存器 USART_DR 來實現的,這是一個雙寄存器,包含了 TDR 和 RDR。當向該寄存器寫數據的時候,串口就會自動發送,當收到數據的時候,也是存在該寄存器內。
????????STM32 庫函數操作 USART_DR 寄存器發送數據的函數是:
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);????????通過該函數向串口寄存器 USART_DR 寫入一個數據。
????????STM32 庫函數操作 USART_DR 寄存器讀取串口接收到的數據的函數是:
uint16_t USART_ReceiveData(USART_TypeDef* USARTx);????????通過該函數可以讀取串口接受到的數據。
5.串口狀態。
????????串口的狀態可以通過狀態寄存器 USART_SR 讀取。USART_SR 的各位描述如下圖所示。
????????RXNE(讀數據寄存器非空),當該位被置 1 的時候,就是提示已經有數據被接收到了,并且可以讀出來了。這時候我們要做的就是盡快去讀取 USART_DR,通過讀 USART_DR 可以將該位清零,也可以向該位寫 0,直接清除。
????????TC(發送完成),當該位被置位的時候,表示 USART_DR 內的數據已經被發送完成了。如果設置了這個位的中斷,則會產生中斷。該位也有兩種清零方式:
????????1)讀 USART_SR,寫USART_DR。
????????2)直接向該位寫 0。
????????在我們固件庫函數里面,讀取串口狀態的函數是:
FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG);????????這個函數的第二個入口參數非常關鍵,它是標示我們要查看串口的哪種狀態,比如上面講解的RXNE(讀數據寄存器非空)以及 TC(發送完成)。例如:
????????我們要判斷讀寄存器是否非空(RXNE),操作庫函數的方法是:
USART_GetFlagStatus(USART1, USART_FLAG_RXNE);????????我們要判斷發送是否完成(TC),操作庫函數的方法是:
USART_GetFlagStatus(USART1, USART_FLAG_TC);????????這些標識號在 MDK 里面是通過宏定義定義的:
#define USART_IT_PE ((uint16_t)0x0028) #define USART_IT_TXE ((uint16_t)0x0727) #define USART_IT_TC ((uint16_t)0x0626) #define USART_IT_RXNE ((uint16_t)0x0525) #define USART_IT_IDLE ((uint16_t)0x0424) #define USART_IT_LBD ((uint16_t)0x0846) #define USART_IT_CTS ((uint16_t)0x096A) #define USART_IT_ERR ((uint16_t)0x0060) #define USART_IT_ORE ((uint16_t)0x0360) #define USART_IT_NE ((uint16_t)0x0260) #define USART_IT_FE ((uint16_t)0x0160)?
6.串口使能。
????????串口使能是通過函數 USART_Cmd()來實現的,這個很容易理解,使用方法是:
USART_Cmd(USART1, ENABLE); //使能串口?
7.開啟串口響應中斷。
????????有些時候當我們還需要開啟串口中斷,那么我們還需要使能串口中斷,使能串口中斷的函數是:
void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT, FunctionalState NewState)????????這個函數的第二個入口參數是標示使能串口的類型,也就是使能哪種中斷,因為串口的中斷類型有很多種。比如在接收到數據的時候(RXNE 讀數據寄存器非空),我們要產生中斷,那么我們開啟中斷的方法是:
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//開啟中斷,接收到數據中斷????????我們在發送數據結束的時候(TC,發送完成)要產生中斷,那么方法是:
USART_ITConfig(USART1,USART_IT_TC,ENABLE);?
8.獲取相應中斷狀態。
????????當我們使能了某個中斷的時候,當該中斷發生了,就會設置狀態寄存器中的某個標志位。經常我們在中斷處理函數中,要判斷該中斷是哪種中斷,使用的函數是:
ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT)????????比如我們使能了串口發送完成中斷,那么當中斷發生了, 我們便可以在中斷處理函數中調用這個函數來判斷到底是否是串口發送完成中斷,方法是:
USART_GetITStatus(USART1, USART_IT_TC)????????返回值是 SET,說明是串口發送完成中斷發生。
串口實驗設計
硬件設計
????????本實驗需要用到的硬件資源有:
????????1) 串口 1
????????串口 1 之前還沒有介紹過,本實驗用到的串口 1 與 USB 串口并沒有在 PCB 上連接在一起,需要通過跳線帽來連接一下。這里我們把 P6 的 RXD 和 TXD 用跳線帽與 PA9 和 PA10 連接起來。
????????連接上這里之后,我們在硬件上就設置完成了,可以開始軟件設計了。?
軟件設計
????????代碼如下:
//初始化 IO 串口 1 //bound:波特率 void uart_init(u32 bound) {GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;NVIC_InitTypeDef NVIC_InitStructure;//①串口時鐘使能,GPIO 時鐘使能,復用時鐘使能RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE); //使能 USART1,GPIOA 時鐘//②串口復位USART_DeInit(USART1); //復位串口 1//③GPIO 端口模式設置GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //ISART1_TX PA.9GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //復用推挽輸出GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化 GPIOA.9GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; //USART1_RX PA.10GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空輸入GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化 GPIOA.10//④串口參數初始化USART_InitStructure.USART_BaudRate = bound; //波特率設置USART_InitStructure.USART_WordLength = USART_WordLength_8b; //字長為 8 位USART_InitStructure.USART_StopBits = USART_StopBits_1; //一個停止位USART_InitStructure.USART_Parity = USART_Parity_No; //無奇偶校驗位USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //無硬件數據流控制USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;//收發模式USART_Init(USART1, &USART_InitStructure); //初始化串口#if EN_USART1_RX //如果使能了接收//⑤初始化 NVICNVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ; //搶占優先級 3NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子優先級 3NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ 通道使能NVIC_Init(&NVIC_InitStructure); //中斷優先級初始化//⑤開啟中斷USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); //開啟中斷#endif//⑥使能串口USART_Cmd(USART1, ENABLE); //使能串口 }????????此代碼的編寫順序即USART的配置順序:
????????我們用標號①~⑥標 示了順序:
????????① 串口時鐘使能,GPIO 時鐘使能
????????② 串口復位
????????③ GPIO 端口模式設置
????????④ 串口參數初始化
????????⑤ 初始化 NVIC 并且開啟中斷
????????⑥ 使能串口
????????配置全雙工的串口 1,那么 TX(PA9) 管腳需要配置為推挽復用輸出,RX(PA10)管腳配置為浮空輸入或者帶上拉輸入。
????????模式配置參考下面表格:
????????使用了串口的中斷接收,必須在 usart.h 里面設置 EN_USART1_RX 為 1(默認設置就是 1 的) 。該函數才會配置中斷使能,以及開啟串口 1 的 NVIC 中斷。這里我們把串口 1 中斷放在組 2,優先級設置為組 2 里面的最低。
????????接下來,根據之前講解的步驟 7,還要編寫中斷服務函數。串口 1 的中斷服務函數 USART1_IRQHandler。
?USART1_IRQHandler 函數
????????void USART1_IRQHandler(void)函數是串口 1 的中斷響應函數,當串口 1 發生了相應的中斷后,就會跳到該函數執行。中斷相應函數的名字是不能隨便定義的,一般我們都遵循 MDK定義的函數名。這些函數名字在啟動文件 startup_stm32f10x_hd.s 文件中可以找到。
????????函數體里面通過函數:
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)????????判斷是否接受中斷,如果是串口接受中斷,則讀取串口接受到的數據:
Res =USART_ReceiveData(USART1);//(USART1->DR); //讀取接收到的數據????????讀到數據后接下來就對數據進行分析。
????????這里我們設計了一個小小的接收協議:通過這個函數,配合一個數組 USART_RX_BUF[],一個接收狀態寄存器 USART_RX_STA(此寄存器其實就是一個全局變量,由作者自行添加。由于它起到類似寄存器的功能,這里暫且稱之為寄存器)實現對串口數據的接收管理。
????????USART_RX_BUF 的大小由 USART_REC_LEN 定義,也就是一次接收的數據最大不能超過USART_REC_LEN 個字節。USART_RX_STA 是一個接收狀態寄存器其各的定義如表 5.3.1.1 所示:
?
設計思路如下:
????????當接收到從電腦發過來的數據,把接收到的數據保存在 USART_RX_BUF 中,同時在接收狀態寄存器(USART_RX_STA)中計數接收到的有效數據個數,當收到回車(回車的表示由 2個字節組成:0X0D 和 0X0A)的第一個字節 0X0D 時,計數器將不再增加,等待 0X0A 的到來,而如果 0X0A 沒有來到,則認為這次接收失敗,重新開始下一次接收。
????????如果順利接收到 0X0A,則標記 USART_RX_STA 的第 15 位,這樣完成一次接收,并等待該位被其他程序清除,從而開始下一次的接收,而如果遲遲沒有收到 0X0D,那么在接收數據超過 USART_REC_LEN 的時候,則會丟棄前面的數據,重新接收。
????????中斷相應函數代碼如下:
void USART1_IRQHandler(void) //串口1中斷服務程序{u8 Res; if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中斷(接收到的數據必須是0x0d 0x0a結尾){Res =USART_ReceiveData(USART1); //讀取接收到的數據if((USART_RX_STA&0x8000)==0)//接收未完成{if(USART_RX_STA&0x4000)//接收到了0x0d{if(Res!=0x0a)USART_RX_STA=0;//接收錯誤,重新開始else USART_RX_STA|=0x8000; //接收完成了 }else //還沒收到0X0D{ if(Res==0x0d)USART_RX_STA|=0x4000;else{USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;USART_RX_STA++;if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收數據錯誤,重新開始接收 } }} } }?
?main.c中的代碼
int main(void){ u16 t; u16 len; u16 times=0;delay_init(); //延時函數初始化 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //設置NVIC中斷分組2:2位搶占優先級,2位響應優先級uart_init(115200); //串口初始化為115200LED_Init(); //LED端口初始化KEY_Init(); //初始化與按鍵連接的硬件接口while(1){if(USART_RX_STA&0x8000){ len=USART_RX_STA&0x3fff;//得到此次接收到的數據長度printf("\r\n您發送的消息為:\r\n\r\n");for(t=0;t<len;t++){USART_SendData(USART1, USART_RX_BUF[t]);//向串口1發送數據while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);//等待發送結束}printf("\r\n\r\n");//插入換行USART_RX_STA=0;}else{times++;if(times%5000==0){printf("\r\n精英STM32開發板 串口實驗\r\n");printf("正點原子@ALIENTEK\r\n\r\n");}if(times%200==0)printf("請輸入數據,以回車鍵結束\n"); if(times%30==0)LED0=!LED0;//閃爍LED,提示系統正在運行.delay_ms(10); }} }????????NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2)函數, 該函數是設置中斷分組號為 2,也就是 2 位搶占優先級和 2 位子優先級。
USART_SendData(USART1, USART_RX_BUF[t]); //向串口 1 發送數據while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);????????第一句,其實就是發送一個字節到串口。
????????第二句,就是我們在我們發送一個數據到串口之后,要檢測這個數據是否已經被發送完成了。USART_FLAG_TC 是宏定義的數據發送完成標識符。
下載驗證
?????? 首先打開串口調試助手。任何一個串口調試助手都是可以的。正點原子中使用的是舊版本的XCOM2.0。
?????????我們在程序上 面設置了必須輸入回車,串口才認可接收到的數據,所以必須在發送數據后再發送一個回車符, 這里 XCOM 提供的發送方法是通過勾選發送新行實現。
?????????只要勾選了這個選項,每次發送數據后,XCOM 都會自動多發一個回車(0X0D+0X0A)。設置好了發送新行,我們再在發送區輸入你想要發送的文字,然后單擊發送,就能實現發送數據。
?????????發送的數據被打印出來了,說明實驗成功。
總結
以上是生活随笔為你收集整理的【STM32学习笔记】(9)——串口通讯(USART)详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 金蝶显示云服务器已离线,金蝶云服务器已离
- 下一篇: 计算机管理主分区改成逻辑分区,win10