基于战舰V3开发板的DAC输出实验
DAC原理解析
DAC外设工作逻辑框图
DACx通道与GPIO引脚的对应关系
DAC_OUT1
PA4
DAC_OUT2
PA5
DAC转换原理
不能直接对寄存器DAC_DORx写入数据,任何输出到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之后,输出即有效,这段时间的长短依电源电压和模拟输出负载的不同会有所变化。
可以触发DAC的外部事件
如果TENx位被置1,DAC转换可以由某外部事件触发(定时器计数器、外部中断线)。配置控制位 TSELx[2:0]可以选择8个触发事件之一触发DAC转换。
每次DAC接口侦测到来自选中的定时器TRGO输出,或者外部中断线9的上升沿,最近存放在寄存器DAC_DHRx中的数据会被传送到寄存器DAC_DORx中。在3个APB1时钟周期后,寄存器DAC_DORx更新为新值。(为什么是3个APB1周期,详见“DAC转换原理”部分)
如果选择软件触发,一旦SWTRIG位置’1’,转换即开始。在数据从DAC_DHRx寄存器传送到DAC_DORx寄存器后,SWTRIG位由硬件自动清’0’。
注意:
① 不能在DAC通道使能时改变TSELx[2:0]位,即DAC工作过程中不可以改变触发源;
② 如果选择软件触发,数据从寄存器DAC_DHRx传送到寄存器DAC_DORx只需要一个APB1时钟周期。
DAC中模拟量与数字量的关系
疑难问题
将GPIO配置为模拟输入模式后,GPIO相应引脚属性的变化
当I/O端口被配置为模拟输入配置时:
● 输出缓冲器被禁止;
● 禁止施密特触发输入,实现了每个模拟I/O引脚上的零消耗。施密特触发输出值被强置
为’0’;
● 弱上拉和下拉电阻被禁止;
● 读取输入数据寄存器时数值为’0’。
下图示出了I/O端口位的高阻抗模拟输入配置:
为什么将GPIO配置为模拟输入模式?
我们看到模拟输入模式中信号行走的路线上,没有用于“数字信号处理的施密特触发器”和“输入数据寄存器”,并且此时的上下拉电阻已经被关闭。
这个施密特触发器被关闭了,GPIO的数字输入功能被取消。如果此时读取输入数据寄存器的值,结果恒为0。由于关闭了施密特触发器,上面提到的因它而起的电平跳变噪声和相应的额外功耗就没有了。
换句话说,当GPIO状态由浮空或上下拉输入状态改为模拟输入状态时,既消除了因为施密特触发器带来的噪声,同时又因它的关闭而降低了芯片动态功耗。
另外,配置在模拟输入状态的GPIO引脚属于高阻态,这点也有利于保持模拟信号的真实性(说白了,就是信号传输通道无任何信号处理装置,可以将原汁原味的模拟信号在CPU与外设之间进行传输)。
DAC应该是输出模拟信号,但为什么要配置为“模拟输入”?
因为一但使能 DACx 通道之后,相应的 GPIO 引脚(PA4 或者 PA5)会自动与 DAC 的模拟输出相连。其实,模拟输入通道与模拟输出通道是一起的,当我们将我们所需的引脚配置为“GPIO的模拟输入模式”后,系统会根据我们所使能的DAC/ADC外设自动进行通道类型的选择。
DAC的毛刺现象是什么?
http://www.elecfans.com/analog/20150723377582.html
请问stm32中的ADC的触发模式分别是在什么情况下用的?
如果想周期性的采集信号,那就用定时器触发;如果想不定时任意时刻的采集信号,那就用软件触发;如果想让外部信号在特定情况下被采集,那就用外部触发。
DAC库函数配置流程
① 使能相应外设的时钟源
RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
② 设置GPIO口模式(根据外设的特性进行设置)
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
③ DAC外设属性的初始化
DAC_InitStructure.DAC_LFSRUnmask_TriangleAmplitude = DAC_LFSRUnmask_Bit0; // 幅值设置
DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Disable; // 输出缓冲器是否使能
DAC_InitStructure.DAC_Trigger = DAC_Trigger_None; // 是否有事件触发DAC工作
DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None; // 是否生成噪声波/三角波
DAC_Init(DAC_Channel_1,&DAC_InitStructure);
④ DAC外设使能并且基于DAC_Channelx一个确定的电压值
DAC_Cmd(DAC_Align_12b_R,ENABLE);
DAC_SetChannel1Data(DAC_Align_12b_R,0);
单通道DAC一般操作的库函数配置
Dac.c
#include "dac.h"
#include "sys.h"
#include "stm32f10x.h"
void DAC_InitConfig()
{
GPIO_InitTypeDef GPIO_InitStructure;
DAC_InitTypeDef DAC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC,ENABLE);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
DAC_InitStructure.DAC_LFSRUnmask_TriangleAmplitude = DAC_LFSRUnmask_Bit0;
DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Disable;
DAC_InitStructure.DAC_Trigger = DAC_Trigger_None;
DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None;
DAC_Init(DAC_Channel_1,&DAC_InitStructure);
DAC_Cmd(DAC_Channel_1,ENABLE);
DAC_SetChannel1Data(DAC_Align_12b_R,0);
}
void DAC_SetDigitalValue(float VoltageValue)
{
u16 DigitalValue = 0;
DigitalValue = VoltageValue/3.3*(4096-1);
DAC_SetChannel1Data(DAC_Align_12b_R,DigitalValue);
}
Dac.h
#ifndef _DAC_H
#define _DAC_H
void DAC_InitConfig();
void DAC_SetDigitalValue(float VoltageValue);
#endif
Adc.c
#include "adc.h"
#include "sys.h"
#include "stm32f10x.h"
void ADC_InitConfig()
{
GPIO_InitTypeDef GPIO_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_ADC1,ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6); // ADC工作频率必须低于14MHz,此时我们将72MHz/6=12MHz
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
ADC_DeInit(ADC1); // 将ADC1外设复位为缺省值
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_NbrOfChannel = 1;
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
ADC_Init(ADC1,&ADC_InitStructure);
ADC_Cmd(ADC1,ENABLE);
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1) == SET); // 复位ADC1校准功能标志清除轮询结束
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1) == SET); // 初始化ADC1校准功能标志清除轮询结束
}
u16 ADC_GetDigitalValue()
{
ADC_RegularChannelConfig(ADC1,ADC_Channel_1,1,ADC_SampleTime_28Cycles5); // 单个规则通道配置
ADC_SoftwareStartConvCmd(ADC1,ENABLE);
while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC) == RESET); // ADC1转换标志置位轮询结束
return ADC_GetConversionValue(ADC1);
}
Adc.h
#ifndef _ADC_H
#define _ADC_H
#include "sys.h"
void ADC_InitConfig();
u16 ADC_GetDigitalValue();
#endif
设备配置
我们将PA1(ADC1_Channel_1)与PA4(DAC_Channel_1)用跳线帽连接起来,DAC与ADC的功能如下:
DAC_Channel_1
将数字量转换为模拟量输出到PA4端口上
ADC1_Channel_1
读取PA1端口上的模拟量并且转换为数字量
“定时器中断+DAC“实现简易正弦波信号发生器
设备配置介绍
定时器
在间隔相等的时间内更新DAC转换所需的数字电压值(电压值范围为[0,4095])
DAC外设
在主函数中用while(1)不断轮询实时将数字电压值转换为模拟量进行输出
LED外设
为了凸显DAC输出信号在不断变化,我们将“实际数字量/数字量的峰值”作为PWM的占空比,将PWM输出至PB5端口上对小灯进行实施亮度调节
Main.c
#include "dac.h"
#include "adc.h"
#include "timer.h"
#include "led.h"
#include "usart.h"
#include "delay.h"
#include "stm32f10x.h"
int main()
{
u16 temp = 0;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
LED_InitConfig();
TIMER_InitConfig();
DAC_InitConfig();
ADC_InitConfig();
delay_init();
uart_init(115200);
while(1)
{
DAC_SetDigitalValue();
delay_ms(50);
temp = ADC_GetDigitalValue();
printf("数字量为%d\n",temp);
}
}
在主函数中,我们主要是通过轮询的方式,将更新的数字量通过DAC外设准换为模拟量输出至指定端口上。
Timer.c
#include "stm32f10x.h"
#include "sys.h"
#include "timer.h"
#include "delay.h"
#include "math.h"
#include "led.h"
u16 DigitalValue = 0;
u16 Peak = 1000;
u16 Cycle = 1000;
extern float LightLevel;
void TIMER_InitConfig()
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period = 1000;
TIM_TimeBaseInitStructure.TIM_Prescaler = 0; // 更新时间为13.89us
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
TIM_Cmd(TIM2,ENABLE);
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
LED_InitConfig();
delay_init();
}
void TIM2_IRQHandler()
{
static u16 counter = 0;
counter++;
counter %= Cycle;
if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET)
{
DigitalValue = ceil(((double)Peak)*sin(((double)counter)/((double)Cycle)*3.14));
LightLevel = ((float)DigitalValue)/((float)Peak);
}
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
在定时器配置中,我们主要是在定时器溢出中断时,对此时输出的数字量进行更新。
Timer.h
#ifndef _TIMER_H
#define _TIMER_H
#include "sys.h"
void TIMER_InitConfig();
u16 CycleCalculate();
#endif
Dac.h
#ifndef _DAC_H
#define _DAC_H
#include "sys.h"
void DAC_InitConfig();
void DAC_SetDigitalValue();
#endif
Dac.c
#include "stm32f10x.h"
#include "sys.h"
#include "dac.h"
#include "timer.h"
extern u16 DigitalValue;
void DAC_InitConfig()
{
GPIO_InitTypeDef GPIO_InitStructure;
DAC_InitTypeDef DAC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
DAC_InitStructure.DAC_LFSRUnmask_TriangleAmplitude = DAC_LFSRUnmask_Bit0;
DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Disable;
DAC_InitStructure.DAC_Trigger = DAC_Trigger_None;
DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None;
DAC_Init(DAC_Channel_1,&DAC_InitStructure);
DAC_Cmd(DAC_Align_12b_R,ENABLE);
DAC_SetChannel1Data(DAC_Align_12b_R,0);
}
void DAC_SetDigitalValue()
{
DigitalValue = DigitalValue>=4095?4095:DigitalValue;
DAC_SetChannel1Data(DAC_Align_12b_R,DigitalValue);
}
Adc.h
#ifndef _ADC_H
#define _ADC_H
#include "sys.h"
void ADC_InitConfig();
u16 ADC_GetDigitalValue();
#endif
Adc.c
#include "adc.h"
#include "sys.h"
#include "stm32f10x.h"
void ADC_InitConfig()
{
GPIO_InitTypeDef GPIO_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_ADC1,ENABLE);
ADC_DeInit(ADC1);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_NbrOfChannel = 1;
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
ADC_Init(ADC1,&ADC_InitStructure);
ADC_Cmd(ADC1,ENABLE);
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1) == SET);
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1) == SET);
}
u16 ADC_GetDigitalValue()
{
ADC_RegularChannelConfig(ADC1,ADC_Channel_1,1,ADC_SampleTime_28Cycles5);
ADC_SoftwareStartConvCmd(ADC1,ENABLE);
while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC) == RESET);
return ADC_GetConversionValue(ADC1);
}