STM32CubeMX教程16 DAC - 输出3.3V内任意电压
读者可访问 GitHub - lc-guo/STM32CubeMX-Series-Tutorial 获取原始工程代码
1、准备材料
STM32CubeMX软件(Version 6.10.0)
keil µVision5 IDE(MDK-Arm)
2、实验目标
使用STM32CubeMX软件配置STM32F407开发板的DAC OUT1实现输出0-3.3V任意模拟电压,然后用ADC1_IN5单通道采集DAC输出的电压,并利用USART1输出信息用于验证
3、实验流程
3.0、前提知识
STM32F407有一个DAC,该DAC拥有两个输出通道OUT1/2,每个通道均可以输出0~VREF+范围内电压、噪声波或三角波型
DAC集成了两个输出缓冲器,可用来降低输出阻抗并在不增加外部运算放大器的情况下直接驱动外部负载,该参数可以在STM32CubeMX中DAC参数配置页面配置,一般选择Enable
DAC输出的触发源一共有8个触发源,可以通过设置DAC控制寄存器DAC_CR的TSEL[2:0]位来决定触发源,其中外部引脚触发源在STM32CubeMX中需要勾选Mode中的External Trigger才可以选择,具体如下表所示 (注释1)

DAC的数字转模拟主要是利用片上的12位电压输出数模转换器来实现的,而这个12位电压输出数模转换器的输入数据为数据输出寄存器DORx中的内容,但是用户不能直接将数据写入数据输出寄存器DORx中,而是需要将数据输入数据保持寄存器DHRx中,然后等待触发源到来/一个时钟周期后,数据将自动从DHRx中转移到DORx中
由于DHRx寄存器位32位寄存器,而我们写入的数据为8/12位的,因此存在数据对齐的问题,采用不同的对齐方式需要将数据写入对应对齐方式的数据保存寄存器中,如下图所示为DAC单/双通道模式下的数据对齐模式,每种模式对应1/2个寄存器(注释1)


举个例子:
本实验采取DAC1单通道模式12位右对齐,因此笔者需要将数据写入 DAC_DHR12R1 寄存器中,而该寄存器的偏址从手册上可以看到为0x80
我们写入DAC寄存器数据时使用的函数为HAL_DAC_SetValue(&hdac,DAC_CHANNEL_1,DAC_ALIGN_12B_R,DacValue),其中DAC_ALIGN_12B_R值为0,因此最后将DacValue值写入了地址为DAC基址+0x00000008UL偏址的 DAC_DHR12R1 寄存器,上述描述如下图所示


DAC输出引脚输出的电压值由DACoutput = VREF+ * DOR / 4095公式计算,通常 VREF+直接与VDDA短接,因此DAC通道输出的电压范围为0-3.3V,如下图所示为DAC通道框图 (注释1)

3.1、CubeMX相关配置
3.1.0、工程基本配置
打开STM32CubeMX软件,单击ACCESS TO MCU SELECTOR选择开发板MCU(选择你使用开发板的主控MCU型号),选中MCU型号后单击页面右上角Start Project开始工程,具体如下图所示


开始工程之后在配置主页面System Core/RCC中配置HSE/LSE晶振,在System Core/SYS中配置Debug模式,具体如下图所示


详细工程建立内容读者可以阅读“STM32CubeMX教程1 工程建立”
3.1.1、时钟树配置
系统时钟使用8MHz外部高速时钟HSE,HCLK、PCLK1和PCLK2均设置为STM32F407能达到的最高时钟频率,具体如下图所示

3.1.2、外设参数配置
本实验需要需要初始化开发板上KEY2和KEY0用户按键,具体配置步骤请阅读“STM32CubeMX教程3 GPIO输入 - 按键响应”
本实验需要需要初始化USART1作为输出信息渠道,具体配置步骤请阅读“STM32CubeMX教程9 USART/UART 异步通信”
本实验需要配置TIM3 100ms更新事件作为ADC1_IN5通道采集触发源的ADC采集,因此需要初始化TIM3和ADC1_IN5,具体配置步骤请阅读“STM32CubeMX教程13 ADC - 单通道转换”,如下图所示为配置简图


接下来配置DAC OUT1,在Pinout & Configuration页面左边功能分类栏目Analog中单击其中DAC,在Mode中勾选OUT1 Configuration
在DAC Out1 Settings中使能 Output Buffer , Trigger 选择默认None,这里不需要触发源,也就是说当DAC启动后DAC就会一直输出下去而不是在每次一触发源来到的时候才输出,具体配置如下图所示

3.1.3、外设中断配置
DAC只有两个关于DMA的下溢事件的中断源,本实验尚不设计DMA,因此这里无需开启DAC的任何中断
但是DAC的输出电压需要由ADC1_IN5来采集,因此这里勾选ADC的全局中断,并设置合适的中断优先级,具体配置如下图所示

