实验十三 DMA实现DAC产生正弦波

一.实验目标

1.通过编程实现DMA方式实现波形产生;

2.通过DAC1输出频率可调的正弦波。

3.使用TIMER6和DMA1控制DAC输出精确频率正弦波

二.知识储备及设计思路

DMA指直接存储器访问,它可以节省CPU资源,可以将数据从一个地址空间复制到另一个地址空间。这样的操作并没有让处理器工作拖延,反而可以被重新排程去处理其他的工作。DMA
传输对于高效能 嵌入式系统算法和网络是很重要的。

我们通过DAC的思路是:将函数值存储在RAM中,通过DMA将数组值传递给DAC,进而实现波形的生成;对于波形频率的控制可以通过定时器来较为精确的实现,即当定时器发生更新事件时,才执行DAC进行转换,通过控制函数值表转换完的时间进而控制输出波形的频率;对于波形幅度的控制可以对函数值数组进行倍增或缩减。注意DAC要关闭输出缓冲器,否则会产生削波;对于函数值要给一定的直流量,才能正常的输出波形。

DMA 简介

DMA,全称为:Direct Memory Access,即直接存储器访问。DMA 传输方式无需 CPU 直接
控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为 RAM 与 I/O
设备 开辟一条直接传送数据的通路,能使 CPU 的效率大为提高。 STM32F407 最多有 2 个
DMA 控制器(DMA1 和 DMA2),共 16 个数据流(每个控制器 8 个), 每一个 DMA
控制器都用于管理一个或多个外设的存储器访问请求。每个数据流总共可以有多达 8
个通道(或称请求)。每个数据流通道都有一个仲裁器,用于处理 DMA 请求间的优先级。
STM32F407 的 DMA 有以下一些特性:

● 双 AHB 主总线架构,一个用于存储器访问,另一个用于外设访问

● 仅支持 32 位访问的 AHB 从编程接口

● 每个 DMA 控制器有 8 个数据流,每个数据流有多达 8 个通道(或称请求)

● 每个数据流有单独的四级 32 位先进先出存储器缓冲区(FIFO),可用于 FIFO 模式或直
接模式。

● 通过硬件可以将每个数据流配置为:

1,支持外设到存储器、存储器到外设和存储器到存储器传输的常规通道

2,支持在存储器方双缓冲的双缓冲区通道

● 8 个数据流中的每一个都连接到专用硬件 DMA 通道(请求)

● DMA 数据流请求之间的优先级可用软件编程(4 个级别:非常高、高、中、低),在
软件优先级相同的情况下可以通过硬件决定优先级(例如,请求 0 的优先级高于请求 1)

● 每个数据流也支持通过软件触发存储器到存储器的传输(仅限 DMA2 控制器)

● 可供每个数据流选择的通道请求多达 8 个。此选择可由软件配置,允许几个外设启动
DMA 请求

● 要传输的数据项的数目可以由 DMA 控制器或外设管理: 1,DMA
流控制器:要传输的数据项的数目是 1 到 65535,可用软件编程

2外设流控制器:要传输的数据项的数目未知并由源或目标外设控制,这些外设通过硬
件发出传输结束的信号

● 独立的源和目标传输宽度(字节、半字、字):源和目标的数据宽度不相等时,DMA
自动封装/解封必要的传输数据来优化带宽。这个特性仅在 FIFO 模式下可用。

● 对源和目标的增量或非增量寻址

● 支持 4 个、8 个和 16 个节拍的增量突发传输。突发增量的大小可由软件配置,通常等
于外设 FIFO 大小的一半

● 每个数据流都支持循环缓冲区管理

● 5 个事件标志(DMA 半传输、DMA 传输完成、DMA 传输错误、DMA FIFO
错误直接模式错误),进行逻辑或运算,从而产生每个数据流的单个中断请求

STM32F407 有两个 DMA 控制器,DMA1 和 DMA2,本章,我们仅针对 DMA1 进行介绍。
STM32F407的 DMA 控制器框图如图 所示:

