STM32 HAL庫之串列埠詳細篇
2021-01-14 02:30:06 軟體

一、基礎認識

(一) 並行通訊

原理:資料的各個位同時傳輸

優點:速度快

缺點:佔用引腳資源多,通常工作時有多條傳輸線進行資料傳輸

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

參考:

正點原子、洋桃電子