目录
4.2 DAC1通道12位右对齐数据保持寄存器:DAC_DHR12R1
1. DAC简介
DAC和ADC恰好相反,DAC模块是12位电压输出数模转换器。DAC可以按照8位或者12位模式进行配置,并且可以与DMA控制器配合使用。在12位模式下,数据可以采用左对齐或右对齐。DAC有两个输出通道,每个通道各有一个转换器。在DAC双通道模式下,每个通道可以单独进行转换;当两个通道组合在一起同步执行更新操作时,也可以同时进行转换。可通过一个输入参考电压引脚(这个引脚是和ADC共享的)来提高分辨率。
DAC主要特性:
- 两个DAC转换器:各对应一个输出通道
- 12位模式下数据采用左对齐或右对齐
- 同步更新功能
- 生成噪声波
- 生成三角波
- DAC双通道单独或同时转换
- 每个通道都具有DMA功能
- DMA下溢错误检测
- 通过外部触发信号进行转换
- 输入参考电压
2. DAC框图
图中VDDA和VSSA为DAC模块模拟部分的供电,而Vref+则是DAC模块的参考电压。DAC_OUTx就是DAC的输出通道(对应于PA4和PA5引脚)。
从图中可以看到,DAC输出是受DORx寄存器直接控制的,但是我们不能直接往DORx寄存器中写入数据,而是通过DHRx间接的传给DORx寄存器,实现对DAC输出的控制。
注意:使能DACx通道后,相应的GPIO引脚(PA4或者PA5)将自动连接到模拟转换器输出DAC_OUTx。为了避免寄生电流消耗,应首先将PA4或PA5引脚配置为模拟模式(AIN)。
3. DAC功能介绍
3.1 DAC通道使能
将DAC_CR控制寄存器中的相应位ENx位置1,即可接通对应的DAC通道。经过一段启动时间后,DAC通道被真正的使能。
注意:ENx位只会使能模拟DAC Channelx 宏单位。即使ENx位复位,DAC Channelx 数字接口仍处于使能状态。
3.2 DAC输出缓冲器使能
DAC集成了两个输出缓冲器,可用来降低输出阻抗并在不增加外部运算放大器的情况下直接驱动外部负载。通过DAC_CR控制寄存器中的相应BOFFx位,可使能或禁止各DAC通道输出缓冲器。
3.3 DAC数据格式
对于DAC单通道x,有三种可能的方式:
8位右对齐:软件必须将数据加载到DAC_DHR8Rx[7:0]位(存储到DHRx[11:4]位)。
12位左对齐:软件必须将数据加载到DAC_DHR12Lx[15:4]位(存储到DHRx[11:0]位)。
12位右对齐:软件必须将数据加载到DAC_DHR12Rx[11:0]位(存储到DHRx[11:0]位)。
根据加载的 DAC_DHRyyyx 寄存器,用户写入的数据将移位并存储到相应的 DHRx(数据保持寄存器 x,即内部非存储器映射寄存器)。之后,DHRx 寄存器将被自动加载,或者通过软件或外部事件触发加载到 DORx 寄存器。
注:本次我们使用的就是STM32的单DAC通道1,采用12位右对齐格式,所以使用的是上述单通道的第三种情况。
如果没有选中硬件触发(寄存器DAC_CR1的TENx位置0),存入寄存器DAC_DHRx的数据会在一个APB1时钟周期后自动传至寄存器DAC_DORx。如果选中硬件触发(寄存器DAC_CR1的TENx位置1),数据传输在触发发生以后3个APB1时钟周期后完成。
一旦数据从DAC_DHRx寄存器装入DAC_DORx寄存器,在经过时间
之后,输出即有效,这段时间的长短依电源电压和模拟输出负载的不同会有所变化。
对于DAC双通道,有三种可能的方式:
8位右对齐:将DAC1通道的数据加载到DAC_DHR8RD[7:0]位(存储到DHR1[11:4]位),将DAC2通道的数据加载到DAC_DHR8RD[15:8]位(存储到DHR2[11:4]位)
12位左对齐:将DAC1通道的数据加载到DAC_DHR12RD[15:4]位(存储到DHR1[11:0]位),将 DAC 2 通道的数据加载到 DAC_DHR12RD [31:20] 位(存储到 DHR2[11:0] 位)
12 位右对齐:将 DAC 1 通道的数据加载到 DAC_DHR12RD [11:0] 位(存储到 DHR1[11:0] 位),将 DAC 2 通道的数据加载到 DAC_DHR12RD [27:16] 位(存储到 DHR2[11:0] 位)
根据加载的 DAC_DHRyyyD 寄存器,用户写入的数据将移位并存储到 DHR1 和 DHR2(数据保持寄存器,即内部非存储器映射寄存器)。之后,DHR1 和 DHR2 寄存器将被自动加载,或者通过软件或外部事件触发分别被加载到 DOR1 和 DOR2 寄存器。
3.4 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 内容时,模拟输出电压将在一段时间 后可用,具体时间取决于电源电压和模拟输出负载。
3.5 DAC输出电压
经过线性转换后,数字输出会转换为0到之间的输出电压。
各DAC通道引脚的模拟输出电压通过以下公式确定:
DACoutput=
3.6 DAC触发选择
如果TENx控制位置1,则可通过外部事件(定时计数器、外部中断线)触发转换。TSELx[2:0]控制位将决定通过8个可能事件中的哪一个来触发转换,
每当DAC接口在所选定时器TRGO输出或所选外部中断线9上检测到上升沿时,DAC_DHRx寄存器中存储的最后一个数据即会转移到DAC_DORx寄存器中。发生触发后再经过三个APB1周期,DAC_DORx寄存器就会得到更新。
如果选择软件触发,一旦SWTRIG位置1,转换即会开始。DAC_DHRx寄存器内容加载到DAC_DORx寄存器中后,SWTRIG即由硬件复位。
ENx位置1时,无法更改TSELx[2:0]位。
如果选择软件触发,DAC_DHRx寄存器的内容只需一个APB1时钟周期即可转换到DAC_DORx寄存器。
3.7 DMA请求
每个 DAC 通道都具有 DMA 功能。两个 DMA 通道用于处理 DAC 通道的 DMA 请求。
当 DMAENx 位置 1 时,如果发生外部触发(而不是软件触发),则将产生 DAC DMA 请求。DAC_DHRx 寄存器的值随后转移到 DAC_DORx 寄存器。 在双通道模式下,如果两个 DMAENx 位均置 1,则将产生两个 DMA 请求。如果只需要一个 DMA 请求,应仅将相应 DMAENx 位置 1。这样,应用程序可以在双通道模式下通过一个 DMA 请求和一个特定 DMA 通道来管理两个 DAC 通道。
DMA下溢
DAC DMA请求没有缓冲队列。这样,如果第二个外部触发到达时尚未收到第一个外部触发的确认,将不会发出新的请求,并且DAC_SR寄存器中的DAM通道下溢标志DMAUDRx将置1,以报告这一错误状况。DMA数据传输随即禁止,并且不再处理其他DMA请求。DAC通道仍将继续转换旧有数据。
软件应通过写入“1”来将 DMAUDRx 标志清零,将所用 DMA 数据流的 DMAEN 位清零, 并重新初始化 DMA 和 DAC 通道,以便正确地重新开始 DMA 传输。软件应修改 DAC 触发 转换频率或减轻 DMA 工作负载,以避免再次发生 DMA 下溢。最后,可通过使能 DMA 数据 传输和转换触发来继续完成 DAC 转换。
对于各 DAC 通道,如果使能 DAC_CR 寄存器中相应的 DMAUDRIEx 位,还将产生中断。
3.8 生成噪声
为了生成可变振幅的伪噪声,可使用LFSR(线性反馈移位寄存器)。将WAVEx[1:0]置为 “01” 即可选择生成噪声。LFSR中的预加载值为0xAAA。在每次发生触发事件后,经过三个APB1时钟周期,该寄存器会依照特定的计算算法完成更新。
3.9 生成三角波
可以在直流电流或满变信号上叠加一个小幅三角波。将WAVEx[1:0]置为 “10” 即可选择DAC生成三角波。振幅通过DAC_CR寄存器中的MAMPx[3:0]位进行配置。每次发生触发事件后,经过三个APB1时钟周期,内部三角波计数器将会递增。在不发生溢出的情况下,该计数器的值将与DAC_DHRx寄存器内容相加,所得总和将存储到DAC_DORx寄存器中。只要小于MAMPx[3:0]位定义的最大振幅,三角波计数器就会一直递增。一旦达到配置的振幅,计数器将会递减至0,然后在递增,以此类推。
可以通过复位WAVEx[1:0]位来将三角波产生功能关闭。
4. 相关寄存器
4.1 DAC控制寄存器:DAC_CR
DAC控制寄存器:DAC_CR(DAC control register)
DAC控制寄存器的低16位用于控制通道1,高16位用于控制通道2。本实验我们使用的是通道1,所以着重介绍最低8位控制通道1。
位[7:6]:WAVE1[1:0]:DAC1通道噪声/三角波生成使能
这些位有软件置1和清零。
00:禁止生成波
01:使能生成噪音波
1x:使能生成三角波
位[5:3]:TSEL1[2:0]:DAC1通道触发器选择
000:定时器6 TRGO事件
001:定时器8 TRGO事件
010:定时器7 TRGO事件
011:定时器5 TRGO事件
100:定时器2 TRGO事件
101:定时器4 TRGO事件
110:外部中断线9
111:软件触发
位2:TEN1:DAC1通道触发使能
此位由软件置1和清零,以使能/禁止DAC1通道触发。
0:禁止DAC1通道触发,写入DAC_DHRx寄存器的数据在一个APB1时钟周期之后转移到DAC_DOR1寄存器
1:使能DAC1通道触发,DAC_DHRx寄存器的数据在三个APB1时钟周期之后转移到DAC_DOR1寄存器
位1:BOFF1:DAC1通道输出缓冲器禁止
此位由软件置1和清零,以使能/禁止DAC1通道输出缓冲器
0:使能DAC1通道输出缓冲器
1:禁止DAC1通道输出缓冲器
位0:EN1:DAC1通道使能
此位由软件置1和清零,以使能/禁止DAC1通道。
0:禁止DAC1通道
1:使能DAC1通道
首先位0使能位(EN1),该位是用来控制DAC通道1使能的,我们要使用DAC通道1,那么该位要设置为1。
DAC通道1触发使能位(TEN1),该位用来控制是否使用触发,如果不使用触发,那么将该位设置为0即可。
DAC通道1触发选择位(TSEL1[2:0]),该位是用来设置是否使用外部触发,如果不使用,那么该位设置为0即可。
DAC通道1噪声/三角波生成使能位(WAVE1[1:0]),在用到波形发生器时,才需要设置该位,不需要使用波形发生器时,将该位设置为0即可。
DAC通道1屏蔽/复制选择器(MAMP[3:0]),在用到波形发生器时才会使用屏蔽/复制发生器,没用到时,该位设置为0即可。
DAC通道1 DMA使能位(DMAEN1),在需要用到DMA功能时,才需要将该位设置为1。
4.2 DAC1通道12位右对齐数据保持寄存器:DAC_DHR12R1
DAC1通道12位右对齐数据保持寄存器:DAC_DHR12R1(DAC channel1 12-bit right-aligned data holding register)
位31:12 保留,必须保持复位值
位11:0 DACC1DHR[11:0]:DAC1通道12位右对齐数据
这些位由软件写入,用于为DAC1通道指定12位数据。
该寄存器用来设置DAC输出,通过写入12位数据到该寄存器,就可以在DAC输出通道1(PA4)得到我们所要的结果。
5. 库函数配置DAC输出
1. 开启PA口时钟,设置PA4为模拟输入
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); //使能GPIOA时钟
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_4; //设置PA4引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN; //模拟输入
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN; //下拉
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化
2. 使能DAC1时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE); //使能DAC时钟
3. 初始化DAC,设置DAC工作模式
初始化DAC主要通过DAC_CR控制寄存器设置来实现,包括:DAC通道1使能、DAC通道1输出缓存关闭、不使用触发、不使用波形发生器等。
void DAC_Init(uint32_t DAC_Channel, DAC_InitTypeDef* DAC_InitStruct);
typedef struct
{
uint32_t DAC_Trigger; //用来设置是否使用触发功能
uint32_t DAC_WaveGeneration; //用来设置是否使用波形发生
uint32_t DAC_LFSRUnmask_TriangleAmplitude; //用来设置屏蔽/幅值选择器,这个变量只有在使用波形发生器时才使用
uint32_t DAC_OutputBuffer; //用来设置输出缓存控制位
}DAC_InitTypeDef;
//本实验中不使用触发,也不使用波形发生,也不用波形发生器,同时也不使用输出缓存
DAC_InitTypeDef DAC_InitType;
DAC_InitType.DAC_Trigger=DAC_Trigger_None; //不使用触发功能 TEN1=0
DAC_InitType.DAC_WaveGeneration=DAC_WaveGeneration_None;//不使用波形发生
DAC_InitType.DAC_LFSRUnmask_TriangleAmplitude=DAC_LFSRUnmask_Bit0;
DAC_InitType.DAC_OutputBuffer=DAC_OutputBuffer_Disable ; //DAC1输出缓存关闭
DAC_Init(DAC_Channel_1,&DAC_InitType);
4. 使能DAC转换通道
DAC_Cmd(DAC_Channel_1, ENABLE); // 使能DAC通道1
5. 设置DAC输出值
通过前四步 DAC 就可以正常工作了,因为我们使用的是 12 位右对齐数据格式,所以需要设置 DHR12R1 寄存器
DAC_SetChannel1Data(DAC_Align_12b_R, 0); // 12位右对齐数据格式设置 DAC 值
第一个参数:设置对齐方式,可以为12位右对齐 DAC_Align_12b_R,12位左对齐 DAC_Align_12b_L以及8位右对齐DAC_Align_8b_R方式。
第二个参数:DAC输入值,初始化设置为0。
DAC_GetDataOutputValue(DAC_Channel_1); // 读出DAC对应通道最后一次转换的数值
6. 实验程序
本次实验,使用ADC采集DAC输出电压,需要使用DAC通道1输出模拟电压,然后通过ADC1的通道1对该输出电压进行读取,并且显示在LCD模块上,DAC输出电压,通过按键进行设置,因为需要使用ADC来采集DAC输出电压,所以需要在开发板上通过跳线帽短接ADC和DAC。
6.1 main.c
#include "stm32f4xx.h"
#include "delay.h"
#include "usart.h"
#include "LED.h"
#include "lcd.h"
#include "usmart.h"
#include "ADC.h"
#include "KEY.h"
#include "DAC.h"
//LCD状态设置函数
void led_set(u8 sta)//只要工程目录下有usmart调试函数,主函数就必须调用这两个函数
{
LED1=sta;
}
//函数参数调用测试函数
void test_fun(void(*ledset)(u8),u8 sta)
{
led_set(sta);
}
int main(void)
{
u16 adcx;
float temp;
u8 t=0,key;
u16 dacval=0;
delay_init(168);
uart_init(115200);
LED_Init();
LCD_Init();
Adc_Init();
Key_Init();
Dac1_Init();
POINT_COLOR=RED;
LCD_ShowString(30,50,200,16,16,"Explorer STM32F4");
LCD_ShowString(30,70,200,16,16,"DAC Test");
LCD_ShowString(30,90,200,16,16,"ATOM@ALIENTEK");
LCD_ShowString(30,110,200,16,16,"2023/05/18");
LCD_ShowString(30,130,200,16,16,"WK_UP:+ KEY1:-");
POINT_COLOR=BLUE;
LCD_ShowString(30,150,200,16,16,"DAC VAL");
LCD_ShowString(30,170,200,16,16,"DAC VAL:0.000V");
LCD_ShowString(30,190,200,16,16,"ADC VAL:0.000V");
DAC_SetChannel1Data(DAC_Align_12b_R,dacval); //12位数据对齐格式设置DAC初始值为0
while(1)
{
t++;
key=KEY_Scan(0);
if(key==4) //KEY_UP按键按下
{
if(dacval<4000)
dacval=dacval+200;
DAC_SetChannel1Data(DAC_Align_12b_R,dacval); //设置DAC的值
}
else if(key==2) //KEY1按键按下
{
if(dacval>200)
dacval=dacval-200;
else
dacval=0;
DAC_SetChannel1Data(DAC_Align_12b_R,dacval);//设置DAC值
}
if(t==10||key==2||key==4)// 有按键按下了,或者定时时间到了
{
adcx=DAC_GetDataOutputValue(DAC_Channel_1); //读取DAC值
LCD_ShowxNum(30+8*8,150,adcx,4,16,0); //显示DAC寄存器的值
temp=(float)adcx*(3.3/4096); //计算得到DAC电压值
adcx=temp;
LCD_ShowxNum(30+8*8,170,temp,1,16,0); //显示电压值的整数部分
temp=temp-adcx; //减掉整数部分,保留下来小数部分
temp=temp*1000; //乘以1000,相当于保留了三位小数
LCD_ShowxNum(30+10*8,170,temp,3,16,0x80); //显示电压值的小数部分
adcx=Get_Adc_Average(ADC_Channel_5,10); //得到ADC的转换值
temp=(float)adcx*(3.3/4096); //得到ADC电压值
adcx=temp;
LCD_ShowxNum(30+8*8,190,temp,1,16,0); //显示电压值整数部分
temp=temp-adcx;
temp=temp*1000;
LCD_ShowxNum(30+8*10,190,temp,3,16,0x80); //显示电压值小数部分
LED0=!LED0;
t=0;
}
delay_ms(10);
}
}
6.2 DAC.c
#include "stm32f4xx.h"
#include "DAC.h"
void Dac1_Init(void)
{
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); //使能GPIOA时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC,ENABLE); //使能DAC时钟
//初始化GPIOA
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AN; //模拟输入
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_4; //PA4引脚
GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_DOWN; //下拉
GPIO_Init(GPIOA,&GPIO_InitStructure);
//初始化DAC通道1
DAC_InitTypeDef DAC_InitStructure;
DAC_InitStructure.DAC_Trigger=DAC_Trigger_None; //不使用触发功能
DAC_InitStructure.DAC_OutputBuffer=DAC_OutputBuffer_Disable; //输出缓存关闭
DAC_InitStructure.DAC_WaveGeneration=DAC_WaveGeneration_None; //不使用波形发生
DAC_InitStructure.DAC_LFSRUnmask_TriangleAmplitude=DAC_LFSRUnmask_Bit0; //屏蔽、幅值设置
DAC_Init(DAC_Channel_1,&DAC_InitStructure);
DAC_Cmd(DAC_Channel_1,ENABLE); //使能DAC通道1
DAC_SetChannel1Data(DAC_Align_12b_R,0); //12位右对齐数据格式
}
//设置通道1输出电压
//vol:0~3300,代表0-3.3V
void Dac1_Set_Vol(u16 vol) //将电压值转换为DAC输出值
{
double temp=vol; //设置浮点型变量temp接收电压值
temp=temp/1000;
temp=temp*4096/3.3; //通过公式计算出电压值,其实就是公式中的ROD;
DAC_SetChannel1Data(DAC_Align_12b_R,temp); //计算后的电压值放在数据寄存器中使用的是12位右对齐数据格式
}
6.3 DAC.h
#ifndef _DAC__H_
#define _DAC__H_
void Dac1_Init(void);
void Dac1_Set_Vol(u16 vol);
#endif
6.4 ADC.c
#include "ADC.h"
#include "delay.h"
//初始化ADC
//这里我们仅以规则通道为例
//我们默认仅开启通道1
void Adc_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
ADC_CommonInitTypeDef ADC_CommonInitStructure;
ADC_InitTypeDef ADC_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);//使能GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);//使能ADC1时钟
//先初始化IO口
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;//模拟输入
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN;//下拉
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化
RCC_APB2PeriphResetCmd(RCC_APB2Periph_ADC1,ENABLE); //ADC1复位
RCC_APB2PeriphResetCmd(RCC_APB2Periph_ADC1,DISABLE); //复位结束
ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;//独立模式
ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;
ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled; //DMA失能
ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div2;
ADC_CommonInit(&ADC_CommonInitStructure);
ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;//12位模式
ADC_InitStructure.ADC_ScanConvMode = DISABLE;//非扫描模式
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;//禁止触发检测,使用软件触发
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//右对齐
ADC_InitStructure.ADC_NbrOfConversion = 1;//1个转换在规则序列中 也就是只转换规则序列1
ADC_Init(ADC1, &ADC_InitStructure);
ADC_Cmd(ADC1, ENABLE);//开启AD转换器
}
//获得ADC值
//ch:通道值 0~16
//返回值:转换结果
u16 Get_Adc(u8 ch)
{
//设置指定ADC的规则组通道,一个序列,采样时间
ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_480Cycles ); //ADC1,ADC通道,480个周期,提高采样时间可以提高精确度
ADC_SoftwareStartConv(ADC1); //使能指定的ADC1的软件转换启动功能
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));//等待转换结束
return ADC_GetConversionValue(ADC1); //返回最近一次ADC1规则组的转换结果
}
//获取通道ch的转换值,取times次,然后平均
//ch:通道编号
//times:获取次数
//返回值:通道ch的times次转换结果平均值
u16 Get_Adc_Average(u8 ch,u8 times)
{
u32 temp_val=0;
u8 t;
for(t=0;t<times;t++)
{
temp_val+=Get_Adc(ch);
delay_ms(5);
}
return temp_val/times;
}
6.5 ADC.h
#ifndef __ADC_H
#define __ADC_H
#include "sys.h"
void Adc_Init(void); //ADC通道初始化
u16 Get_Adc(u8 ch); //获得某个通道值
u16 Get_Adc_Average(u8 ch,u8 times);//得到某个通道给定次数采样的平均值
#endif