DMA 控制器执行直接存储器传输:因为采用 AHB 主总线,它可以控制 AHB 总线矩阵来
启动 AHB 事务。它可以执行下列事务:

1.外设到存储器的传输

2.存储器到外设的传输

3.存储器到存储器的传输

三.引脚说明与硬件连接

我们在DAC-电压输出的实验基础上进行本实验的开发。打开开发板的原理图,可以看到DAC的引脚是PA4和PA5,分别是通道1和通道2的输出,如图1所示,在使用过程中将这两个管脚接在万用表或者示波器上即可观察DAC两个通道的输出。

图1 单片机内部DAC的输出引脚

图2 开发板上DAC输出脚位置

如图2所示,在开发板上,这两个引脚接在了J2排母上,可以采用杜邦线进行连接。

在实际使用中,我们选择了输出1通道,在实际调试时可以将1通道接在示波器,共地之后就能在示波器上看到DAC的输出波形。

DAC常用的寄存器如下(以DAC1为例):

控制寄存器(CR):

控制寄存器具体介绍如下:

其他寄存器可以在数据手册中查阅,这里仅介绍比较重要的控制寄存器。该寄存器可以配置DAC的使能情况,并配置输出缓冲器,此外还包括DMA、中断等功能的配置,可以通过更改寄存器的值来改变模式。

在HAL库开发模式下,可以用库函数代替寄存器的配置过程。所使用的结构体为:

所使用的主要结构体为DAC_HandleTypeDef,其中主要成员的含义为:

Instance:寄存器基地址

State:DAC通讯状态

……

大部分的定义都是通过对instance成员赋值进行的,具体可参考工程代码。

四.程序整体框图和设计

未命名文件(2)

本实验主要通过DMA的内存-外设功能将函数值表传递给DAC,通过独立按键对幅度和频率进行控制,同时在LCD上显示当前的频率和幅度值。

下面是介绍主要程序内容:

1.DAC配置过程

和DAC实验相比,不同的地方在于本例使用了DMA,故在配置时有所不同。

1
2
3
4
5
| DAC_HandleTypeDef hdac; DMA_HandleTypeDef hdma_dac1;//两个结构体对DMA下的DAC进行配置 |
| ------------------------------------------------------------ |

| |
| ---- |
1
2
3
4
| void MX_DAC_Init(void) {  DAC_ChannelConfTypeDef sConfig = {0};   /\*\* DAC Initialization  \*/  hdac.Instance = DAC;  if (HAL_DAC_Init(&hdac) != HAL_OK)  {  Error_Handler();  }  /\*\* DAC channel OUT1 config  \*/  sConfig.DAC_Trigger = DAC_TRIGGER_T6_TRGO;  sConfig.DAC_OutputBuffer = DAC_OUTPUTBUFFER_DISABLE;  if (HAL_DAC_ConfigChannel(&hdac, \&sConfig, DAC_CHANNEL_1) != HAL_OK)  {  Error_Handler();  }  } |
| ------------------------------------------------------------ |

>

这里是CubeMX配置的初始化函数。定义了触发类型为定时器触发,输出缓冲器禁用等。

1
2
3
4
5
| void HAL_DAC_MspInit(DAC_HandleTypeDef\* dacHandle) {  GPIO_InitTypeDef GPIO_InitStruct = {0};  if(dacHandle-\>Instance==DAC)  {  /\* USER CODE BEGIN DAC_MspInit 0 \*/  /\* USER CODE END DAC_MspInit 0 \*/  /\* DAC clock enable \*/  \__HAL_RCC_DAC_CLK_ENABLE();   \__HAL_RCC_GPIOA_CLK_ENABLE();  /\*\*DAC GPIO Configuration  PA4 ------\> DAC_OUT1  \*/  GPIO_InitStruct.Pin = GPIO_PIN_4;  GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;  GPIO_InitStruct.Pull = GPIO_NOPULL;  HAL_GPIO_Init(GPIOA, \&GPIO_InitStruct); |
| ------------------------------------------------------------ |

| |
| ---- |
1
2
3
4
| /\* DAC DMA Init \*/  /\* DAC1 Init \*/  hdma_dac1.Instance = DMA1_Stream5;  hdma_dac1.Init.Channel = DMA_CHANNEL_7;  hdma_dac1.Init.Direction = DMA_MEMORY_TO_PERIPH;  hdma_dac1.Init.PeriphInc = DMA_PINC_DISABLE;  hdma_dac1.Init.MemInc = DMA_MINC_ENABLE;  hdma_dac1.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;  hdma_dac1.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;  hdma_dac1.Init.Mode = DMA_CIRCULAR;  hdma_dac1.Init.Priority = DMA_PRIORITY_MEDIUM;  hdma_dac1.Init.FIFOMode = DMA_FIFOMODE_DISABLE;  if (HAL_DMA_Init(&hdma_dac1) != HAL_OK)  {  Error_Handler();  }  \__HAL_LINKDMA(dacHandle,DMA_Handle1,hdma_dac1);  /\* DAC interrupt Init \*/  HAL_NVIC_SetPriority(TIM6_DAC_IRQn, 1, 0);  HAL_NVIC_EnableIRQ(TIM6_DAC_IRQn);  /\* USER CODE BEGIN DAC_MspInit 1 \*/  /\* USER CODE END DAC_MspInit 1 \*/  } } |
| ------------------------------------------------------------ |

>

这一段是对DMA的具体配置。首先对GPIO的PA4即DAC输出引脚进行配置,然后对DMA1的7通道进行使能,这可以在手册中查找到:

可以找到数据流5、通道7即为DAC1的DMA通道。在配置完通道之后,又继续配置了存储器到外设模式,外设地址增量禁止,存储器地址增量使能,传输数据量为单字,进行循环DMA传输。优先级是中等,没有使用输出FIFO。然后将两个结构连接起来,DMA即可给DAC传送数据了。

2.定时器配置过程

1
2
3
4
| void MX_TIM6_Init(void) {  TIM_MasterConfigTypeDef sMasterConfig = {0};   htim6.Instance = TIM6;  htim6.Init.Prescaler = 0;  htim6.Init.CounterMode = TIM_COUNTERMODE_UP;  htim6.Init.Period = 328;  htim6.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;  if (HAL_TIM_Base_Init(&htim6) != HAL_OK)  {  Error_Handler();  }  sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;  if (HAL_TIMEx_MasterConfigSynchronization(&htim6, \&sMasterConfig) != HAL_OK)  {  Error_Handler();  }  } |
| ------------------------------------------------------------ |

>

这里是CubeMX自动生成的配置代码。具体含义为:将定时器分频系数设置为0(即不进行分频操作,按手册知其时钟为84MHz;计数模式为增加,即从0增加到period项设置的值;period项设置了计数的个数,也即经过多少时钟脉冲后产生一次更新。在计数完成后重新装载,再次计数。

以上是具体的配置代码,其他部分详见工程文件。

五.实验结果

将DAC1输出接入示波器,可以看到有波形输出。按下开发板上的按键对频率和幅度进行调整,可以得到不同状态的波形:

六.STM32CubeMX配置关键硬件截图

本例在上一章节DAC输出程序模板的配置文件上进行开发,所用到的外设如图所示:

其中需要特别配置的有DMA、DAC、TIM6等。下面将分别介绍一下它们的配置细节。

1.DAC

在这里对DMA进行配置,关闭输出缓冲器,触发调整为timer6的更新事件,关闭输出波形模式。在DMA
Setting选项卡里面点击Add,设置循环模式,Word数据长度即可。

2.TIM

在定时器配置中将预分频设为0,计数模式为增加,计数周期为352,自动重载打开,触发事件是更新事件。这里352计算如下:由于时钟频率为84MHz,正弦表长度是256,计数x次后输出正弦表的一个元素,那么我需要1kHz的波形频率时,需要满足(1/84M)*x*256=1ms,计算得x=328.125,近似为328。

3.DMA

对DAC增加DMA配置后,DMA列表自动添加了DMA的配置项。