第二十六章 DAC实验 
	  
 
[mw_shl_code=c,true]1.硬件平台:正点原子探索者STM32F407开发板
2.软件平台:MDK5.1
3.固件库版本:V1.4.0
[/mw_shl_code]
 
 
    
	 
 
	上几章,我们介绍了STM32F4的ADC使用,本章我们将向大家介绍STM32F4的DAC功能。在本章中,我们将利用按键(或USMART)控制STM32F4内部DAC1来输出电压,通过ADC1的通道5采集DAC的输出电压,在LCD模块上面显示ADC获取到的电压值以及DAC的设定输出电压值等信息。本章将分为如下几个部分: 
 
	26.1 STM32F4 DAC简介 
 
	26.2 硬件设计 
 
	26.3 软件设计 
 
	26.4 下载验证  
 
	  
 
	26.1
STM32F4 DAC简介 
	STM32F4的DAC模块(数字/模拟转换模块)是12位数字输入,电压输出型的DAC。DAC可以配置为8位或12位模式,也可以与DMA控制器配合使用。DAC工作在12位模式时,数据可以设置成左对齐或右对齐。DAC模块有2个输出通道,每个通道都有单独的转换器。在双DAC模式下,2个通道可以独立地进行转换,也可以同时进行转换并同步地更新2个通道的输出。DAC可以通过引脚输入参考电压Vref+(通ADC共用)以获得更精确的转换结果。 
 
	STM32F4的DAC模块主要特点有: 
 
	① 2个DAC转换器:每个转换器对应1个输出通道  
 
	② 8位或者12位单调输出  
 
	③ 12位模式下数据左对齐或者右对齐  
 
	④ 同步更新功能  
 
	⑤ 噪声波形生成  
 
	⑥ 三角波形生成  
 
	⑦ 双DAC通道同时或者分别转换 
 
	⑧ 每个通道都有DMA功能  
 
	单个DAC通道的框图如图26.1.1所示:
 
 
	 
 
	 
 
	图26.1.1 DAC通道模块框图 
 
	图中VDDA和VSSA为DAC模块模拟部分的供电,而Vref+则是DAC模块的参考电压。DAC_OUTx就是DAC的输出通道了(对应PA4或者PA5引脚)。 
 
	从图26.1.1可以看出,DAC输出是受DORx寄存器直接控制的,但是我们不能直接往DORx寄存器写入数据,而是通过DHRx间接的传给DORx寄存器,实现对DAC输出的控制。前面我们提到,STM32F4的DAC支持8/12位模式,8位模式的时候是固定的右对齐的,而12位模式又可以设置左对齐/右对齐。单DAC通道x,总共有3种情况: 
 
	①     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通道1,采用12位右对齐格式,所以采用第③种情况。 
 
	如果没有选中硬件触发(寄存器DAC_CR1的TENx位置’0’),存入寄存器DAC_DHRx的数据会在一个APB1时钟周期后自动传至寄存器DAC_DORx。如果选中硬件触发(寄存器DAC_CR1的TENx位置’1’),数据传输在触发发生以后3个APB1时钟周期后完成。
