【WB32库开发】第13章(中)DMA直接存储器访问——存储器到外设

上一节中我们简单讲了DMA中的存储器到存储器例程,本节讲解如何配置DMA从存储器到外设的数据传输。

WB32的外设有很多,但涉及到DMA从存储器到外设的数据传输,一般指使用串口这个外设。

本节我们就通过固件库例程DMAC中的DMAC_MemoryToUart工程,来讲解如何配置DMA从存储器到串口传输数据。

13.4 DMA从存储器到串口的数据传输的配置

本节使用代码的主要功能是将定义的字符数组中的字符串传输到串口的输出寄存器中,通过串口调试器可以看到DMA传送来的数据。

13.4.1 预处理代码及宏定义部分

#include "wb32f10x.h"
#include <stdio.h>
#include "bsp_uart1.h"

char memSrc[16] = "Hello world!!!\r\n";                      //定义要传输的字符数组
UART_InitTypeDef UART_InitStructure;                         
DMAC_Channel_InitTypeDef DMAC_Channel_InitStruct;            
NVIC_InitTypeDef NVIC_InitStructure;                         

注意:
1)在这部分可以看到三个结构体宏定义,这是我们本节例程代码中将要配置到的三个结构体,分别为串口初始化配置、DMAC通道初始化配置还有中断初始化配置。

13.4.2 UART2初始化

  //开启GPIOA 与 UART2 的时钟,UART1挂载在APB1上,UART2挂载在APB2上,使能外设时钟时请注意。
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_BMX1 |RCC_APB1Periph_GPIOA, ENABLE);
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_BMX2 |RCC_APB2Periph_UART2, ENABLE);
  
  //UART2使用的GPIO端口初始化:PA2、PA3
  GPIO_Init(GPIOA, GPIO_Pin_2 | GPIO_Pin_3, GPIO_MODE_AF |GPIO_OTYPE_PP |GPIO_PUPD_UP |GPIO_SPEED_HIGH |GPIO_AF7);
  
  //UART结构体成员配置
  UART_DeInit(UART2);
  UART_InitStructure.UART_BaudRate = 115200;
  UART_InitStructure.UART_WordLength = UART_WordLength_8b;
  UART_InitStructure.UART_StopBits = UART_StopBits_One;
  UART_InitStructure.UART_Parity = UART_Parity_None;
  UART_InitStructure.UART_AutoFlowControl = UART_AutoFlowControl_None;
  UART_Init(UART2, &UART_InitStructure);
  UART_TxFIFOThresholdConfig(UART2, UART_TxFIFOThreshold_8);            //串口先入先出起点配置
  UART_FIFOCmd(UART2, ENABLE);
  UART_ProgrammableTHREModeCmd(UART2, ENABLE);                          //使能串口发送为空(Transport Empty)中断模式

本例程采用UART1来打印数据和获取数据,故在main函数中使用uart1_init(72000000, 115200);初始化UART1;但我们使用UART2来打印DMAC2传输的数据,故使用串口初始化结构体对UART2进行配置。

配置UART2为波特率115200,传输字长8位,停止位1位,无优先级。

