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