首先我們來看看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傳輸,讓系統等待下一次的接收
附錄:常用寄存器操作及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
