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  模式為:浮空輸入

參數設置

Baud Rate

任意設置,未做限制,輸入框

 

Word Length

數據位可選8位或9位

Parity

校驗位可選無校驗(None)、偶校驗(Even)、奇校驗(Odd)

Stop Bits

停止位可選1位、2位

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);

四、串口掃描接收

(一)相關函數

阻塞接收函數

在阻塞模式下接收大量數據。

當沒有啟用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 :子優先級

數字越小優先級越高

自動生成的代碼中已經使能了中斷

(二)相關函數

接收中斷開啟,只開啟一次中斷

以非阻塞模式接收一定數量的數據,當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;

中斷接收回調函數

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;
}

參考:

正點原子、洋桃電子

Tags: