STM32F103(二十)DAC
Posted 自信且爱笑‘
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了STM32F103(二十)DAC相关的知识,希望对你有一定的参考价值。
学习板:STM32F103ZET6
DAC
- 一、DAC
- 二、DAC相关寄存器
- 1、 DAC控制寄存器(DAC_CR)
- 2、DAC软件触发寄存器(DAC_SWTRIGR)
- 3、 DAC通道 1 的 12 位右对齐数据保持寄存器(DAC_DHR12R1)
- 4、 DAC通道 1 的 12 位左对齐数据保持寄存器(DAC_DHR12L1)
- 5、DAC通道 1 的 8 位右对齐数据保持寄存器(DAC_DHR8R1)
- 6、DAC通道 2 的 12 位右对齐数据保持寄存器(DAC_DHR12R2)
- 7、DAC通道 2 的 12 位左对齐数据保持寄存器(DAC_DHR12L2)
- 8、DAC通道 2 的 8 位右对齐数据保持寄存器(DAC_DHR8R2)
- 9、双DAC的 12 位右对齐数据保持寄存器(DAC_DHR12RD)
- 10、双DAC的 12 位左对齐数据保持寄存器(DAC_DHR12LD)
- 11、双DAC的 8 位右对齐数据保持寄存器(DAC_DHR8RD)
- 12、DAC通道 1 数据输出寄存器(DAC_DOR1)
- 13、DAC通道 2 数据输出寄存器(DAC_DOR2)
- 三、DAC相关库函数
- 1、DAC取消初始化函数DAC_DeInit()
- 2、DAC初始化函数DAC_Init()
- 3、结构体初始化函数DAC_StructInit()
- 4、DAC使能函数DAC_Cmd()
- 5、DMA使能函数DAC_DMACmd()
- 6、软件触发使能函数DAC_SoftwareTriggerCmd()
- 7、两通道软件触发使能函数DAC_DualSoftwareTriggerCmd()
- 8、噪声波或三角波使能函数DAC_WaveGenerationCmd()
- 9、设置通道1输出数据函数DAC_SetChannel1Data()
- 10、设置通道2输出数据函数DAC_SetChannel2Data()
- 11、双通道模式下设置输入数据函数DAC_SetDualChannelData()
- 12、获取DAC转换结果函数DAC_GetDataOutputValue()
- 四、DAC编程顺序
- 五、例1——输出一个三角波
一、DAC
1、简介
只有大容量STM32F1中含有内部DAC,是12位数字输入,电压输出型的DAC。在F1中有俩个DAC转换器:
可以设置8位或12位模式,其中12位模式下可设置数据左对齐或右对齐;在双DAC模式下可以并行转换并输出。
2、 DAC 通道模块框图
上图中:
1:触发条件,有软件触发、定时器内部信号触发、外部中断线9触发
2:DAC的CR寄存器
3:CR寄存器设置的DMA请求使能
4:DAC通道触发使能,由CR寄存器设置
5:设置三角波时的幅值,由CR寄存器设置
6:使能噪声波输出或使能三角波输出使能,由CR寄存器设置
7:DHRx寄存器,12位左右对齐寄存器、8位寄存器。将需要数字to模拟的值存储在这个寄存器中,然后再自动转入到DORx寄存器,再传送到数字to模拟转换器中转换。
8:DORx寄存器,直接将数据传送到数字to模拟转换器中,但是不能对它直接操作,需要使用DHRx寄存器间接操作。
9:STM32F1内部DAC的供电、参考电压,一般VDDA=3.3V; VVSS=0; VREF+=VDDA=3.3V,
10:DAC输出
3、DAC时钟
DAC的时钟挂载在APB1外设下
打开APB1时钟使能函数:
二、DAC相关寄存器
1、 DAC控制寄存器(DAC_CR)
位0
该位使能DAC1通道
位1
该位使能或失能DAC通道1输出缓存,使用输出缓存后输出结果会有偏差,如缓冲输出后结果不能达到0。所以要使用输出缓冲,需要在算法上做一些补偿。一般情况下不使用输出缓存。
位2:5
位2选择是否使能触发,位5:3选择哪种触发方式,有TIM6、8、7、5、2、4的定时器内部信号、外部中断线9、软件触发。
可以位2置1使能触发,然后位5:3选择触发方式,最常见的触发方式是软件触发。
当然许多时候可以不用触发,位2直接值0(或者不设置),此时不需要触发,直接将DAC_DHRx的数据转入DAC_DOR1。
强调一下:DORx寄存器直接控制数字to模拟转换器x,而不能对DORx寄存器直接赋值,需要对DHRx寄存器赋值后,再转入到DORx寄存器。
位7:6
使能噪声波发生器、三角波发生器
噪声波发生,必须使能DAC触发,即本寄存器的位2必须置1!
位11:8
这4位来设置三角波的幅值的。当DHRx寄存器设置有一个值时,这个值会传入到DORx寄存器中,如果这个值比位11:8设置的幅值小,则从DORx寄存器的这个值开始累加,,每次触发事件后3个时钟周期累加一次。当这个值大于定义的幅值时,开始递减到0,再开始累加…循环递增递减。
三角波发生,必须使能DAC触发,即本寄存器的位2必须置1!
三角波发生,本寄存器的位11:8必须先设置再使能DAC!
位12
使能DMA
剩下的高位设置DAC2,与DAC1设置一样,不在赘述。
2、DAC软件触发寄存器(DAC_SWTRIGR)
该寄存器只有位0和位1有效,位0设置DAC1软件触发,位1设置DAC2软件触发。与CR寄存器的位21:29=111、位5:3=111功能一样,当旦寄存器DAC_DHRx的数据传入寄存器DAC_DORx后,该寄存器的对应位硬件置0,关闭对应通道的软件触发。
3、 DAC通道 1 的 12 位右对齐数据保持寄存器(DAC_DHR12R1)
12位数据右对齐模式时由该寄存器写入数据到DHR1寄存器,再转入DOR1寄存器。
4、 DAC通道 1 的 12 位左对齐数据保持寄存器(DAC_DHR12L1)
12位数据左对齐模式时由该寄存器写入数据到DHR1寄存器,再转入DOR1寄存器。
5、DAC通道 1 的 8 位右对齐数据保持寄存器(DAC_DHR8R1)
8位数据模式时,不存在左右对齐,由该寄存器写入数据到DHR1寄存器,再转入DOR1寄存器。
6、DAC通道 2 的 12 位右对齐数据保持寄存器(DAC_DHR12R2)
DAC2 12位数据右对齐模式时由该寄存器写入数据到DHR2寄存器,再转入DOR2寄存器。
7、DAC通道 2 的 12 位左对齐数据保持寄存器(DAC_DHR12L2)
DAC2 12位数据左对齐模式时由该寄存器写入数据到DHR2寄存器,再转入DOR2寄存器。
8、DAC通道 2 的 8 位右对齐数据保持寄存器(DAC_DHR8R2)
DAC2 8位数据模式时,不存在左右对齐,由该寄存器写入数据到DHR2寄存器,再转入DOR2寄存器。
单DAC:
双DAC:
9、双DAC的 12 位右对齐数据保持寄存器(DAC_DHR12RD)
位11:0存储DAC1的数据,位27:16存储DAC2的数据
10、双DAC的 12 位左对齐数据保持寄存器(DAC_DHR12LD)
位15:4存储DAC1的数据,位31:20存储DAC2的数据
11、双DAC的 8 位右对齐数据保持寄存器(DAC_DHR8RD)
位7:0存储DAC1的数据,位15:8存储DAC2的数据
12、DAC通道 1 数据输出寄存器(DAC_DOR1)
该寄存器储存DAC1通道转换结果数据,为12位有效寄存器,故这个转换结果在0~4095
13、DAC通道 2 数据输出寄存器(DAC_DOR2)
该寄存器储存DAC2通道转换结果数据,为12位有效寄存器,故这个转换结果在0~4095
三、DAC相关库函数
DAC的相关库函数定义在《stm32f10x_dac.h》中
1、DAC取消初始化函数DAC_DeInit()
可以看到,该函数只是将DAC的时钟失能。
2、DAC初始化函数DAC_Init()
参数1: DAC_Channel
这个函数写的不完美,加下面这句代码就好了:
assert_param(IS_DAC_CHANNEL(DAC_Channel));
参数2: DAC_InitStruct
结构体类型,看一下成员变量:
①DAC_Trigger
选择触发方式:不触发、TIM6、8、7、5、2、4定时器内部信号触发、外部中断线触发、软件触发
②DAC_WaveGeneration
使能噪声波、三角波
③DAC_LFSRUnmask_TriangleAmplitude
设置屏蔽位或幅值。
当第二个参数DAC_WaveGeneration为噪声时,选择上图前面的参数,如选择DAC_LFSRUnmask_Bits4_0参数,表示不屏蔽LSFR位[4:0] 。
当第二个参数DAC_WaveGeneration为三角波时,选择上图中后面的camshaft。如选择DAC_TriangleAmplitude_1023表示三角波的幅值为1023
当然细心的话,就注意一下最后这个地方:
这里的0x00000B00并不固定,可以是B~F的值。
④DAC_OutputBuffer
该参数选择是否输出缓冲,之前也总结过,输出缓冲时导致结果会有一个偏差,所以一般情况下不使用缓冲。
3、结构体初始化函数DAC_StructInit()
将2中DAC_Init()函数的参数2的结构体初始化,初始化为:
不触发、无噪声和三角波、不屏蔽LSFR位0 或三角波幅值等于1、允许输出缓冲。
4、DAC使能函数DAC_Cmd()
参数1:
哪个通道:
参数2:
ENABLE、DISABLE
5、DMA使能函数DAC_DMACmd()
参数1:
哪个通道
参数2:
ENABLE、DISABLE
6、软件触发使能函数DAC_SoftwareTriggerCmd()
注意是对SWTRIGR寄存器的操作,不是CR寄存器对触发条件的设置。
参数1:
哪个通道
参数2:
ENABLE、DISABLE
7、两通道软件触发使能函数DAC_DualSoftwareTriggerCmd()
也是对SWTRIGR寄存器的设置,不过不同的是,同时设置位0和位1,所以DAC1和DAC2同时设置为触发或不触发。
8、噪声波或三角波使能函数DAC_WaveGenerationCmd()
参数1:
选择哪个通道
参数2:
选择噪声波还是三角波
参数3:
ENABLE或DISABLE
9、设置通道1输出数据函数DAC_SetChannel1Data()
参数1:
选择12位左对齐、12位右对齐、8位数
参数2:
输入的12位或8位数
10、设置通道2输出数据函数DAC_SetChannel2Data()
参数1:
选择12位左对齐、12位右对齐、8位数
参数2:
输入的12位或8位数
11、双通道模式下设置输入数据函数DAC_SetDualChannelData()
参数1:
选择12位左对齐、12位右对齐、8位数
参数2:
通道2的输入值
参数3:
通道1的输入值
注意:通道2的数据写前面(高位)、通道1的数据写后面(低位)
12、获取DAC转换结果函数DAC_GetDataOutputValue()
参数:
哪个通道
返回值:
返回一个16位数
四、DAC编程顺序
1、使能PA4、PA5时钟,并设置为模拟输入
DAC1与DAC2分别在PA4与PA5引脚上,需要使能GPIOA的时钟。
同时为了避免寄生的干扰和额外的功耗,引脚PA4或者PA5在应当设置成模拟输入
2、使能DAC时钟
DAC挂载在APB1上,故用RCC_APB2PeriphClockCmd()函数使能时钟
3、初始化DAC
使用 DAC_Init()函数设置触发方式、噪声波或三角波或无、噪声波是的屏蔽位或三角波时的幅值、是否输出缓冲
4、使能DAC转换通道
使用DAC_Cmd()函数使能DAC转换通道
5、设置DAC的输入值
使用DAC_SetChannelxData()(x=1、2)设置DAC的输出值
6、说明
以上五个步骤是一般情况下需要设置的,实际应用中需要根据实际情况来设置,如设置DMA、是否需要读取通道转换值等等。
五、例1——输出一个三角波
1、方法一(直接输出法)
直接用DAC_SetChannel1Data()
输出即可。在DAC初始化时,可以不设置触发和三角波
(1)完整代码
完整代码:
dac…h代码:
#ifndef _DAC_
#define _DAC_
void dac_Init(void);
#endif
dac.c代码:
#include "stm32f10x.h"
#include "dac.h"
void dac_Init()
{
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_Trigger=DAC_Trigger_None;
DAC_InitStructure.DAC_WaveGeneration=DAC_WaveGeneration_None;
//DAC_InitStructure.DAC_LFSRUnmask_TriangleAmplitude=DAC_TriangleAmplitude_1;
DAC_InitStructure.DAC_OutputBuffer=DAC_OutputBuffer_Disable;
DAC_Init(DAC_Channel_1,&DAC_InitStructure);
DAC_Cmd(DAC_Channel_1,ENABLE);
}
main.c代码:
#include "stm32f10x.h"
#include "dac.h"
u16 temp=0;
int main(void)
{
dac_Init();
while(1)
{
if(temp==0)
{
while(++temp<4096)
DAC_SetChannel1Data(DAC_Align_12b_R,temp);
}
if(temp>=4095)
{
while(--temp>0)
DAC_SetChannel1Data(DAC_Align_12b_R,temp);
}
}
}
(2)效果
(3)幅值和频率改变
如果需要调节幅值,只需改变主函数中4096、4095的值,如果需要改变频率,可以设置temp每次+2、-2…
2、方法2(定时器法)
(1)完整代码
timer.h代码:
#ifndef _TIM2_
#define _TIM2_
void TIM2_Init();
#endif
timer.c代码:
#include "timer.h"
#include "stm32f10x.h"
void TIM2_Init()
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period=1;
TIM_TimeBaseInitStructure.TIM_Prescaler=2;
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
TIM_SelectOutputTrigger(TIM2, TIM_TRGOSource_Update);
TIM_Cmd(TIM2,ENABLE);
}
dac.h代码:
#ifndef _DAC_
#define _DAC_
void dac_Init(void);
#endif
dac.c代码:
#include "stm32f10x.h"
#include "dac.h"
void dac_Init()
{
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_Trigger=DAC_Trigger_T2_TRGO;
DAC_InitStructure.DAC_WaveGeneration=DAC_WaveGeneration_Triangle;
DAC_InitStructure.DAC_LFSRUnmask_TriangleAmplitude=DAC_TriangleAmplitude_4095;
DAC_InitStructure.DAC_OutputBuffer=DAC_OutputBuffer_Disable;
DAC_Init(DAC_Channel_1,&DAC_InitStructure);
DAC_SetChannel1Data(DAC_Align_12b_R, 4095);
DAC_Cmd(DAC_Channel_1,ENABLE);
}
main.c代码:
#include "stm32f10x.h"
#include "dac.h"
#include "timer.h"
u16 temp=0;
int main(void)
{
TIM2_Init();
dac_Init();
while(1)
{
}
}
(2)效果
(3)分析——频率改变方案
本例使用定时器2内部信号触发,定时器2挂载在APB1总线下,APB1时钟默认为36MHZ,而DAC的时钟也是36MHZ。
然后理解下图的话:
即三角波每输出一个点的时间:定时器2触发一次时间+3个APB1时钟周期。
所以定时器设置重装载值和预分频系数时:
若将预分频系数设置为:TIM_Prescaler=1
,则定时器每计数一次为一个APB1时钟,若重装载值也设置为:TIM_Period=1
则定时器每发生一次触发时间为1个APB1时钟周期。此时三角波每+1的时间为:1+3=4个APB1时钟周期。
同理:若TIM_Prescaler=1
且TIM_Period=5
,则定时器每发生一次触发时间为5个APB1时钟周期。此时三角波每+1的时间为:5+3=8个APB1时钟周期。
以上是(通用)定时器预分频系数和重装载值对输出三角波形频率的影响。
还有一个影响输出频率的因素是通道设置的初值,即上图中所说的:与DAC_DHRx寄存器的数值
这个值使用以下这行代码设置
DAC_SetChannel1Data(DAC_Align_12b_R, 100);
这个值就是初值,即:这个值+三角波累加值(之前所说的4个APB1时钟周期+1)与4096比较,(其实是与初始化dac函数的DAC_InitStructure.DAC_LFSRUnmask_TriangleAmplitude=DAC_TriangleAmplitude_4095;
设置的幅值比较)
当超过4096时,开始递减,没超过时递增。所以:
DAC_SetChannel1Data(DAC_Align_12b_R, 100);
上面这个行代码也影响着频率。
如当:
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period=1;
TIM_TimeBaseInitStructure.TIM_Prescaler=1;
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
即三角波每输出一个点的时间为4个APB1时钟周期;
再当:
此时三角波输出的内部计数器从0计数到3995时,就已经达到上限4095,开始从3995递减到0 。此时一个三角波周期内,三角波内部计数器计数次数:0~3995 ~0 。为3995×2=7990次。
共用时间:7990×4=31960个APB1时钟周期。则输出频率为:36000000÷31960≈1.126kHZ
综上,调节以下俩个地方可以调节频率。
幅值的调节为下图位置:
如改为:
此时幅值变为一半:
以上是关于STM32F103(二十)DAC的主要内容,如果未能解决你的问题,请参考以下文章
STM32F103VET6基于STM32CubeMX 配置DAC-三角波输出示例