通信的兩種方式:
並行通信
-傳輸原理:數據各個位同時傳輸。
-優點:速度快
-缺點:占用引腳資源多
串行通信
-傳輸原理:數據按位順序傳輸。
-優點:占用引腳資源少
-缺點:速度相對較慢
串行通信分類(按照數據傳送方向)
單工(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