一旦数据从DAC_DHRx寄存器装入DAC_DORx寄存器,在经过时间 之后,输出即有效,这段时间的长短依电源电压和模拟输出负载的不同会有所变化。我们可以从STM32F407ZGT6的数据手册查到 的典型值为3us,最大是6us。所以DAC的转换速度最快是333K左右。 
 
	本章我们将不使用硬件触发(TEN=0),其转换的时间框图如图26.1.2所示: 
 
	
  
 
 
 
	图26.1.2 TEN=0时DAC模块转换时间框图 
 
	当DAC的参考电压为Vref+的时候,DAC的输出电压是线性的从0~Vref+,12位模式下DAC输出电压与Vref+以及DORx的计算公式如下: 
 
	DACx输出电压=Vref*(DORx/4095) 
 
	接下来,我们介绍一下要实现DAC的通道1输出,需要用到的一些寄存器。首先是DAC控制寄存器DAC_CR,该寄存器的各位描述如图26.1.3所示: 
 
	
  
 
 
 
	图26.1.3 寄存器DAC_CR各位描述 
 
	       DAC_CR的低16位用于控制通道1,而高16位用于控制通道2,我们这里仅列出比较重要的最低8位的详细描述,如图26.1.4所示: 
 
	
  
 
 
 
	图26.1.4 寄存器DAC_CR低八位详细描述 
 
	       首先,我们来看DAC通道1使能位(EN1),该位用来控制DAC通道1使能的,本章我们就是用的DAC通道1,所以该位设置为1。 
 
	       再看关闭DAC通道1输出缓存控制位(BOFF1),这里STM32F4的DAC输出缓存做的有些不好,如果使能的话,虽然输出能力强一点,但是输出没法到0,这是个很严重的问题。所以本章我们不使用输出缓存。即设置该位为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功能,故还是设置为0。 
 
	       通道2的情况和通道1一模一样,这里就不不细说了。在DAC_CR设置好之后,DAC就可以正常工作了,我们仅需要再设置DAC的数据保持寄存器的值,就可以在DAC输出通道得到你想要的电压了(对应IO口设置为模拟输入)。本章,我们用的是DAC通道1的12位右对齐数据保持寄存器:DAC_DHR12R1,该寄存器各位描述如图26.1.5所示: 
 
	
  
 
 
 
	图26.1.5 寄存器DAC_DHR12R1各位描述 
 
	       该寄存器用来设置DAC输出,通过写入12位数据到该寄存器,就可以在DAC输出通道1(PA4)得到我们所要的结果。 
 
	       通过以上介绍,我们了解了STM32F4实现DAC输出的相关设置,本章我们将使用DAC模块的通道1来输出模拟电压。这里我们用到的库函数以及相关定义分布在文件stm32f4xx_dac.c以及头文件stm32f4xx_dac.h中。实现上面功能的详细设置步骤如下:  
 
	1)开启PA口时钟,设置PA4为模拟输入。 
 
	STM32F407ZGT6的DAC通道1是接在PA4上的,所以,我们先要使能GPIOA的时钟,然后设置PA4为模拟输入。 
 
	这里需要特别说明一下,虽然DAC引脚设置为输入,但是STM32F4内部会连接在DAC模拟输出上,这在我们引脚复用映射章节有讲解。 
 
	 
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);//使能GPIOA时钟 
 
	  
 
	 
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; 
 
	 
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;//模拟输入 
 
	 
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN;//下拉 
 
	 
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化 
 
	对于DAC通道与引脚对应关系,这在STM32F4的数据手册引脚表上有列出,如下图:
 
	 
 
	图26.1.6 DAC通道引脚对应关系 
 
	2)使能DAC1时钟。 
 
	同其他外设一样,要想使用,必须先开启相应的时钟。STM32F4的DAC模块时钟是由APB1提供的,所以我们先要在通过调用函数RCC_APB1PeriphClockCmd来使能DAC1时钟。  
 
	 
RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);//使能DAC时钟 
 
	3)初始化DAC,设置DAC的工作模式。 
 
	该部分设置全部通过DAC_CR设置实现,包括:DAC通道1使能、DAC通道1输出缓存关闭、不使用触发、不使用波形发生器等设置。这里DAC初始化是通过函数DAC_Init完成的: 
 
	void DAC_Init(uint32_t DAC_Channel,
DAC_InitTypeDef* DAC_InitStruct); 
 
	跟前面一样,首先我们来看看参数设置结构体类型DAC_InitTypeDef的定义: 
 
	typedef struct 
 
	{ 
 
	  uint32_t
DAC_Trigger;  
 
	  uint32_t
DAC_WaveGeneration;  
 
	  uint32_t
DAC_LFSRUnmask_TriangleAmplitude;  
 
	  uint32_t
DAC_OutputBuffer;   
 
	}DAC_InitTypeDef; 
 
	这个结构体的定义还是比较简单的,只有四个成员变量,下面我们一一讲解。 
 
	第一个参数DAC_Trigger用来设置是否使用触发功能,前面已经讲解过这个的含义,这里我们不是用触发功能,所以值为DAC_Trigger_None。 
 
	第二个参数DAC_WaveGeneratio用来设置是否使用波形发生,这里我们前面同样讲解过不使用。所以值为DAC_WaveGeneration_None。 
 
	第三个参数DAC_LFSRUnmask_TriangleAmplitude用来设置屏蔽/幅值选择器,这个变量只在使用波形发生器的时候才有用,这里我们设置为0即可,值为DAC_LFSRUnmask_Bit0。 
 
	第四个参数DAC_OutputBuffer是用来设置输出缓存控制位,前面讲解过,我们不使用输出缓存,所以值为DAC_OutputBuffer_Disable。到此四个参数设置完毕。看看我们的实例代码: 
 
	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);     //初始化DAC通道1 
 
	4)使能DAC转换通道 
 
	初始化DAC之后,理所当然要使能DAC转换通道,库函数方法是: 
 
	DAC_Cmd(DAC_Channel_1, ENABLE);  //使能DAC通道1 
 
	5)设置DAC的输出值。 
 
	通过前面4个步骤的设置,DAC就可以开始工作了,我们使用12位右对齐数据格式,所以我们通过设置DHR12R1,就可以在DAC输出引脚(PA4)得到不同的电压值了。设置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对应通道最后一次转换的数值,函数是: 
 
	DAC_GetDataOutputValue(DAC_Channel_1); 
 
	设置和读出一一对应很好理解,这里就不多讲解了。 
 
	最后,再提醒一下大家,本例程,我们使用的是3.3V的参考电压,即Vref+连接VDDA。 
 
	通过以上几个步骤的设置,我们就能正常的使用STM32F4的DAC通道1来输出不同的模拟电压了。 
 
	26.2 硬件设计 
	本章用到的硬件资源有: 
 
	1)  指示灯DS0 
 
	2)  KEY_UP和KEY1按键 
 
	3)  串口 
 
	4)  TFTLCD模块 
 
	5)  ADC 
 
	6)  DAC  
 
	本章,我们使用DAC通道1输出模拟电压,然后通过ADC1的通道1对该输出电压进行读取,并显示在LCD模块上面,DAC的输出电压,我们通过按键(或USMART)进行设置。 
 
	我们需要用到ADC采集DAC的输出电压,所以需要在硬件上把他们短接起来。ADC和DAC的连接原理图如图26.2.1所示: 
 
	
  
 
 
 
	图26.2.1 ADC、DAC与STM32F4连接原理图 
 
	       P12是多功能端口,我们只需要通过跳线帽短接P14的ADC和DAC,就可以开始做本章实验了。如图26.2.2所示: 
 
	
  
 
 
 
	图26.2.2 硬件连接示意图 
 
	  
 
	26.3 软件设计 
	打开本章实验工程可以发现,我们相比ADC实验,在库函数中主要是添加了dac支持的相关文件stm32f4xx_dac.c以及包含头文件stm32f4xx_dac.h。同时我们在HARDWARE分组下面新建了dac.c源文件以及包含对应的头文件dac.h。这两个文件用来存放我们编写的ADC相关函数和定义。打开dac.c,代码如下: 
 
	//DAC通道1输出初始化 
 
	void Dac1_Init(void) 
 
	{   
 
	 
GPIO_InitTypeDef 
GPIO_InitStructure; 
 
	DAC_InitTypeDef DAC_InitType; 
 
	        
 
	 
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);//①使能PA时钟 
 
	 
RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);//②使能DAC时钟 
 
	           
 
	 
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; 
 
	 
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;//模拟输入 
 
	 
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN;//下拉 
 
	 
GPIO_Init(GPIOA, &GPIO_InitStructure);//①初始化GPIO 
 
	  
 
	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 ;/输出缓存关闭  
 
	 
