39. DAC—输出正弦波

本章参考资料:《STM32F10X-中文参考手册》DAC章节。

学习本章时,配合《STM32F10X-中文参考手册》DAC章节一起阅读,效果会更佳,特别是涉及到寄存器说明的部分。

39.1. DAC简介

DAC为数字/模拟转换模块,故名思议,它的作用就是把输入的数字编码,转换成对应的模拟电压输出,它的功能与ADC相反。 在常见的数字信号系统中,大部分传感器信号被化成电压信号,而ADC把电压模拟信号转换成易于计算机存储、处理的数字编码, 由计算机处理完成后,再由DAC输出电压模拟信号,该电压模拟信号常常用来驱动某些执行器件,使人类易于感知。如音频信号的采集及还原就是这样一个过程。

STM32具有片上DAC外设,它的分辨率可配置为8位或12位的数字输入信号,具有两个DAC输出通道,这两个通道互不影响, 每个通道都可以使用DMA功能,都具有出错检测能力,可外部触发。

39.2. DAC功能框图剖析

STM32的DAC模块框图见图 DAC功能框图

DAC功能框图

整个DAC模块围绕框图下方的“数字至模拟转换器x”展开,它的左边分别是参考电源的引脚:\(V_{\text{DDA}}\)\(V_{\text{SSA}}\)\(V_{ref +}\), 其中STM32的DAC规定了它的参考电压:math:V_{ref +}输入范围为2.4——3.3V。 “数字至模拟转换器x”的输入为DAC的数据寄存器“DORx”的数字编码,经过它转换得的模拟信号由图中右侧的“DAC_OUTx”输出。 而数据寄存器“DORx”又受“控制逻辑”支配,它可以控制数据寄存器加入一些伪噪声信号或配置产生三角波信号。图中的左上角为DAC的触发源, DAC根据触发源的信号来进行DAC转换,其作用就相当于DAC转换器的开关,它可以配置的触发源为外部中断源触发、定时器触发或软件控制触发。 如本章实验中需要控制正弦波的频率,就需要定时器定时触发DAC进行数据转换。

39.2.1. 参考电压

与ADC外设类似,DAC也使用VREF+引脚作为参考电压, 在设计原理图的时候一般把VSSA接地,把VREF+和VDDA 接3.3V, 可得到DAC的输出电压范围为:0~3.3V。

如果想让输出的电压范围变宽,可以在外部加一个电压调理电路,把0~3.3V的DAC输出抬升到特定的范围即可。

39.2.2. 数模转换及输出通道

框图中的“数字至模拟转换器x”是核心部件,整个DAC外设都围绕它而展开。它以左边的VREF+作为参考电源, 以DAC的数据寄存器“DORx”的数字编码作为输入,经过它转换得的模拟信号由右侧的“DAC_OUTx”通道输出。其中各个部件中的“x”是指设备的标号, 在STM32中具有2个这样的DAC部件,每个DAC有1个对应的输出通道连接到特定的引脚,即:PA4-通道1,PA5-通道2,为避免干扰,使用DAC功能时, DAC通道引脚需要被配置成模拟输入功能(AIN)。

39.2.3. 触发源及DHRx寄存器

在使用DAC时,不能直接对上述DORx寄存器写入数据,任何输出到DAC通道x的数据都必须写入到DHRx寄存器中(其中包含DHR8Rx、DHR12Lx等, 根据数据对齐方向和分辨率的情况写入到对应的寄存器中)。

数据被写入到DHRx寄存器后,DAC会根据触发配置进行处理,若使用硬件触发,则DHRx中的数据会在3个APB1时钟周期后传输至DORx, DORx随之输出相应的模拟电压到输出通道;若DAC设置为外部事件触发,可以使用定时器(TIMx_TRGO)、 EXTI_9信号或软件触发(SWTRIGx)这几种方式控制数据DAC转换的时机,例如使用定时器触发,配合不同时刻的DHRx数据,可实现DAC输出正弦波的功能。

39.3. DAC初始化结构体详解

在ST的标准库中,把控制DAC相关的各种配置封装到了结构体DAC_InitTypeDef中, 它主要包含了DAC_CR控制寄存器的各寄存器位的配置,见 代码清单:DAC-1

代码清单:DAC-1 DAC_InitTypeDef结构体
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
typedef struct {
    /*DAC触发方式 */
    uint32_t DAC_Trigger;

    /*是否自动输出噪声或三角波 */
    uint32_t DAC_WaveGeneration;

    /*选择噪声生成器的低通滤波或三角波的幅值 */
    uint32_t DAC_LFSRUnmask_TriangleAmplitude;

    /*选择是否使能输出缓冲器 */
    uint32_t DAC_OutputBuffer;

} DAC_InitTypeDef;

各个结构体成员的介绍如下,解说中各模式后括号内的英文为该模式在标准库中使用宏:

(1) DAC_Trigger

本成员用于配置DAC的触发模式,当DAC产生相应的触发事件时,才会把DHRx寄存器的值转移到DORx寄存器中进行转换。 本结构体成员可以选择的触发模式如下:硬件触发模式(DAC_Trigger_None),DHRx寄存器内的数据会在3个APB1时钟周期内自动转换至DORx进行转换; 定时器触发模式(DAC_Trigger_T2/4/5/6/7_TRGO),使用定时器2、4、5、6、7控制DHRx寄存器的数据按时间转移到DORx中进行转换, 利用这种方式可以输出特定的波形;EXTI_9触发方式(DAC_Trigger_Ext_IT9),当产生EXTI_9事件时(如GPIO中断事件), 触发转换;软件触发模式(DAC_Trigger_Software),在本模式下,向DAC_SWTRIGR寄存器写入配置即可触发信号进行转换。

(2) DAC_WaveGeneration

本成员用于设置是否使用DAC输出伪噪声或三角波(DAC_WaveGeneration_None/Noise/Triangle),使用伪噪声和三角波输出时, DAC都会把LFSR寄存器的值叠加到DHRx数值上,产生伪噪声和三角波,若希望产生自定义的输出时,直接配置为DAC_WaveGeneration_None即可。

(3) DAC_LFSRUnmask_TriangleAmplitude

本成员通过控制DAC_CR的MAMP2位设置LFSR寄存器位的数据,即当使用伪噪声或三角波输出时要叠加到DHRx的值,非噪声或三角波输出模式下, 本配置无效。使用伪噪声输出时LFSR=0xAAA,MAMP2寄存器位可以屏蔽LFSR的某些位, 这时把本结构体成员赋值为DAC_LFSRUnmask_Bit0~DAC_LFSRUnmask_Bit11_0等宏即可;使用三角波输出时,本结构体设置三角波的最大幅值, 可选择为DAC_TriangleAmplitude_1~ DAC_TriangleAmplitude_4096等宏,见图 DAC输出三角波 , DAC在DHRx值的基础上升,幅值达到MAMPx设置的最大幅度时下降,形成三角波的输出。

DAC输出三角波

(4) DAC_OutputBuffer

本结构体成员用于控制是否使能DAC的输出缓冲(DAC_OutputBuffer_Enable/Disable), 使能了DAC的输出缓冲后可以减小输出阻抗,适合直接驱动一些外部负载。

39.4. DAC输出正弦波实验

利用STM32的DAC配合TIM定时器,可以输出随时间变化的电压,本章的实验以输出正弦波为例,演示如何控制输出电压波形。

39.4.1. 硬件设计

STM32的DAC外设有固定的输出通道,分别为PA4和PA5,不过,霸道开发板已经在板载SPI-FLASH芯片中使用了这两个引脚, 所以用作DAC通道输出电压时会受到干扰,影响实验,见图 SPIFLASH占用了DAC使用的输出通道

SPIFLASH占用了DAC使用的输出通道

警告

PA4引脚在上述SPI-FLASH的电路中已通过上拉电阻接到3.3V,PA5引脚则连接到FLASH芯片的CLK引脚中, 这些都会干扰DAC实验输出的电压信号,导致得不到正确的波形。在实验时,需要把开发板中SPI-FLASH附近的上拉电阻R6拆掉, 拆掉R6后,本实验中的PA4、PA5的波形输出正常,且不会影响后续SPI-FLASH的使用(只要DAC和SPI-FLASH不在同一个实验中使用即可)。

注解

在设计DAC专门的实际应用时,DAC的输出通道应独占,不与其它设备共用。本开发板的设计是考虑到各种资源分配,才不得不占用DAC通道的。

拆掉相应的元件后,实验时直接使用示波器测量PA4和PA5引脚的输出即可。

39.4.2. 软件设计

为了使工程更加有条理,我们把DAC控制相关的代码独立分开存储,方便以后移植。新建“bsp_dac.c”及“bsp_dac.h”文件, 这些文件也可根据您的喜好命名,它们不属于STM32标准库的内容,是由我们自己根据应用需要编写的。

39.4.2.1. 编程要点

1) 计算获取正弦波数据表;

2) 根据正弦波数据表的周期内点数和周期计算定时器触发间隔;

3) 初始化DAC输出通道,初始化DAC工作模式;

4) 配置触发DAC用的定时器;

5) 配置DMA自动转运正弦波数据表。

配置完成后,即可在PA4、PA5引脚中检测到信号输出。

39.4.2.2. 代码分析

生成正弦波数据表

要输出正弦波,实质是要控制DAC以v=sin(t)的正弦函数关系输出电压,其中v为电压输出,t为时间。

而由于模拟信号连续而数字信号是离散的,所以使用DAC产生正弦波时,只能按一定时间间隔输出正弦曲线上的点, 在该时间段内输出相同的电压值,若缩短时间间隔,提高单个周期内的输出点数,可以得到逼近连续正弦波的图形, 见图 DAC按点输出正弦波数据 ,若在外部电路加上适当的电容滤波,可得到更完美的图形。

DAC按点输出正弦波数据

由于正弦曲线是周期函数,所以只需要得到单个周期内的数据后按周期重复即可,而单个周期内取样输出的点数又是有限的, 所以为了得到呈v=sin(t)函数关系电压值的数据通常不会实时计算获取,而是预先计算好函数单个周期内的电压数据表,并且转化成以DAC寄存器表示的值。

如sin函数值的范围为[-1: +1],而STM32的DAC输出电压范围为[0~3.3]V,按12位DAC分辨率表示的方法, 可写入寄存器的最大值为212 = 4096,即范围为[0:4096]。所以,实际输出时,会进行如下处理:

1) 抬升sin函数的输出为正值:v = sin(t)+1 , 此时,v的输出范围为[0:2];

2) 扩展输出至DAC的全电压范围: v = 3.3*(sin(t)+1)/2 , 此时,v的输出范围为[0:3.3],正是DAC的电压输出范围,扩展至全电压范围可以充分利用DAC的分辨率;

3) 把电压值以DAC寄存器的形式表示:Reg_val = 212/3.3 * v = 211*(sin(t)+1), 此时,存储到DAC寄存器的值范围为[0:4096];

4) 实践证明,在sin(t)的单个周期内,取32个点进行电压输出已经能较好地还原正弦波形, 所以在t∈[0:2π]区间内等间距根据上述Reg_val公式运算得到32个寄存器值,即可得到正弦波表;

5) 控制DAC输出时,每隔一段相同的时间从上述正弦波表中取出一个新数据进行输出, 即可输出正弦波。改变间隔时间的单位长度,可以改变正弦波曲线的周期。

为方便起见,我们使用了Python和Matlab脚本制作正弦波表,脚本的代码存储在本工程的目录下,感兴趣可以打开文件查看, 以下列出Python脚本代码,见 代码清单:DAC-2

代码清单:DAC-2 制作正弦波数据表的python脚本(工程目录下的sinWave.py文件)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#! python3
#coding=utf-8

"""
Python版本:3.x
外部库:matplotlib1.5.3、numpy1.11.2

运行方式:
在命令行中输入:python sinWave.py

运行结果:
命令行中会打印计算得的各点数据,
在当前目录下会生成py_dac_sinWav.c文件,包含上述数据,
并且会弹出描绘曲线的对话框。
"""

import matplotlib.pyplot as plt
import numpy as np
import math

#修改本变量可以更改点数,如16、32、64等
POINT_NUM = 32

pi = math.pi

#一个周期 POINT_NUM 个点
n = np.linspace(0,2*pi,POINT_NUM)

#计算POINT_NUM个点的正弦值
a = map(math.sin,n)

r =[]
for i in a:
    #调整幅值至在0~1区间
    i+=1

    #按3.3V电压调整幅值
    i*= 3.3/2

    #求取dac数值,12位dac LSB = 3.3V/2**12
    ri = round(i*2**12/3.3)

    #检查参数
    if ri >= 4095:
        ri = 4095

    #得到dac数值序列
    r.append( ri )


print(list(map(int,r)))

#写入序列到文件
with open("py_dac_sinWav.c",'w',encoding= 'gb2312') as f:
    print(list(map(int,r)),file= f)

#绘图
plt.plot(n,r,"-o")
plt.show()

Python脚本的实现原理就是前面介绍的正弦波数据表的制作过程,运行后,该脚本把得到的正弦波表数据输出到目录下的py_dac_sinWav.c文件中, 见 代码清单:DAC-3 ,并且根据取样点描绘出示意图,见图 python脚本根据正弦波表描绘的曲线图。 Matlab脚本原理相同,此处不再列出,实际上使用C语言也能制作正弦波表,只是画图不方便而已。

代码清单:DAC-3 生成的正弦波数据表
1
2
3
4
[2048, 2460, 2856, 3218, 3532, 3786, 3969, 4072,
 4093, 4031, 3887, 3668, 3382, 3042, 2661, 2255,
 1841, 1435, 1054,  714,  428,  209,   65,    3,
   24,  127,  310,  564,  878, 1240, 1636, 2048]
python脚本根据正弦波表描绘的曲线图

DAC宏定义

制作好正弦波数据表后,开始使用MDK编写STM32的DAC工程,首先设置好相关的宏, 见 代码清单:DAC-4

代码清单:DAC-4 DAC宏定义(bsp_dac.h文件)
1
2
//DAC DHR12RD寄存器,12位、右对齐、双通道
#define DAC_DHR12RD_ADDRESS      (DAC_BASE+0x20)

此处定义的宏DAC_DHR12RD_ ADDRESS是寄存器DHR12RD的地址,该寄存器是12位右对齐的双通道寄存器, 见图 DHR12RD寄存器说明。在本实验中将会使用DMA把正弦波数据表的点数据赋值到该寄存器中, 往该寄存器赋值后的数据会在DAC被触发的时候搬运到2个DAC转换器,然后在这2个通道中输出以12位右对齐表示的这两个通道的电压。DAC中还有其它寄存器, 它们的功能类似,可以在《STM32中文参考手册》中了解到。

DHR12RD寄存器说明

与DAC控制相关的引脚固定是PA4和PA5,就不使用宏定义了,在源代码中会直接使用引脚号操作。

DAC GPIO和模式配置

代码清单:DAC-5 DAC GPIO和模式配置
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/**
* @brief  使能DAC的时钟,初始化GPIO
* @param  无
* @retval 无
*/
static void DAC_Config(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    DAC_InitTypeDef  DAC_InitStructure;

    /* 使能GPIOA时钟 */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

    /* 使能DAC时钟 */
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);

    /* DAC的GPIO配置,模拟输入 */
    GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_4 | GPIO_Pin_5;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    /* 配置DAC 通道1 */
    //使用TIM2作为触发源
    DAC_InitStructure.DAC_Trigger = DAC_Trigger_T2_TRGO;
    //不使用波形发生器
    DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None;
    //不使用DAC输出缓冲
    DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Disable;
    DAC_Init(DAC_Channel_1, &DAC_InitStructure);

    /*以同样的配置 初始化DAC 通道2 */
    DAC_Init(DAC_Channel_2, &DAC_InitStructure);

    /* 使能通道1 由PA4输出 */
    DAC_Cmd(DAC_Channel_1, ENABLE);
    /* 使能通道2 由PA5输出 */
    DAC_Cmd(DAC_Channel_2, ENABLE);

    /* 使能DAC的DMA请求 */
    DAC_DMACmd(DAC_Channel_2, ENABLE);
}

在DAC_Config函数中,完成了DAC通道的GPIO的初始化和DAC模式配置。 其中GPIO按照要求被配置为模拟输入模式(没有模拟输出模式),在该模式下才能正常输出模拟信号。

配置DAC工作模式时,则使用了DAC_InitTypeDef 类型的初始化结构体,把DAC通道1和2都配置成了使用定时器TIM2触发、 不使用波形发生器以及不使用DAC输出缓冲的模式。

初始化完GPIO和DAC模式后,还使用了DAC_Cmd、DAC_DMACmd函数使能了通道以及DMA的请求。由于本实验中对DAC1和2的操作是同步的, 所以只要把DMA与DAC通道2关联起来即可,当使用DMA设置通道2的数据值时,同时更新通道1的内容。

定时器配置及计算正弦波的频率

初始化完DAC后,需要配置触发用的定时器,设定每次触发的间隔,以达到控制正弦波周期的目的。

代码清单:DAC-6 配置定时器
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* @brief  配置TIM
* @param  无
* @retval 无
*/
static void DAC_TIM_Config(void)
{
    TIM_TimeBaseInitTypeDef    TIM_TimeBaseStructure;

    /* 使能TIM2时钟,TIM2CLK 为72M */
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

    /* TIM2基本定时器配置 */
    //定时周期 20
    TIM_TimeBaseStructure.TIM_Period = (20-1);
    //预分频,不分频 72M / (0+1) = 72M
    TIM_TimeBaseStructure.TIM_Prescaler = 0x0;
    //时钟分频系数
    TIM_TimeBaseStructure.TIM_ClockDivision = 0x0;
    //向上计数模式
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);

    /* 配置TIM2触发源 */
    TIM_SelectOutputTrigger(TIM2, TIM_TRGOSource_Update);

    /* 使能TIM2 */
    TIM_Cmd(TIM2, ENABLE);
}

因为前面的DAC配置了TIM2当触发源,所以这里将对TIM2进行配置。TIM2的定时周期被配置为20,向上计数,不分频。 即TIM2每隔20*(1/72M)秒就会触发一次DAC事件,作DAC触发源使用的定时器并不需要设置中断,当定时器计数器向上计数至指定的值时, 产生Update事件,同时触发DAC把DHRx寄存器的数据转移到DORx,从而开始进行转换。

根据定时器的配置,可推算出正弦波频率的计算方式:

按默认配置,STM32系统时钟周期为:

\(T_{\text{systick}} = 1/72000000\)

定时器TIM2的单个时钟周期:

\(T_{\text{tim}} = (TIM\_ Prescaler + 1) \times T_{\text{systick}}\)

定时器触发周期:

\(T_{\text{update}} = \ (TIM\_ Period + 1) \times T_{\text{tim}}\),

根据正弦波单个周期的点数N,求出正弦波单个周期时间为:

\(T_{\sin} = T_{\text{update}} \times N\)

对应正弦波的频率为:

\[f_{\sin} = \frac{1}{T_{\sin}} = \frac{1}{T_{\text{systick}} \times (TIM\_ Prescaler + 1) \times (TIM\_\text{Period} + 1) \times N}\]

根据上述公式,代入本工程的配置,可得本实验中的正弦波频率为112500:

\[f_{\sin} = \frac{1}{T_{\sin}} = \frac{72000000}{(0 + 1) \times (19 + 1) \times 32} = 112500\]

在实际应用中,可以根据工程里的正弦波点数和定时器配置生成特定频率的正弦波。

DMA配置

本工程的数据传输由DMA完成,其代码见 代码清单:DAC-7

代码清单:DAC-7 DMA配置
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
//正弦波单个周期的点数
#define POINT_NUM 32

/* 波形数据 -----------------------------------------------*/
const uint16_t Sine12bit[POINT_NUM] = {
    2048  , 2460  , 2856  , 3218  , 3532  , 3786  , 3969  , 4072  ,
    4093  , 4031  , 3887  , 3668  , 3382  , 3042  , 2661  , 2255  ,
    1841  , 1435  , 1054  , 714   , 428   , 209   , 65    , 3     ,
    24    , 127   , 310   , 564   , 878   , 1240  , 1636  , 2048
};


uint32_t DualSine12bit[POINT_NUM];

/**
* @brief  DAC初始化函数
* @param  无
* @retval 无
*/
void DAC_Mode_Init(void)
{
    uint32_t Idx = 0;

    DAC_Config();
    DAC_TIM_Config();

    /* 填充正弦波形数据,双通道右对齐*/
    for (Idx = 0; Idx < POINT_NUM; Idx++) {
    DualSine12bit[Idx] = (Sine12bit[Idx] << 16) + (Sine12bit[Idx]);
    }
    DAC_DMA_Config();
}

/**
* @brief  配置DMA
* @param  无
* @retval 无
*/
static void DAC_DMA_Config(void)
{
    DMA_InitTypeDef  DMA_InitStructure;

    /* 使能DMA2时钟 */
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2, ENABLE);

    /* 配置DMA2 */
    //外设数据地址
    DMA_InitStructure.DMA_PeripheralBaseAddr = DAC_DHR12RD_ADDRESS;
    //内存数据地址 DualSine12bit
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&DualSine12bit ;
    //数据传输方向内存至外设
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
    //缓存大小为POINT_NUM字节
    DMA_InitStructure.DMA_BufferSize = POINT_NUM;
    //外设数据地址固定
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    //内存数据地址自增
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    //外设数据以字为单位
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
    //内存数据以字为单位
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
    //循环模式
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
    //高DMA通道优先级
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;
    //非内存至内存模式
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;

    DMA_Init(DMA2_Channel4, &DMA_InitStructure);

    /* 使能DMA2-14通道 */
    DMA_Cmd(DMA2_Channel4, ENABLE);
}

在上述代码中,定义了由脚本得到的正弦波数据表Sine12bit变量,一共为POINT_NUM(32)个点。在DAC_Mode_Init函数中, 调用了前面介绍的DAC_Config和DAC_TIM_Config初始化DAC和定时器,然后在for循环中把单通道的正弦波数据表Sine12bit复制扩展成为双通道的数据DualSine12bit, 扩展后的数据将会直接被DMA搬运至DAC的DHR12RD寄存器中。

复制完数据后,DAC_Mode_Init调用下面的DAC_DMA_Config函数初始化DMA,配置的重点是要设置好DHR12RD寄存器的地址, 正弦波数据的内存地址(注意是双通道数据DualSine12bit),DMA缓存的个数(即单个周期的正弦波点数)以及DMA工作在循环模式。

经过这样的配置后,定时器每间隔一定的时间就会触发DMA搬运双通道正弦波表的一个数据到DAC双通道寄存器进行转换, 每完成一个周期后DMA重新开始循环,从而达到连续输出波形的目的。

主函数

代码清单:DAC-8 主函数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
/**
* @brief  主函数
* @param  无
* @retval 无
*/
int main(void)
{
    /*初始化DAC,开始DAC转换*/
    DAC_Mode_Init();

    while (1);
}

本工程的主函数非常简单,直接调用DAC_Mode_Init即可完成所有的配置,此时再使用示波器测量PA4、PA5引脚可查看其输出的波形。。

39.4.3. 下载验证

注意

参考本章的硬件设计部分说明,实验前需要拆掉SPI-FLASH芯片附近的电阻,霸道V1拆R6,霸道V2拆R223。

用USB线连接开发板的“USB转串口”接口跟电脑,把编译好的程序下载到开发板,使用示波器测量PA4、 PA5的引脚可看到正弦波形(示波器使用x10倍档测量更加准确), 见图 使用示波器测量出的电压波形 ,注意观察图中示波器测量出的频率值和电压峰值。

使用示波器测量出的电压波形