首先我们来看看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