1.DAC基础
大多数STM32芯片会自带DAC输出模块(12 位数字输入,电压输出型的 DAC)
例如常用的STM32F103RCT6 ( RAM48K FLASH 256K),芯片的DAC有两个输出通道
本次实验使用单 DAC 通道 1,采用 12 位右对齐格式输出。
STM32F103 参考手册P185:
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.实验结果
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环节其他部分的最高频:
1111kHz,说明循环中其他计算环节耗时约0.9us。
加入DAC环节一次运行耗时约 1/630kHz=1.6us。
所以进行一次DAC耗时大概占0.7us。这个值比参考手册给出的Tsetting小很多,但是需要注意的是DAC_SetChannel1Data 的源码中貌似没有ACK环节,即没有检测DAC是否已经完成。所以进一步DAC高频极限测试可以使用DAC 方波输出+示波器进行测试。