DAC_Init(DAC_Channel_1,&DAC_InitType);  //③初始化DAC通道1 
 
	  
 
	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) 
 
	{ 
 
	       double
temp=vol; 
 
	       temp/=1000; 
 
	       temp=temp*4096/3.3; 
 
	       DAC_SetChannel1Data(DAC_Align_12b_R,temp);//12位右对齐数据格式} 
 
	此部分代码就2个函数,Dac1_Init函数用于初始化DAC通道1。这里基本上是按我们上面的步骤来初始化的,我们用序号①~⑤已经标示这些步骤。经过这个初始化之后,我们就可以正常使用DAC通道1了。第二个函数Dac1_Set_Vol,用于设置DAC通道1的输出电压,实际就是将电压值转换为DAC输入值。 
 
	其他头文件代码就比较简单,这里我们不做过多讲解,接下来我们来看看主函数代码: 
 
	int main(void) 
 
	{  
 
	       u16
adcx; 
 
	       float
temp; 
 
	      u8 t=0, key;     
 
	       u16
dacval=0; 
 
	       NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2 
 
	       delay_init(168);  //初始化延时函数 
 
	       uart_init(115200);         //初始化串口波特率为115200 
 
	       LED_Init();                                //初始化LED  
 
	      LCD_Init();                                //LCD初始化 
 
	       Adc_Init();
                        //adc初始化 
 
	       KEY_Init();
                       //按键初始化 
 
	       Dac1_Init();                        //DAC通道1初始化     
 
	       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,"2014/5/6");        
 
	       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
VOL:0.000V");              
 
	       LCD_ShowString(30,190,200,16,16,"ADC
VOL:0.000V"); 
 
	       
 
	   
DAC_SetChannel1Data(DAC_Align_12b_R,dacval);//初始值为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==2)      
 
	              { 
 
	                     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(94,150,adcx,4,16,0);          //显示DAC寄存器值 
 
	                     temp=(float)adcx*(3.3/4096);                     //得到DAC电压值 
 
	                     adcx=temp; 
 
	                    LCD_ShowxNum(94,170,temp,1,16,0);          //显示电压值整数部分 
 
	                    temp-=adcx; 
 
	                     temp*=1000; 
 
	                     LCD_ShowxNum(110,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(94,190,temp,1,16,0);          //显示电压值整数部分 
 
	                    temp-=adcx; 
 
	                     temp*=1000; 
 
	                     LCD_ShowxNum(110,190,temp,3,16,0X80);
    //显示电压值的小数部分 
 
	                     LED0=!LED0;           
 
	                     t=0; 
 
	              }          
 
	              delay_ms(10);  
 
	       }      
 
	} 
 
	此部分代码,我们先对需要用到的模块进行初始化,然后显示一些提示信息,本章我们通过KEY_UP(WKUP按键)和KEY1(也就是上下键)来实现对DAC输出的幅值控制。按下KEY_UP增加,按KEY1减小。同时在LCD上面显示DHR12R1寄存器的值、DAC设计输出电压以及ADC采集到的DAC输出电压。 
 
	26.4 下载验证 
	在代码编译成功之后,我们通过下载代码到ALIENTEK探索者STM32F4开发板上,可以看到LCD显示如图26.4.1所示: 
 
	
  
 
 
 
	图26.4.1 DAC实验测试图 
 
	同时伴随DS0的不停闪烁,提示程序在运行。此时,我们通过按KEY_UP按键,可以看到输出电压增大,按KEY1则变小。 
 
 
	 
 
	正点原子探索者STM32F407开发板购买地址:http://item.taobao.com/item.htm?id=41855882779 
 
 
	    
 
 
	
  
 
  
  |