STM32-HAL庫-UART學習


首先我們來看看HAL庫為我們提供了哪些函數

/* 以阻塞模式發送數據 */
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
/* 以阻塞模式接收數據 */
HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)

/* 以非阻塞模式發送數據 */
HAL_StatusTypeDef   HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
/* 以非阻塞模式接收數據 */
HAL_StatusTypeDef   HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)

/* 以DMA模式發送數據 */
HAL_StatusTypeDef   HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
/* 以DMA模式接收數據 */
HAL_StatusTypeDef   HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)

/* 關閉串口中斷,關閉串口DMA */
HAL_StatusTypeDef HAL_UART_DMAStop(UART_HandleTypeDef *huart)
/* 關閉串口接收中斷,關閉串口接收DMA */
HAL_StatusTypeDef HAL_UART_AbortReceive(UART_HandleTypeDef *huart)
/* 關閉串口發送中斷,關閉串口發送DMA */
HAL_StatusTypeDef HAL_UART_AbortTransmit(UART_HandleTypeDef *huart)

 

printf重定向

  以UART1為例

/* usart.c */
#include <stdio.h>
/* 將printf重定向到串口1,即可使用printf通過串口1發送信息 */
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif
PUTCHAR_PROTOTYPE
{
    HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);/
    return ch;
}

非阻塞模式接收定長數據

  使用UART1定長接收10個字符

  工程初始化

Mode(模式)  ->  Asynchronous(異步)

NVIC Settings(嵌套向量中斷控制器設置)  ->  USART1 global interrupt(USART1全局中斷)  ->  Enabled(使能)

  設置接收緩沖區

/* main.c */
uint8_t  uart1_receive_buffer[10];

  開啟串口接收中斷

/* main.c */
/* 接收來自串口1的數據並將將其存放在緩沖區中,接收長度為10 */
HAL_UART_Receive_IT(&huart1, uart1_receive_buffer, 10);

  接收回調函數

    當接收字符小於10個時,不會觸發中斷

/* 本函數在數據接收完成后將會自動被調用 */
/*
串口接收完成回調函數 */ void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { /* 判斷調用回調函數的是哪個串口 */ if(huart -> Instance == USART1 ) { /* 接收回調處理 */ } }

 DMA模式接收不定長數據

  開啟串口空閑中斷

/* usart.c */ 
void MX_USART1_UART_Init(void)
{
  /* USER CODE BEGIN USART1_Init 2 */

  /* 使能串口1空閑中斷 */
  __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
  
/* USER CODE END USART1_Init 2 */ }

  建立接收緩沖區

/* stm32f1xx_it.c */
/* 定義接收緩沖區,其大小根據實際需求決定 */
uint8_t USART1_RX_BUFFER_FLAG = 'A';
uint8_t USART1_RX_BUFFER_A[USART1_RX_BUFFER_SIZE];
uint8_t USART1_RX_BUFFER_B[USART1_RX_BUFFER_SIZE];
uint8_t USART1_RX_BufLen_A;
uint8_t USART1_RX_BufLen_B;

 

/* stm32f1xx_it.h */
#define  USART1_RX_BUFFER_SIZE 128
extern uint8_t  USART1_RX_BUFFER_FLAG;
extern uint8_t  USART1_RX_BUFFER_A[USART1_RX_BUFFER_SIZE];
extern uint8_t  USART1_RX_BUFFER_B[USART1_RX_BUFFER_SIZE];
extern uint8_t  USART1_RX_BufLen_A;
extern uint8_t  USART1_RX_BufLen_B;

  空閑中斷使能及開啟DMA接收

