网站首页 > 博客文章 正文
1)实验平台:正点原子STM32mini开发板
2)摘自《正点原子STM32 不完全手册(HAL 库版)》关注官方微信号公众号,获取更多资料:正点原子
第二十二章 DAC 实验
上两章,我们介绍了 STM32 的 ADC 使用,本章我们将向大家介绍 STM32 的 DAC 功能。
在本章中,我们将利用按键(或 USMART)控制 STM32 内部 DAC 1 来输出电压,通过 ADC1
的通道 1 采集 DAC 的输出电压,在 LCD 模块上面显示 ADC 获取到的电压值以及 DAC 的设定
输出电压值等信息。本章将分为如下几个部分:
22.1 STM32 DAC 简介
22.2 硬件设计
22.3 软件设计
22.4 下载验证
22.1 STM32 DAC 简介
大容量的 STM32F103 具有内部 DAC,MiniSTM32 选择的是 STM32F103RCT6 属于大
容量产品,所以是带有 DAC 模块的。
STM32 的 DAC 模块(数字/模拟转换模块)是 12 位数字输入,电压输出型的 DAC。DAC
可以配置为 8 位或 12 位模式,也可以与 DMA 控制器配合使用。DAC 工作在 12 位模式时,
数据可以设置成左对齐或右对齐。DAC 模块有 2 个输出通道,每个通道都有单独的转换器。
在双 DAC 模式下,2 个通道可以独立地进行转换,也可以同时进行转换并同步地更新 2 个
通道的输出。
STM32 的 DAC 模块主要特点有:
① 2 个 DAC 转换器:每个转换器对应 1 个输出通道
② 8 位或者 12 位单调输出
③ 12 位模式下数据左对齐或者右对齐
④ 同步更新功能
⑤ 噪声波形生成
⑥ 三角波形生成
⑦ 双 DAC 通道同时或者分别转换
⑧ 每个通道都有 DMA 功能
单个 DAC 通道的框图如图 22.1.1 所示:
图中 VDDA 和 VSSA 为 DAC 模块模拟部分的供电,Vref+是参考电压输入引脚,不过
我们使用的 STM32F103RCT6,只有 64 引脚,没有 Vref 引脚,参考电压直接来自 VDDA,
也就是固定为 3.3V。DAC_OUTx 就是 DAC 的输出通道了(对应 PA4 或者 PA5 引脚)。
从图 22.1.1 可以看出,DAC 输出是受 DORx 寄存器直接控制的,但是我们不能直接往
DORx 寄存器写入数据,而是通过 DHRx 间接的传给 DORx 寄存器,实现对 DAC 输出的控
制。前面我们提到,STM32 的 DAC 支持 8/12 位模式,8 位模式的时候是固定的右对齐的,
而 12 位模式又可以设置左对齐/右对齐。单 DAC 通道 x,总共有 3 种情况:
① 8 位数据右对齐:用户将数据写入 DAC_DHR8Rx[7:0]位(实际是存入 DHRx[11:4]
位)。
② 12 位数据左对齐:用户将数据写入 DAC_DHR12Lx[15:4]位(实际是存入 DHRx[11:0]
位)。
③ 12 位数据右对齐:用户将数据写入DAC_DHR12Rx[11:0]位(实际是存入DHRx[11:0]
位)。
我们本章使用的就是单 DAC 通道 1,采用 12 位右对齐格式,所以采用第③种情况。
如果没有选中硬件触发(寄存器 DAC_CR1 的 TENx 位置’0’
),存入寄存器 DAC_DHRx
的数据会在一个 APB1 时钟周期后自动传至寄存器 DAC_DORx。如果选中硬件触发(寄存器
DAC_CR1 的 TENx 位置’1’),数据传输在触发发生以后 3 个 APB1 时钟周期后完成。 一
旦数据从 DAC_DHRx 寄存器装入 DAC_DORx 寄存器,在经过时间tSRTTING
之后,输出即有效,这段时间的长短依电源电压和模拟输出负载的不同会有所变化。我们可以从
STM32F103RCT6 的数据手册查到tSRTTING
的典型值为 3us,最大是 4us。所以 DAC 的转换
速度最快是 250K 左右。
本章我们将不使用硬件触发(TEN=0),其转换的时间框图如图 22.1.2 所示:
当 DAC 的参考电压为 Vref+的时候(对 STM32F103RC 来说就是 3.3V),DAC 的输出
电压是线性的从 0~Vref+,12 位模式下 DAC 输出电压与 Vref+以及 DORx 的计算公式如下:
DACx 输出电压=Vref*(DORx/4095)
接下来,我们介绍一下要实现 DAC 的通道 1 输出,需要用到的一些寄存器。首先是
DAC 控制寄存器 DAC_CR,该寄存器的各位描述如图 22.1.3 所示:
DAC_CR 的低 16 位用于控制通道 1,而高 16 位用于控制通道 2,我们这里仅列出比较
重要的最低 8 位的详细描述,如图 22.1.4 所示:
首先,我们来看 DAC 通道 1 使能位(EN1),该位用来控制 DAC 通道 1 使能的,本章我
们就是用的 DAC 通道 1,所以该位设置为 1。
再看关闭 DAC 通道 1 输出缓存控制位(BOFF1),这里 STM32 的 DAC 输出缓存做的
有些不好,如果使能的话,虽然输出能力强一点,但是输出没法到 0,这是个很严重的问题。
所以本章我们不使用输出缓存。即设置该位为 1。
DAC 通道 1 触发使能位(TEN1),该位用来控制是否使用触发,里我们不使用触发,
所以设置该位为 0。
DAC 通道 1 触发选择位(TSEL1[2:0]),这里我们没用到外部触发,所以设置这几个位
为 0 就行了。
DAC 通道 1 噪声/三角波生成使能位(WAVE1[1:0]),这里我们同样没用到波形发生器,
故也设置为 0 即可。
DAC 通道 1 屏蔽/复制选择器(MAMP[3:0]),这些位仅在使用了波形发生器的时候有
用,本章没有用到波形发生器,故设置为 0 就可以了。
最后是 DAC 通道 1 DMA 使能位(DMAEN1),本章我们没有用到 DMA 功能,故还是
设置为 0。
通道 2 的情况和通道 1 一模一样,这里就不不细说了。在 DAC_CR 设置好之后,DAC
就可以正常工作了,我们仅需要再设置 DAC 的数据保持寄存器的值,就可以在 DAC 输出
通道得到你想要的电压了(对应 IO 口设置为模拟输入)。本章,我们用的是 DAC 通道 1 的
12 位右对齐数据保持寄存器:DAC_DHR12R1,该寄存器各位描述如图 22.1.5 所示:
该寄存器用来设置 DAC 输出,通过写入 12 位数据到该寄存器,就可以在 DAC 输出通
道 1(PA4)得到我们所要的结果。
通过以上介绍,我们了解了 STM32 实现 DAC 输出的相关设置,本章我们将使用 DAC
模块的通道 1 来输出模拟电压。这里我们用到的库函数以及相关定义分布在文件
stm32f1xx_dac.c 以及头文件 stm32f1xx_dac.h 中。实现上面功能的详细设置步骤如下::
1)开启 PA 口时钟,设置 PA4 为模拟输入。
STM32F103ZET6 的 DAC 通道 1 是接在 PA4 上的,所以,我们先要使能 PORTA 的时
钟,然后设置 PA4 为模拟输入(虽然是输入,但是 STM32 内部会连接在 DAC 模拟输出上)。
程序如下:
__HAL_RCC_DAC_CLK_ENABLE();
//使能 DAC 时钟
__HAL_RCC_GPIOA_CLK_ENABLE();
//开启 GPIOA 时钟
GPIO_Initure.Pin=GPIO_PIN_4; //PA4
GPIO_Initure.Mode=GPIO_MODE_ANALOG; //模拟
GPIO_Initure.Pull=GPIO_NOPULL;
//不带上下拉
HAL_GPIO_Init(GPIOA,&GPIO_Initure);
2)初始化 DAC,设置 DAC 的工作模式。
HAL 库中提供了一个 DAC 初始化函数 HAL_DAC_Init,该函数声明如下:
HAL_StatusTypeDef HAL_DAC_Init(DAC_HandleTypeDef* hdac);
该函数并没有设置任何 DAC 相关寄存器,也就是说没有对 DAC 进行任何配置,它只
是 HAL 库提供用来在软件上初始化 DAC,也就是说,为后面 HAL 库操作 DAC 做好准备。
它有一个很重要的作用就是在函数内部会调用 DAC 的 MSP 初始化函数 HAL_DAC_MspInit,
该函数声明如下:
void HAL_DAC_MspInit(DAC_HandleTypeDef* hdac);
一般情况下,步骤 1 中的与 MCU 相关的时钟使能和 IO 口配置都放在该函数中实现。
HAL 库提供了一个很重要的 DAC 配置函数 HAL_DAC_ConfigChannel,该函数用来配
置 DAC 通道的触发类型以及输出缓冲。该函数声明如下:
HAL_StatusTypeDef HAL_DAC_ConfigChannel(DAC_HandleTypeDef* hdac,
DAC_ChannelConfTypeDef* sConfig, uint32_t Channel);
第一个入口参数非常简单,为 DAC 初始化句柄,和 HAL_DAC_Init 保存一致即可。
第三个入口参数 Channel 用来配置 DAC 通道,比如我们使用 PA4,也就是 DAC 通道 1,
所以配置值为 DAC_CHANNEL_1 即可。
接下来我们看看第二个入口参数 sConfig,该参数是 DAC_ChannelConfTypeDef 结构体
指针类型,结构体 DAC_ChannelConfTypeDef 定义如下:
typedef struct
{
uint32_t DAC_Trigger;
// DAC 触发类型
uint32_t DAC_OutputBuffer; //输出缓冲
}DAC_ChannelConfTypeDef;
成员变量DAC_Trigger 用来设置DAC 触发类型,DAC_OutputBuffer 用来设置输出缓冲,
这在我们前面都有讲解。DAC 初始化配置实例代码如下:
DAC_HandleTypeDef DAC1_Handler;
DAC_ChannelConfTypeDef DACCH1_Config;
DAC1_Handler.Instance=DAC;
HAL_DAC_Init(&DAC1_Handler); //初始化 DAC
DACCH1_Config.DAC_Trigger=DAC_TRIGGER_NONE; //不使用触发功能
DACCH1_Config.DAC_OutputBuffer=DAC_OUTPUTBUFFER_DISABLE;
HAL_DAC_ConfigChannel(&DAC1_Handler,&DACCH1_Config,DAC_CHANNEL_1);
3)使能 DAC 转换通道
初始化 DAC 之后,理所当然要使能 DAC 转换通道,HAL 库函数是:
HAL_StatusTypeDef HAL_DAC_Start(DAC_HandleTypeDef* hdac, uint32_t Channel);
该函数非常简单,第一个参数是 DAC 句柄,第二个用来设置 DAC 通道。
4)设置 DAC 的输出值。
通过前面 3 个步骤的设置,DAC 就可以开始工作了,我们使用 12 位右对齐数据格式,,
就可以在 DAC 输出引脚(PA4)得到不同的电压值了,HAL 库函数为:
HAL_StatusTypeDef HAL_DAC_SetValue(DAC_HandleTypeDef* hdac,
uint32_t Channel, uint32_t Alignment, uint32_t Data);
该函数从入口参数可以看出,它是配置 DAC 的通道输出值,同时通过第三个入口参数
设置对齐方式。
最后,再提醒一下大家,本例程,我们使用的是 3.3V 的参考电压,即 Vref+连接 VDDA。
通过以上几个步骤的设置,我们就能正常的使用 STM32 的 DAC 通道 1 来输出不同的模拟
电压了。
22.2 硬件设计
本章用到的硬件资源有:
1) 指示灯 DS0
2) WK_UP 和 KEY0 按键
3) 串口
4) TFTLCD 模块
5) ADC
6) DAC
本章,我们使用 DAC 通道 1 输出模拟电压,然后通过 ADC1 的通道 1 对该输出电压进行读取,并显示在 LCD 模块上面,DAC 的输出电压,我们通过按键(或 USMART)进行
设置。
我们需要用到 ADC 采集 DAC 的输出电压,所以需要在硬件上把他们短接起来。ADC
和 DAC 的连接原理图如图 22.2.1 所示:
如上图所示,我们只需要通过杜邦线将 PA4 和 PA1 连接起来。就可以了。
22.3 软件设计
打开本章实验工程可以发现,我们相比 ADC 实验,在库函数中主要是添加了 dac 支持
的相关文件 stm32f1xx_hal_dac.c 以及包含头文件 stm32f1xx_hal_dac.h。同时我们在
HARDWARE 分组下面新建了 dac.c 源文件以及包含对应的头文件 dac.h。这两个文件用来存
放我们编写的 ADC 相关函数和定义。打开 dac.c,代码如下:
DAC_HandleTypeDef DAC1_Handler;//DAC 句柄
//初始化 DAC
void DAC1_Init(void)
{
DAC_ChannelConfTypeDef DACCH1_Config;
DAC1_Handler.Instance=DAC;
HAL_DAC_Init(&DAC1_Handler); //初始化 DAC
DACCH1_Config.DAC_Trigger=DAC_TRIGGER_NONE; //不使用触发功能
DACCH1_Config.DAC_OutputBuffer=DAC_OUTPUTBUFFER_DISABLE;
//DAC1 输出缓冲关闭
HAL_DAC_ConfigChannel(&DAC1_Handler,&DACCH1_Config,
DAC_CHANNEL_1);//DAC 通道 1 配置
HAL_DAC_Start(&DAC1_Handler,DAC_CHANNEL_1); //开启 DAC 通道 1
}
//DAC 底层驱动,时钟配置,引脚 配置
//此函数会被 HAL_DAC_Init()调用
//hdac:DAC 句柄
void HAL_DAC_MspInit(DAC_HandleTypeDef* hdac)
{
GPIO_InitTypeDef GPIO_Initure;
__HAL_RCC_DAC_CLK_ENABLE(); //使能 DAC 时钟
__HAL_RCC_GPIOA_CLK_ENABLE();
//开启 GPIOA 时钟
GPIO_Initure.Pin=GPIO_PIN_4;
//PA4
GPIO_Initure.Mode=GPIO_MODE_ANALOG; //模拟
GPIO_Initure.Pull=GPIO_NOPULL;
//不带上下拉
HAL_GPIO_Init(GPIOA,&GPIO_Initure);
}
//设置通道 1 输出电压
//vol:0~3300,代表 0~3.3V
void DAC1_Set_Vol(u16 vol)
{
double temp=vol;
temp/=1000;
temp=temp*4096/3.3;
HAL_DAC_SetValue(&DAC1_Handler,DAC_CHANNEL_1,
DAC_ALIGN_12B_R,temp);//12 位右对齐数据格式设置 DAC 值
}
此部分代码就 2 个函数,Dac1_Init 函数用于初始化 DAC 通道 1。这里基本上是按我们
上面的步骤来初始化的,我们用序号①~⑤已经标示这些步骤。经过这个初始化之后,我们
就可以正常使用 DAC 通道 1 了。第二个函数 Dac1_Set_Vol,用于设置 DAC 通道 1 的输出
电压,实际就是将电压值转换为 DAC 输入值。
其他头文件代码就比较简单,这里我们不做过多讲解,接下来我们来看看主函数代码:
int main(void)
{
u16 adcx;
float temp;
u8 t=0;
u16 dacval=0;
u8 key;
HAL_Init();
//初始化 HAL 库
Stm32_Clock_Init(RCC_PLL_MUL9); //设置时钟,72M
delay_init(72);
//初始化延时函数
uart_init(115200);
//初始化串口
usmart_dev.init(84);
//初始化 USMART
LED_Init();
//初始化 LED
KEY_Init();
//初始化按键
LCD_Init();
//初始化 LCD FSMC 接口
MY_ADC_Init();
//初始化 ADC1 通道 1
DAC1_Init();
//初始化 DAC1
POINT_COLOR=RED;
LCD_ShowString(30,50,200,16,16,"Mini STM32");
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,"2019/11/15");
LCD_ShowString(30,130,200,16,16,"WK_UP:+ KEY1:-");
POINT_COLOR=BLUE;//设置字体为蓝色
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");
HAL_DAC_SetValue(&DAC1_Handler,DAC_CHANNEL_1,DAC_ALIGN_12B_R,0);
//初始值为 0
while(1)
{
t++;
key=KEY_Scan(0);
if(key==WKUP_PRES)
{
if(dacval<4000)dacval+=200;
HAL_DAC_SetValue(&DAC1_Handler,DAC_CHANNEL_1,
DAC_ALIGN_12B_R,dacval);//设置 DAC 值
}else if(key==2)
{
if(dacval>200)dacval-=200;
else dacval=0;
HAL_DAC_SetValue(&DAC1_Handler,DAC_CHANNEL_1,
DAC_ALIGN_12B_R,dacval);//设置 DAC 值
}
if(t==10||key==KEY1_PRES||key==WKUP_PRES)
//WKUP/KEY1 按下了,或者定时时间到了
{
adcx=HAL_DAC_GetValue(&DAC1_Handler,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_1,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);
}
}
此部分代码,我们先对需要用到的模块进行初始化,然后显示一些提示信息,本章我们
通过 KEY_UP(WKUP 按键)和 KEY1(也就是上下键)来实现对 DAC 输出的幅值控制。
按下 KEY_UP 增加,按 KEY1 减小。同时在 LCD 上面显示 DHR12R1 寄存器的值、DAC
设计输出电压以及 ADC 采集到的 DAC 输出电压。
本章,我们还可以利用 USMART 来设置 DAC 的输出电压值,故需要将 Dac1_Set_Vol
函数加入 USMART 控制,方法前面已经有详细的介绍了,大家这里自行添加,或者直接查
看我们光盘的源码。
从 main 函数代码可以看出,按键设置输出电压的时候,每次都是以 0.161V 递增或递减
的,而通过 USMART 调用 Dac1_Set_Vol 函数,则可以实现任意电平输出控制(当然得在
DAC 可控范围内)。
22.4 下载验证
在代码编译成功之后,我们通过下载代码到 ALIENTEK MiniSTM32 开发板上,可以看
到 LCD 显示如图 22.4.1 所示:
同时伴随 DS0 的不停闪烁,提示程序在运行。此时,我们通过按 WK_UP 按键,可以
看到输出电压增大,按 KEY0 则变小。
大家可以试试在 USMART 调用 Dac1_Set_Vol 函数,来设置 DAC 通道 1 的输出电压,
如图 22.4.2 所示:
猜你喜欢
- 2024-09-09 国产鼠标新标杆 钛度智能游戏鼠标拆解
- 2024-09-09 STM32学习,从点灯开始(stm32f103点灯程序教程)
- 2024-09-09 学嵌入式STM32系列芯片应该怎么选择,都有什么区别
- 2024-09-09 STM32单片机详细教学(二):STM32系列单片机的介绍
- 2024-09-09 STM32F103资料下载和介绍(stm32f103zet6资料)
- 2024-09-09 「正点原子Linux连载」第十七章GPIO中断试验(一)
- 2024-09-09 基于STM32的温度控制系统(含代码)
- 2024-09-09 STM32F103 串口的使用方法(stm32f103串口1)
- 2024-09-09 你知道怎么用STM32控制舵机吗?(stm32f407控制舵机)
- 2024-09-09 (STM32F103)神操作——如何快速读写内部flash?
你 发表评论:
欢迎- 最近发表
- 标签列表
-
- powershellfor (55)
- messagesource (56)
- aspose.pdf破解版 (56)
- promise.race (63)
- 2019cad序列号和密钥激活码 (62)
- window.performance (66)
- qt删除文件夹 (72)
- mysqlcaching_sha2_password (64)
- ubuntu升级gcc (58)
- nacos启动失败 (64)
- ssh-add (70)
- jwt漏洞 (58)
- macos14下载 (58)
- yarnnode (62)
- abstractqueuedsynchronizer (64)
- source~/.bashrc没有那个文件或目录 (65)
- springboot整合activiti工作流 (70)
- jmeter插件下载 (61)
- 抓包分析 (60)
- idea创建mavenweb项目 (65)
- vue回到顶部 (57)
- qcombobox样式表 (68)
- vue数组concat (56)
- tomcatundertow (58)
- pastemac (61)
本文暂时没有评论,来添加一个吧(●'◡'●)