STM32F103(二十)DAC

Posted 自信且爱笑‘

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了STM32F103(二十)DAC相关的知识,希望对你有一定的参考价值。

学习板:STM32F103ZET6

DAC

一、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=1TIM_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的主要内容,如果未能解决你的问题,请参考以下文章

STM32F103的DAC——实现音频输出

STM32F103VET6基于STM32CubeMX 配置DAC-三角波输出示例

STM32F103VET6基于HAL库和标准库下DAC 心形波形输出

STM32F103(二十一)DMA(超详细的~)

STM32F103(二十四)一篇博客精通《485通信》

STM32F103(二十四)一篇博客精通《485通信》