前言:详解DAC数模转换原理+DAC输出模拟电压的测量比对实验程序(使用 DAC 通道 1 输出模拟电压,然后通过 ADC1 的通道 1 对该输出电压进行读取,并显示在 LCD 模块上面,DAC 的输出电压可以通过按键(或 USMART)进行调整。)
目录
1.数模转换DAC原理
硬件设计
2.实验程序讲解
源码
1.数模转换DAC原理
STM32的DAC模块(数字/模拟转换模块)是12位数字输入, 电压输出型的DAC。DAC可以配置为8位或12位模式,也可以与DMA控制器配合使用。DAC工作在12位模式时,数据可以设置成左对齐或右对齐。DAC模块有2个输出通道,每个通道都有单独的转换器。在双DAC模式下,2个通道可以 独立地进行转换,也可以同时进行转换并同步地更新2个通道的输出。DAC可以通过引脚输入参考电压VREF+以获得更精确的转换结果。
1.1.STM32的DAC模块主要特点有:
- ①2个DAC转换器:每个转换器对应1个输出通道
- ②8位或者12位单调输出
- ③12位模式下数据左对齐或者右对齐
- ④同步更新功能
- ⑤噪声波形生成
- ⑥三角波形生成
- ⑦双DAC通道同时或者分别转换
- ⑧每个通道都有DMA功能
1.2.DAC模块方图:
在DHRx相应寄存器写值就可以被控制写到DORx数据寄存器输出,
控制MAMPx和WAVEBx等寄存器可以相应的位可以生成三角波、噪音波,
下图中的左上角是触发控制,可以通过外部的定时器的事件触发或者软件触发。
VDDA和VSSA为DAC模块模拟部分的供电。
Vref+则是DAC模块的参考电压。(1.8v~13.3v)
DAC_OUTx就是DAC的输出通道了,F4的DAC1_OUT1对应PA4,DAC1_OUT2对应PA5引脚。
使用DAC时,引脚要配置为模拟模式(AIN)。
1.3.DAC转换:
1.4.DAC数据格式:
1.5.DAC触发选择:
1.6. DAC输出电压:
除以4095是因为是最高12位,参考电压一般都是3.3V 。
1.7.DAC通道使能:
1.8.DAC输出缓冲器使能:
1.9.DAC相关寄存器:
硬件设计
本次实验使用STM32F4的DAC引脚PA4,ADC引脚PA5。(DAC输出,ADC测量输入)
可以使用跳线帽将两个引脚连接到一起,实现例如输入100数值的DAC它的电压是不是0.3v?可以用内部的ADC再去测量这个电压,测量出来看一下是不是0.3v,进行一个比较。
2.实验程序讲解
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 的数据手册引脚表上有列出,如下图:
APB1 提供的,所以我们先要在通过调用函数 RCC_APB1PeriphClockCmd 来使能 DAC1 时
钟。
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); //初始化 DAC 通道 1
DAC_Cmd(DAC_Channel_1, ENABLE); //使能 DAC 通道 1
DAC_SetChannel1Data(DAC_Align_12b_R, 0); //12 位右对齐数据格式设置 DAC 值
DAC_GetDataOutputValue(DAC_Channel_1);
源码
main.c
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "lcd.h"
#include "adc.h"
#include "dac.h"
#include "key.h"
#include "beep.h"
int main(void)
{
u16 adcx;
float temp;
u8 t=0;
u16 dacval=0;
u8 key;
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,"2024/6/12");
LCD_ShowString(30,130,200,16,16,"WK_UP:+ KEY1:-");
POINT_COLOR=BLACK;//设置字体为黄色
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);
GPIO_ResetBits(GPIOF,GPIO_Pin_9); //LED0????GPIOF.9??,? ??LED0=0;
GPIO_SetBits(GPIOF,GPIO_Pin_10); //LED1????GPIOF.10??,? ??LED1=1;
GPIO_ResetBits(GPIOF,GPIO_Pin_8); //BEEP????, ??BEEP=0;
delay_ms(800); //??800ms
GPIO_SetBits(GPIOF,GPIO_Pin_9); //LED0????GPIOF.0??,? ??LED0=1;
GPIO_ResetBits(GPIOF,GPIO_Pin_10); //LED1????GPIOF.10??,? ??LED1=0;
delay_ms(800); //??800ms
GPIO_SetBits(GPIOF,GPIO_Pin_9); //LED0????GPIOF.0??,? ??LED0=1;
GPIO_SetBits(GPIOF,GPIO_Pin_10); //LED1????GPIOF.10??,? ??LED1=1;
delay_ms(1800); //??800ms
GPIO_ResetBits(GPIOF,GPIO_Pin_9); //LED0????GPIOF.0??,? ??LED0=0;
GPIO_ResetBits(GPIOF,GPIO_Pin_10); //LED1????GPIOF.10??,? ??LED1=0;
GPIO_SetBits(GPIOF,GPIO_Pin_8); //BEEP????, ??BEEP=1;
delay_ms(1800); //??800ms
for(i=0;i<5;i++)
{
GPIO_ResetBits(GPIOF,GPIO_Pin_8); //BEEP????, ??BEEP=0;
GPIO_SetBits(GPIOF,GPIO_Pin_9); //LED0????GPIOF.9??,? ??LED0=1;
delay_ms(800);
GPIO_ResetBits(GPIOF,GPIO_Pin_9); //LED0????GPIOF.9??,? ??LED0=0;
delay_ms(800);
}
for(i=0;i<5;i++)
{
GPIO_SetBits(GPIOF,GPIO_Pin_10); //LED0????GPIOF.10??,? ??LED1=1;
delay_ms(800);
GPIO_ResetBits(GPIOF,GPIO_Pin_10); //LED0????GPIOF.10??,? ??LED1=0;
delay_ms(800);
}
}
}
dac.c
#include "dac.h"
//DAC通道1输出初始化
void Dac1_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
DAC_InitTypeDef DAC_InitType;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);//使能GPIOA时钟
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);//初始化
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); //使能DAC通道1
DAC_SetChannel1Data(DAC_Align_12b_R, 0); //12位右对齐数据格式设置DAC值
}
//设置通道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位右对齐数据格式设置DAC值
}
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;
}
相关知识:
一、运算放大器(Operational Amplifier,简称 Op-Amp 或 运放)是一种高度灵活且多功能的电子组件,具有非常高的电压增益、高输入阻抗和低输出阻抗的特性。它在电子电路中扮演着核心角色,可以执行一系列复杂的信号处理任务。
以下是运算放大器的一些主要作用:
1. **信号放大**: - 运算放大器可以放大微弱的输入信号,使之达到足够的幅度,以便于后续电路处理或测量。它可以用于电压放大,既可以在同相配置中使用,也可以在反相配置中使用。
2. **数学运算**: - 运放可以实现信号的加法、减法、积分和微分等数学运算,这些功能对于信号处理、控制系统和数据转换等领域至关重要。
3. **滤波**: - 结合电阻和电容,运算放大器可以构成各种类型的滤波器,包括低通、高通、带通和带阻滤波器,用于信号的清洁和频率选择。
4. **比较器**: - 运放可以用于比较两个电压信号,当输入信号超过某个阈值时,产生逻辑电平的变化,这在过零检测和电压监控电路中很常见。
5. **阻抗变换**: - 运放可以用作缓冲器,将高阻抗信号转换为低阻抗信号,以匹配不同电路的阻抗要求。
6. **精密整流和稳压**: - 在某些配置下,运放可以用于精密整流电路,以及作为反馈环路的一部分来实现稳压。
7. **信号转换**: - 运放可以将电压信号转换为电流信号,反之亦然,这对于驱动负载或与电流敏感设备接口很有用。
8. **频率和脉冲生成**: - 运算放大器可以用于产生特定频率的信号,如正弦波、方波或三角波,以及用于脉冲宽度调制(PWM)。
9. **数据转换**: - 在模数转换器(ADC)和数模转换器(DAC)中,运放通常用于信号的调节和校准。
由于运算放大器的灵活性和多功能性,它几乎存在于所有现代电子设备中,从简单的音频放大器到复杂的工业控制系统,再到高精度测量仪器,都有其身影。
二、引脚复用和重映射
复用功能
首先、我们可以这样去理解stm32引脚的复用功能。以stm32F103RCT6芯片引脚PA9、PA10为例。
这两个芯片引脚定义如下:
PA9引脚: PA9/USART1_TX/TIM1_CH2
PA10引脚:PA10/USART1_RX/TIM1_CH3
1、这里的PA9引脚和PA10引脚我们可以理解为引脚名,用于区分两个不同的引脚。
2、可以看到PA9引脚、PA10引脚都有三种功能。其中第一项PA9和PA10是其默认功能,默认功能为GPIO功能,也即是作为通用的输入输出端口使用。
3、这样我们就知道,当PA9引脚和PA10引脚不在作为默认的GPIO功能使用,而是作为USART1_TX/USART1_RX或者作为TIM1_CH2/TIM1_CH3功能使用时,就是对PA9引脚和PA10引脚的复用。
4、总而言之,对于stm32来说,由于其内部各种外设的存在,往往每个引脚都会有几种不同的功能,这几种不同的功能都可以使用这一个端口引脚。但是由于stm32的端口引脚都有一个自己的默认功能存在,当该引脚不在作为默认功能使用时对于该引脚来说就是复用。由于大多数引脚的默认功能和其引脚名称PA9引脚或者PA10引脚一样都是作为GPIO功能使用,因此当不在作为GPIO功能而是作为其他外设的相关功能使用时就是对引脚的复用。重映射功能
为了让工程师能够更好的安排布局及方便布线,在stm32中引入了外设引脚的重映射功能。即一个外设的引脚除了具有默认的引脚外还可以通过配置重映射寄存器的方式将这个外设的引脚映射到其他的引脚上去。
同样的以PA9引脚和PA10引脚为例,对于stm32F103RCT6芯片来说,有如下引脚定义:
PA9引脚: PA9/USART1_TX/TIM1_CH2
PA10引脚:PA10/USART1_RX/TIM1_CH3
PB6引脚: PB6/I2C1_SCL/TIM4_CH1/USART1_TX
PB7引脚: PB7/I2C1_SDA/FSMC_NADV/TIM4_CH2/USART1_RX1、首先、我们要明确一点,重映射的概念是对于芯片的各种外设本身来说的而非GPIO。因为引脚作为GPIO功能使用时一般是其默认的功能,而重映射的概念是建立在对引脚的复用功能上的。也即是当引脚复用为非GPIO功能时才可能会使用到重映射的功能。
2、USART1_REMAP=0表示没有使用重映射功能的情况;USART1_REMAP=1则表示使用重映射功能的情况。
3、从上面表中可以看到,默认情况下(没有使用重映射),USART1的TX和RX引脚默认使用的就是PA9引脚和PA10引脚。
4、在开启重映射功能时,USART1的TX和RX引脚还可以重映射到PB6和PB7引脚上去。