1、電平標准
根據通訊使用的電平標准不同,串口通訊可分為TTL標准和RS-232標准,如下表:
從圖中可以看到,TTL電平標准使用5V表示高電平,使用0V表示低電平。在R232電平標准中,為了增加串口通訊的遠距離傳輸及抗干擾能力,使用的是-15V表示高電平,使用+15V表示低電平。如下圖為RS232和TLL電平標准表示同一個信號時的對比。
在電子電路中,一般使用TTL電平進行通訊,而在PC機中則使用RS232電平進行通訊。所以為了使電子設備可以和PC機進行串口通訊,必須對TTL電平和RS232電平的信號進行互相轉換。
2、串口協議
串口通訊的英文全稱為Serial Communication,這是一種在設備間非常常用的串行通訊方式。
串口通訊的協議,串口通訊的數據包由發送設備通過自身的TXD接口傳輸到接收設備的RXD接口。在串口通訊的協議中,規定了數據包的內容,該內容由起始位、數據位、校驗位以及停止位組成,通訊雙方的數據包格式要約定一致才能正常收發數據。格式如下圖:
在數據幀格式中,校驗位可以要也可以不要。
一般在串口通信中,空閑狀態下,IO口的電平為高電平。
3、串口波特率
串口通訊一般使用的是異步通訊,異步通訊是沒有時鍾信號的,為了保證兩個設備能夠正常通訊,必須在兩個設備間約定好收發的速率,波特率就是設備的收發速率,波特率表示的是單位時間內收發的bit位,即一個bit的收發時長。比如波特率為9600的設備,那么該設備1S的時間內可以收發9600個bit,發送一個bit的時長位1/9600≈104us。
4、數據幀的起始信號和停止信號
串口通訊的一個數據包是從起始信號開始的,直到停止信號結束。數據包的起始信號由一個邏輯0的數據位表示,而數據包的停止信號可由0.5、1、1.5或2個邏輯1的數據位表示。
有效數據:
在數據包的起始位之后緊接着的就是要傳輸的主體數據內容,也稱為有效數據,有效數據的長度常被約定位5、6、7或8位。
數據校驗:
在有效數據之后,有一個可選的數據校驗位,由於數據通信相對更容易受到外部干擾導致傳輸數據出現偏差,可以在傳輸過程中加上校驗位來解決這個問題,校驗方法有奇校驗、偶校驗、0校驗、1校驗及無校驗。
奇校驗要求有效數據和校驗位中“1”的個數為奇數,比如一個8位長的有效數據位:01101001,此時總共有4個“1”,為達到奇校驗的效果,校驗位為“1”,最后傳輸的數據將是8位的有效數據加上1位的校驗位總共9位。
偶校驗與奇校驗要求剛好相反,要求有效數據和校驗位中“1”的個數為偶數,比如數據幀:11001010,此時有效數據“1”的個數位4,所以偶校驗位為“0”。
0校驗是不管有效數據中的內容是什么,校驗位總為“0”。
1校驗是不管有效數據中的內容是什么,校驗位總為“1”。
5、UART和USART的區別
UART是指通用異步收發器,UASRT是指通用同步/異步收發器。從名稱上可是看出,USART是在UART基礎上增加了同步功能,即USART是UART的增強型。
STM32的USART一般做異步通信,即UART功能。
6、USART功能概述
使用USART雙向通信至少需要三個腳:GND、RX、TX。
RX:接收數據引腳,通過采樣技術來區別數據和噪音,從而恢復數據。
TX:發送數據引腳,當發送器被禁止時,輸出引腳恢復到它的IO端口配置。當發送器被激活,並且不發送數據時,TX引腳處於高電平,也就是說USART的空閑狀態的電平時高電平。這里需要注意一點的是,當與設備相連的時候,如果設備斷電,這需要將發送器禁止,使TX口輸出低電平,否者有可能引起IO口電流倒灌。
TX腳位在單線和智能卡模式里被同時用於數據的發送和接收。
USART總線在發送或接收前應處於空閑狀態,即高電平。
一個起始位,起始位為1。
一個數據字,可以使8位數據或9位數據。發送或接收數據的時候,是先發送或接收低位數據。
停止位可設置為0.5、1、1.5、2個停止位,發送或接收停止位說明數據幀發送或接收完成。
使用分數波特率發生器,由12位整數和4位小數組成。
一個狀態寄存器USART_SR
數據寄存器USART_DR
一個波特率寄存器USART_BRR,12位表示波特率的整數,4位表示波特率的小數。
一個智能卡模式下的保護時間寄存器USART_GTPR
USART的框圖如下:
7、STM32的USART波特率配置
STM32的每個串口都有一個獨立的波特率寄存器USART_BRR,該寄存器由USART分頻器除法因子USARTDIV的整數部分和小數部分組成。bit4~bit15這12位組成了整數部分DIV_Mantissa,bit0~bit3這4個位組成了分數部分DIV_Fraction。
波特率的計數公式 = CLK/(16*USARTDIV)。
CLK是USART的時鍾頻率,USART2、USART3、USART4、USART5使用的是APB1總線時鍾,一般是36MHZ(STM32F103系列);而USART1則使用的是APB2總線時鍾,一般是72MHZ(STM32F103系列)。
可以根據需要的波特率的值換算出USARTDIV的值。比如設置波特率為9600,則USARTDIV為:
USARTDIV = CLK / (16 * 9600)
如果CLK = 72MHZ,則:
USARTDIV = 72000000/(16*9600) = 468.75
將小數部分換算為16進制:
DIV_Fraction = 0.75*16 = 12 = 0x0C
將整數部分轉為16進制:
DIV_Mantissa = 468 = 0x1D4
所以波特率寄存器USART_BRR的值為0x1D4C。
設置波特率時的誤差如下圖:
注意不要在通信過程中改變波特率寄存器的值。
8、USART中斷請求
USART中斷請求表:
USART中斷映像圖:
從中斷映像圖中可以看出,USART的各種中斷事件被連接到同一個中斷向量,也就是說USART產生的中斷都會進入到同一個中斷服務函數內。
各種中斷事件:
發送期間:發送完成、清除發送、發送數據。
接收期間:空閑總線檢測、溢出錯誤、接收數據寄存器非空、校驗錯誤、LIN斷開符號檢測、噪音標志(僅在多緩沖器通信)和幀錯誤(僅在多緩沖器通信)。
如果設置了對應的使能控制位,這些事件就可以產生各自的中斷,可以在中斷服務函數內通過判斷狀態寄存器的位來區分是哪個中斷。
9、USART寄存器
只說明一些用到的位。
USART_SR狀態寄存器:
Bit7 TXE位:當TDR寄存器中的數據被硬件轉移到移位寄存器的時候,TXE被硬件置位。
Bit6 TC位:當發送完一幀有效數據的時候,TC就會置位。
Bit5 RXNE位:當USART的RDR移位寄存器中的數據被轉移到USART_DR寄存器中時,RXNE位被置1,也就是收到數據的時候RXNE位被置1;如果USART_CR1寄存器中的RXNEIE使能,且USART的中斷使能,則產生中斷,就是所謂的接收中斷。RXNE位可以通過對USART_DR寄存器的讀取操作來清除,也可以對USART_SR寄存器的bit5寫0來清除。
USART_DR數據寄存器:
該寄存器用來發送和接收數據。
USART_BRR波特比率寄存器:
該寄存器用來設置USART的波特率。
USART_CR1控制寄存器:
Bit13 UE位:這個位用來使能和停止USART模塊,這有UE位被置1的時候,USART才能工作。
Bit12 M位:該位用來定義數據字的長度,當M=0時,數據字的長度為8個bit;當M=1時,數據字的長度為9個bit。
Bit10 PCE位:該位用來控制是否使用校驗,當PCE=0時,禁止校驗控制;當PCE=1時,使能校驗控制。
Bit9 PS位:該位用來選擇校驗方式,當PS=0時,使用偶校驗方式;當PS=1時,使用奇校驗。
Bit8 PEIE位:該位用來控制校驗錯誤中斷,當PEIE=0時,禁止校驗錯誤產生中斷;當PEIE=1時,使能校驗錯誤產生中斷。
Bit7 TXEIE位:這個是發送緩沖區空中斷使能位,當TXEIE=0時,禁止發送緩沖區空時產生中斷;當TXEIE=1時,使能發送緩沖區空時產生中斷。
Bit6 TCIE位:這個是發送完成 中斷使能,當TCIE=0時,禁止發送完成產生中斷;當TCIE=1時,使能發送完成產生中斷。
Bit5 RXNEIE位:這個是接收緩沖區非空中斷使能,當RXNEIE=0時,禁止接收完成時產生中斷;當RXNEIE=1時,使能接收完成時產生中斷。
Bit4 IDLEIE位:這個是IDLE總線空閑中斷使能位,當IDLEIE=0時,禁止檢測到總線空閑時產生中斷;當IDLEIE=1時,使能檢測到總線空閑時產生中斷。
Bit3 TE位:這個是發送使能位,當TE=0時,禁止USART發送數據;當TE=1時,使能USART發送數據。
Bit2 RE位:這個是接收使能位,當RE=0時,禁止USART接收數據;當RE=1時,使能USART接收數據。
這里主要區分發送緩沖區空和發送完成這兩個點,發送緩沖器空是指USART_DR寄存器的數值被硬件轉移到以為寄存器的時候,也就是說要發送的數值進入移位寄存器的時候,發送緩沖器空這個條件就會成立。而發送完成是指移位寄存器里的數據發送完成。
當將一個數據寫入USART_DR寄存器的時候,硬件將USART_DR寄存器的值送入移位寄存器,送完之后會置位USART_SR寄存器的bit7 TXE位,而當移位寄存器里的數值發送完成之后會置位USART_SR寄存器的bit6 TC位。
USART_CR2控制寄存器:
Bit12~Bit13 STOP位:這個是停止位長度的選擇位,當STOP=00時,使用1個停止位;當STOP=01時,使用0.5個停止位;當STOP=10時,使用2個停止位;當STOP=11時,使用1.5個停止位;
需要注意的是UART4和UART5不能使用0.5和1.5個停止位。
10、HAL庫操作USART
在工程之中添加stm32f1xx_hal_uart.c和stm32f1xx_hal_uart.h文件。
初始化代碼如下:
1 UART_HandleTypeDef UART_Handler; 2
3 uint8_t aRxBuffer; 4
5 void USART_Init(void) 6 { 7
8 UART_Handler.Instance = USART1; 9 UART_Handler.Init.BaudRate = 9600; 10 UART_Handler.Init.Mode = UART_MODE_TX_RX; 11 UART_Handler.Init.StopBits = UART_STOPBITS_1; 12 UART_Handler.Init.WordLength = UART_WORDLENGTH_8B; 13 UART_Handler.Init.Parity = UART_PARITY_NONE; 14 UART_Handler.Init.HwFlowCtl = UART_HWCONTROL_NONE; 15 HAL_UART_Init(&UART_Handler); 16
17 //HAL_UART_Receive_IT(&UART_Handler,aRxBuffer,2);
18 __HAL_UART_ENABLE_IT(&UART_Handler, UART_IT_RXNE); 19
20 HAL_NVIC_SetPriority(USART1_IRQn,2,1); 21 HAL_NVIC_EnableIRQ(USART1_IRQn); 22
23 }
使用HAL_UART_Init()函數初始化USART設備。
從初始化代碼內,可以看到,需要選擇初始化的USART、波特率、USART的模式(接收和發送)、停止位長度的選擇、數據的長度選擇、校驗的選擇、硬件控制流的選擇。還需要設置USART的時鍾和腳位,但是HAL_UART_Init()函數內會調用USART的時鍾和腳位的初始化回調函數,可以在回調函數內設置腳位和時鍾。
初始化完USART之后,打開USART的接收中斷,由於HAL庫的接收程序比較復雜,這里直接調用宏__HAL_UART_ENABLE_IT(&UART_Handler, UART_IT_RXNE)來打開USART的接收中斷。
IO和RCC初始化函數如下:
1 void HAL_UART_MspInit(UART_HandleTypeDef *husart) 2 { 3 GPIO_InitTypeDef GPIO_InitStruct; 4
5 __HAL_RCC_GPIOA_CLK_ENABLE(); 6 __HAL_RCC_USART1_CLK_ENABLE(); 7
8 GPIO_InitStruct.Pin = GPIO_PIN_9; 9 GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; 10 GPIO_InitStruct.Pull = GPIO_NOPULL; 11 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; 12 HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); 13
14 GPIO_InitStruct.Pin = GPIO_PIN_10; 15 GPIO_InitStruct.Mode = GPIO_MODE_INPUT; 16 GPIO_InitStruct.Pull = GPIO_NOPULL; 17 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; 18 HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); 19
20 }
這里注意的是RX腳,既可以設置成浮空輸入模式也可以設置成復用輸入模式,這兩個是一樣的。
中斷處理函數如下:
1 void USART1_IRQHandler(void) 2 { 3 //HAL_UART_IRQHandler(&UART_Handler);
4
5 if(__HAL_UART_GET_FLAG(&UART_Handler,UART_FLAG_RXNE)) 6 { 7 aRxBuffer = UART_Handler.Instance->DR; 8 HAL_UART_Transmit(&UART_Handler,&aRxBuffer,1,1000); 9 } 10 }
由於HAL庫的中斷服務處理比較繁瑣,這里直接讀取USART的中斷標志位來處理接收中斷。
收到數據到,通過讀取USART_DR寄存器的值來獲取接收到的數值,這里將接收到的數據再發送出去。
HAL庫中斷處理函數說明:
在HAL庫中斷處理中,進入中斷處理函數之后會調用HAL_UART_IRQHandler()函數,在HAL_UART_IRQHandler()函數內判斷是那種類型的中斷,然后去執行響應的操作。比如接收到數據后,會在HAL_UART_IRQHandler()函數內調用UART_Receive_IT()函數。
這里需要注意一個問題,在UART_Receive_IT()函數中,接收完成后會清除掉接收中斷的使能位,實際在用HAL庫來處理中斷的時候,接收中斷使能位莫名會被清掉。
在UART_Receive_IT()函數中會調用接收數據的回調函數HAL_UART_RxCpltCallback()來處理數據。
使用HAL_UART_Receive_IT(&UART_Handler,aRxBuffer,len)函數來接收數據的時候,如果數據長度不為1,則接收到1個數據的時候,不會進入到HAL_UART_RxCpltCallback()函數中,只有收到實際設置的數據長度時,才會進入HAL_UART_RxCpltCallback()函數。通過進入UART_Receive_IT()函數查看代碼的時候,會發現,當接收完實際設置的數據長度時,會清除掉接收中斷的使能位,這里需要小心,防止收到一次數據之后,不能再進入接收中斷。
添加支持printf()函數的代碼:
1 //加入以下代碼,支持printf函數,而不需要選擇use MicroLIB
2 #pragma import(__use_no_semihosting)
3 //標准庫需要的支持函數
4 struct __FILE 5 { 6 int handle; 7
8 }; 9
10 FILE __stdout; 11 //定義_sys_exit()以避免使用半主機模式
12 void _sys_exit(int x) 13 { 14 x = x; 15 } 16 //重定義fputc函數
17 int fputc(int ch, FILE *f) 18 { 19 while(__HAL_UART_GET_FLAG(&UART_Handler,UART_FLAG_TC) == RESET){}; 20
21 UART_Handler.Instance->DR = (uint8_t) ch; 22
23 return ch; 24 }
加上以上代碼后就可以使用printf()函數發送UART數據了,比如printf("hello world!")。