Stm32 HAL库 USART(发送+接收)全部采用DMA形式

Stm32 HAL库 USART(发送+接收)全部采用DMA形式

本文主要参考的是俄国一位大神的文章



1. 例程简介

1.1 DMA

  • DMA (直接存储区访问,为实现数据高速在外设寄存器与存储器之间或者存储器与存储器之间传输提供了高效的方法。其他的采用DMA的优点,相信您已经查阅了众多资料,此处就不浪费时间了。

1.2 U(S)ART

  • U(S)ART通常可以采用如下几种方式:
    • 轮询模式(无DMA,无IRQ):应用程序必须轮询状态位以检查是否已收到新字符并以足够快的速度读取以获取所有字节
    • 中断模式(无DMA):UART触发中断,CPU跳转到服务程序以处理数据接收
    • DMA模式:DMA用于在硬件级别将数据从USART RX数据寄存器传输到用户存储器。此时不需要应用程序交互,除非必要时按应用程序处理接收的数据

有个国外的帅气大神写的这篇文章,把几种方式的优缺点讲的非常清楚,DMA的不定长方式的使用也讲的很透彻,推荐各位参考

  • 本文主要介绍采用HAL库实现USART的DMA方式的发送+接收本文面向困扰于HAL库DMA方式的小白,仅仅给出最简单的实现方式,帮助他们快速入门,各位大佬们随便看看就好

2. 具体实现步骤

2.1 CubeMx-5.1.0 界面操作

  1. 引脚(GPIO)配置 如下图所示,小编把TXRX脚分别称为(GPS_TXGPS_RX)切勿介怀,时钟配置,工程等配置参考其他大佬的CubeMx-5.1.0入门教程 引脚(GPIO)配置

  2. 串口基本参数配置 这里采用的是串口3(USART3),如下图所示:
    串口基本参数配置

  3. NVIC设置 这里采用的是使能USART3全局中断,如下图所示
    NVIC配置

  4. DMA配置 这里是项目的核心,如下图进行设置

  • RX,TX分别采用DMA1 的3和2通道
  • RX选择方向为外设到内存,TX选择方向为内存到外设
  • RX和TX的增地址均为内存,外设地址不增
  • 外设和内存数据宽度均为为byte
  • RX,TX模式均为Normal这里RX不要采用Circle模式,否则等等主程序的判断条件会失效
    DMA配置
  1. 点击GENERATE CODE按钮,生成初始化程序:
    高级设置里面选择 HAL库
    选择HAL库
    生成代码的目录结构为:
    生成代码目录结构

2.2 编程展示

  • usart.c文件中,你会看到生成如下三个函数
函数名函数作用
MX_USART3_UART_Init(..)串口的初始化,包括串口的基本参数配置,GPIO,DMA配置等,会自动调用HAL_UART_MspInit(..)函数,所以,main.c只需要调用该函数进行串口初始化即可,无需再调用下面两个函数
HAL_UART_MspInit(..)串口底层函数初始化,包括GPIO,DMA等
HAL_UART_MspDeInit(..)串口解初始化,暂时不知道怎么用

请看我生成的代码,里面的设置就对应了我们在CubeMx里面的设置操作,可以仔细对应一下,加深理解

void MX_USART3_UART_Init(void)
{

  huart3.Instance = USART3;
  huart3.Init.BaudRate = 9600;
  huart3.Init.WordLength = UART_WORDLENGTH_8B;
  huart3.Init.StopBits = UART_STOPBITS_1;
  huart3.Init.Parity = UART_PARITY_NONE;
  huart3.Init.Mode = UART_MODE_TX_RX;
  huart3.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart3.Init.OverSampling = UART_OVERSAMPLING_16;
  huart3.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
  huart3.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
  if (HAL_UART_Init(&huart3) != HAL_OK)
  {
    Error_Handler();
  }

}

void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(uartHandle->Instance==USART3)
  {
  /* USER CODE BEGIN USART3_MspInit 0 */

  /* USER CODE END USART3_MspInit 0 */
    /* USART3 clock enable */
    __HAL_RCC_USART3_CLK_ENABLE();
  
    __HAL_RCC_GPIOD_CLK_ENABLE();
    /**USART3 GPIO Configuration    
    PD8     ------> USART3_TX
    PD9     ------> USART3_RX 
    */
    GPIO_InitStruct.Pin = GPS_TX_Pin|GPS_RX_Pin;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF7_USART3;
    HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);

    /* USART3 DMA Init */
    /* USART3_RX Init */
    hdma_usart3_rx.Instance = DMA1_Channel3;
    hdma_usart3_rx.Init.Request = DMA_REQUEST_2;
    hdma_usart3_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
    hdma_usart3_rx.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_usart3_rx.Init.MemInc = DMA_MINC_ENABLE;
    hdma_usart3_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
    hdma_usart3_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
    hdma_usart3_rx.Init.Mode = DMA_NORMAL;
    hdma_usart3_rx.Init.Priority = DMA_PRIORITY_MEDIUM;
    if (HAL_DMA_Init(&hdma_usart3_rx) != HAL_OK)
    {
      Error_Handler();
    }

    __HAL_LINKDMA(uartHandle,hdmarx,hdma_usart3_rx);

    /* USART3_TX Init */
    hdma_usart3_tx.Instance = DMA1_Channel2;
    hdma_usart3_tx.Init.Request = DMA_REQUEST_2;
    hdma_usart3_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
    hdma_usart3_tx.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_usart3_tx.Init.MemInc = DMA_MINC_ENABLE;
    hdma_usart3_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
    hdma_usart3_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
    hdma_usart3_tx.Init.Mode = DMA_NORMAL;
    hdma_usart3_tx.Init.Priority = DMA_PRIORITY_MEDIUM;
    if (HAL_DMA_Init(&hdma_usart3_tx) != HAL_OK)
    {
      Error_Handler();
    }

    __HAL_LINKDMA(uartHandle,hdmatx,hdma_usart3_tx);

    /* USART3 interrupt Init */
    HAL_NVIC_SetPriority(USART3_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(USART3_IRQn);
  /* USER CODE BEGIN USART3_MspInit 1 */

  /* USER CODE END USART3_MspInit 1 */
  }
}
  • main.c里面,主函数可以采用下面的代码形式(方便展示取出无用注释):