13.4.3 DMAC2初始化

  DMAC_DeInit(DMAC2);
  
  /* 设置传输源地址为数组memSrc的首地址 */
  DMAC_Channel_InitStruct.DMAC_SourceBaseAddr = (uint32_t)memSrc;  
  /* 设置传输目标地址为UART2的串口数据寄存器 */            
  DMAC_Channel_InitStruct.DMAC_DestinationBaseAddr = (uint32_t)&UART2->THR;
  DMAC_Channel_InitStruct.DMAC_Interrupt = DMAC_Interrupt_Enable;
  DMAC_Channel_InitStruct.DMAC_SourceTransferWidth = DMAC_SourceTransferWidth_8b;
  DMAC_Channel_InitStruct.DMAC_DestinationTransferWidth = DMAC_DestinationTransferWidth_8b;
  /* 设置存放数据的源地址自增 */
  DMAC_Channel_InitStruct.DMAC_SourceAddrInc = DMAC_SourceAddrInc_Increment;
  /* 设置存放数据的外设(串口)地址不增 */
  DMAC_Channel_InitStruct.DMAC_DestinationAddrInc = DMAC_DestinationAddrInc_NoChange;
  DMAC_Channel_InitStruct.DMAC_SourceTransactionLength = DMAC_SourceTransactionLength_1;
  DMAC_Channel_InitStruct.DMAC_DestinationTransactionLength = DMAC_DestinationTransactionLength_8;
  /* 设置DMAC传输的方向为从内存到外设 */
  DMAC_Channel_InitStruct.DMAC_TransferTypeAndFlowControl = DMAC_TransferTypeAndFlowControl_MemoryToPeripheral_DMAC;
  /* 内存挂载在AHB总线上,所以设置接口为AHB */
  DMAC_Channel_InitStruct.DMAC_SourceMasterInterface = DMAC_SourceMasterInterface_AHB;
  /* 串口挂载在APB2总线上,所以设置接口为APB */
  DMAC_Channel_InitStruct.DMAC_DestinationMasterInterface = DMAC_DestinationMasterInterface_APB;
  /* 此处设置传输的字节数为16,这与我们申请的字符数组的数目是相同的:char memSrc[16] */
  DMAC_Channel_InitStruct.DMAC_BlockTransferSize = 16;                                               
  DMAC_Channel_InitStruct.DMAC_SourceHandshakingInterfaceSelect = DMAC_SourceHandshakingInterfaceSelect_Hardware;
  DMAC_Channel_InitStruct.DMAC_DestinationHandshakingInterfaceSelect = DMAC_DestinationHandshakingInterfaceSelect_Hardware;
  DMAC_Channel_InitStruct.DMAC_SourceHandshakingInterfacePolarity = DMAC_SourceHandshakingInterfacePolarity_High;
  DMAC_Channel_InitStruct.DMAC_DestinationHandshakingInterfacePolarity = DMAC_DestinationHandshakingInterfacePolarity_High;
  DMAC_Channel_InitStruct.DMAC_AutomaticSourceReload = DMAC_AutomaticSourceReload_Disable;
  DMAC_Channel_InitStruct.DMAC_AutomaticDestinationReload = DMAC_AutomaticDestinationReload_Disable;
  DMAC_Channel_InitStruct.DMAC_FlowControlMode = DMAC_FlowControlMode_0;
  DMAC_Channel_InitStruct.DMAC_FIFOMode = DMAC_FIFOMode_0;
  DMAC_Channel_InitStruct.DMAC_ChannelPriority = 0;
  DMAC_Channel_InitStruct.DMAC_ProtectionControl = 0x1;
  DMAC_Channel_InitStruct.DMAC_SourceHardwareHandshakingInterfaceAssign = 0;
  /* 设置DMAC的硬件握手接口为UART2_TX */
  DMAC_Channel_InitStruct.DMAC_DestinationHardwareHandshakingInterfaceAssign = DMAC_HardwareHandshakingInterface_UART2_TX;
  DMAC_Channel_InitStruct.DMAC_MaximumAMBABurstLength = 0;
  DMAC_Channel_Init(DMAC2, DMAC_Channel_0, &DMAC_Channel_InitStruct);
  /* 通过如下三个中断标识符我们可以了解到DMAC的工作状况 */
  DMAC_ITConfig(DMAC2, DMAC_Channel_0, DMAC_IT_BLOCK, ENABLE);
  DMAC_ITConfig(DMAC2, DMAC_Channel_0, DMAC_IT_TFR, ENABLE);
  DMAC_ITConfig(DMAC2, DMAC_Channel_0, DMAC_IT_ERR, ENABLE);
  /* 配置中断初始化结构体成员 */
  NVIC_InitStructure.NVIC_IRQChannel = DMAC2_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);
  
  printf("DMA transfer enable.\r\n");
  /* 当使能DMAC2后,程序即可完成内存数据向串口的传输 */
  DMAC_Cmd(DMAC2, ENABLE);
  DMAC_ChannelCmd(DMAC2, DMAC_Channel_0, ENABLE);
  

学习本节课代码时,可以结合上一节中的例程进行对比,本节代码将重点配置部分注释出来,大家可以对比学习。

初学者不必理解每一个结构体成员具体是如何配置的,我们最应该关心的是被传输数据的源地址,和想要传送到的目标地址。

确定了这两条后,即可根据使用模式的不同,来配置传输方向和源地址与目标地址的增量模式。如本例程中从内存中读取数据到串口,即DMA传输方向设置为从内存向外设方向;由于串口的位置固定且接收到一个数据就会发送一个数据,所以串口的地址不需要改变,只需要将源地址的内存自增取值,串口即可接收到数据。

另外还需注意的是数据传输大小,此处的值应与我们申请的变量的大小相等,这样才能保证传输的数据的完整。

