STM32F103VET6双通道DAC实战:如何用串口实时调节PA4/PA5电压(附完整代码)
在嵌入式开发中,尤其是涉及模拟信号生成、闭环控制或者可编程电源等场景时,能够通过数字方式精确、实时地控制输出电压是一项非常核心的能力。STM32F103VET6这款经典的Cortex-M3内核MCU,内置了两个12位的数模转换器(DAC),分别对应PA4和PA5引脚。很多开发者初次接触时,可能会直接使用库函数进行简单输出,但一旦需求升级到“通过串口指令动态、独立地调节两路电压”,就会遇到一系列工程上的“坑”:寄存器配置的微妙差异、DMA通道的独立性问题、触发源的冲突,以及如何构建一个稳定、响应及时的软件架构。
网上能找到的资料,要么是单通道的简单例程,要么是双通道输出固定波形(如正弦波),恰恰缺少了“实时、独立、可控”这个工程实践中最关键的一环。本文将从一个完整的、可立即移植到项目中的工程角度出发,为你拆解如何实现STM32F103VET6双通道DAC的串口实时调压。我们会绕过那些泛泛而谈的理论,直击配置陷阱、分享调试心得,并提供一套经过验证的、模块化的代码方案。无论你是在做电机驱动测试、传感器模拟信号发生,还是需要动态偏置电压,这篇文章都能为你提供一个坚实的起点。
1. 理解核心挑战:为何双通道独立实时控制不简单
在开始动手写代码之前,我们有必要先厘清STM32F103的DAC模块在双通道工作模式下的几个特殊设计。这些设计正是导致我们不能简单套用单通道模式来实现双通道独立控制的原因。
首先,STM32F103的DAC有两个独立的转换通道(Channel1对应PA4,Channel2对应PA5),每个通道都有自己独立的数据保持寄存器。这是实现独立输出的硬件基础。但是,手册里也提供了一个名为DAC_DHR12RD的“双DAC 12位右对齐数据保持寄存器”。这个寄存器允许你一次性写入两个通道的数据,但它设计用于需要严格同步输出的场景,比如生成立体声音频。如果你的目标是让两个通道输出不同且独立变化的电压,那么必须避开这个“双寄存器”,转而使用每个通道专属的单通道数据寄存器,即DAC_DHR12R1(通道1)和DAC_DHR12R2(通道2)。
其次,为了实现“实时”更新,我们通常不希望CPU频繁地通过软件去写DAC数据寄存器,这会占用大量CPU资源。更优雅的方式是使用DMA(直接存储器访问) 配合定时器触发。DAC可以在每次定时器事件到来时,自动将DMA传输过来的新数据加载到其数据寄存器并开始转换。这里的关键在于:两个DAC通道有各自独立的DMA请求。通道1的DMA请求映射到DMA2的通道3,而通道2的DMA请求映射到DMA2的通道4。这意味着你需要为两个通道分别配置独立的DMA流。
最后,关于触发源。DAC支持多种触发方式,包括定时器触发、外部中断触发等。为了实现周期性(哪怕周期极短)的自动更新,我们选择定时器触发。这里有一个容易踩坑的地方:两个DAC通道可以共用同一个定时器作为触发源吗? 理论上可以,但如果你希望两个通道的更新时刻可以独立配置(例如不同的更新速率),或者避免潜在的时序冲突,更稳妥的做法是为每个通道分配一个独立的定时器。在资源紧张的STM32F103上,我们可以使用TIM2和TIM4等通用定时器。
注意:确保你使用的定时器支持触发输出(TRGO)功能。STM32F103的TIM2、TIM3、TIM4、TIM5等高级/通用定时器都支持此功能。
理解了这三点,我们的软件架构就清晰了:
- 为DAC通道1和通道2分别初始化GPIO和DAC外设本身。
- 为每个通道配置一个独立的定时器(如TIM2和TIM4),并设置好更新频率。
- 为每个通道配置其对应的DMA通道(DMA2_Channel3和DMA2_Channel4),将内存中的数据缓冲区与DAC的数据寄存器连接起来。
- 编写一个函数,当串口收到新指令时,只需更新内存缓冲区中的数据,DMA和定时器会自动在下一个触发周期将新电压值输出。
2. 硬件与工程环境准备
在深入代码细节前,确保你的硬件和软件环境已就绪。
硬件清单:
- 主控:STM32F103VET6核心板或开发板。
- 输出引脚:PA4 (DAC1_OUT1) 和 PA5 (DAC1_OUT2)。务必确认你的板子没有将这些引脚用于其他功能(如LED、按键)。
- 测量工具:万用表或示波器,用于验证输出电压是否准确。
- 串口工具:USB转TTL模块,连接MCU的USART1(PA9/PA10)到电脑,用于发送控制指令。
- 电源:稳定的3.3V供电。DAC的输出电压范围是0~VDDA(通常接3.3V),参考电压VREF+也必须接VDDA。
软件环境:
- 开发IDE:Keil MDK-ARM, STM32CubeIDE, 或 IAR Embedded Workbench 均可。本文代码示例基于标准外设库(Standard Peripheral Library),这也是很多现有项目的基础。
- 串口助手:PC端任意一款串口调试助手(如SecureCRT, Putty, 或国产的XCOM),用于发送指令。
工程初始化: 创建一个新的工程,选择正确的芯片型号(STM32F103VE),并初始化必要的系统时钟(通常配置为72MHz)。确保工程中包含了以下关键的外设库文件:
stm32f10x_gpio.cstm32f10x_dac.cstm32f10x_tim.cstm32f10x_dma.cstm32f10x_usart.c
接下来,我们将分模块构建整个系统。
3. 核心驱动模块:DAC、定时器与DMA的独立配置
这是整个系统的底层硬件驱动层。我们将创建bsp_dac.c和bsp_dac.h文件。为了避免与原文的代码结构雷同,我们采用更模块化的设计,并增加详细的配置说明。
3.1 头文件定义与地址映射
在bsp_dac.h中,我们明确定义两个通道数据寄存器的地址,这是实现独立控制的第一步。
#ifndef __BSP_DAC_H
#define __BSP_DAC_H
#include "stm32f10x.h"
/* 双通道DAC独立数据寄存器地址 (12位右对齐) */
#define DAC_DHR12R1_ADDR (DAC_BASE + 0x08) // 通道1独立数据寄存器
#define DAC_DHR12R2_ADDR (DAC_BASE + 0x14) /

&spm=1001.2101.3001.5002&articleId=150480809&d=1&t=3&u=324d520ded2e44edabe1189ce44611a6)
341

被折叠的 条评论
为什么被折叠?



