1.DAC基础

大多数STM32芯片会自带DAC输出模块(12 位数字输入,电压输出型的 DAC)

例如常用的STM32F103RCT6 ( RAM48K  FLASH 256K),芯片的DAC有两个输出通道

本次实验使用单 DAC 通道 1,采用 12 位右对齐格式输出。

 STM32F103 参考手册P185:

esp32 pio 打印堆栈_esp32 pio 打印堆栈

DAC过程,简要概括为:给DAC_DORx寄存器赋值,然后DAC模块处理,经过t_setting 后,模拟输出引脚变化  。从STM32F103RCT6 的数据手册查到 t_setting 的最大是 4us。所以 DAC 的转换速度最快是: 1/4μs=250KHz 左右。 同时,这个频率也远小于芯片主频72MHz,满足方波占空比调制-DAC输出的要求。

2.启用DAC的一般步骤

开启PORTA时钟。  STM32F103RCT6的DAC通道1在PA4 。所以先开启引脚A系列的时钟,引脚模式设置为模拟输入(因为使能 DAC 通道之后,相应的 GPIO 引脚会自动与 DAC 的模拟输出相连,设置为输入是为了避免额外的干扰-之后可以测试一下设置输出模式是否可行)

Code:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE ); //使能 PORTA时钟
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //引脚配置 模拟输入

step2:使能DAC1的时钟。 查数据手册,找到DAC模块的时钟,调用固件库中的函数开启:

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

step3:初始化工作模式:

本次使用单通道使能,通道1输出缓存关闭,触发响应关闭,波形发生器(三角、方波)关闭。 只需要最基础的DAC输出,调用的函数是dac固件库中的:

void DAC_Init(uint32_t DAC_Channel, DAC_InitTypeDef* DAC_InitStruct)

函数的参数都在结构体DAC_InitType中定义:

typedef struct

{

  uint32_t DAC_Trigger;    //在此值为 DAC_Trigger_None。

  uint32_t DAC_WaveGeneration;     //是否使用波形发生  None

  uint32_t DAC_LFSRUnmask_TriangleAmplitude;  //屏蔽/幅值选择器,只有在波形发生时生效

  uint32_t DAC_OutputBuffer;          // Disable  DAC1 输出缓存关闭

}DAC_InitTypeDef;

/*是否启用触发功能:有些时候需要按键可调输出的电压值。其中一种方法是使用外部中断EXTI9, 另外一种方法就是使用软件触发。如果将DAC_InitTypeDef.DAC_Trigger设置为DAC_Trigger_None, 那么,不需要其他任何的触发源,直接使用DAC_SetChannelxData(),就可以设定输出电压的大小。如果使用了软件触发,那么,每次在使用DAC_SetChannelxData()修改输出电压后,还需要调用DAC_SoftwareTriggerCmd(),目的是使能软件触发*/

step4:通道1输出使能

DAC_Cmd(DAC_Channel_1, ENABLE); //使能 DAC1

step5:设置DAC值:

void DAC_SetChannel1Data(uint32_t DAC_Align, uint16_t Data)

也是固件中的函数,源代码:

/**
  * @brief  Set the specified data holding register value for DAC channel1.
  * @param  DAC_Align: Specifies the data alignment for DAC channel1.
  *   This parameter can be one of the following values:
  *     @arg DAC_Align_8b_R: 8bit right data alignment selected
  *     @arg DAC_Align_12b_L: 12bit left data alignment selected
  *     @arg DAC_Align_12b_R: 12bit right data alignment selected
  * @param  Data : Data to be loaded in the selected data holding register.
  * @retval None
  */
void DAC_SetChannel1Data(uint32_t DAC_Align, uint16_t Data)
{  
  __IO uint32_t tmp = 0;
  
  /* Check the parameters */
  assert_param(IS_DAC_ALIGN(DAC_Align));
  assert_param(IS_DAC_DATA(Data));
  
  tmp = (uint32_t)DAC_BASE; 
  tmp += DHR12R1_OFFSET + DAC_Align;

  /* Set the DAC channel1 selected data holding register */
  *(__IO uint32_t *) tmp = Data;
}

首先第一个参数选12位D输入还是8位D(Right or Left)输入

第二个参数就是输出的值 。注意第二个参数值 12位的话是在0~4095(2^12-1) 8位在0~255

此外,也可以在软件内读取通道输出值:DAC_GetDataOutputValue(DAC_Channel_1);

3.DAC initial

#include "dac.h"

//	 
//本程序源自:   
//正点原子@ALIENTEK
//Copyright(C) 广州市星翼电子科技有限公司 2009-2019									  
//
//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值
}

initial函数就是把上面的步骤做了一个封装。

void Dac1_Set_Vol(u16 vol)函数支持float电压换算,不然每次调用DAC_SetChannel1Data()时都得换算一下。

4.DAC高频实验

在主函数里测DAC的最高频,然后在LCD上现实。 首先用一个up-down循环,让DAC输出一直变化,然后每次变化Fre++ 。 每秒在定时器中清空一次Fre,然后记录最高的Fre,即为DAC最高频。

