1 前言
USART全稱universal synchronous asynchronous receiver transmitter通用同步異步接收發送器;速率最高可達4.5Mbits/s,波特率460800;
數據按位順序發送的串行通信接口簡稱串口,USART模塊是采用串行通信接口最常見的模塊,為了方便,就把USART簡稱為串口;
USART接口通過RX,TX,GND同其他設備相連;當TX引腳被禁止時,該引腳恢復GPIO的配置;當TX引腳使能且未發送數據時,該引腳處於高電平(空閑態);
USART接口的數據字長度可編程,停止位長度可編程;可配置為DMA多緩沖通信;
2 USART的幀格式
串口數據應該遵循USART幀的格式,才能被串口識別;
首先總線需要持續至少一個空閑幀,然后連續發送數據幀,數據幀與數據幀之間有時會有斷開幀,斷開幀后需要接1-2bit停止位,連接下個數據幀;
斷開幀只能為10bit或11bit低電平的幀(CR1_SBK[0]);然后接1或2bit的高電平作為停止位,然后接下一個數據幀;
數據幀的數據字有兩種格式,(1)8 bit 數據位;(2)8bit 數據位 + 1 bit 奇偶校驗位;
3 USART的寄存器使用
每個USART都有7個自己的寄存器;用來配置該USART的所有功能;
有許多功能諸如硬件流控制,LIN模式,智能卡模式等,由於沒用過或是用不上,實在晦澀難懂費時費力,故在此全部跳過;
以下給出了USART作為常用串口收發數據的工作框圖,以及相關的寄存器配置;
3.1 工作框圖
3.2 相關寄存器配置
1)首先需要配置USART的6個參數:
波特率USART_BRR,字長M,停止位STOP,校驗位PCE,PS,PEIE,USART的收發模式TE和RE和硬件流控制CTSIE,CTSE,RTSE;
2)USART提供了8個中斷:TXEIE, TCIE, RXNEIE, PEIE, IDLEIE, CTSIE, LBDIE, EIE;
8個中斷使能均可以進入USART的中斷函數,根據需要配置合適的中斷使能位為1;通常為RXNEIE位;
3)然后使能接收器RE和發送器TE;
4)然后使能UE中斷;
4 USART的代碼示例
4.1 標准庫提供的常用USART接口
標准庫為所有的外設都提供了封裝寄存器的API接口函數,文件名為stm32f10x_peripheral.c;以下為usart外設的常用函數;
//串口USARTx的參數配置初始化函數; void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct); //使能串口,(主要是分頻器和輸出的設置) void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState); //使能串口中斷,(就是那8個中斷,均可以進入中斷函數) void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT, FunctionalState NewState); //都是處理一個字節; void USART_SendData(USART_TypeDef* USARTx, uint16_t Data); uint16_t USART_ReceiveData(USART_TypeDef* USARTx); //讀取SR寄存器的狀態,SR的狀態都是硬件設置的; FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG); //讀取SR寄存器和CRx控制寄存器的狀態,和上面一個功能相同的; ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT); //修改SR寄存器的狀態,單功能通訊用不上; void USART_ClearFlag(USART_TypeDef* USARTx, uint16_t USART_FLAG);
4.2 USART1使用代碼
#include "usartDemo.h" u8 USART1_RX_BUF[256]; //接收緩存 u8 USART1_RX_CNT = 0; //接收字節計數 u8 USART1_REV_0D = 0; //收到\r u8 USART1_REV_0A = 0; //收到\r和\n //usart1初始化之后,便可以通過串口讀寫了; void Usart1_Init(u32 bound) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE); NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //設置NVIC中斷分組2:2位搶占優先級,2位響應優先級 0-3; //USART1外設中斷配置 NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ; //搶占優先級3 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子優先級3 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能 NVIC_Init(&NVIC_InitStructure); //GPIO初始化 USART1_TX PA9 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //輸出需要配置速率,輸入不需要配置速率; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //復用推挽輸出,<中文..手冊>8.1.11外設的GPIO配置 GPIO_Init(GPIOA, &GPIO_InitStructure); //GPIO初始化 USART1_RX PA10 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空輸入 GPIO_Init(GPIOA, &GPIO_InitStructure); //USART1初始化 USART_InitStructure.USART_BaudRate = bound; USART_InitStructure.USART_WordLength = USART_WordLength_8b; 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; //CR1中的TE,RE USART_Init(USART1, &USART_InitStructure); USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//CR1中的RXNEIE中斷 USART_Cmd(USART1, ENABLE); //CR1中的UE } void USART1_Send_Data(u8 *buf,u16 len) { u16 t; for(t=0;t<len;t++) { USART_SendData(USART1,buf[t]); while(USART_GetFlagStatus(USART1,USART_FLAG_TC)==RESET); //發送字節完成后,TC硬件置1; } //先讀SR,后寫DR清除TC位; USART1_RX_CNT = 0; USART1_REV_0D = 0; USART1_REV_0A = 0; } void USART1_IRQHandler(void) { u8 Res; if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET) { Res =USART_ReceiveData(USART1); //讀DR,硬件清0 RXNE位; USART1_RX_BUF[USART1_RX_CNT]=Res; //接收數據 USART1_RX_CNT++; if(Res==0x0d) USART1_REV_0D = 1; if(USART1_REV_0D&&(Res==0x0a)) USART1_REV_0A = 1; } //RXNE為1,讀數據的同時又來了數據,那么新的數據丟失;產生溢出錯誤,讀完數據后RXNE為0,但ORE標志還在; //RXNE為1,又來了數據,產生接收溢出錯誤,置位ORE; if(USART_GetFlagStatus(USART1,USART_FLAG_ORE) == SET) { USART_ReceiveData(USART1); // USART_ClearFlag(USART1,USART_FLAG_ORE);//先讀SR,后讀DR,可以復位ORE位;應該不用軟件清除了; } // USART_ClearFlag(USART1,USART_IT_RXNE); //讀DR可以清除RXNE,應該不用軟件清除了; } int main(void) { Usart1_Init(460800); while(1) { if(USART1_REV_0A) { USART1_Send_Data(USART1_RX_BUF,USART1_RX_CNT); } } }
4.2.1 在前面代碼的基礎上不使用串口中斷,直接通過SR狀態位來判斷數據的收發;
將上面代碼的usart1初始化代碼中CR1的RXNEIE配置行注釋掉,然后修改main函數如下即可;
int main(void) { Usart1_Init(460800); while(1) { if ((USART_GetFlagStatus(USART1,USART_FLAG_RXNE)==SET)) { USART1_RX_BUF[USART1_RX_CNT] = USART_ReceiveData(USART1); if(USART1_RX_BUF[USART1_RX_CNT]==0x0a) USART1_REV_0A = 1; USART1_RX_CNT++; } if(USART1_REV_0A) { USART1_REV_0A = 0; for(int i=0;i<USART1_RX_CNT;i++) { USART_SendData(USART1, USART1_RX_BUF[i]); while(USART_GetFlagStatus(USART1, USART_FLAG_TC)==RESET); } USART1_RX_CNT=0; } } }
5 總結
5.1 USART的功能沒想到還挺多的,寄存器看起來就有些費時了,很多概念都是新的,不好理解,直接拉低了效率;
於是覺得這樣不行,應該用什么看什么,用到再看,學海無涯,精力有限;
另外人家費心費力寫好標准庫不就是為了幫開發人員省時間嗎?了解一下即可,以后沒必要深入;
5.2 代碼測試,參考網頁以及正點原子的例程;
本來是嘗試直接在接收函數里將接收到的函數發送回去的;大概因為都是寄存器的操作,並且又是在中斷函數里容易被中斷打斷;
所以不穩定,容易出現寄存器操作到一半被中斷打斷;讀寫序列打亂,導致程序丟幀或不能正常運行;
參考了別人的代碼,還是要用標志變量來表示接收完成,然后接收和發送分開來執行;這樣寄存器操作比較穩定,邏輯也清楚;
5.3 關於NVIC中斷優先級管理和串口的DMA傳輸功能,占個坑;
5.4 參考網頁:https://www.cnblogs.com/pertor/p/9488446.html
5.5 本文代碼github:https://github.com/caesura-k/stm32f1_usart