/* main.c */
int main(void)
{
    /* USER CODE BEGIN 2 */
/* 使能串口1空閑中斷 */ __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
/* 以DMA方式接收數據 */ HAL_UART_Receive_DMA(&huart1, (uint8_t*)USART1_RX_BUFFER_A, USART1_RX_BUFFER_SIZE);

/* USER CODE END 2 */

  中斷服務函數

  為了避免數據處理時間過長從而影響下一組數據的接收,這里采用交替緩沖區來實現。

  當然,如果采用環形緩沖區將會得到更好的效果,我以后會補充環形緩沖區的寫法。

  在這里不采用 HAL_UART_DMAStop 寫法,因為他會同時關掉發送和接收 DMA。

/* stm32f1xx_it.c */
void USART1_IRQHandler(void)
{
    /* USER CODE BEGIN USART1_IRQn 1 */
    
    /* 檢查中斷來源是否是空閑中斷 */
    if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE) == SET)
    {
        /* 清除空閑中斷標志 */
        __HAL_UART_CLEAR_IDLEFLAG(&huart1);

        /* 關閉接收中斷,關閉接收 DMA */
        HAL_UART_AbortReceive(&huart1);

     /* 交替使用緩沖區 */
        if(USART1_RX_BUFFER_FLAG == 'A')
        {
            /* 將緩沖區 B 作為當前 DMA 緩沖區開啟 DMA */
            HAL_UART_Receive_DMA(&huart1, (uint8_t*)USART1_RX_BUFFER_B, USART1_RX_BUFFER_SIZE);
            USART1_RX_BUFFER_FLAG = 'B';

            /* 計算緩沖區 A 的有效長度, 並對緩沖區 A 中的數據進行處理 */
            USART1_RX_BufLen_A = USART1_RX_BUFFER_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);
            USART1_RX_HANDLE(USART1_RX_BUFFER_A, USART1_RX_BufLen_A);

            /* 清空接收緩沖區 A */
            memset(USART1_RX_BUFFER_A, 0, USART1_RX_BufLen_A);
            USART1_RX_BufLen_A = 0;
        }
        else
        {
            /* 將緩沖區 A 作為當前 DMA 緩沖區開啟 DMA */
            HAL_UART_Receive_DMA(&huart1, (uint8_t*)USART1_RX_BUFFER_A, USART1_RX_BUFFER_SIZE);
            USART1_RX_BUFFER_FLAG = 'A';

            /* 計算緩沖區 B 的有效長度, 並對緩沖區 B 中的數據進行處理 */
            USART1_RX_BufLen_B = USART1_RX_BUFFER_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);
            USART1_RX_HANDLE(USART1_RX_BUFFER_B, USART1_RX_BufLen_B);

            /* 清空接收緩沖區 B */
            memset(USART1_RX_BUFFER_B, 0, USART1_RX_BufLen_B);
            USART1_RX_BufLen_B = 0;
        }
    }

    /* USER CODE END USART1_IRQn 1 */
}

  數據處理函數

/* stm32f1xx_it.c */
void USART1_RX_HANDLE(uint8_t *USART1_RX_BUFFER, uint8_t length)
{
    HAL_UART_Transmit(&huart1, (uint8_t *)USART1_RX_BUFFER, length, 0xFFFF);
}

  以前的一些筆記(僅作為記錄)

暫時只提供一個思路,未來會做出補充

大致思路

先使用DMA模式來接收數據,當數據接收完成后,會產生一個空閑中斷,什么時候產生空閑中斷什么地方就是數據的末尾

有了空閑中斷來了以后,先把空閑中斷的標志清零,然后停止DMA傳輸,通過讀寄存器的方式得到DMA傳輸的余量,把總長減去余量就等於有效數據的長度

這樣一來我們就得到了一個裝有有效數據的數組,和有效數據在這個數組的前多少位,這時候就可以開始數據處理了

數據處理完成以后把緩沖數組給清零,然后再開DMA傳輸

如果你的數據處理函數太長,甚至於會影響到串口接收下一組數據,那么可以先把緩沖數組備份,然后馬上清零,開DMA傳輸,然后再去慢慢的處理剛剛備份的數據

大致流程

1.在STM32CubeMX中開啟串口中斷(USART1 -> NVIC Settings -> USART1  global interrupt -> Enable)

2.在STM32CubeMX中開啟DMA傳輸(USART1 -> DMA Settings -> Add )

3.建立接收緩沖區

4.開啟DMA模式接收,接收長度為接收緩存區大小

5.開啟串口1空閑中斷

6.在串口1的中斷處理函數中調用空閑中斷處理函數

7.建立空閑中斷處理函數,並在該函數中完成判斷確認及清除空閑中斷標志,最后調用空閑中斷回調函數

8.建立空閑中斷回調函數,在該函數中先關閉DMA傳輸,然后通過DMA傳輸余量計算接收數據長度,接着你就得到有效數據在緩存數組中的長度了,開始對其進行數據處理,在數據處理結束以后,把緩沖區清零,重置有效長度變量,開啟DMA傳輸,讓系統等待下一次的接收
View Code

 