#include "led.h"
#include "delay.h"
#include "sys.h"
#include "usart.h"
#include "lcd.h"
#include "adc.h"
#include "dac.h"
#include "timer.h"



u32 Fre=0; //extern 变量定义为全局变量
u16 adcx;
float temp;
u8 dvup=1;	

u32 HighestFre=0;
u16 dacval=0;
	
   	
 int main(void)
 { 

	 
	 
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);// 设置中断优先级分组2
	delay_init();	    	 //延时函数初始化	  
	uart_init(9600);	 	//串口初始化为9600
	LED_Init();		  		//初始化与LED连接的硬件接口
 	LCD_Init();			   	//初始化LCD 				 	
 	Adc_Init();		  		//ADC初始化
	Dac1_Init();		 	//DAC通道1初始化	
	TIM3_Int_Init(9999,7199);//10Khz的计数频率,计数到10000为1000ms  
	 
 
	POINT_COLOR=RED;//设置字体为红色 
	LCD_ShowString(60,50,200,16,16,"Mini STM32");	
	LCD_ShowString(60,70,200,16,16,"DAC TEST");	
	LCD_ShowString(60,90,200,16,16,"2020/6/30");	
	LCD_ShowString(60,120,200,16,16,"HF:");	
	//显示提示信息											      
	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);//设置DAC1通道 12位R对其,输出0    
	
	while(1)
	{
		
		if(dvup){
			if(dacval<4000)  dacval+=20;
			else dvup=0;
		}
		else{
			if(dacval>200)  dacval-=20;
			else dvup=1;
		}
		
		DAC_SetChannel1Data(DAC_Align_12b_R, dacval);//DAC输出
		Fre++;


	}										    
}

在定时器中断中设置刷新LCD,main程序内尽可能快地更新DAC次数。

定时器 timer.c 中的IRQHandler:

extern u32 Fre;
extern u16 adcx;
extern float temp;
extern u8 dvup;	
extern u32 HighestFre;
extern u16 dacval;
u32 tempu32;


void TIM3_IRQHandler(void)   //TIM3中断
{
	if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) //检查指定的TIM中断发生与否:TIM 中断源 
		{
		TIM_ClearITPendingBit(TIM3, TIM_IT_Update  );  //清除TIMx的中断待处理位:TIM 中断源
		
		if(Fre>HighestFre) HighestFre=Fre;
		tempu32=HighestFre;	
		LCD_ShowxNum(100,120,tempu32,8,16,0); //显示highest frequency

		
		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;	   
			
		Fre=0;
		}
}

4.实验结果

esp32 pio 打印堆栈_esp32 pio 打印堆栈_02

CPU基本都在进行DAC操作。和估计的250kHz在一个量级,但是会高一些,可能是定时器不够准确,以及Tsetting会比标示的更短一些。

主程序循环时,主要耗时的是DAC环节:

while(1)
     {
         if(dvup){
             if(dacval<4000)  dacval+=20;
             else dvup=0;
         }
         else{
             if(dacval>200)  dacval-=20;
             else dvup=1;
         }
        DAC_SetChannel1Data(DAC_Align_12b_R, dacval);//DAC输出
         Fre++;
     }

 尝试修改源码:

/**
  * @brief  Set the specified data holding register value for DAC channel1.
  * @param  DAC_Align: Specifies the data alignment for DAC channel1.
  *   This parameter can be one of the following values:
  *     @arg DAC_Align_8b_R: 8bit right data alignment selected
  *     @arg DAC_Align_12b_L: 12bit left data alignment selected
  *     @arg DAC_Align_12b_R: 12bit right data alignment selected
  * @param  Data : Data to be loaded in the selected data holding register.
  * @retval None
  */
void DAC_SetChannel1Data(uint32_t DAC_Align, uint16_t Data)
{  
  __IO uint32_t tmp = 0;
  
  /* Check the parameters */
  //assert_param(IS_DAC_ALIGN(DAC_Align));
  //assert_param(IS_DAC_DATA(Data));
  
  tmp = (uint32_t)DAC_BASE; 
  tmp += DHR12R1_OFFSET + DAC_Align;

  /* Set the DAC channel1 selected data holding register */
  *(__IO uint32_t *) tmp = Data;
}

修改时把两行 assert_param 注释了,略过了参数检查环节。最高频率几乎没有提升。说明CPU运算不是主要耗时项目。

在主程序中把DAC_SetChannel1Data注释了,测得不带DAC环节其他部分的最高频:

esp32 pio 打印堆栈_DAC_03

1111kHz,说明循环中其他计算环节耗时约0.9us。

加入DAC环节一次运行耗时约 1/630kHz=1.6us。

 所以进行一次DAC耗时大概占0.7us。这个值比参考手册给出的Tsetting小很多,但是需要注意的是DAC_SetChannel1Data 的源码中貌似没有ACK环节,即没有检测DAC是否已经完成。所以进一步DAC高频极限测试可以使用DAC 方波输出+示波器进行测试。