int main(void)
{
    // 发送测试字符串
    uint8_t str[] = "\r\n-------------USART_DMA_Sending------------------\r\n";
    // 接收缓存区大小为20
    uint8_t recvStr[20] = {0};
    /* MCU Configuration--------------------------------------------------------*/

    /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
    HAL_Init();

    /* Configure the system clock */
    SystemClock_Config();

    /* Initialize all configured peripherals */
    MX_GPIO_Init();     // 初始化串口和晶振相关IO口的时钟
    MX_DMA_Init();      // 初始化DMA的时钟,配置RX,TX的中断优先级

    //串口的初始化(串口配置,GPIO配置,DMA配置等)
    MX_USART3_UART_Init();

    /**********************我们添加的代码*****************************/
    
    // 采用DMA发送 str,按照str实际大小发送,不发送字符串末尾的'0'
    HAL_UART_Transmit_DMA(&huart3, str, sizeof(str) - 1); 
    
    HAL_Delay(1000);
    
    // 采用DMA接收函数,在while函数前要先使能一次DMA接收,存放在recvStr数组中,大小为20
    /**
     * 该函数能够,使能DMA传输完成中断,半传输完成中断,可以采用在stm32f4xx_it.c文件中
     * 对DMA1_Channel3_IRQHandler(void)添加断点观察
     * 可以先发送10个字符,再发送10个字符,会观察到两次停到这个地方,这是由于传输完成中断,半传输完成中断的作用
     * 因为我们设置的大小为 20
     */
    HAL_UART_Receive_DMA(&huart3, (uint8_t *)recvStr, 20);
    /***************************************************************/
    
    while (1)
    {
	    /**********************我们添加的代码*****************************/
	    //循环等待hdma_usart3_rx.State状态的变换,当接收到20个字符的大小后,DMA传输结束
	    //注:因我们采用的是正常模式,而非循环模式,所以才会这么使用,循环模式下,该标志位貌似不起作用
		if(hdma_usart3_rx.State == HAL_DMA_STATE_READY)
		{
			// 将接收到的字符打印出来进行观察
			HAL_UART_Transmit_DMA(&huart3, recvStr, sizeof(recvStr) - 1);
			HAL_Delay(1000);
			// 清除缓存区内容,方便进行下次接收
			memset(recvStr,0,sizeof(recvStr));
			// 软件将标志位清零
			hdma_usart3_rx.State = HAL_DMA_STATE_BUSY;
			// 继续继续下一回合的DMA接收,因为采用的非循环模式,再次调用会再次使能DMA
			HAL_UART_Receive_DMA(&huart3, (uint8_t *)recvStr, 20);
		}
        /***************************************************************/
    }
}

3. 实验结果展示

如下图所示,发送了3次20字符的数据,接收共计60个字符的数据,但是频率快了会造成数据丢失,后面慢慢优化吧。
在这里插入图片描述

  • 31
    点赞
  • 188
    收藏
    觉得还不错? 一键收藏
  • 13
    评论
在使用STM32 HAL库进行串口通信时,可以使用中断接收DMA发送的方式来提高通信效率。 首先需要初始化串口,并配置接收中断和DMA发送。以下是一个示例代码: ``` UART_HandleTypeDef huart1; void MX_USART1_UART_Init(void) { huart1.Instance = USART1; huart1.Init.BaudRate = 115200; huart1.Init.WordLength = UART_WORDLENGTH_8B; huart1.Init.StopBits = UART_STOPBITS_1; huart1.Init.Parity = UART_PARITY_NONE; huart1.Init.Mode = UART_MODE_TX_RX; huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; huart1.Init.OverSampling = UART_OVERSAMPLING_16; if (HAL_UART_Init(&huart1) != HAL_OK) { Error_Handler(); } /* Enable the UART Parity Error Interrupt */ HAL_UART_Receive_IT(&huart1, rxBuffer, 1); /* Enable the DMA transfer for transmit */ HAL_UART_Transmit_DMA(&huart1, txBuffer, strlen((char *)txBuffer)); } void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart == &huart1) { /* process received data */ HAL_UART_Receive_IT(&huart1, rxBuffer, 1); } } void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) { if (huart == &huart1) { /* transmit completed */ } } void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart) { if (huart == &huart1) { /* handle UART error */ } } ``` 在上面的代码中,`USART1`是串口的实例,`rxBuffer`和`txBuffer`是接收发送缓冲区。在串口初始化时,使用`HAL_UART_Receive_IT`函数开启接收中断,并使用`HAL_UART_Transmit_DMA`函数开启DMA发送。在接收中断回调函数`HAL_UART_RxCpltCallback`中,可以对接收到的数据进行处理,并继续接收下一个字节。在发送完成回调函数`HAL_UART_TxCpltCallback`中,可以进行一些操作,例如将发送缓冲区中的数据更新,等待下一次发送。在出现UART错误时,`HAL_UART_ErrorCallback`函数会被调用,可以在该函数中处理错误。 需要注意的是,在使用DMA发送时,需要保证发送缓冲区的数据不会被修改,直到DMA发送完成。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值