13.4.4 DMAC2中断服务函数

void DMAC2_IRQHandler(void)
{
  if(DMAC_GetITStatus(DMAC2, DMAC_Channel_0, DMAC_IT_BLOCK) != RESET)
  {
    DMAC_ClearITPendingBit(DMAC2, DMAC_Channel_0, DMAC_IT_BLOCK);
    printf("DMA block transfer complete.\r\n");
  }

  if(DMAC_GetITStatus(DMAC2, DMAC_Channel_0, DMAC_IT_TFR) != RESET)
  {
    DMAC_ClearITPendingBit(DMAC2, DMAC_Channel_0, DMAC_IT_TFR);
    printf("DMA transfer complete.\r\n");
  }

  if(DMAC_GetITStatus(DMAC2, DMAC_Channel_0, DMAC_IT_ERR) != RESET)
  {
    DMAC_ClearITPendingBit(DMAC2, DMAC_Channel_0, DMAC_IT_ERR);
    printf("DMA transfer error!!!\r\n");
  }
}

此中断服务函数意在检查DMA是否正确传输,具体注释可参考上节内容。

13.4 实验现象

代码编译后烧录到WB32中,将UART1与CH340连接好,接在电脑上,打开串口调试工具,发送任意字符(本例中我发送’1’),实验结果如下:
在这里插入图片描述
UART1打印出的信息显示本次数据传输成功,但是由于我们连接的是UART1,所以无法从串口调试器中看到通过DMA传输给UART2的字符串。

13.5 小结

本节我们结合例程学习了如何配置DMA从存储器到外设传输数据,但例程的实验结果并不理想。

那么如何正确的实现本节例程的实验呢?下节内容,我将带着大家一起优化本节例程,使得本节例程使用UART2即可串口打印处所有信息,并在进入中断服务函数后点亮板载LED灯。

这里给出下节内容使用的例程下载地址,大家下载后直接放入固件库例程DMAC文件夹下即可:

在这里插入图片描述
链接:https://pan.baidu.com/s/1NlR9gGGBYI_Ms1H6GFWuiA
提取码:CSDN

版权声明:本文为CSDN博主「北海村夫」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_44499162/article/details/122324836

上一节中我们简单讲了DMA中的存储器到存储器例程,本节讲解如何配置DMA从存储器到外设的数据传输。

WB32的外设有很多,但涉及到DMA从存储器到外设的数据传输,一般指使用串口这个外设。

本节我们就通过固件库例程DMAC中的DMAC_MemoryToUart工程,来讲解如何配置DMA从存储器到串口传输数据。

13.4 DMA从存储器到串口的数据传输的配置

本节使用代码的主要功能是将定义的字符数组中的字符串传输到串口的输出寄存器中,通过串口调试器可以看到DMA传送来的数据。

13.4.1 预处理代码及宏定义部分

#include "wb32f10x.h"
#include <stdio.h>
#include "bsp_uart1.h"

char memSrc[16] = "Hello world!!!\r\n";                      //定义要传输的字符数组
UART_InitTypeDef UART_InitStructure;                         
DMAC_Channel_InitTypeDef DMAC_Channel_InitStruct;            
NVIC_InitTypeDef NVIC_InitStructure;                         

注意:
1)在这部分可以看到三个结构体宏定义,这是我们本节例程代码中将要配置到的三个结构体,分别为串口初始化配置、DMAC通道初始化配置还有中断初始化配置。

13.4.2 UART2初始化

  //开启GPIOA 与 UART2 的时钟,UART1挂载在APB1上,UART2挂载在APB2上,使能外设时钟时请注意。
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_BMX1 |RCC_APB1Periph_GPIOA, ENABLE);
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_BMX2 |RCC_APB2Periph_UART2, ENABLE);
  
  //UART2使用的GPIO端口初始化:PA2、PA3
  GPIO_Init(GPIOA, GPIO_Pin_2 | GPIO_Pin_3, GPIO_MODE_AF |GPIO_OTYPE_PP |GPIO_PUPD_UP |GPIO_SPEED_HIGH |GPIO_AF7);
  
  //UART结构体成员配置
  UART_DeInit(UART2);
  UART_InitStructure.UART_BaudRate = 115200;
  UART_InitStructure.UART_WordLength = UART_WordLength_8b;
  UART_InitStructure.UART_StopBits = UART_StopBits_One;
  UART_InitStructure.UART_Parity = UART_Parity_None;
  UART_InitStructure.UART_AutoFlowControl = UART_AutoFlowControl_None;
  UART_Init(UART2, &UART_InitStructure);
  UART_TxFIFOThresholdConfig(UART2, UART_TxFIFOThreshold_8);            //串口先入先出起点配置
  UART_FIFOCmd(UART2, ENABLE);
  UART_ProgrammableTHREModeCmd(UART2, ENABLE);                          //使能串口发送为空(Transport Empty)中断模式

