对于高职教师来说,必不可少的一个任务就是参加企业实践。这个暑假,本人也没闲着,报名参加了上海市电子信息类教师企业实践。7月8日到13日,有幸来到美丽的泉城济南,远离了上海的酷暑,走进了百科荣创科技发展有限公司。在这短短的一周时间里,我结合自己的教学经验和企业的需求,对一个简易函数信号发生器的嵌入式项目做了教学内容的转换。接下来,就用博客的形式,把这次转换后的成果分享出来,如果您觉得有用,还望多多点赞和转发!
针对本项目,笔者根据自己的理解,力求做到循序渐进和逐步深入,计划用6篇文章来展开,本文是第二篇,我们先来聊聊STM32的DAC,并做一个基本输出的实验。
二、STM32的DAC
2.1 DAC简介
DAC为数字/模拟转换模块,顾名思义,它的作用就是把输入的数字编码,转换成对应的模拟电压输出,它的功能与ADC相反。在常见的数字信号系统中,大部分传感器信号被化成电压信号,而ADC把电压模拟信号转换成易于计算机存储、处理的数字编码,由计算机处理完成后,再由DAC输出电压模拟信号,该电压模拟信号常常用来驱动某些执行器件,使人类易于感知。如音频信号的采集及还原就是这样一个过程。
2.2 STM32的DAC功能概览
STM32具有片上DAC外设,它的分辨率可配置为8位或12位的数字输入信号,具有两个DAC输出通道,这两个通道互不影响,每个通道都可以使用DMA功能,都具有出错检测能力,可外部触发。图4是STM32的DAC功能框图。
整个DAC模块围绕框图下方的“数字至模拟转换器x”展开,它的左边分别是参考电源的引脚:VDDA、VSSA及VREF+,其中STM32的DAC规定了它的参考电压输入范围为2.4~3.3V。“数字至模拟转换器x”的输入为DAC的数据寄存器“DORx”的数字编码,经过它转换得的模拟信号由图中右侧的“DAC_OUTx”输出。而数据寄存器“DORx”又受“控制逻辑”支配,它可以控制数据寄存器加入一些伪噪声信号或配置产生三角波信号。
图中的左上角为DAC的触发源,DAC根据触发源的信号来进行DAC转换,其作用就相当于DAC转换器的开关,它可以配置的触发源为外部中断源触发、定时器触发或软件控制触发。如本项目实验中需要控制三角波和正弦波的频率,就需要定时器定时触发DAC进行数据转换。
2.3 参考电压
与ADC外设类似,DAC也使用VREF+ 引脚作为参考电压,在设计原理图的时候一般把VSSA 接地,把VREF+和VDDA接3.3V,可得到DAC的输出电压范围为:0~3.3V。如果想让输出的电压范围变宽,可以在外部加一个电压调理电路,把0~3.3V的DAC输出抬升到特定的范围即可。
2.4 输出通道
框图中的“数字至模拟转换器x”是核心部件,整个DAC外设都围绕它而展开。它以左边的VREF+作为参考电源,以DAC的数据寄存器“DORx”的数字编码作为输入,经过它转换得的模拟信号由右侧的“DAC_OUTx”通道输出。其中各个部件中的“x”是指设备的标号,在STM32中具有2个这样的DAC部件,每个DAC有1个对应的输出通道连接到特定的引脚(PA4-通道1,PA5-通道2)。为避免干扰,使用DAC功能时,DAC通道引脚需要被配置成模拟输入功能(AIN)。
2.5 触发源及DHRx寄存器
在使用DAC时,不能直接对上述DORx寄存器写入数据,任何输出到DAC通道x的数据都必须写入到DHRx寄存器中(其中包含DHR8Rx、DHR12Lx等,根据数据对齐方向和分辨率的情况写入到对应的寄存器中)。
数据被写入到DHRx寄存器后,DAC会根据触发配置进行处理,若使用硬件触发,则DHRx中的数据会在3个APB1时钟周期后传输至DORx,DORx随之输出相应的模拟电压到输出通道;若DAC设置为外部事件触发,可以使用定时器(TIMx_TRGO)、EXTI_9信号或软件触发(SWTRIGx)这几种方式控制数据DAC转换的时机,例如使用定时器触发,配合不同时刻的DHRx数据,可实现DAC输出三角波和正弦波的功能。
2.6 DAC初始化结构体详解
在ST的标准库中,把控制DAC相关的各种配置封装到了结构体 DAC_InitTypeDef 中,该结构体的主要成员见代码清单1。
//---------------------------------------------
// 代码清单1:DAC_InitTypeDef结构体
//---------------------------------------------
typedef struct {
//DAC触发方式
uint32_t DAC_Trigger;
//是否自动输出噪声或三角波
uint32_t DAC_WaveGeneration;
//选择噪声生成器的低通滤波或三角波的幅值
uint32_t DAC_LFSRUnmask_TriangleAmplitude;
//选择是否使能输出缓冲器
uint32_t DAC_OutputBuffer;
} DAC_InitTypeDef;
(1)DAC_Trigger
本成员用于配置DAC的触发模式,当DAC产生相应的触发事件时,才会把DHRx寄存器的值转移到DORx寄存器中进行转换。本结构体成员可以选择的触发模式如下:
- 硬件触发模式(DAC_Trigger_None),DHRx寄存器内的数据会在3个APB1时钟周期内自动转换至DORx进行转换;
- 定时器触发模式(DAC_Trigger_T2/4/5/6/7_TRGO),使用定时器2、4、5、6、7控制DHRx寄存器的数据按时间转移到DORx中进行转换,利用这种方式可以输出特定的波形;
- EXTI_9 触发方式(DAC_Trigger_Ext_IT9),当产生EXTI_9 事件时(如GPIO中断事件),触发转换;
- 软件触发模式(DAC_Trigger_Software),在本模式下,向DAC_SWTRIGR寄存器写入配置即可触发信号进行转换。
(2)DAC_WaveGeneration
本成员用于设置是否使用DAC输出伪噪声或三角波:
- DAC_WaveGeneration_None/Noise/Triangle:使用伪噪声和三角波输出时,DAC都会把LFSR寄存器的值叠加到DHRx数值上,产生伪噪声和三角波,若希望产生自定义的输出时,直接配置为DAC_WaveGeneration_None即可。
(3)DAC_LFSRUnmask_TriangleAmplitude
- 当使用伪噪声或三角波输出时要叠加到 DHRx 的值,非噪声或三角波输出模式下,本配置无效。
- 使用伪噪声输出时,把本结构体成员赋值为DAC_LFSRUnmask_Bit0~DAC_LFSRUnmask_Bit11_0 等宏即可;
- 使用三角波输出时,本结构体设置三角波的最大幅值,可选择为DAC_TriangleAmplitude_1~DAC_TriangleAmplitude_4096 等宏,如图5所示,DAC在DHRx值的基础上升,达到最大幅度时下降,形成三角波的输出。
(4)DAC_OutputBuffer
本结构体成员用于控制是否使能 DAC 的输出缓冲(
DAC_OutputBuffer_Enable/Disable),使能了 DAC 的输出缓冲后可以减小输出阻抗,适合直接驱动一些外部负载。
2.7 与DAC相关的库函数
代码清单2里是几个与DAC有关的库函数,下面简要介绍一下这几个函数:
- DAC_Cmd() 函数用来使能DAC转换通道;
- DAC开始工作后,我们就可以使用 DAC_SetChannel1Data() 函数在DAC的输出通道1(PA4引脚)得到不同的电压值了。第一个参数设置对齐方式,可以为12位右对齐 DAC_Align_12b_R、12位左对齐 DAC_Align_12b_L 以及8位右对齐 DAC_Align_8b_R 方式。第二个参数就是DAC的输入值了,这个很好理解,初始可以设置为0。
- DAC_GetDataOutputValue() 函数看名字也很好理解,是获取DAC指定通道上的值。注意,读到的值是最近一次转换的值。
//--------------------------------------
// 代码清单2:几个常用的DAC库函数
//--------------------------------------
//使能DAC通道1
DAC_Cmd(DAC_Channel_1, ENABLE);
//12位右对齐数据格式设置DAC值
DAC_SetChannel1Data(DAC_Align_12b_R, 0);
//读出DAC通道1最后一次转换的数值
DAC_GetDataOutputValue(DAC_Channel_1);
三、DAC基本输出
我们先来做一个DAC的基本输出实验,使用DAC通道1输出模拟电压,然后通过万用表对该输出电压进行测量,看是否符合我们设定的值。此外,还可以通过按键对该输出电压进行设置。
3.1 工程文件清单
如图6所示,在按键的基础工程上新增针对DAC的驱动文件 dac.c 和 dac.h,在 main.c 文件里编写主控制逻辑。
3.2 工程代码剖析
(1)dac.h源码剖析
该文件源码见代码清单3,主要是DAC初始化和功能函数的声明,每个函数的功能和参数将在剖析 dac.c 源码时解读。
//---------------------------------------------------
// 代码清单3:dac.h
//---------------------------------------------------
#ifndef _DAC_H_
#define _DAC_H_
#include "sys.h"
//---------------------------------------------------
// 函数声明
//---------------------------------------------------
void DAC1_Init(void); //DAC通道1初始化
void DAC1_Set_Vol(uint16_t vol); //设置DAC通道1输出电压
#endif
(2)dac.c源码剖析
该文件就是所有DAC驱动函数的定义,如代码清单4所示。
/**
************************************************************************
* 代码清单4:dac.c
* 描 述:DAC初始化、驱动
* 平 台:百科荣创STM32F407核心板
* 作 者:老耿
* 日 期:2024-7-10
* 固 件 库:ST3.5.0
* 版 本:V1.0
* 说 明:
* 修改记录:无
************************************************************************
**/
//必要的头文件
#include "dac.h"
/**
************************************************************************
* 函 数 名:DAC1_Init
* 功 能:DAC通道1的配置
* 入口参数:无
* 出口参数:无
* 说 明:DAC通道1对应PA4引脚,注意要配置成模拟输入
************************************************************************
**/
void DAC1_Init(void)
{
//定义必要的初始化结构体
GPIO_InitTypeDef gpio_initstruct;
DAC_InitTypeDef dac_initstruct;
//打开GPIOA和DAC的外设时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);
//PA4初始化为模拟输入
gpio_initstruct.GPIO_Pin = GPIO_Pin_4;
gpio_initstruct.GPIO_Mode = GPIO_Mode_AN;
gpio_initstruct.GPIO_PuPd = GPIO_PuPd_DOWN;
GPIO_Init(GPIOA, &gpio_initstruct);
//不使用触发功能
dac_initstruct.DAC_Trigger = DAC_Trigger_None;
//不使用波形发生
dac_initstruct.DAC_WaveGeneration = DAC_WaveGeneration_None;
//屏蔽、幅值设置
dac_initstruct.DAC_LFSRUnmask_TriangleAmplitude = DAC_LFSRUnmask_Bit0;
//DAC1输出缓存关闭
dac_initstruct.DAC_OutputBuffer = DAC_OutputBuffer_Disable;
//初始化DAC通道1
DAC_Init(DAC_Channel_1,&dac_initstruct);
//使能DAC通道1
DAC_Cmd(DAC_Channel_1, ENABLE);
//12位右对齐数据格式设置DAC值
DAC_SetChannel1Data(DAC_Align_12b_R, 0);
}
/**
************************************************************************
* 函 数 名:DAC1_Set_Vol
* 功 能:设置通道1输出电压
* 入口参数:vol --- 0~3300,代表0~3.3V
* 出口参数:无
* 说 明:无
************************************************************************
**/
void DAC1_Set_Vol(uint16_t vol)
{
double temp = vol;
temp /= 1000;
temp = temp*4096/3.3;
DAC_SetChannel1Data(DAC_Align_12b_R, temp);//12位右对齐数据格式设置DAC值
}
(3)main.c源码剖析
主程序比较简单,如代码清单5所示,在完成必要的初始化之后,检测到S4按下,就累加DAC的值,检测到S3按下,就递减DAC的值。
/******************************************************
* 代码清单5:main.c
* 项 目:DAC基本输出
* 任务描述:使用DAC通道1输出模拟电压,然后通过万用表测量
* 该输出电压进行测量,看是否符合我们设定的值。
* 此外,还可以通过按键对该输出电压进行设置。
* 实验平台:百科荣创STM32F407开发板
* 作 者:老耿
* 日 期:2024/07/10
******************************************************/
//------------------------------------------------------
// 必要的头文件
//------------------------------------------------------
#include "sys.h"
#include "delay.h"
#include "dac.h"
#include "key.h"
//------------------------------------------------------
// 主函数
//------------------------------------------------------
int main(void)
{
uint16_t dacval = 0; //存放DAC值
uint8_t key; //存放键值
//设置系统中断优先级分组2
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
Delay_Init(); //延时初始化
Key_Init(); //按键初始化
DAC1_Init(); //DAC通道1初始化
//设置DAC初始值为0
DAC_SetChannel1Data(DAC_Align_12b_R, dacval);
while(1)
{
key = Key_Scan(0); //检测按下的键
if(key == 4) //如果按下S4
{ //DAC值累加200,约0.16V
if(dacval < 4000)
dacval += 200;
DAC_SetChannel1Data(DAC_Align_12b_R, dacval);
}
else if(key == 3) //如果按下S3
{ //DAC值递减200
if(dacval > 200)
dacval -= 200;
else
dacval = 0;
DAC_SetChannel1Data(DAC_Align_12b_R, dacval);
}
}
}
3.3 验证与测试
将万用表按照图7所示去测量DAC的输出电压,每单击一次S4按键,DAC值增加200,换算成电压值即:200*3.3/4096≈0.161V。同理,每单击一次S3按键,DAC值减少200。具体效果见文末的演示视频。
简易信号发生器项目的前奏,先搞定DAC基本输出
(第二部分完,共六部分)