STM32 HAL庫之串口詳細篇
一、基礎認識
(一) 並行通訊
原理:數據的各個位同時傳輸
優點:速度快
缺點:佔用引腳資源多,通常工作時有多條數據線進行數據傳輸
8bit數據傳輸典型連接圖:
傳輸的數據是二進位:11101010,則通訊使用8條線同時進行數據傳輸,發送端一次性發送8位數據,接收端一次性接收8位數據。
(二) 串列通訊
原理:數據按位順序傳輸
優點:佔用引腳資源少
缺點:速度相對較慢,通常工作時只有一條數據線進行數據傳輸
8bit數據傳輸典型連接圖:
傳輸的數據是二進位:11101010,則通訊使用8條線同時進行數據傳輸,發送端一次性發送8位數據,接收端一次性接收8位數據。
8bit數據傳輸典型連接圖:
傳輸的數據是二進位:11101010,則通訊使用1條線進行數據傳輸,發送端一次性發送1位數據,接收端一次性接收1位數據。
串列通訊的分類:
1.單工:數據只能在一個方向上傳輸,通訊雙方數據只能由一方傳輸到另一方
2.半雙工:數據可以錯時雙向傳輸,通訊雙方數據可以支援兩個方向傳輸,但是同一時間只能由一方傳輸到另外一方。
3.全雙工:數據可以同時雙向傳輸,通訊雙方數據可以同時進行雙向傳輸,對於其中一個設備來說,設備需要支援發送數據時可以進行數據接收。
串列通訊的通訊方式:
l 同步通訊:帶時鐘同步訊號的傳輸,如SPI、IIC、USART(同步)
l 非同步通訊:不帶時鐘同步訊號的傳輸,如UART、USART(非同步)
常見數據傳輸協議:
(三) UART和USART
UART:通用非同步收發器
USART:通用同步/非同步收發器,其可選使用非同步方式,那將和UART無區別,如果是同步,則需要多一根時鐘線(USART_CK)
(四) STM32的USART注意:
l 通常USART1介面的通訊速率較快,其它USART介面較慢。如STM32F103C8T6的USART1介面通訊速率是4.5Mbps,其它USART介面的通訊速率是2.25Mbps。
l 片上所有的USART介面都可以使用DMA操作
l USART的擴展及距離:
UART和COM是物理介面形式(物理介面)
TTL和RS-232是電平標準(電訊號)
串口接收:
l 掃描模式
l 中斷模式
l DMA模式
二、串口基礎配置
模式選擇:
Asynchronous 非同步通訊
Synchronous 同步通訊
Single Wire (Half-Duplex) 單線/半雙工
Multiprocessor Communication 多處理器
支援局域互連網路LIN、智慧卡(SmartCard)協議與lrDA(紅外線數據協會) SIR ENDEC規範。
默認的TX GPIO:
l 模式為:推挽式復用功能
l 輸出速率:高速
默認的RX GPIO:
l 模式為:浮空輸入
參數設置
l Baud Rate
任意設置,未做限制,輸入框
l Word Length
數據位可選8位或9位
l Parity
校驗位可選無校驗(None)、偶校驗(Even)、奇校驗(Odd)
l Stop Bits
停止位可選1位、2位
l Data Direction
數據方向,可選收發(Receive and Transmit)、只接收(Receive Only)、只發送(Transmit Only)
三、阻塞發送函數
以阻塞模式發送大量數據
當沒有啟用UART奇偶校驗( PCE sign0 ),並且單詞長度配置為9位( m1 – m0 sign01 )時,*發送的數據作為一組U16處理。在9位/無奇偶校驗傳輸的情況下,pData需要作為uint16_t指針處理
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
參數:
huart: 指向uart _ handletypedef結構的huart指針,該結構包含指定uart模組的配置資訊。
PData: 指向數據緩衝區的pData指針(U8或u16數據元素)。
Size: 要發送的數據元素( u8或U16 )的大小
Timeout:超時持續時間,單位ms,0就是0ms超時,數據最大發送時間,超過則返回異常
返回:
HAL 狀態
typedef enum { HAL_OK = 0x00U, HAL_ERROR = 0x01U, HAL_BUSY = 0x02U, HAL_TIMEOUT = 0x03U } HAL_StatusTypeDef;
例如:
HAL_UART_Transmit(&huart1,"dongxiaodong\r\n",strlen("dongxiaodong\r\n"),0xFFFF);
四、串口掃描接收
(一)相關函數
l 阻塞接收函數
在阻塞模式下接收大量數據。
當沒有啟用UART奇偶校驗( PCE sign0 ),並且單詞長度配置為9位( m1 – m0 sign01 )時,*接收到的數據作為一組U16處理。
HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
huart: 指向uart _ handletypedef結構的huart指針,該結構包含指定uart模組的配置資訊。
pData:指向數據緩衝區的指針( u8或U16數據元素)。
Size: 要接收的數據元素數量( u8或U16 )。
Timeout:超時持續時間,單位ms,0就是0ms超時,數據接收最大等待時間,超出則異常
返回:
HAL 狀態
typedef enum { HAL_OK = 0x00U, HAL_ERROR = 0x01U, HAL_BUSY = 0x02U, HAL_TIMEOUT = 0x03U } HAL_StatusTypeDef;
例如:
uint8_t data=0; while (1) { if(HAL_UART_Receive(&huart1,&data,1,0)==HAL_OK){ } }
(二)程式碼實現
HAL_UART_Transmit(&huart1,"dongxiaodong\r\n",strlen("dongxiaodong\r\n"),0xFFFF); uint8_t data=0; while (1) { //串口接收數據 if(HAL_UART_Receive(&huart1,&data,1,0)==HAL_OK){ //將接收的數據發送 HAL_UART_Transmit(&huart1,&data,1,0); } }
其中timeout為0表示沒有延時,所以串口接收函數是不阻塞的,while循環將一直輪詢
加個延時函數
這樣一來的話,接收數據就異常了,會接收數據不全,所以這樣是不可靠的
那改成這樣呢?
uint8_t data[100]={0}; if(HAL_UART_Receive(&huart1,data,100,1000)==HAL_OK){ }
接收100個數據,等待時間為1秒,這樣的話接收區沒滿時,每次運行這條語句都要延時等待1S,這時相當於一個HAL_Dealy(1000),這會阻塞while循環。只有數據接收到剛剛等於100才能返回HAL_OK,所以不能用於接收變成數據,這是不理想的。
五、 串口中斷接收
(一)cubemx設置
使能串口中斷
優先順序選擇
Preemption Priorit:搶佔優先順序
Sub Priority :子優先順序
數字越小優先順序越高
自動生成的程式碼中已經使能了中斷
(二)相關函數
l 接收中斷開啟,只開啟一次中斷
以非阻塞模式接收一定數量的數據,當UART奇偶校驗未啟用(PCE = 0),且字長配置為9位(M1-M0 = 01)時,
*接收到的數據作為一組u16處理。在這種情況下,Size必須指出數字
*的u16可通過pData。
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
參數:
huart: 指向uart _ handletypedef結構的huart指針,該結構包含指定uart模組的配置資訊。
pData:指向數據緩衝區的指針(u8或u16數據元素)。
Size:需要接收的數據元素(u8或u16)的數量。
返回:
HAL 狀態
typedef enum { HAL_OK = 0x00U, HAL_ERROR = 0x01U, HAL_BUSY = 0x02U, HAL_TIMEOUT = 0x03U } HAL_StatusTypeDef;
l 中斷接收回調函數
HAL_UART_RxHalfCpltCallback();一半數據接收完成時調用
HAL_UART_RxCpltCallback();數據完全接受完成後調用
函數原型
void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart); void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);
(三) 編程實現方法1
uint8_t my_uart1_redata=0; //開啟串口接收中斷 void my_uart1_enable_inpterr(){ //開啟一次中斷 HAL_UART_Receive_IT(&huart1,&my_uart1_redata,1); } //串口收到數據回調 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){ if(huart->Instance == USART1)//判斷串口號 { //發送 HAL_UART_Transmit(&huart1,&my_uart1_redata,1,100); //開啟一次中斷 HAL_UART_Receive_IT(&huart1,&my_uart1_redata,1); } }
存在問題:
數據發送太快之後就可能導致單片機無法再接收數據,以至於永久性損壞,通常可以在主循環里判斷標誌位再次啟動,可以避免永久性損壞問題。
(四) 編程實現方法2
修改stm32f1xx_it.c裡面的串口中斷
#include <usart.h> void USART1_IRQHandler(void) { /* USER CODE BEGIN USART1_IRQn 0 */ //正在接收 if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE) != RESET) { //NET_UART_RECV(READ_REG(huart1.Instance->RDR)); my_uart1_callback(huart1.Instance->DR); __HAL_UART_CLEAR_FLAG(&huart1,UART_FLAG_RXNE); } //溢出-如果發生溢出需要先讀SR,再讀DR暫存器 則可清除不斷入中斷的問題 if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_ORE)== SET) { __HAL_UART_CLEAR_FLAG(&huart1,UART_FLAG_ORE); //讀SR //READ_REG(huart1.Instance->RDR); //讀DR } //手動關閉自帶的串口中斷處理 #if 0 /* USER CODE END USART1_IRQn 0 */ HAL_UART_IRQHandler(&huart1); /* USER CODE BEGIN USART1_IRQn 1 */ #endif /* USER CODE END USART1_IRQn 1 */ }
標準函數
//開啟串口接收中斷 void my_uart1_enable_inpterr(){ //開啟一次中斷 __HAL_UART_ENABLE_IT(&huart1, UART_IT_RXNE); //使能接收中斷 } //串口收到數據回調 void my_uart1_callback(uint8_t rdata){ //發送 HAL_UART_Transmit(&huart1,&rdata,1,1); }
修改了HAL自帶的串口中斷函數,可以有效的避免接收中斷失效問題,但是你測試的時候會發現串口助手發送的數據和串口助手接收到的數據不完整,這是正常的,因為中斷接收是很快的,而發送是阻塞的,而實際也不會這樣使用,所以一般都會用數組做緩衝區接收串口數據。
六、 配置串口為中斷接收DMA發送
l STM32可用DMA的外設:定時器、ADC、SPI、IIC、USART
l 使用DMA必須開啟中斷
l 串口DMA模式最大為u16個位元組,則65535
(一)cubmx設置
通用配置
中斷開啟
DMA發送設置
Dirction : DMA傳輸方向
四種傳輸方向:
l 外設到記憶體 Peripheral To Memory
l 記憶體到外設 Memory To Peripheral
l 記憶體到記憶體 Memory To Memory
l 外設到外設 Peripheral To Peripheral
Priority: 傳輸速度
l 最高優先順序 Very Hight
l 高優先順序 Hight
l 中等優先順序 Medium
l 低優先順序;Low
Priority: 優先順序
l 最高優先順序 Very Hight
l 高優先順序 Hight
l 中等優先順序 Medium
l 低優先順序;Low
mode:模式
l Normal:正常模式,當一次DMA數據傳輸完後,停止DMA傳送 ,也就是只傳輸一次
l Circular: 循環模式,傳輸完成後又重新開始繼續傳輸,不斷循環永不停止
Increment Address:地址增加
l Peripheral:設置傳輸數據的時候外設地址是不變還是遞增。如果設置 為遞增,那麼下一次傳輸的時候地址加 Data Width個位元組,勾選表示遞增。
l Memory:設置傳輸數據時候記憶體地址是否遞增。如果設置 為遞增,那麼下一次傳輸的時候地址加 Data Width個位元組,這個Src Memory一樣,只不過針對的是記憶體。,勾選表示遞增。
data width:數據寬度
byte:位元組,通用8位,與u8相同
word:字長,與硬體的位數相同,STM32是32位,所以對應是u32
Half Word:半個字長,所以對應是u16
(二) 編程實現
串口DMA發送
#include "string.h" extern DMA_HandleTypeDef hdma_usart1_tx; //發送數組數據 void my_uart1_send_data(uint8_t *tdata,uint16_t tnum){ //等待發送狀態OK while(HAL_DMA_GetState(&hdma_usart1_tx) == HAL_DMA_STATE_BUSY) HAL_Delay(1); //發送數據 HAL_UART_Transmit_DMA(&huart1,tdata,tnum); } //發送字元串 void my_uart1_send_string(uint8_t *tdata){ //等待發送狀態OK while(HAL_DMA_GetState(&hdma_usart1_tx) == HAL_DMA_STATE_BUSY) HAL_Delay(1); //發送數據 HAL_UART_Transmit_DMA(&huart1,tdata,strlen(tdata)); }
串口庫函數中斷接收
uint8_t my_uart1_redata=0; //開啟串口接收中斷 void my_uart1_enable_inpterr(){ //開啟一次中斷 HAL_UART_Receive_IT(&huart1,&my_uart1_redata,1); } //串口收到數據回調 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){ if(huart->Instance == USART1)//判斷串口號 { //發送 my_uart1_send_data(&my_uart1_redata,1); //開啟一次中斷 HAL_UART_Receive_IT(&huart1,&my_uart1_redata,1); } }
主函數
//開啟中斷 my_uart1_enable_inpterr(); //發送數據 my_uart1_send_data("1dongxiaodong_DMA_1\r\n",strlen("1dongxiaodong_DMA_1\r\n")); my_uart1_send_data("2dongxiaodong_DMA_2\r\n",strlen("2dongxiaodong_DMA_2\r\n")); my_uart1_send_string("3dongxiaodong_DMA_3\r\n");
七、 串口DMA收和發
(一)CubeMX配置
通用配置
中斷開啟
DMA發送設置
DMA接收設置,要注意這裡是循環
(二)編程實現
收發函數原型
#include "string.h" extern DMA_HandleTypeDef hdma_usart1_tx; //發送數組數據 void my_uart1_send_data(uint8_t *tdata,uint16_t tnum){ //等待發送狀態OK while(HAL_DMA_GetState(&hdma_usart1_tx) == HAL_DMA_STATE_BUSY) HAL_Delay(1); //發送數據 HAL_UART_Transmit_DMA(&huart1,tdata,tnum); } //發送字元串 void my_uart1_send_string(uint8_t *tdata){ //等待發送狀態OK while(HAL_DMA_GetState(&hdma_usart1_tx) == HAL_DMA_STATE_BUSY) HAL_Delay(1); //發送數據 HAL_UART_Transmit_DMA(&huart1,tdata,strlen(tdata)); } uint8_t my_uart1_redata=0; //開啟串口接收中斷 void my_uart1_enable_inpterr(){ //開啟一次中斷 //HAL_UART_Receive_IT(&huart1,&my_uart1_redata,1); HAL_UART_Receive_DMA(&huart1,&my_uart1_redata,1);//設置接收緩衝區 } //串口收到數據回調 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){ if(huart->Instance == USART1)//判斷串口號 { //發送 my_uart1_send_data(&my_uart1_redata,1); //開啟一次中斷 //HAL_UART_Receive_IT(&huart1,&my_uart1_redata,1); } }
主函數使用
//初始化DMA接收 my_uart1_enable_inpterr(); //發送函數調用 my_uart1_send_data("1dongxiaodong_DMA_1\r\n",strlen("1dongxiaodong_DMA_1\r\n")); my_uart1_send_data("2dongxiaodong_DMA_2\r\n",strlen("2dongxiaodong_DMA_2\r\n")); my_uart1_send_string("3dongxiaodong_DMA_3\r\n");
八、printf實現
#include <stdio.h> int fputc(int ch,FILE *f) { uint32_t temp = ch; HAL_UART_Transmit(&huart1,(uint8_t *)&temp,1,0xFFFF); //huart1是串口的句柄 HAL_Delay(2); return ch; }
參考:
正點原子、洋桃電子