3.2、生成代码
3.2.0、配置Project Manager页面
单击进入Project Manager页面,在左边Project分栏中修改工程名称、工程目录和工具链,然后在Code Generator中勾选“Gnerate peripheral initialization as a pair of 'c/h' files per peripheral”,最后单击页面右上角GENERATE CODE生成工程,具体如下图所示


详细Project Manager配置内容读者可以阅读“STM32CubeMX教程1 工程建立”实验3.4.3小节
3.2.1、外设初始化调用流程
在生成的工程代码主函数中增加了MX_DAC_Init()函数,该函数对启用的DAC触发方式、输出缓存进行了配置
然后调用HAL_DAC_Init()函数对DAC进行了初始化,并调用了HAL_DAC_MspInit()函数
在HAL_DAC_MspInit()函数中对DAC OUT1的输出引脚PA4做了引脚复用配置,并且使能了DAC的时钟,如果配置了中断,在该函数中还会出现中断优先级及中断使能相关代码
上述DAC初始化调用流程如下图所示

3.2.2、外设中断调用流程
本实验只开启了ADC的全局中断,ADC全局中断调用流程请阅读“STM32CubeMX教程13 ADC - 单通道转换”4.2.2小节
3.2.3、添加其他必要代码
在adc.c中重新实现ADC采集完毕中断回调函数HAL_ADC_ConvCpltCallback(),与”STM32CubeMX教程13 ADC - 单通道转换”实验内容一致,目的就是获取ADC1_IN5通道采集值并通过USART1输出,具体代码如下所示
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) { if(hadc->Instance == ADC1) { uint32_t val=HAL_ADC_GetValue(&hadc1); uint32_t Volt=(3300*val)>>12; printf("val:%d, Volt:%d\r\n",val,Volt); } }
在主函数中启动DAC输出,并设置默认的DAC输出值,然后启动定时器和ADC采集,并在主循环中实现按下按键KEY2将DAC输出值增加500,按下按键KEY0将DAC输出值减少500,具体代码如下图所示

源代码如下所示
/*主循环外代码*/ printf("Reset\r\n"); HAL_DAC_Start(&hdac,DAC_CHANNEL_1); uint32_t DacValue=1000; HAL_DAC_SetValue(&hdac,DAC_CHANNEL_1,DAC_ALIGN_12B_R,DacValue); HAL_ADC_Start_IT(&hadc1); HAL_TIM_Base_Start(&htim3); /*主循环中代码*/ /*按键KEY2被按下*/ if(HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin) == GPIO_PIN_RESET) { HAL_Delay(50); if(HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin) == GPIO_PIN_RESET) { printf("---now DacValue is: %d---\r\n", DacValue); DacValue += 500; if(DacValue>4095) DacValue=4095; HAL_DAC_SetValue(&hdac,DAC_CHANNEL_1,DAC_ALIGN_12B_R,DacValue); while(!HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin)); } } /*按键KEY0被按下*/ if(HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin) == GPIO_PIN_RESET) { HAL_Delay(50); if(HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin) == GPIO_PIN_RESET) { printf("---now DacValue is: %d---\r\n", DacValue); DacValue -= 500; HAL_DAC_SetValue(&hdac,DAC_CHANNEL_1,DAC_ALIGN_12B_R,DacValue); while(!HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin)); } }
值得提醒的是本实验使用DAC1_IN5(PA5)采集DAC OUT1(PA4)输出值,需要使用跳线帽/杜邦线将PA5和PA4两个引脚短接
4、常用函数
/*DAC软件启动输出函数*/ HAL_StatusTypeDef HAL_DAC_Start(DAC_HandleTypeDef *hdac, uint32_t Channel) /*DAC软件停止输出函数*/ HAL_StatusTypeDef HAL_DAC_Stop(DAC_HandleTypeDef *hdac, uint32_t Channel) /*DAC输出值设置*/ HAL_StatusTypeDef HAL_DAC_SetValue(DAC_HandleTypeDef *hdac, uint32_t Channel, uint32_t Alignment, uint32_t Data)
5、烧录验证
烧录程序,开发板上电后打开串口助手,可以看到间隔100ms输出一次采集到的DAC OU1输出值,默认输出值为1000,按下KEY2按键后输出值增加500,按下KEY0按键后输出值减少500,可以从串口输出信息看到采集到的值和我们预想的效果一致,如下图所示为整个过程串口输出信息

6、注释详解
注释1:图片来源STM32F4xx 中文参考手册
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 全网最简单!3分钟用满血DeepSeek R1开发一款AI智能客服,零代码轻松接入微信、公众号、小程