本例程采用UART1来打印数据和获取数据,故在main函数中使用uart1_init(72000000, 115200);初始化UART1;但我们使用UART2来打印DMAC2传输的数据,故使用串口初始化结构体对UART2进行配置。

配置UART2为波特率115200,传输字长8位,停止位1位,无优先级。

13.4.3 DMAC2初始化

  DMAC_DeInit(DMAC2);
  
  /* 设置传输源地址为数组memSrc的首地址 */
  DMAC_Channel_InitStruct.DMAC_SourceBaseAddr = (uint32_t)memSrc;  
  /* 设置传输目标地址为UART2的串口数据寄存器 */            
  DMAC_Channel_InitStruct.DMAC_DestinationBaseAddr = (uint32_t)&UART2->THR;
  DMAC_Channel_InitStruct.DMAC_Interrupt = DMAC_Interrupt_Enable;
  DMAC_Channel_InitStruct.DMAC_SourceTransferWidth = DMAC_SourceTransferWidth_8b;
  DMAC_Channel_InitStruct.DMAC_DestinationTransferWidth = DMAC_DestinationTransferWidth_8b;
  /* 设置存放数据的源地址自增 */
  DMAC_Channel_InitStruct.DMAC_SourceAddrInc = DMAC_SourceAddrInc_Increment;
  /* 设置存放数据的外设(串口)地址不增 */
  DMAC_Channel_InitStruct.DMAC_DestinationAddrInc = DMAC_DestinationAddrInc_NoChange;
  DMAC_Channel_InitStruct.DMAC_SourceTransactionLength = DMAC_SourceTransactionLength_1;
  DMAC_Channel_InitStruct.DMAC_DestinationTransactionLength = DMAC_DestinationTransactionLength_8;
  /* 设置DMAC传输的方向为从内存到外设 */
  DMAC_Channel_InitStruct.DMAC_TransferTypeAndFlowControl = DMAC_TransferTypeAndFlowControl_MemoryToPeripheral_DMAC;
  /* 内存挂载在AHB总线上,所以设置接口为AHB */
  DMAC_Channel_InitStruct.DMAC_SourceMasterInterface = DMAC_SourceMasterInterface_AHB;
  /* 串口挂载在APB2总线上,所以设置接口为APB */
  DMAC_Channel_InitStruct.DMAC_DestinationMasterInterface = DMAC_DestinationMasterInterface_APB;
  /* 此处设置传输的字节数为16,这与我们申请的字符数组的数目是相同的:char memSrc[16] */
  DMAC_Channel_InitStruct.DMAC_BlockTransferSize = 16;                                               
  DMAC_Channel_InitStruct.DMAC_SourceHandshakingInterfaceSelect = DMAC_SourceHandshakingInterfaceSelect_Hardware;
  DMAC_Channel_InitStruct.DMAC_DestinationHandshakingInterfaceSelect = DMAC_DestinationHandshakingInterfaceSelect_Hardware;
  DMAC_Channel_InitStruct.DMAC_SourceHandshakingInterfacePolarity = DMAC_SourceHandshakingInterfacePolarity_High;
  DMAC_Channel_InitStruct.DMAC_DestinationHandshakingInterfacePolarity = DMAC_DestinationHandshakingInterfacePolarity_High;
  DMAC_Channel_InitStruct.DMAC_AutomaticSourceReload = DMAC_AutomaticSourceReload_Disable;
  DMAC_Channel_InitStruct.DMAC_AutomaticDestinationReload = DMAC_AutomaticDestinationReload_Disable;
  DMAC_Channel_InitStruct.DMAC_FlowControlMode = DMAC_FlowControlMode_0;
  DMAC_Channel_InitStruct.DMAC_FIFOMode = DMAC_FIFOMode_0;
  DMAC_Channel_InitStruct.DMAC_ChannelPriority = 0;
  DMAC_Channel_InitStruct.DMAC_ProtectionControl = 0x1;
  DMAC_Channel_InitStruct.DMAC_SourceHardwareHandshakingInterfaceAssign = 0;
  /* 设置DMAC的硬件握手接口为UART2_TX */
  DMAC_Channel_InitStruct.DMAC_DestinationHardwareHandshakingInterfaceAssign = DMAC_HardwareHandshakingInterface_UART2_TX;
  DMAC_Channel_InitStruct.DMAC_MaximumAMBABurstLength = 0;
  DMAC_Channel_Init(DMAC2, DMAC_Channel_0, &DMAC_Channel_InitStruct);
  /* 通过如下三个中断标识符我们可以了解到DMAC的工作状况 */
  DMAC_ITConfig(DMAC2, DMAC_Channel_0, DMAC_IT_BLOCK, ENABLE);
  DMAC_ITConfig(DMAC2, DMAC_Channel_0, DMAC_IT_TFR, ENABLE);
  DMAC_ITConfig(DMAC2, DMAC_Channel_0, DMAC_IT_ERR, ENABLE);
  /* 配置中断初始化结构体成员 */
  NVIC_InitStructure.NVIC_IRQChannel = DMAC2_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);
  
  printf("DMA transfer enable.\r\n");
  /* 当使能DMAC2后,程序即可完成内存数据向串口的传输 */
  DMAC_Cmd(DMAC2, ENABLE);
  DMAC_ChannelCmd(DMAC2, DMAC_Channel_0, ENABLE);
  

