1. 从需求到方案:为什么双通道DAC独立控制是个技术活?
大家好,我是老李,一个在嵌入式领域摸爬滚打了十多年的工程师。最近在做一个工业控制的小项目,需要让STM32同时输出两路独立的、电压值可以随时调整的模拟信号。听起来很简单对吧?不就是用DAC嘛。但当我真正动手时,发现网上关于STM32双通道DAC独立输出、并且能通过串口实时调节的资料,要么语焉不详,要么就是直接甩给你一个输出固定波形(比如正弦波)的例程,完全不符合我的需求。
我的场景是这样的:假设我在做一个精密温控系统,一路DAC输出控制加热功率(PA4),另一路输出控制冷却风扇转速(PA5)。上位机软件会根据实时温度,通过串口发送指令,要求我动态调整这两路的电压。这就要求两个通道必须能独立、实时、精确地响应命令。如果你直接用ST官方库或者常见开发板的例程,大概率会发现,它们配置双通道DAC时,经常使用一个叫 DAC_DHR12RD 的“双通道共用寄存器”。这个寄存器一次写入,会同时更新两个通道的数据,导致PA4和PA5输出永远是一样的电压,这显然不行。
所以,这个“实战”要解决的核心矛盾就是:如何打破双通道DAC的“连体”设定,让PA4和PA5真正分家,各听各的命令? 这背后涉及到对DAC寄存器结构的深入理解、DMA通道的独立配置,以及触发机制的隔离。我折腾了好一阵,反复看数据手册和野火的范例,终于搞明白了。今天我就把自己踩过的坑和最终的解决方案,掰开揉碎了讲给你听,保证你跟着做一遍就能搞定。
2. 核心原理拆解:寄存器、DMA与触发器的“三角关系”
要想实现独立控制,我们得先弄清楚STM32的DAC是怎么工作的。很多人调不通,就是因为没理清这三者之间的关系。
2.1 关键第一步:给每个DAC通道“上户口”
这是最容易被忽略,也最关键的一步。在STM32的参考手册里,DAC每个通道其实都有自己专属的数据保持寄存器。对于12位右对齐的数据格式:
- 通道1(对应PA4) 的寄存器是
DHR12R1。 - 通道2(对应PA5) 的寄存器是
DHR12R2。
但是,很多库函数和例程为了方便输出同步的双通道波形(比如立体声音频),默认使用了一个“打包”寄存器 DHR12RD。向这个寄存器写入一个32位数,高16位给通道2,低16位给通道1,硬件会同时更新两个通道。这效率很高,但牺牲了独立性。
所以,我们的首要任务就是“分家”。我们不能再用那个打包的地址,必须显式地使用每个通道自己的寄存器地址。在代码里,我们需要像下面这样重新定义:
// 这是DAC外设的基地址,在stm32f10x.h中定义
#define DAC_BASE 0x40007400
// 重新定义每个通道独立的12位右对齐数据保持寄存器地址
#define DAC_DHR12R1_ADDRESS (DAC_BASE + 0x08)
#define DAC_DHR12R2_ADDRESS (DAC_BASE + 0x14)
0x08 和 0x14 这两个偏移量,就是直接从参考手册的存储器映射表里查出来的。做了这一步,我们才算给PA4和PA5各自建立了一条独立的“数据输送专线”。
2.2 DMA配置:为每条专线配备专属“快递员”
光有地址还不够,我们需要一种高效、不占用CPU的方式来把我们要输出的数字量(比如,想输出2.0V,对应数字量约为2481)搬运到刚才定义的寄存器里。这就是DMA(直接存储器访问)的用武之地。
你可以把DMA想象成两个不知疲倦的快递员(DMA通道)。我们需要为每个DAC通道配置一个独立的DMA通道:
- 通道1(PA4) 通常使用 DMA2 通道3。
- 通道2(PA5) 通常使用 DMA2 通道4。
配置时,核心是告诉每个“快递员”三件事:
- 你要把货送到哪里去? (
DMA_PeripheralBaseAddr):这里就填我们刚才重新定义的DAC_DHR12R1_ADDRESS或DAC_DHR12R2_ADDRESS。 - 你去哪里取货? (
DMA_MemoryBaseAddr):指向一个我们定义在内存中的变量,比如Voltage_Ch1_Buffer。 - 一次搬多少货? (
DMA_BufferSize):对于我们实时控制电压的场景,每次只更新一个值,所以这里设置为 1。
这里有个非常重要的细节:DMA的传输数据宽度。DAC的数据寄存器是12位的,但STM32的DMA传输通常以字(32位)或半字(16位)为单位。为了兼容和避免对齐问题,我们通常配置为“字”传输。这意味着,我们放在内存变量里的数据,需要是一个32位的数,并且要把12位的DAC值放在这个32位数的低16位内。后面在代码部分我会展示一个非常巧妙的赋值技巧。
2.3 定时器触发:给“快递员”下达出发指令
DMA配置好了,但它什么时候开始搬运呢?我们需要一个“触发器”。DAC支持多种触发方式,包括软件触发和定时器触发。为了实现稳定、周期性的更新(尽管我们可能每秒才改一次电压,但硬件机制需要周期性触发),我们一般使用定时器触发。
这里又有一个关键点:两个DAC通道最好使用不同的定时器作为触发源。比如,通道1用TIM2的TRGO事件触发,通道2用TIM4的TRGO事件触发。为什么要用两个定时器?主要是为了隔离。如果两个通道共用同一个定时器,虽然硬件上可能支持,但在一些复杂的时序场景下,可能会产生意想不到的干扰。用两个定时器,各自独立计时,各自触发自己的DMA,是最稳妥、最清晰的方案。
两个定


490

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



