2021/10/15 智慧傢俱 嵌入式實訓 第五天 串列埠通訊 (1)
通訊的兩種方式:
並行通訊
-傳輸原理:資料各個位同時傳輸。
-優點:速度快
-缺點:佔用引腳資源多
序列通訊
-傳輸原理:資料按位順序傳輸。
-優點:佔用引腳資源少
-缺點:速度相對較慢
序列通訊分類(按照資料傳送方向)
單工(a):
資料傳輸只支援資料在一個方向上傳輸
半雙工(b):
允許資料在兩個方向上傳輸,但是,在某一時刻,只允許資料在一個方向上傳輸,它實際上是一種切換方向的單工通訊;
全雙工(c):
允許資料同時在兩個方向上傳輸,因此,全雙工通訊是兩個單工通訊方式的結合,它要求傳送裝置和接收裝置都有獨立的接收和傳送能力。
序列通訊的通訊方式
同步通訊(帶時鐘同步訊號傳輸):
SPI,IIC通訊介面
非同步通訊(不帶時鐘同步訊號傳輸):
UART(通用非同步收發器),單匯流排 約定傳輸速度
STM32的串列埠通訊介面
UART:通用非同步收發器。
USART:通用同步非同步收發器。
大容量STM32F10x系列晶片,包含3個USART和2個UART
UART非同步通訊方式引腳連線方法:
-RXD:資料輸入引腳。資料接受。
-TXD:資料傳送引腳。資料傳送。
UART非同步通訊方式特點:
全雙工非同步通訊。
分數波特率發生器系統,提供精確的波特率。
-傳送和接受共用的可程式設計波特率,最高可達4.5Mbits/s
可程式設計的資料字長度(8位或者9位);
可配置的停止位(支援1或者2位停止位);
可配置的使用DMA多緩衝器通訊。
單獨的傳送器和接收器使能位。
檢測標誌:① 接受緩衝器 ②傳送緩衝器空 ③傳輸結束標誌
多個帶標誌的中斷源。觸發中斷。
其他:校驗控制,四個錯誤檢測標誌。
串列埠通訊過程
傳送資料:通過匯流排往USARTx控制器中的DR暫存器(TDR),寫入資料,USARTx控制器會自動通過Tx管腳傳送出去。
接收資料:USARTx控制通過Rx管腳接收到位資料,組合成8位資料,存在DR暫存器(RDR),直接讀取DR暫存器。
STM32串列埠非同步通訊需要定義的引數
1 起始位
2 資料位(8位或者9位)
3 奇偶校驗位(第9位)
4 停止位(1,15,2位)
5 波特率設定
協議規定了什麼?硬體層、電平標準、通訊資料格式、傳輸速率
開始位 +資料位 +奇偶校驗位 +停止位
位數 1 5~8 0~1 1
電平 0 0/1 0/1 1
開始位:低電平 -- 裝置檢測下降沿,代表開始
資料位:5 -- 0000 0101 5~8位 -- 8位
奇偶校驗位:校驗一幀資料是否完整
奇偶校驗:資料位中1的個數+奇偶位中1的個數之和。
如果是奇校驗:個數之和必須為奇數。
如果是偶校驗:個數之和必須為偶數。
例如:傳送方:0110 0011 -- 0x63 採用奇校驗 奇偶校驗位為1
接收方:0110 0011 1 --- 正確
0100 0011 1 --- 錯誤
0000 0011 1 --- 奇偶校驗正確,資料錯誤 -- 現在採用CRC校驗。
停止位:高電平 -- 匯流排空閒狀態為高電平
常用的幀格式:1+8+0+1 -- 1個開始位+8個數據位+0個奇偶校驗位+1個停止位
通訊速率:波特率 bps 每秒鐘傳送的位數,常見的波特率:9600 115200等等
進行通訊的兩個裝置,波特率必須一樣。
常用的串列埠相關暫存器
USART_SR狀態暫存器
USART_DR資料暫存器
USART_BRR波特率暫存器 (填寫下面計算後的數值)
USART_CR1控制暫存器
根據該圖可瞭解序列通訊的相關配置。根據下半圖,可得到波特率的計算方法:
串列埠操作相關庫函式
void USART_Init(); //串列埠初始化:波特率,資料字長,奇偶校驗,硬體流控以及收發使能 void USART_Cmd();//使能串列埠 void USART_ITConfig();//使能相關中斷 void USART_SendData();//傳送資料到串列埠,DR uint16_t USART_ReceiveData();//接受資料,從DR讀取接受到的資料 FlagStatus USART_GetFlagStatus();//獲取狀態標誌位,SR void USART_ClearFlag();//清除狀態標誌位,SR ITStatus USART_GetITStatus();//獲取中斷狀態標誌位,SR void USART_ClearITPendingBit();//清除中斷狀態標誌位,SR
實驗4串列埠實驗的程式碼中FWLib資料夾下stm32f10x_usart.c下的stm32f10x_usart.h下,可找到相關函式的宣告與定義,然後可再進行追溯。
串列埠配置一般步驟
①串列埠時鐘使能,GPIO時鐘使能:RCC_APB2PeriphClockCmd();
②串列埠復位:USART_DeInit(); 這一步不是必須的
③GPIO埠模式設定:GPIO_Init(); 模式設定為GPIO_Mode_AF_PP
④串列埠引數初始化:USART_Init();
⑤開啟中斷並且初始化NVIC(如果需要開啟中斷才需要這個步驟)
NVIC_Init();
USART_ITConfig();
⑥使能串列埠:USART_Cmd();
⑦編寫中斷處理函式:USARTx_IRQHandler();
⑧串列埠資料收發:
void USART_SendData();//傳送資料到串列埠,DR
uint16_t USART_ReceiveData();//接受資料,從DR讀取接受到的資料
⑨串列埠傳輸狀態獲取:
FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG);
void USART_ClearITPendingBit(USART_TypeDef* USARTx, uint16_t USART_IT);
具體實現
第一步:新建模版,並使能串列埠時鐘和GPIO時鐘。
跟之前一樣,先建立一個簡單的模版。
在USER資料夾下找到system_stm32f10x.c下的stm32f10x_rcc.h.中找到void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);之前就知道這個函式是用來使能的,然後先Go To xxx找到函式的定義後,在函式中assert_param(IS_RCC_APB2_PERIPH(RCC_APB2Periph));
檢視函式中的第一個引數RCC_APB2Periph
Go To xxx後我們可以看到,該函式即可使能GPIOA又可使能USART1。故使能語句可這麼編寫
void My_USART1_Init(){ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 , ENABLE); }
串列埠復位非必須,故可省略。
第二步:GPIO埠模式設定
在USER資料夾下找到system_stm32f10x.c下的stm32f10x_gpio.h.中找到void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);該函式之前已經講解過了,程式可修改為:
另外一個USARTX_RX是輸入 ,根據上面的圖知道設為上拉輸入或者浮空
//2. 配置模式 GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//推輓輸出 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_Init(GPIOA, &GPIO_InitStructure);
void My_USART1_Init(){ GPIO_InitTypeDef GPIO_InitStructure; //定義結構體 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE); //使能GPIO時鐘 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 , ENABLE); //使能串列埠時鐘 GPIO_InitStructure.GPIO_Mode= GPIO_Mode_AF_PP; //設定為複用推輓輸出 GPIO_InitStructure.GPIO_Pin= GPIO_Pin_9; //引腳9 GPIO_InitStructure.GPIO_Speed=GPIO_Speed_10MHz; //速度10MHz GPIO_Init(GPIOA,&GPIO_InitStructure); //GPIOA模式設定 GPIO_InitStructure.GPIO_Mode= GPIO_Mode_IN_FLOATING;//浮空輸入 GPIO_InitStructure.GPIO_Pin= GPIO_Pin_10; //引腳10 GPIO_InitStructure.GPIO_Speed=GPIO_Speed_10MHz; //速度10MHz GPIO_Init(GPIOA,&GPIO_InitStructure); //GPIOA模式設定 }
第三步:串列埠引數初始化和使能串列埠
在USER資料夾下找到system_stm32f10x.c下的stm32f10x_usart.h.中找到void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct);、void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState);,程式可修改為:
/* 配置USART模式 */ //1. 時鐘使能 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //2. 模式配置 USART_InitTypeDef USART_InitStructure; USART_InitStructure.USART_BaudRate = brr; //波特率 (資料傳輸速度) USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //非硬體流 (如何決定收發的時機) USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收發模式 (全雙工) USART_InitStructure.USART_WordLength = USART_WordLength_8b; //RS232協議(資料格式):資料位 8個 USART_InitStructure.USART_Parity = USART_Parity_No; //RS232協議(資料格式):奇偶校驗位 0個 USART_InitStructure.USART_StopBits = USART_StopBits_1; //RS232協議(資料格式):停止位 1個 USART_Init(USART1, &USART_InitStructure);
void My_USART1_Init(){ GPIO_InitTypeDef GPIO_InitStructure; //定義GPIO結構體 USART_InitTypeDef USART_InitStructure;//定義USART結構體 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE); //使能GPIO時鐘 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 , ENABLE); //使能串列埠時鐘 GPIO_InitStructure.GPIO_Mode= GPIO_Mode_AF_PP; //設定為複用推輓輸出 GPIO_InitStructure.GPIO_Pin= GPIO_Pin_9; //引腳9 GPIO_InitStructure.GPIO_Speed=GPIO_Speed_10MHz; //速度10MHz GPIO_Init(GPIOA,&GPIO_InitStructure); //GPIOA模式設定 GPIO_InitStructure.GPIO_Mode= GPIO_Mode_IN_FLOATING;//浮空輸入 GPIO_InitStructure.GPIO_Pin= GPIO_Pin_10; //引腳10 GPIO_InitStructure.GPIO_Speed=GPIO_Speed_10MHz; //速度10MHz GPIO_Init(GPIOA,&GPIO_InitStructure); //GPIOA模式設定 USART_InitStructure.USART_BaudRate=115200;//波特率 USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None ;//硬體流:無 USART_InitStructure.USART_Mode=USART_Mode_Rx|USART_Mode_Tx;//TX和RX都使能故使用| 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_Cmd(USART1,ENABLE); //使能串列埠 }
第四步:開啟中斷並且初始化NVIC
在FWLIB資料夾下找到misc下找到void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup),函式中找到;assert_param(IS_NVIC_PRIORITY_GROUP(NVIC_PriorityGroup));,在Go To xxx,檢視填寫格式,選擇NVIC_PriorityGroup_2,即:兩位響應優先順序和兩位搶佔優先順序。
在USER資料夾下找到system_stm32f10x.c下的stm32f10x_usart.h.中找到void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT, FunctionalState NewState)
寫NVIC函式
//3. 配置接收中斷(串列埠回顯時註釋掉) USART_ITConfig(USART1,USART_IT_RXNE,ENABLE); //接收中斷開啟 NVIC_InitTypeDef NVIC_InitStruct = {0}; NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn; NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0; NVIC_Init(&NVIC_InitStruct);
//4. 初始狀態 -- 使能 USART_Cmd(USART1, ENABLE);
第五步:編寫中斷處理函式
在檔案stm32f10x_usart.h檔案下開啟:ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT);
void USART1_IRQHandler(void){ u8 res; if(USART_GetITStatus(USART1,USART_IT_RXNE)){//若是接收到中斷 res=USART_ReceiveData(USART1);//接收資料 USART_SendData(USART1,res);//接收到在傳送資料,才可以在串列埠監視器中看到資料 } }
最後編譯就通過了,注意要把模版中資料夾SYSTEM中的uart刪除,因為定義重複了。
之後就可以上傳程式,然後利用串列埠偵錯程式,注意偵錯程式中的設定要跟程式一樣,波特率為115200,停止位1,等等。最終的實驗現象就是傳送什麼,最後在除錯軟體中就會看到什麼。
SYSTEM資料夾下,usart.c檔案中,可看到以下程式碼(實驗4串列埠實驗):
main函式中呼叫函式
void USART1_IRQHandler(void) //串列埠1中斷服務程式 { u8 Res; #if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS為真,則需要支援OS. OSIntEnter(); #endif if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中斷(接收到的資料必須是0x0d 0x0a結尾) { Res =USART_ReceiveData(USART1); //讀取接收到的資料 if((USART_RX_STA&0x8000)==0)//接收未完成,判斷最高位是不是0,是0表示接收未完成,則往下執行。若為1則不往下執行(上一次接收沒清空) { if(USART_RX_STA&0x4000)//接收到了0x0d,若第二位為1則表示接收到0x0d,在往下判斷下一位是不是0x0a,若不是重新開始,是的話則則給第一位置1 { if(Res!=0x0a)USART_RX_STA=0;//接收錯誤,重新開始 else USART_RX_STA|=0x8000; //接收完成了 } else //還沒收到0X0D,第二位不是1,則判斷什麼時候接收到0x0d時,將第二位置為1 { if(Res==0x0d)USART_RX_STA|=0x4000; else//若一直沒收到的話,則一直講Res變數中的數值給變數,且通過位與來判斷該位是否溢位(若第1、2位有資料給他清零) { USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ; USART_RX_STA++; if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收資料錯誤,重新開始接收 } } } } #if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS為真,則需要支援OS. OSIntExit(); #endif } #endif
實驗4程式碼詳細解釋
usart.h
#ifndef __USART_H #define __USART_H #include "stdio.h" #include "sys.h" ////////////////////////////////////////////////////////////////////////////////// //本程式只供學習使用,未經作者許可,不得用於其它任何用途 //ALIENTEK STM32開發板 //串列埠1初始化 //正點原子@ALIENTEK //技術論壇:www.openedv.com //修改日期:2012/8/18 //版本:V1.5 //版權所有,盜版必究。 //Copyright(C) 廣州市星翼電子科技有限公司 2009-2019 //All rights reserved //******************************************************************************** //V1.3修改說明 //支援適應不同頻率下的串列埠波特率設定. //加入了對printf的支援 //增加了串列埠接收命令功能. //修正了printf第一個字元丟失的bug //V1.4修改說明 //1,修改串列埠初始化IO的bug //2,修改了USART_RX_STA,使得串列埠最大接收位元組數為2的14次方 //3,增加了USART_REC_LEN,用於定義串列埠最大允許接收的位元組數(不大於2的14次方) //4,修改了EN_USART1_RX的使能方式 //V1.5修改說明 //1,增加了對UCOSII的支援 #define USART_REC_LEN 200 //定義最大接收位元組數 200 #define EN_USART1_RX 1 //使能(1)/禁止(0)串列埠1接收 extern u8 USART_RX_BUF[USART_REC_LEN]; //接收緩衝,最大USART_REC_LEN個位元組.末位元組為換行符 extern u16 USART_RX_STA; //接收狀態標記 //如果想串列埠中斷接收,請不要註釋以下巨集定義 void uart_init(u32 bound); #endif
bound 是波特率
extern 定義了一些變數 外部變數
接收從電腦傳來的資料存入buf
0x0D=回車 0x0A=換行 二個結束符
接收完成 bit15-1 bit14-1 bit13~0 ----接收的位數
void USART1_IRQHandler(void) //串列埠1中斷服務程式 { u8 Res; #if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS為真,則需要支援OS. OSIntEnter(); #endif 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 ;//=0x3fff最多2^13-1資料 USART_RX_STA++;//有效資料個數++ if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收資料錯誤,重新開始接收 } //有效的資料個數不能大於定義的資料個數 } } } #if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS為真,則需要支援OS. OSIntExit(); #endif } #endif
工程檔案可供參考:
https://wwa.lanzoui.com/iG84Jvh1loj