学习本节课代码时,可以结合上一节中的例程进行对比,本节代码将重点配置部分注释出来,大家可以对比学习。

初学者不必理解每一个结构体成员具体是如何配置的,我们最应该关心的是被传输数据的源地址,和想要传送到的目标地址。

确定了这两条后,即可根据使用模式的不同,来配置传输方向和源地址与目标地址的增量模式。如本例程中从内存中读取数据到串口,即DMA传输方向设置为从内存向外设方向;由于串口的位置固定且接收到一个数据就会发送一个数据,所以串口的地址不需要改变,只需要将源地址的内存自增取值,串口即可接收到数据。

另外还需注意的是数据传输大小,此处的值应与我们申请的变量的大小相等,这样才能保证传输的数据的完整。

13.4.4 DMAC2中断服务函数

void DMAC2_IRQHandler(void)
{
  if(DMAC_GetITStatus(DMAC2, DMAC_Channel_0, DMAC_IT_BLOCK) != RESET)
  {
    DMAC_ClearITPendingBit(DMAC2, DMAC_Channel_0, DMAC_IT_BLOCK);
    printf("DMA block transfer complete.\r\n");
  }

  if(DMAC_GetITStatus(DMAC2, DMAC_Channel_0, DMAC_IT_TFR) != RESET)
  {
    DMAC_ClearITPendingBit(DMAC2, DMAC_Channel_0, DMAC_IT_TFR);
    printf("DMA transfer complete.\r\n");
  }

  if(DMAC_GetITStatus(DMAC2, DMAC_Channel_0, DMAC_IT_ERR) != RESET)
  {
    DMAC_ClearITPendingBit(DMAC2, DMAC_Channel_0, DMAC_IT_ERR);
    printf("DMA transfer error!!!\r\n");
  }
}

此中断服务函数意在检查DMA是否正确传输,具体注释可参考上节内容。

13.4 实验现象

代码编译后烧录到WB32中,将UART1与CH340连接好,接在电脑上,打开串口调试工具,发送任意字符(本例中我发送’1’),实验结果如下:
在这里插入图片描述
UART1打印出的信息显示本次数据传输成功,但是由于我们连接的是UART1,所以无法从串口调试器中看到通过DMA传输给UART2的字符串。

13.5 小结

本节我们结合例程学习了如何配置DMA从存储器到外设传输数据,但例程的实验结果并不理想。

那么如何正确的实现本节例程的实验呢?下节内容,我将带着大家一起优化本节例程,使得本节例程使用UART2即可串口打印处所有信息,并在进入中断服务函数后点亮板载LED灯。

这里给出下节内容使用的例程下载地址,大家下载后直接放入固件库例程DMAC文件夹下即可:

在这里插入图片描述
链接:https://pan.baidu.com/s/1NlR9gGGBYI_Ms1H6GFWuiA
提取码:CSDN

版权声明:本文为CSDN博主「北海村夫」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_44499162/article/details/122324836

生成海报
点赞 0

北海村夫

我还没有学会写个人说明!

暂无评论

发表评论

相关推荐