1 DAC的基本介绍
1.1 STM32DAC简介
Digital-to-Analog Converter的缩写。指数/模转换器或者数字/模拟转换器。是指将离散的数字信号转换为连续变量的模拟信号的器件。典型的数字模拟转换器将表示一定比例电压值的数字信号转换为模拟信号。
STM32的DAC模块(数字/模拟转换模块)是12位数字输入,电压输出型的DAC。DAC可以配置为8位或12位模式,也可以与DMA控制器配合使用。DAC工作在12位模式时,数据可以设置成左对齐或右对齐。DAC模块有2个输出通道,每个通道都有单独的转换器。在双DAC模式下,2个通道可以独立地进行转换,也可以同时进行转换并同步地更新2个通道的输出。DAC可以通过引脚输入参考电压VREF+以获得更精确的转换结果。
1.2 DAC的主要特征
1)2个DAC转换器:每个转换器对应1个输出通道;
2)8位或者12位单调输出;
3)12位模式下数据左对齐或者右对齐;
4)同步更新功能;
5)噪声波形生成;
6)三角波形生成;
7)双DAC通道同时或者分别转换;
8)每个通道都有DMA功能;
9)外部触发转换;
10)输入参考电压VREF+。
2 DAC功能描述
2.1 单个DAC框图
在框图中下边的一列是DAC的各个引脚,它们的名称、信号类型和作用见下图:
注意: 一旦使能DACx通道,相应的GPIO引脚(PA4或者PA5)就会自动与DAC的模拟输出相连(DAC_OUTx)。为了避免寄生的干扰和额外的功耗,引脚PA4或者PA5在之前应当设置成模拟输入(AIN)。
这里需要注意的是:STM32F103ZET6一共只有两个DAC_OUTx引脚,分别为DAC_OUT1(PA4)、DAC_OUT2(PA5)。并且在做DAC功能的时候,引脚GPIO模式应该是模拟输入!!!
为什么要使用模拟输入模式呢?因为一旦使能DACx通道后,相应的GPIO引脚(PA4或者PA5)会自动与DAC的模拟输出相连,设置为输入,是为了额外额外的干扰。
2.2 使能DAC通道
将DAC_CR寄存器的ENx位置’1’即可打开对DAC通道x的供电。经过一段启动时间tWAKEUP,DAC通道x即被使能。
注意: ENx位只会使能DAC通道x的模拟部分,即便该位被置’0’,DAC通道x的数字部分仍然工作。
2.3 使能DAC输出缓存
DAC集成了2个输出缓存,可以用来减少输出阻抗,无需外部运放即可直接驱动外部负载。每个DAC通道输出缓存可以通过设置DAC_CR寄存器的BOFFx位来使能或者关闭。
但是,如果STM32的DAC输出缓存使能的话,虽然输出能力强一点,但是输出没有办法减到0,所以一般都不使用这个功能。
2.4 DAC数据格式
根据上面可知,数据写入DAC_DHRx寄存器,然而实际上DAC_DHRx一共有6个寄存器!这是为什么呢?这就和DAC数据格式有很大的关系了。
根据选择的配置模式,数据按照下文所述写入指定的寄存器:
单DAC通道x,有3种情况:
1)8位数据右对齐:用户须将数据写入寄存器DAC_DHR8Rx[7:0]位(实际是存入寄存器DHRx[11:4]位)
2)12位数据左对齐:用户须将数据写入寄存器DAC_DHR12Lx[15:4]位(实际是存入寄存器DHRx[11:0]位)
3)12位数据右对齐:用户须将数据写入寄存器DAC_DHR12Rx[11:0]位(实际是存入寄存器DHRx[11:0]位) 一般采用第三种方式。
根据对DAC_DHRyyyx寄存器的操作,经过相应的移位后,写入的数据被转存到DHRx寄存器中(DHRx是内部的数据保存寄存器x)。随后,DHRx寄存器的内容或被自动地传送到DORx寄存器,或通过软件触发或外部事件触发被传送到DORx寄存器。
单DAC通道模式的数据寄存器:
双DAC通道,有3种情况:
1)8位数据右对齐:用户须将DAC通道1数据写入寄存器DAC_DHR8RD[7:0]位(实际是存入寄存器DHR1[11:4]位),将DAC通道2数据写入寄存器DAC_DHR8RD[15:8]位(实际是存入寄存器DHR2[11:4]位)
2)12位数据左对齐:用户须将DAC通道1数据写入寄存器DAC_DHR12LD[15:4]位(实际是存入寄存器DHR1[11:0]位),将DAC通道2数据写入寄存器DAC_DHR12LD[31:20]位(实际是存入寄存器DHR2[11:0]位)
3)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是内部的数据保存寄存器x)。随后,DHR1和DHR2的内容或被自动地传送到DORx寄存器,或通过软件触发或外部事件触发被传送到DORx寄存器。
双DAC通道模式的数据寄存器:
2.5 DAC转换
由框图可以看出,DAC受DORx寄存器直接控制的,但是不能直接对寄存器DAC_DORx写入数据,而是通过DHRx间接地传给DORx寄存器,实现对DAC的输出控制,任何输出到DAC通道x的数据都必须写入DAC_DHRx寄存器(数据实际写入DAC_DHR8Rx、DAC_DHR12Lx、DAC_DHR12Rx、DAC_DHR8RD、DAC_DHR12LD、或者DAC_DHR12RD寄存器)。
如果没有选中硬件触发(寄存器DAC_CR1的TENx位置’0’),存入寄存器DAC_DHRx的数据会在一个APB1时钟周期后自动传至寄存器DAC_DORx。
如果选中硬件触发(寄存器DAC_CR1的TENx位置’1’),数据传输在触发发生以后3个APB1时钟周期后完成。
一旦数据从DAC_DHRx寄存器装入DAC_DORx寄存器,在经过时间tSETTLING之后,输出即有效,这段时间的长短依电源电压和模拟输出负载的不同会有所变化。
TEN=0 触发失能时转换的时间框图:
2.6 DAC输出电压
当DAC的参考电压位VREF+的时候,数字输入经过DAC被线性地转换为模拟电压输出,其范围为0到VREF+。
任一DAC通道引脚上的输出电压满足下面的关系:DAC输出 = VREF x (DOR / 4095)。
注意:此时数据格式:应该选择12位数据右对齐。
2.7 选择DAC触发
在框图的左上方,是指DAC转换可以由某外部事件触发(定时器计数器、外部中断线)。如果TENx位被置1,DAC转换可以由某外部事件触发(定时器计数器、外部中断线)。配置控制位TSELx[2:0]可以选择8个触发事件之一触发DAC转换。
外部触发:
每次DAC接口侦测到来自选中的定时器TRGO输出,或者外部中断线9的上升沿,最近存放在寄存器DAC_DHRx中的数据会被传送到寄存器DAC_DORx中。在3个APB1时钟周期后,寄存器DAC_DORx更新为新值。
如果选择软件触发,一旦SWTRIG位置’1’,转换即开始。在数据从DAC_DHRx寄存器传送到DAC_DORx寄存器后,SWTRIG位由硬件自动清‘0’。
注意: 1)不能在ENx为’1’时改变TSELx[2:0]位。
2)如果选择软件触发,数据从寄存器DAC_DHRx传送到寄存器DAC_DORx只需要一个APB1时钟周期。
2.8 DMA请求
任一DAC通道都具有DMA功能。2个DMA通道可分别用于2个DAC通道的DMA请求。
如果DMAENx位置’1’,一旦有外部触发(而不是软件触发)发生,则产生一个DMA请求,然后DAC_DHRx寄存器的数据被传送到DAC_DORx寄存器。
在双DAC模式下,如果2个通道的DMAENx位都为’1’,则会产生2个DMA请求。如果实际只需要一个DMA传输,则应只选择其中一个DMAENx位置’1’。这样,程序可以在只使用一个DMA请求,一个DMA通道的情况下,处理工作在双DAC模式的2个DAC通道。
DAC的DMA请求不会累计,因此如果第2个外部触发发生在响应第1个外部触发之前,则不能处理第2个DMA请求,也不会报告错误。
2.9 双DAC通道转换
在需要2个DAC同时工作的情况下,为了更有效地利用总线带宽,DAC集成了3个供双DAC模式使用的寄存器:DHR8RD、DHR12RD和DHR12LD,只需要访问一个寄存器即可完成同时驱动2个DAC通道的操作。
对于双DAC通道转换和这些专用寄存器,共有11种转换模式可用。这些转换模式在只使用一个DAC通道的情况下,仍然可通过独立的DHRx寄存器操作。
3 DAC相关配置寄存器
3.1 DAC控制寄存器(DAC_CR)
作用:配置DAC通道使能、触发使能、输出缓存、噪声输出使能、三角波输出使能。
3.2 DAC软件触发寄存器(DAC_SWTRIGR)
作用:配置DAC通道软件触发使能。
3.3 DAC通道数据保持寄存器(DAC_DHRxRx)
DAC通道1的12位右对齐数据保持寄存器(DAC_DHR12R1)
DAC通道1的12位左对齐数据保持寄存器(DAC_DHR12L1)
DAC通道1的8位右对齐数据保持寄存器(DAC_DHR8R1)
除了DAC通道1,还有DAC通道2的对应的三个数据保持寄存器。
3.3 DAC数据输出寄存器(DAC_DORx)
DAC通道1数据输出寄存器(DAC_DOR1)
除了DAC通道1,还有DAC通道2的对应的数据输出寄存器。
4 DAC相关配置库函数
4.1 1个初始化函数
void DAC_Init(uint32_t DAC_Channel, DAC_InitTypeDef* DAC_InitStruct);
作用:配置外部触发的方式、噪声波生成、三角波生成、输出缓存。
4.2 1个使能函数
void DAC_Cmd(uint32_t DAC_Channel, FunctionalState NewState);
作用:配置DAC某个通道使能。
4.3 3个输出值函数
void DAC_SetChannel1Data(uint32_t DAC_Align, uint16_t Data);
void DAC_SetChannel2Data(uint32_t DAC_Align, uint16_t Data);
uint16_t DAC_GetDataOutputValue(uint32_t DAC_Channel);
作用:前两个向各自通道以某种对齐方式放入数值,第三个获得某个通道输出的数值。
5 DAC一般步骤
实验目标:利用按键控制STM32内部DAC的通道1来输出电压,同时通过ADC1的通道1采集DAC的输出电压。
1)开启输出引脚、DAC时钟,设置为模拟输入。调用函数:RCC_APBxPeriphClockCmd();
2)初始化DAC,设置DAC的工作模式。调用函数:DAC_Init();
3)使能DAC转换通道。调用函数:DAC_Cmd();
4)设置DAC的输出值。调用函数:DAC_SetChannelxData()。
下面按照这个一般步骤来进行一个简单的DAC程序:
//DAC通道1输出初始化
void Dac1_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
DAC_InitTypeDef DAC_InitType;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE ); //使能PORTA通道时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE ); //使能DAC通道时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; // 端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //模拟输入
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_SetBits(GPIOA,GPIO_Pin_4) ;//PA.4 输出高
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输出缓存关闭 BOFF1=1
DAC_Init(DAC_Channel_1,&DAC_InitType); //初始化DAC通道1
DAC_Cmd(DAC_Channel_1, ENABLE); //使能DAC1
DAC_SetChannel1Data(DAC_Align_12b_R, 0); //12位右对齐数据格式设置DAC值
}
//设置通道1输出电压
//vol:0~3300,代表0~3.3V
void Dac1_Set_Vol(u16 vol)
{
float temp=vol;
temp/=1000;
temp=temp*4096/3.3;
DAC_SetChannel1Data(DAC_Align_12b_R,temp);//12位右对齐数据格式设置DAC值
}
int main(void)
{
u16 adcx;
float temp;
u8 t=0;
u16 dacval=0;
u8 key;
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断优先级分组为组2:2位抢占优先级,2位响应优先级
uart_init(115200); //串口初始化为115200
KEY_Init(); //初始化按键程序
LED_Init(); //LED端口初始化
LCD_Init(); //LCD初始化
usmart_dev.init(72); //初始化USMART
Adc_Init(); //ADC初始化
Dac1_Init(); //DAC初始化
POINT_COLOR=RED;//设置字体为红色
LCD_ShowString(60,50,200,16,16,"WarShip STM32");
LCD_ShowString(60,70,200,16,16,"DAC TEST");
LCD_ShowString(60,90,200,16,16,"ATOM@ALIENTEK");
LCD_ShowString(60,110,200,16,16,"2015/1/15");
LCD_ShowString(60,130,200,16,16,"WK_UP:+ KEY1:-");
//显示提示信息
POINT_COLOR=BLUE;//设置字体为蓝色
LCD_ShowString(60,150,200,16,16,"DAC VAL:");
LCD_ShowString(60,170,200,16,16,"DAC VOL:0.000V");
LCD_ShowString(60,190,200,16,16,"ADC VOL:0.000V");
DAC_SetChannel1Data(DAC_Align_12b_R, 0);//初始值为0
while(1)
{
t++;
key=KEY_Scan(0);
if(key==WKUP_PRES)
{
if(dacval<4000)dacval+=200;
DAC_SetChannel1Data(DAC_Align_12b_R, dacval);//设置DAC值
}else if(key==KEY1_PRES)
{
if(dacval>200)dacval-=200;
else dacval=0;
DAC_SetChannel1Data(DAC_Align_12b_R, dacval);//设置DAC值
}
if(t==10||key==KEY1_PRES||key==WKUP_PRES) //WKUP/KEY1按下了,或者定时时间到了
{
adcx=DAC_GetDataOutputValue(DAC_Channel_1);//读取前面设置DAC的值
LCD_ShowxNum(124,150,adcx,4,16,0); //显示DAC寄存器值
temp=(float)adcx*(3.3/4096); //得到DAC电压值
adcx=temp;
LCD_ShowxNum(124,170,temp,1,16,0); //显示电压值整数部分
temp-=adcx;
temp*=1000;
LCD_ShowxNum(140,170,temp,3,16,0X80); //显示电压值的小数部分
adcx=Get_Adc_Average(ADC_Channel_1,10); //得到ADC转换值
temp=(float)adcx*(3.3/4096); //得到ADC电压值
adcx=temp;
LCD_ShowxNum(124,190,temp,1,16,0); //显示电压值整数部分
temp-=adcx;
temp*=1000;
LCD_ShowxNum(140,190,temp,3,16,0X80); //显示电压值的小数部分
LED0=!LED0;
t=0;
}
delay_ms(10);
}
}
6 PWM DAC基本原理
虽然STM32F103ZET6具有内部DAC,但是也仅仅只有两条DAC通道,并且STM32还有其他的很多型号是没有DAC的。通常情况下,采用专用的D/A芯片来实现,但是这样就会带来成本的增加。
不过STM32所有的芯片都有PWM输出,并且PWM输出通道很多,资源丰富。因此,我们可以使用PWM+简单的RC滤波来实现DAC的输出从而节省成本。
6.1 PWM DAC的构成原理
PWM本质上其实就是是一种周期一定,而高低电平占空比可调的方波。实际电路的典型PWM波形,如下图所示:
针对PWM的波形进行以下分析:
1)高电平阶段:计数器当前值从0-CCRx阶段(总时间=CCRx*每两个计数之间的间隔时间);
2)低电平阶段:计数器当前值从CCRx-ARR-1阶段(总时间=(ARR-1-CCRx)*每两个计数之间的间隔时间)。
根据PWM的波形,可以用分段函数来进行表示:
其中:T是STM32中计数脉冲的基本周期,也就是STM32定时器的计数频率的倒数;N是PWM波一个周期的计数脉冲个数,也就是STM32的ARR-1的值;n是PWM波一个周期中高电平的计数脉冲个数,也就是STM32的CCRx的值;VH和VL分别是PWM波的高低电平电压值;k为谐波次数;t为时间。
我们将分段函数①式展开成傅里叶级数,得到公式②:
从②式可以看出,式中第1个方括弧为直流分量,第2项为1次谐波分量,第3项为大于1次的高次谐波分量。
式②中的直流分量与n成线性关系,并随着n从0到N,直流分量从VL到VL+VH之间变化。而STM32的DAC功能也就是电压输出,这正是电压输出的DAC所需要的。
因此,如果能把式②中除直流分量外的谐波过滤掉,则可以得到从PWM波到电压输出DAC的转换,即:PWM波可以通过一个低通滤波器进行解调。式②中的第2项的幅度和相角与n有关,频率为1/(NT),其实就是PWM的输出频率。该频率是设计低通滤波器的依据。如果能把1次谐波很好过滤掉,则高次谐波就应该基本不存在了。
6.2 PWM DAC的具体实现
通过上面的了解,我们可以得到PWM DAC的分辨率,计算公式如下:分辨率=log2(N)
这里假设n的最小变化为1,当N=256的时候,分辨率就是8位。而STM32的定时器都是16位的,可以很容易得到更高的分辨率,分辨率越高,速度就越慢。不过我们在本章要设计的DAC分辨率为8位。
在8位分辨条件下,我们一般要求1次谐波对输出电压的影响不要超过1个位的精度,也就是3.3/256=0.01289V。假设VH为3.3V,VL为0V,那么一次谐波的最大值是2*3.3/π=2.1V,这就要求我们的RC滤波电路提供至少-20lg(2.1/0.01289)=-44dB的衰减。
STM32的定时器最快的计数频率是72Mhz,8为分辨率的时候,PWM频率为72M/256=281.25Khz。如果是1阶RC滤波,则要求截止频率为1.77Khz,如果为2阶RC滤波,则要求截止频率为22.34Khz。
二阶RC滤波截止频率计算公式为:f=1/2πRC
以上公式要求R55=R56=R,C63=C64=C(R55*C63=R56*C64=RC)。根据这个公式,我们计算出图25.1.2的截止频率为:33.8Khz超过了22.34Khz,这个和我们前面提到的要求有点出入,原因是该电路我们还需要用作PWM DAC音频输出,而音频信号带宽是22.05Khz,为了让音频信号能够通过该低通滤波,同时为了标准化参数选取,所以确定了这样的参数。实测精度在0.5LSB以内。
6.3 PWM DAC实例
硬件连接:
单片机:STM32F103ZET6
硬件资源:指示灯DS0,WK_UP和KEY1按键,ADC,PWM DAC
具体的硬件连接的图如下所示:
STM32控制程序:
//设置输出电压
//vol:0~330,代表0~3.3V
void PWM_DAC_Set(u16 vol)
{
float temp=vol;
temp/=100;
temp=temp*256/3.3;
TIM_SetCompare1(TIM1,temp);
}
int main(void)
{
u16 adcx;
float temp;
u8 t=0;
u16 pwmval=0;
u8 key;
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
uart_init(115200); //串口初始化为115200
KEY_Init(); //KEY初始化
LED_Init(); //LED端口初始化
usmart_dev.init(72); //初始化USMART
LCD_Init(); //LCD初始化
Adc_Init(); //ADC初始化
TIM1_PWM_Init(255,0); //TIM1 PWM初始化, Fpwm=72M/256=281.25Khz.
TIM_SetCompare1(TIM1,100);//初始值为0
POINT_COLOR=RED;//设置字体为红色
LCD_ShowString(60,50,200,16,16,"WarShip STM32");
LCD_ShowString(60,70,200,16,16,"PWM DAC TEST");
LCD_ShowString(60,90,200,16,16,"ATOM@ALIENTEK");
LCD_ShowString(60,110,200,16,16,"2015/1/15");
LCD_ShowString(60,130,200,16,16,"WK_UP:+ KEY1:-");
//显示提示信息
POINT_COLOR=BLUE;//设置字体为蓝色
LCD_ShowString(60,150,200,16,16,"PWM VAL:");
LCD_ShowString(60,170,200,16,16,"DAC VOL:0.000V");
LCD_ShowString(60,190,200,16,16,"ADC VOL:0.000V");
TIM_SetCompare1(TIM1,pwmval);//初始值
while(1)
{
t++;
key=KEY_Scan(0);
if(key==WKUP_PRES)
{
if(pwmval<250)pwmval+=10;
TIM_SetCompare1(TIM1,pwmval); //输出
}else if(key==KEY1_PRES)
{
if(pwmval>10)pwmval-=10;
else pwmval=0;
TIM_SetCompare1(TIM1,pwmval); //输出
}
if(t==10||key==KEY1_PRES||key==WKUP_PRES) //WKUP/KEY1按下了,或者定时时间到了
{
adcx=TIM_GetCapture1(TIM1);
LCD_ShowxNum(124,150,adcx,4,16,0); //显示DAC寄存器值
temp=(float)adcx*(3.3/256); //得到DAC电压值
adcx=temp;
LCD_ShowxNum(124,170,temp,1,16,0); //显示电压值整数部分
temp-=adcx;
temp*=1000;
LCD_ShowxNum(140,170,temp,3,16,0x80); //显示电压值的小数部分
adcx=Get_Adc_Average(ADC_Channel_1,20); //得到ADC转换值
temp=(float)adcx*(3.3/4096); //得到ADC电压值
adcx=temp;
LCD_ShowxNum(124,190,temp,1,16,0); //显示电压值整数部分
temp-=adcx;
temp*=1000;
LCD_ShowxNum(140,190,temp,3,16,0x80); //显示电压值的小数部分
t=0;
LED0=!LED0;
}
delay_ms(10);
}
}