STM32F0x HAL库学习笔记(7)DMA数据的传输配置:串口数据的DMA发送与接收

本文开发环境:

  • MCU型号:STM32F051R8T6
  • IDE环境: MDK 5.25
  • 代码生成工具:STM32CubeMx 5.0.1
  • HAL库版本:v1.9.0(STM32Cube MCU Package for STM32F0 Series)

本文内容:

  1. DMA 内存到内存的数据传输
  2. DMA 内存到串口的数据传输
  3. DMA 串口数据到内存的传输


1. DMA 简介

DMA(直接存储器访问)提供了数据传输功能,如果我们不需要对数据进行处理,只需要原封不动的从一个地址搬移到另一个地址,那么DMA是一个不错的选择,特别在数据量很大的情况,可以大大的减轻内核的负担,再程序实现之前,建议了解一下DMA的基本原理和事件,以便更好的理解配置的过程。关于与DMA数据手册和网上已经有了非常多资源介绍,这里不再赘述。

2. DMA数据内存到内存的搬运

2.1 CubeMx 配置

内存到内存的配置比较简单,我们只需要指定使用哪个DMA通道即可,接着我们再到程序中指定具体的源内存地址和目标内存地址,即可实现DMA的数据搬运功能,首先添加一个DMA通道,具体如下所示:
在这里插入图片描述
当我们添加完了以后,就可以开始配置DMA参数,我们依次配置,如下所示:
在这里插入图片描述
设置里面有一个Mode,这里只能选择Normal模式,表示每启动一次搬运,则搬运一次后停止,而不是一直不断的搬运。Increment Address 表示搬运数据过程中内存地址是否增长,比如你想要源数组原封不动的搬运到目标数组,那么就需要源地址和目标地址都勾选地址增长,如果你需要把第源数组的第一个值,赋值给目标数组里面所有的元素,则不勾选源地址的地址增长,且勾选目标地址的增长。数据宽度我们都选择Byte,即使选择一字节长度来搬运数据。

2.2 Example

当我们配置好这些以后,就可以生成代码了(当然这建立在你工程的时钟模块等基本配置已经设置好的情况),如果你不清楚STM32CubeMx如何新建一个工程,可以参考一个简单的IO工程配置。接着我们可以使用以下函数来调用一次DMA数据传输:

while (1)
{
    /* USER CODE END WHILE */
		uint8_t scr_data[20] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19};
  	    uint8_t dst_data[20] = {0};
		HAL_Delay(50);
		
		HAL_DMA_Start(&hdma_memtomem_dma1_channel1, (uint32_t)&scr_data, (uint32_t)&dst_data,20);
		HAL_DMA_PollForTransfer(&hdma_memtomem_dma1_channel1,HAL_DMA_FULL_TRANSFER,20);
}

当我们运行这段程序后,在HAL_DMA_PollForTransfer()之后打印出dst_data[]数组的值,可以发现和scr_data[]的值一模一样,这证明我本次DMA搬运是成功的。你可以通过硬件调试直接查看数组的值。

这里简单解释一下HAL_DMA_Start()函数和HAL_DMA_PollForTransfert()函数:

2.2.1 HAL_DMA_Start() 函数

HAL_DMA_Start()函数开启了一次DMA传输,从 scr_data 向 dst_data 搬运数据,总共20个字节。当开启这是传输后,DMA将被标记为忙状态,CPU将运行以后的程序。

2.2.2 HAL_DMA_PollForTransfer() 函数

需要注意的是,由于我们CPU没必要也不应该去等DMA传输完成后再运行程序,所以当我们要开始一次新的传输之前,我们需要判断上次开启的DMA是不是已经传输完成了,这时候我们使用 HAL_DMA_PollForTransfer()来查看,第二个参数表示传输的完整度,可以选择Full或者是Half,我们这里选择了Full,只有传输完所有数据了,才认为是传输完成。这部分设计到了DMA传输事件的相关内容。这个函数如果检测到DMA完成了,还会把DMA的状态设置会待续。所以如果我们忘记使用这个函数,就会发现我们的HAL_DMA_Start()只能调用一次,此后每一次,都会返回DMA忙碌的错误状态。

3. 内存到外设的传输

我们可以把数据从内存传输到内存,也可以把数据传输到外设,当然也可以把外设的数据传到了内存。这里以内存到外设为例,把数组的值传到串口发送寄存器中,实现数据自动发送。

3.1 CubeMx 配置

如果我们没有配置串口,会发现添加DMA的时候,没有串口相关的DMA配置供我们选择,所以首先添配置串口(如果你已经配置好了,这一步就可以跳过):
在这里插入图片描述
以上是串口配置基本配置,前文以后介绍,不在赘述,接着我们需要配置DMA相关的参数:
在这里插入图片描述
Mode我们选择Normal,即发送一次既可,如果选择Circular,则会一直循环发送。由于我们是把数据发送到串口发送寄存器,所以目标地址不能增长。最后还需要勾选串口的全局中断使能,否则DMA传送只能发送一次(注1),具体操作如下:
在这里插入图片描述

3.2 Example

我们使用HAL_UART_Transmit_DMA()发送函数来开启一次串口的DMA传送,具体代码如下:

... ...
  uint8_t tx_str[] = "Welcome to Yonas's Blog!\r\n";
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
		HAL_UART_Transmit_DMA(&huart1,tx_str,sizeof(tx_str));
		HAL_Delay(500);
... ...
  }

我们定义了tx_str[]字符串,然后就可以使用HAL_UART_Transmit_DMA()函数发送这个字符串了,这里直接只用用sizeof()来计算大小,这样以便我们随意修改字符串数组。
当我们运行以上代码时候,就可以发现程序以500ms的速度向串口打印数据:
在这里插入图片描述

3. 外设到内存的传输

3.1 串口中 DMA 的配置

在这个示例中,选择DMA的模式为Circular模式,这是为了每一次串口中断,都可以发生搬运。
在这里插入图片描述

3.2 NVIC 中 DMA 的配置

需要开启串口中断和DMA中断,并且勾选生成代码,具体擦做如下:
在这里插入图片描述
接着还需要配置 Code generation ,具体操作如下:
在这里插入图片描述

3.3 Example

当我们配置好DMA后,就可以进行DMA传送了,以下是一个示例,它们被写在了main.c中:

... ...
uint8_t rx_buf[1];                             //接收串口数据存放的数组
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
		HAL_UART_Transmit(&huart1,rx_buf,sizeof(rx_buf),200);
}
... ...
void main(void)
{
	... ..
	HAL_UART_Receive_DMA(&huart1,rx_buf,1);                 //设置DMA接收到的数据存放在rx_buf中
	while(1)
	{
		... ...
	}
}

以上程序示例了一个功能,串口会返回接收到的数据。其原理是,当串口接收到1(注1)个数据以后,DMA将数据搬移到rx_buf(注2)数组中,完成后调用回到函数HAL_UART_RxCpltCallback(),而该函数会将rx_buf数组的值打印出来。

由 HAL_UART_Receive_DMA 第三个参数决定
由 HAL_UART_Receive_DMA 第二个参数决定

4. DMA 的回调函数的理解

这里再简单介绍一下DMA的回到函数,当串口数据传输完成,传输错误,传输一半,DMA 会调用回到函数,以下是HAL库HAL_UART_Transmit_DMA()函数的定义,可以发现库是在这里注册了回调函数:


    HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
    {
      /* Check that a Tx process is not already ongoing */
      if(huart->gState == HAL_UART_STATE_READY)
      {
        if((pData == NULL ) || (Size == 0U))
        {
          return HAL_ERROR;
        }
    
        /* In case of 9bits/No Parity transfer, pData buffer provided as input paramter 
           should be aligned on a u16 frontier, as data copy into TDR will be 
           handled by DMA from a u16 frontier. */
        if ((huart->Init.WordLength == UART_WORDLENGTH_9B) && (huart->Init.Parity == UART_PARITY_NONE))
        {
          if((((uint32_t)pData)&1U) != 0U)
          {
            return  HAL_ERROR;
          }
        }
    
        /* Process Locked */
        __HAL_LOCK(huart);
    
        huart->pTxBuffPtr = pData;
        huart->TxXferSize = Size;
        huart->TxXferCount = Size;
    
        huart->ErrorCode = HAL_UART_ERROR_NONE;
        huart->gState = HAL_UART_STATE_BUSY_TX;
    
        /* Set the UART DMA transfer complete callback */
        huart->hdmatx->XferCpltCallback = UART_DMATransmitCplt;
    
        /* Set the UART DMA Half transfer complete callback */
        huart->hdmatx->XferHalfCpltCallback = UART_DMATxHalfCplt;
    
        /* Set the DMA error callback */
        huart->hdmatx->XferErrorCallback = UART_DMAError;
    
        /* Set the DMA abort callback */
        huart->hdmatx->XferAbortCallback = NULL;
    
        /* Enable the UART transmit DMA channel */
        HAL_DMA_Start_IT(huart->hdmatx, (uint32_t)huart->pTxBuffPtr, (uint32_t)&huart->Instance->TDR, Size);
    
        /* Clear the TC flag in the ICR register */
        __HAL_UART_CLEAR_FLAG(huart, UART_CLEAR_TCF);
    
        /* Process Unlocked */
        __HAL_UNLOCK(huart);
    
        /* Enable the DMA transfer for transmit request by setting the DMAT bit
           in the UART CR3 register */
        SET_BIT(huart->Instance->CR3, USART_CR3_DMAT);
    
        return HAL_OK;
      }
      else
      {
        return HAL_BUSY;
      }
    }

从以上代码可以看出,UART_DMATransmitCplt()函数是传输完成的回调函数,进一步跟踪代码可以发现,在本例代码中,它最终调用的是串口发送完成的回调函数,这里虽然用户可以修改,但是非常不建议,毕竟作为CubeMx生成的代码下一次刷新又会改变到。我们可以重新定义串口发送完成回调函数:

/* USER CODE BEGIN 0 */
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
	uint8_t tx_str[] = "Data Transfer completed\r\n";
	HAL_UART_Transmit(&huart1,tx_str,sizeof(tx_str),200);
}

/* USER CODE END 0 */

添加这段代码到工程中,比如main.c后,可以发现发送完数据后,串口打印出“Data Transfer completed ”的提示信息,运行程序结果如下:
在这里插入图片描述

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值