附錄:常用寄存器操作及C語言函數介紹

有必要了解的C/C++知識

初始化函數

/* 頭文件<string.h>或<cstring> */

/* 函數原型 */
void *memset(void *s,int ch,size_t n)

/* 解釋 */
/* 將s中的前n個字節(typedef unsigned int size_t)用ch替換,並返回s */

/* 應用 */
/* 對數組a中所有元素作清零處理 */
memset(a,0,sizeof(a));

有必要了解的寄存器操作 

讀取狀態寄存器(USART_SR)

/* stm32f1xx_it.c */
#define __HAL_UART_GET_FLAG(__HANDLE__, __FLAG__) (((__HANDLE__)->Instance->SR & (__FLAG__)) == (__FLAG__))

/* 應用舉例 */
/* 若串口1總線空閑,則.... */

if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE)==SET){ /* 串口空閑處理 */}

位4:IDLE:監測到總線空閑 (IDLE line detected)

  • 當檢測到總線空閑時,該位被硬件置位。
  • 如果USART_CR1中的IDLEIE為’1’,則產生中斷。由軟件序列清除該位(先讀USART_SR,然后讀USART_DR)。
  • 0:沒有檢測到空閑總線; 1:檢測到空閑總線
  • 注意:IDLE位不會再次被置高直到RXNE位被置起(即又檢測到一次空閑總線)

位5:RXNE:讀數據寄存器非空 (Read data register not empty)

  • 當RDR移位寄存器中的數據被轉移到USART_DR寄存器中,該位被硬件置位。
  • 如果 USART_CR1寄存器中的RXNEIE為1,則產生中斷。
  • 對USART_DR的讀操作可以將該位清 零。RXNE位也可以通過寫入0來清除,只有在多緩存通訊中才推薦這種清除程序。
  • 0:數據沒有收到; 1:收到數據,可以讀出

中斷使能操作(開中斷)

/* stm32f1xx_hal_uart.h */
/* 本寄存器操作涉及到控制寄存器1,2,3 */ #define __HAL_UART_ENABLE_IT(__HANDLE__, __INTERRUPT__) ((((__INTERRUPT__) >> 28U) == UART_CR1_REG_INDEX)? ((__HANDLE__)->Instance->CR1 |= ((__INTERRUPT__) & UART_IT_MASK)): \ (((__INTERRUPT__) >> 28U) == UART_CR2_REG_INDEX)? ((__HANDLE__)->Instance->CR2 |= ((__INTERRUPT__) & UART_IT_MASK)): \ ((__HANDLE__)->Instance->CR3 |= ((__INTERRUPT__) & UART_IT_MASK)))

/* 應用舉例 */
/* 開啟串口1空閑中斷 */
 __HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE);

清除空閑中斷標志

/* stm32f1xx_hal_uart.h */
#define __HAL_UART_CLEAR_IDLEFLAG(__HANDLE__) __HAL_UART_CLEAR_PEFLAG(__HANDLE__)

/* 應用舉例 */
/* 清除串口1空閑中斷標志 */
__HAL_UART_CLEAR_IDLEFLAG(&huart1);

讀取DMA通道x傳輸數量寄存器(DMA_CNDTRx)(x = 1…7)

/* stm32f1xx_hal_dma.h */
#define
__HAL_DMA_GET_COUNTER(__HANDLE__) ((__HANDLE__)->Instance->CNDTR)

/* 這一個寄存器的操作按理說應該放在DMA的學習部分,不過這一操作在串口程序中會用到,所以就在這里一並做出介紹 */

/* 應用舉例 */
/* 串口1通過DMA接收到的數據的剩余長度 */
length = __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);

位 15:0:NDT[15:0]:數據傳輸數量 (Number of data to transfer)

  • 數據傳輸數量為0至65535。這個寄存器只能在通道不工作(DMA_CCRx的EN=0)時寫入。
  • 通道開啟后該寄存器變為只讀,指示剩余的待傳輸字節數目。寄存器內容在每次DMA傳輸后遞減。
  • 數據傳輸結束后,寄存器的內容或者變為0;或者當該通道配置為自動重加載模式時,寄存器的內容將被自動重新加載為之前配置時的數值。
  • 當寄存器的內容為0時,無論通道是否開啟,都不會發生任何數據傳輸。
 

stm32f1xx_it.h


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM