STM32F4xx系列提供的DAC模块是12 位电压输出数模转换器。DAC可以按 8 位或 12 位模式进行配置,并且可与DMA控制器配合使用。在 12 位模式下,数据可以采用左对齐或右对齐。DAC有两个输出通道,每个通道各有一个转换器。在DAC双通道模式下,每个通道可以单独进行转换;当两个通道组合在一起同步执行更新操作时,也可以同时进行转换。可通过一个输入参考电压引脚VREF+(与ADC共享)来提高分辨率。
DAC通道框图
DAC引脚
DAC通道使能
将 DAC_CR 寄存器中的相应 ENx 位置 1,即可接通对应 DAC 通道。经过一段启动时间tWAKEUP 后,DAC 通道被真正使能。
注意:ENx 位只会使能模拟 DAC Channelx 宏单元。即使 ENx 位复位, DAC Channelx 数字接口仍处于使能状态。
DAC输出缓冲器使能
DAC 集成了两个输出缓冲器,可用来降低输出阻抗并在不增加外部运算放大器的情况下直接驱动外部负载。通过 DAC_CR 寄存器中的相应 BOFFx 位,可使能或禁止各 DAC 通道输出缓冲器。
DAC数据格式
DAC同ADC一样,数据分为8 位右对齐、12 位左对齐和12 位右对齐,为方便数据写入和精度要求,一般采用12 位右对齐格式。
DAC转换
DAC_DORx 无法直接写入,任何数据都必须通过加载 DAC_DHRx 寄存器(写入DAC_DHR8Rx、DAC_DHR12Lx、DAC_DHR12Rx、DAC_DHR8RD、DAC_DHR12LD 或DAC_DHR12LD)才能传输到 DAC 通道 x。
如果未选择硬件触发(DAC_CR 寄存器中的 TENx 位复位),那么经过一个 APB1 时钟周期后,DAC_DHRx 寄存器中存储的数据将自动转移到 DAC_DORx 寄存器。但是,如果选择硬件触发(置位 DAC_CR 寄存器中的 TENx 位)且触发条件到来,将在三个 APB1 时钟周期后进行转移。
当 DAC_DORx 加载了 DAC_DHRx 内容时,模拟输出电压将在一段时间 t SETTLING 后可用,具体时间取决于电源电压和模拟输出负载。
DAC输出电压
经过线性转换后,数字输入会转换为0到VREF+之间的输出电压。
各DAC通道引脚的模拟输出电压通过以下公式确定:
DACoutput = VREF* DOR/4095
DAC触发选择
如果 TENx 控制位置 1,可通过外部事件(定时计数器、外部中断线)触发转换。TSELx[2:0]控制位将决定通过 8 个可能事件中的哪一个来触发转换,如下表所示:
每当 DAC 接口在所选定时器 TRGO 输出或所选外部中断线 9 上检测到上升沿时,DAC_DHRx寄存器中存储的最后一个数据即会转移到DAC_DORx 寄存器中。发生触发后再经过三个APB1 周期,DAC_DORx 寄存器将会得到更新。
如果选择软件触发,一旦 SWTRIG 位置 1,转换即会开始。DAC_DHRx 寄存器内容加载到DAC_DORx 寄存器中后,SWTRIG 即由硬件复位。
DMA请求
每个 DAC 通道都具有 DMA 功能。两个 DMA 通道用于处理 DAC 通道的 DMA 请求。这里不做详细介绍。
生成波形
可以根据需要,配置相应的寄存器生成噪声、三角波,也可以不生成波形。
下面了解下DAC相关的寄存器:
DAC 控制寄存器 (DAC_CR)
位 29 DMAUDRIE2:DAC 2 通道 DMA 下溢中断使能 (DAC channel2 DMA underrun interrupt enable)
此位由软件置 1 和清零。
0:禁止 DAC 2 通道 DMA 下溢中断
1:使能 DAC 2 通道 DMA 下溢中断
位 28 DMAEN2:DAC 2 通道 DMA 使能 (DAC channel2 DMA enable)
此位由软件置 1 和清零。
0:禁止 DAC 2 通道 DMA 模式
1:使能 DAC 2 通道 DMA 模式
位 27:24 MAMP2[3:0]:DAC 2 通道掩码/振幅选择器 (DAC channel2 mask/amplitude selector)
这些位由软件写入,用于在生成噪声波模式下选择掩码,或者在生成三角波模式下选择振幅。
0000:不屏蔽 LFSR 的位 0/三角波振幅等于 1
0001:不屏蔽 LFSR 的位 [1:0]/三角波振幅等于 3
0010:不屏蔽 LFSR 的位 [2:0]/三角波振幅等于 7
0011:不屏蔽 LFSR 的位 [3:0]/三角波振幅等于 15
0100:不屏蔽 LFSR 的位 [4:0]/三角波振幅等于 31
0101:不屏蔽 LFSR 的位 [5:0]/三角波振幅等于 63
0110:不屏蔽 LFSR 的位 [6:0]/三角波振幅等于 127
0111:不屏蔽 LFSR 的位 [7:0]/三角波振幅等于 255
1000:不屏蔽 LFSR 的位 [8:0]/三角波振幅等于 511
1001:不屏蔽 LFSR 的位 [9:0]/三角波振幅等于 1023
1010:不屏蔽 LFSR 的位 [10:0]/三角波振幅等于 2047
=1011:不屏蔽 LFSR 的位 [11:0]/三角波振幅等于 4095
位 23:22 WAVE2[1:0]:DAC 2 通道噪声/三角波生成使能 (DAC channel2 noise/triangle wave generation enable)
这些位由软件置 1 或清零。
00:禁止生成波
01:使能生成噪声波
1x:使能生成三角波
注意:只在位 TEN2 = 1 (使能 DAC 2 通道触发)时使用
位 21:19 TSEL2[2:0]:DAC 2 通道触发器选择 (DAC channel2 trigger selection)
这些位用于选择 DAC 2 通道的外部触发事件
000:定时器 6 TRGO 事件
001:定时器 8 TRGO 事件
010:定时器 7 TRGO 事件
011:定时器 5 TRGO 事件
100:定时器 2 TRGO 事件
101:定时器 4 TRGO 事件
110:外部中断线 9
111:软件触发
注意:只在位 TEN2 = 1 (使能 DAC 2 通道触发)时使用。
位 18 TEN2:DAC 2 通道触发使能 (DAC channel2 trigger enable)
此位由软件置 1 和清零,以使能/禁止 DAC 2 通道触发
0:禁止 DAC 2 通道触发,写入 DAC_DHRx 寄存器的数据在一个 APB1 时钟周期之后转移到 DAC_DOR2 寄存器
1:使能 DAC 2 通道触发,DAC_DHRx 寄存器的数据在三个 APB1 时钟周期之后转移到DAC_DOR2 寄存器
注意:如果选择软件触发,DAC_DHRx 寄存器 的内容只需一个 APB1 时钟周期即可转移到DAC_DOR2 寄存器。
位 17 BOFF2:DAC 2 通道输出缓冲器禁止 (DAC channel2 output buffer disable)
此位由软件置 1 和清零,以使能/禁止 DAC 2 通道输出缓冲器。
0:使能 DAC 2 通道输出缓冲器
1:禁止 DAC 2 通道输出缓冲器
位 16 EN2:DAC 2 通道使能 (DAC channel2 enable)
此位由软件置 1 和清零,以使能/禁止 DAC 2 通道。
0:禁止 DAC 2 通道
1:使能 DAC 2 通道
位 13 DMAUDRIE1:DAC 1 通道 DMA 下溢中断使能 (DAC channel1 DMA Underrun Interrupt enable)
此位由软件置 1 和清零。
0:禁止 DAC 1 通道 DMA 下溢中断
1:使能 DAC 1 通道 DMA 下溢中断
位 12 DMAEN1:DAC 1 通道 DMA 使能 (DAC channel1 DMA enable)
此位由软件置 1 和清零。
0:禁止 DAC 1 通道 DMA 模式
1:使能 DAC 1 通道 DMA 模式
位 11:8 MAMP1[3:0]:DAC 1 通道掩码/振幅选择器 (DAC channel1 mask/amplitude selector)
这些位由软件写入,用于在生成噪声波模式下选择掩码,或者在生成三角波模式下选择振幅。
0000:不屏蔽 LFSR 的位 0/三角波振幅等于 1
0001:不屏蔽 LFSR 的位 [1:0]/三角波振幅等于 3
0010:不屏蔽 LFSR 的位 [2:0]/三角波振幅等于 7
0011:不屏蔽 LFSR 的位 [3:0]/三角波振幅等于 15
0100:不屏蔽 LFSR 的位 [4:0]/三角波振幅等于 31
0101:不屏蔽 LFSR 的位 [5:0]/三角波振幅等于 63
0110:不屏蔽 LFSR 的位 [6:0]/三角波振幅等于 127
0111:不屏蔽 LFSR 的位 [7:0]/三角波振幅等于 255
1000:不屏蔽 LFSR 的位 [8:0]/三角波振幅等于 511
1001:不屏蔽 LFSR 的位 [9:0]/三角波振幅等于 1023
1010:不屏蔽 LFSR 的位 [10:0]/三角波振幅等于 2047
=1011:不屏蔽 LFSR 的位 [11:0]/三角波振幅等于 4095
位 7:6 WAVE1[1:0]:DAC 1 通道噪声/三角波生成使能 (DAC channel1 noise/triangle wave generation enable)
这些位将由软件置 1 和清零。
00:禁止生成波
01:使能生成噪声波
1x:使能生成三角波
注意:只在位 TEN1 = 1 (使能 DAC 1 通道触发)时使用。
位 5:3 TSEL1[2:0]:DAC 1 通道触发器选择 (DAC channel1 trigger selection)
这些位用于选择 DAC 1 通道的外部触发事件。
000:定时器 6 TRGO 事件
001:定时器 8 TRGO 事件
010:定时器 7 TRGO 事件
011:定时器 5 TRGO 事件
100:定时器 2 TRGO 事件
101:定时器 4 TRGO 事件
110:外部中断线 9
111:软件触发
注意:只在位 TEN1 = 1 (使能 DAC 1 通道触发)时使用。
位 2 TEN1:DAC 1 通道触发使能 (DAC channel1 trigger enable)
此位由软件置 1 和清零,以使能/禁止 DAC 1 通道触发。
0:禁止 DAC 1 通道触发,写入 DAC_DHRx 寄存器的数据在一个 APB1 时钟周期之后转移到 DAC_DOR1 寄存器
1:使能 DAC 1 通道触发,DAC_DHRx 寄存器的数据在三个 APB1 时钟周期之后转移到DAC_DOR1 寄存器
注意:如果选择软件触发, DAC_DHRx 寄存器的内容只需一个 APB1 时钟周期即可转移到DAC_DOR1 寄存器。
位 1 BOFF1:DAC 1 通道输出缓冲器禁止 (DAC channel1 output buffer disable)
此位由软件置 1 和清零,以使能/禁止 DAC 1 通道输出缓冲器。
0:使能 DAC 1 通道输出缓冲器
1:禁止 DAC 1 通道输出缓冲器
位 0 EN1:DAC 1 通道使能 (DAC channel1 enable)
此位由软件置 1 和清零,以使能/禁止 DAC 1 通道。
0:禁止 DAC 1 通道
1:使能 DAC 1 通道
DAC 软件触发寄存器 (DAC_SWTRIGR)
位 1 SWTRIG2:DAC 2 通道软件触发 (DAC channel2 software trigger)
此位由软件置 1 和清零,以使能/禁止软件触发。
0:禁止软件触发
1:使能软件触发
注意:一旦 DAC_DHR2 寄存器值加载到 DAC_DOR2 寄存器中,该位即会由硬件清零(一个APB1 时钟周期之后)。
位 0 SWTRIG1:DAC 1 通道软件触发 (DAC channel1 software trigger)
此位由软件置 1 和清零,以使能/禁止软件触发。
0:禁止软件触发
1:使能软件触发
注意:一旦 DAC_DHR1 寄存器值加载到 DAC_DOR1 寄存器中,该位即会由硬件清零(一个APB1 时钟周期之后)。
DAC 1 通道 12 位右对齐数据保持寄存器 (DAC_DHR12R1)
位 11:0 DACC1DHR[11:0]:DAC 1 通道 12 位右对齐数据 (DAC channel1 12-bit right-aligned data)
这些位由软件写入,用于为 DAC 1 通道指定 12 位数据。
下面开始编写DAC1初始化函数和数据写入函数
void DAC1_Init()
{
//1. 开PA时钟
RCC- >AHB1ENR |= 1< < 0;
//2. 模式:模拟
GPIOA- >MODER |= 3< < 8;
//3. 开DAC1时钟(DAC1和DAC2共用一个时钟)
RCC- >APB1ENR |= 1< < 29;
DAC- >CR &= 0XFFFF0000;
DAC- >CR |= 7< < 3; //软件触发,不生产波形
DAC- >CR |= 0< < 1; //使能输出缓冲驱动器
DAC- >CR |= 1< < 0; //使能通道
Delay_us(10);
}
//使能软件触发函数
//一旦 DAC_DHR1寄存器值加载到DAC_DOR1寄存器中,该位即会由硬件清零(一个APB1时钟周期之后)
void DAC1_Trigger()
{
DAC- >SWTRIGR |= 1< < 0;
}
//把数据写入12位右对齐数据保持寄存器
void DAC1_SendDat(u16 *dat)
{
DAC- >DHR12R1 = *dat;
}
接下来编写测试函数
u16 DAC_buf[1024] = {0};
void DAC1_GetSine()//把DAC_buf赋值为正弦波的数组,用于模拟呼吸灯
{
u16 i = 0;
for(i=0;i< 256;i++) //0-π/2
{
DAC_buf[i] = (u16)((1.65+1.65*sin(3.14/512*i))*4096/3.3);
}
for(i=1;i<=256;i++) //π/2~π
{
DAC_buf[255+i] = DAC_buf[256-i];
}
for(i=1;i< 256;i++) //π~3/2π
{
DAC_buf[511+i] = 4096 - DAC_buf[i];
}
for(i=1;i< 256;i++) //3/2π~2π
{
DAC_buf[767+i] = DAC_buf[768-i];
}
}
#include "stm32f4xx.h"
#include "delay.h"
#include "math.h"
#include "dac.h"
int main()
{
u32 i=0;
DAC1_Init();
DAC1_GetSine();
while(1)
{
for(i=0;i< 1024;i++)
{
DAC1_SendDat(&DAC_buf[i]);
DAC1_Trigger();
Delay_ms(5);
}
}
}
对于STM32来说,并不是所有型号都有;就算有DAC,资源也非常有限。而STM32所有的芯片都有PWM输出,并且PWM输出通道很多,资源丰富。因此,我们可以使用PWM来实现DAC的输出从而节省成本。
void DAC1_Init()
{
//1. 开PA时钟
RCC- >AHB1ENR |= 1< < 0;
//2. 模式:模拟
GPIOA- >MODER |= 3< < 8;
//3. 开DAC1时钟(DAC1和DAC2共用一个时钟)
RCC- >APB1ENR |= 1< < 29;
DAC- >CR &= 0XFFFF0000;
DAC- >CR |= 7< < 3; //软件触发,不生产波形
DAC- >CR |= 0< < 1; //使能输出缓冲驱动器
DAC- >CR |= 1< < 0; //使能通道
Delay_us(10);
}
//使能软件触发函数
//一旦 DAC_DHR1寄存器值加载到DAC_DOR1寄存器中,该位即会由硬件清零(一个APB1时钟周期之后)
void DAC1_Trigger()
{
DAC- >SWTRIGR |= 1< < 0;
}
//把数据写入12位右对齐数据保持寄存器
void DAC1_SendDat(u16 *dat)
{
DAC- >DHR12R1 = *dat;
}
接下来编写测试函数
u16 DAC_buf[1024] = {0};
void DAC1_GetSine()//把DAC_buf赋值为正弦波的数组,用于模拟呼吸灯
{
u16 i = 0;
for(i=0;i< 256;i++) //0-π/2
{
DAC_buf[i] = (u16)((1.65+1.65*sin(3.14/512*i))*4096/3.3);
}
for(i=1;i<=256;i++) //π/2~π
{
DAC_buf[255+i] = DAC_buf[256-i];
}
for(i=1;i< 256;i++) //π~3/2π
{
DAC_buf[511+i] = 4096 - DAC_buf[i];
}
for(i=1;i< 256;i++) //3/2π~2π
{
DAC_buf[767+i] = DAC_buf[768-i];
}
}
#include "stm32f4xx.h"
#include "delay.h"
#include "math.h"
#include "dac.h"
int main()
{
u32 i=0;
DAC1_Init();
DAC1_GetSine();
while(1)
{
for(i=0;i< 1024;i++)
{
DAC1_SendDat(&DAC_buf[i]);
DAC1_Trigger();
Delay_ms(5);
}
}
}
对于STM32来说,并不是所有型号都有;就算有DAC,资源也非常有限。而STM32所有的芯片都有PWM输出,并且PWM输出通道很多,资源丰富。因此,我们可以使用PWM来实现DAC的输出从而节省成本。