目錄
一、串口通信協議????????
二、USART模塊介紹
(1)移位寄存器
(2)控制電路
(3)波特率
(4)C語言接口
三、串口的引腳初始化
(1)引腳分布表
(2)重映射表
(3)GPIO配置表
(4)C語言接口使用
四、標志位的使用
(1)讀寫標志位
(2)錯誤標志位
五、發送數據代碼
六、接收數據代碼
一、串口通信協議????????
? ? ? ? 在學習網絡通信的時候,我們曾認識到兩臺主機想要進行通信,必須要有協議的存在,否則對方根本不知道你說了啥,因為在網絡中沒有信息的概念,只有二進制電平信號。如果不規定協議,對方的操作系統就不知道如何解析該二進制信號。于是演變出來OSI的7層模型,每一層都相當于一層協議,對數據包進行封裝,從而讓對方的同層協議可以解析提取真正想要的數據。
????????那么我們STM32單片機自然也可以認為屬于網絡協議中的物理層協議。因為他并沒有經過交換機路由器等設備,僅僅只是把兩臺主機直連,所以他并不可能在數據鏈路層、或者網絡層。但是也有人認為他屬于數據鏈路層,以為他有數據幀封裝,盡管這個封裝十分簡單。而且他沒有mac地址其實是因為他沒有負責的連接環境,但是本質上他這個簡單的協議是可以做到傳輸mac地址的。其實這種理解也是有道理的,因為規定上物理層沒有任何協議存在,是單純的數據流傳輸。
? ? ? ? 真實的物理連接圖:
????????正是因為它屬于物理層協議,那么可以預見的是他的協議必然很簡單。我們來看看他的協議格式:
????????它的協議格式非常簡單,由一位低電平起始位+7-8位數據位+(一位校驗位)+一位高電平停止位組成。
? ? ? ? 其中這里的數據位為7-8位是可以選擇的,不過我們一般都是要傳遞一個字節,所以一般情況下只會選擇8位數據位,至于校驗位,如果你的精確度要求高可以選擇,通常來說并無較大差異。
二、USART模塊介紹
????????在結構圖中可以看到UART有三個,APB1和APB2線上都存在。可以根據你對時鐘頻率的不同選取。
(1)移位寄存器
????????在這幅圖中有兩個移位寄存器,他的作用就是接收和發送數據。
????????當我們想要發送數據的時候,就把值填入發送寄存器。之后該模塊會自動把該寄存器的數據在時鐘的頻率下,一位位得把數據的二進制流交給控制電路。
? ? ? ? 反過來,接收數據的時候,是先交給到了控制電路,控制電路對接收到的數據幀,進行解包(去除起始位、停止位、并檢驗校驗位是否正確),把數據取出交給接收寄存器,然后由該寄存器把數據向上傳遞即可。
? ? ? ? 在這個過程中兩個寄存器的作用主要是串轉并、并轉串。因為在物理線路中的傳遞都是二進制流的串行數據,而用戶想看到的則是8位一起的并行數據。
(2)控制電路
? ? ? ? 控制電路主要就是進行封包和解包分用的。在這里可以通過配置寄存器來決定數據包的格式:數據位是7位還是8位?有沒有校驗位?、停止位是多少個時鐘周期的高電平?
(3)波特率
? ? ? ? 波特率指的是一分鐘的時間,比特位傳輸的數量。比如你的串口選擇的波特率是9600,那么他一分鐘能傳遞的比特位就是9600個,即9600/8=1200個字節。大多數情況下波特率都會被配置成9600、115200、921600,這個和時鐘的頻率有關,盡可能選擇可以由時鐘頻率直接分頻得到的,精確度較高。
? ? ? ? 在這幅圖中,波特率是由時鐘經過波特率寄存器BRR和一個16分頻的分頻器得到的。因為STM32的時鐘頻率往往是36、72MHz比較大,經過這些分頻降低頻率更好用。不過如果你對數據的傳輸時間限制很短,可以嘗試較高的頻率。
(4)C語言接口
在庫函數中可以找到一個USART_Init,他就是用來配置UART(上述)模塊的函數。可以在這里設置波特率、數據位長度、校驗方式、接收還是發送數據等。
當然還需要一個時鐘總開關使能
USART_Cmd(USART1,ENABLE);//使能USART模塊
三、串口的引腳初始化
? ? ? ? 我們在上面已經了解了UART模塊的配置方法。那么既然要向外傳輸數據,必然會有引腳暴露出來供我們使用,那么他們是誰呢?
????????USART的引腳有這5個,其中Tx和Rx最為熟悉,是用來傳輸數據的。而硬件流控我們暫時并不會涉及到,也不過多講解。同步模式的時鐘線,則是如果你想讓兩個STM32單片機進行數據交流,則可以用這個時鐘線把兩臺主機連接到一起,讓他們都遵循一個時鐘,在該時鐘的指導下進行串口通信,保證數據的準確性。
(1)引腳分布表
在數據手冊中有這樣兩幅圖,他們表示了當前芯片的封裝情況、重映射情況。
由于他比較多,我直接把和UASRT相關的引腳內容挑出來看,所以知道了我們在不使用重映射的情況下USART的輸入輸出引腳是接在PA9和PA10上面的。如果你就想使用默認情況,則直接配置PA9和PA10兩個引腳即可。
當然,在重映射后他們的位置則變為了PB6和PB7。注意你如果把他們配置成了USART的引腳,則原本的GPIO功能是無法使用的。
(2)重映射表
在上面直接看引腳分布表比較麻煩,可以直接到使用手冊中查看。內容是一樣的。
(3)GPIO配置表
????????我們已經找到了其引腳,配置成什么模式呢?
在使用手冊中同樣有一個表格,明確說明了各種外設在使用GPIO的時候,應當被配置為什么模式。
在這里我們看到全雙工模式下,Tx要配置成推挽復用輸出、Rx要配置成上拉輸入。
????????復用推挽輸出和通用推挽輸出的區別就是:一個是由CPU直接控制引腳的電平高低,另一個把引腳電平的控制權交給外設模塊自己操作,更加方便。
(4)C語言接口使用
默認情況:
重映射情況:
四、標志位的使用
????????在USART的框圖中我們曾有一部分沒有講解,就是右上角的標志位。通過讀取標志位,可以讓我們時刻檢查到USART模塊的運行情況。
(1)讀寫標志位
TxE:檢查能否填入數據到發送寄存器
TC:檢查檔次發送過程是否結束
RxNE:
(2)錯誤標志位
????????我們在通過USART協議傳輸數據的時候,難免會出現某些原因,使得數據的發送端和接收端并不一致,我們就認為他出錯了。雖然USART的控制電路會自動檢測錯誤,但是我們上層應用要想知道仍需要查看這些標志位來獲取狀態。
? ? ? ? 錯誤標志位是一個協議中非常重要的存在。通過檢驗錯誤標志位,你可以對不同的錯誤做出不同的處理,比如:如果接受數據錯誤,你可以選擇向對端發送消息,告訴他你的數據錯了,請重新發。有沒有回想到我們之前學習TCP也是這樣的!
PE:
FE:
比如沒有接收到停止位,就是一種幀格式錯誤。
NE:
我們實際在接受一個USART協議的信號的時候,會在一個時鐘周期內多次檢測,看看每次檢測的結果是不是一樣的,如果不一樣則表示有噪音,該數據不可信。
ORE:
當接受寄存器中有數據沒有被上層應用取走,他會停留在原地(字節1),如果有數據又來了,他就保存在了接收端的移位寄存器(字節2),此時仍然沒有問題。但是如果繼續來了數據(字節3),他就會覆蓋掉移位寄存器中的字節2,此時控制電路就會監測到該情況,并把ORE置1。
五、發送數據代碼
向發送寄存器寫入數據的函數
對上述代碼進行封裝:
void Init_USART()
{//USART配置RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);USART_InitTypeDef USART_InitStruct;USART_InitStruct.USART_BaudRate=115200;USART_InitStruct.USART_Mode=USART_Mode_Tx | USART_Mode_Rx;USART_InitStruct.USART_WordLength=USART_WordLength_8b;USART_InitStruct.USART_Parity=USART_Parity_No;USART_InitStruct.USART_StopBits=USART_StopBits_1;USART_Init(USART1,&USART_InitStruct);//GPIO配置RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);GPIO_InitTypeDef GPIO_InitStruct;GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF_PP;GPIO_InitStruct.GPIO_Pin=GPIO_Pin_9;GPIO_InitStruct.GPIO_Speed=GPIO_Speed_10MHz;GPIO_Init(GPIOA,&GPIO_InitStruct);GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IPU;GPIO_InitStruct.GPIO_Pin=GPIO_Pin_10;GPIO_InitStruct.GPIO_Speed=GPIO_Speed_10MHz;GPIO_Init(GPIOA,&GPIO_InitStruct);//使能USARTUSART_Cmd(USART1,ENABLE);}void My_USART_SendBytes(USART_TypeDef* USARTx,uint8_t* pData,uint16_t Number)
{for(uint16_t i=0;i<Number;i++){//不為空的時候就在while循環中出不來while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);//走到這說明發送寄存器為空了USART_SendData(USARTx,pData[i]);//如果移位寄存器沒有清空,就不退出這個函數while(USART_GetFlagStatus(USART1,USART_FLAG_TC)==RESET);}
}int main(void)
{Init_USART();uint8_t datas[]={10,50,90,100,66};while(1){My_USART_SendBytes(USART1,datas,5);}
}
如果你上述步驟都正確的話你是可以成功的通過STM32單片機向電腦主機發送消息的。結果如下:
六、接收數據代碼
具體使用如下:
示例代碼:
void Init_USART()
{//USART配置RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);USART_InitTypeDef USART_InitStruct;USART_InitStruct.USART_BaudRate=115200;USART_InitStruct.USART_Mode=USART_Mode_Tx | USART_Mode_Rx;USART_InitStruct.USART_WordLength=USART_WordLength_8b;USART_InitStruct.USART_Parity=USART_Parity_No;USART_InitStruct.USART_StopBits=USART_StopBits_1;USART_Init(USART1,&USART_InitStruct);//GPIO配置RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);GPIO_InitTypeDef GPIO_InitStruct;GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF_PP;GPIO_InitStruct.GPIO_Pin=GPIO_Pin_9;GPIO_InitStruct.GPIO_Speed=GPIO_Speed_10MHz;GPIO_Init(GPIOA,&GPIO_InitStruct);GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IPU;GPIO_InitStruct.GPIO_Pin=GPIO_Pin_10;GPIO_InitStruct.GPIO_Speed=GPIO_Speed_10MHz;GPIO_Init(GPIOA,&GPIO_InitStruct);//使能USARTUSART_Cmd(USART1,ENABLE);}void My_USART_SendBytes(USART_TypeDef* USARTx,uint8_t* pData,uint16_t Number)
{for(uint16_t i=0;i<Number;i++){//不為空的時候就在while循環中出不來while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);//走到這說明發送寄存器為空了USART_SendData(USARTx,pData[i]);//如果移位寄存器沒有清空,就不退出這個函數while(USART_GetFlagStatus(USART1,USART_FLAG_TC)==RESET);}
}void My_Led_Init(void)
{//GPIO配置RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);GPIO_InitTypeDef GPIO_InitStruct;GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_OD;GPIO_InitStruct.GPIO_Pin=GPIO_Pin_13;GPIO_InitStruct.GPIO_Speed=GPIO_Speed_2MHz;GPIO_Init(GPIOC,&GPIO_InitStruct);//初始設置為滅,即GPIOC_Pin_13為高電平GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_SET);
}int main(void)
{My_Led_Init();Init_USART();uint8_t datas[]={10,50,90,100,66};while(1){// My_USART_SendBytes(USART1,datas,5);//接收數據,并處理while(USART_GetFlagStatus(USART1,USART_FLAG_RXNE)==RESET);uint8_t byteEecv=USART_ReceiveData(USART1);//處理//0滅if(byteEecv==0){GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_SET);}//1亮else if(byteEecv==1){GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_RESET);}}}
如果你的操作正確,則可以看到用串口控制芯片上的LED了。