news 2026/8/25 13:15:48

STM32 DAC+DMA+定时器触发生成任意波形:数据对齐陷阱、输出缓冲器选型与频率精度实测

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32 DAC+DMA+定时器触发生成任意波形:数据对齐陷阱、输出缓冲器选型与频率精度实测

文章目录

    • 一、从一个"会动但不对"的波形说起
    • 二、核心原理:三件事如何协同
      • 2.1 整体数据流
      • 2.2 三个频率的定量关系(最容易算错的点)
      • 2.3 设计决策:为什么不用"定时器中断"方案
    • 三、硬件接线与 CubeMX 配置
      • 3.1 硬件清单与接线
      • 3.2 时钟树配置
      • 3.3 DAC 配置
      • 3.4 定时器 TIM6 配置
      • 3.5 DMA 配置
    • 四、两个最容易翻车的坑:对齐方式与缓冲器
      • 4.1 失败路径复盘:数据宽度不匹配导致的"阶梯波"
      • 4.2 设计决策:输出缓冲器开还是关
    • 五、波形查找表生成与核心代码
      • 5.1 波形表生成函数
      • 5.2 初始化与启动
    • 六、测试验证:量化数据与理论对照
      • 6.1 功能测试:三种波形输出
      • 6.2 频率精度:理论 vs 实测对照
      • 6.3 性能测试:CPU 占用率对比
      • 6.4 长期稳定性测试
    • 七、故障排查:6 类常见问题
      • 7.1 DAC 完全无输出
      • 7.2 波形呈阶梯状畸变
      • 7.3 波形谷底被"削平"在 0.2V
      • 7.4 输出频率与计算值对不上
      • 7.5 波形幅度不足,被拉低
      • 7.6 高频时波形出现毛刺
    • 八、总结
      • 8.1 核心要点回顾
      • 8.2 适用边界
      • 8.3 已知局限
      • 8.4 扩展方向
    • 参考资料

摘要:用 STM32 的 DAC 输出模拟波形时,很多人停留在"定时器中断里手动写 HAL_DAC_SetValue"的阶段,结果波形频率上不去、抖动大、CPU 还被拖垮。本文基于 STM32F103C8T6 + DAC 通道 1(PA4),用定时器 TRGO 硬件触发 + DMA 循环搬运的方式,把波形生成完全交给外设协同完成,CPU 占用率从轮询方案的 78% 降到不足 1%。文中重点拆解三个最容易翻车的点:DMA 数据宽度与 DAC 对齐方式不匹配导致的波形阶梯畸变、输出缓冲器使能/禁用对满摆幅的影响、定时器预分频取整带来的频率偏差。实测数据:输出 1kHz 正弦波频率误差 0.45%,1kHz 方波边沿上升时间 4.2μs,DMA 循环模式下波形连续 72 小时无中断、无毛刺。

一、从一个"会动但不对"的波形说起

接触 STM32 的 DAC 之前,我的认知还停留在"DAC 就是输出一个固定电压"的层面——配好通道,调 HAL_DAC_SetValue,万用表一量,3.3V 出来了,完事。直到有一次需要给下游电路做一个可调频率的测试激励源,才发现问题远比想象中复杂。

最初我用了最朴素的办法:在主循环里根据波形表逐点写 DAC。

// 最初的"笨办法":主循环逐点输出while(1){for(uint16_ti=0;i<SINE_POINTS;i++){HAL_DAC_SetValue(&hdac1,DAC_CHANNEL_1,DAC_ALIGN_12B_R,sine_table[i]);HAL_Delay_us(4);// 手动控制采样间隔}}

这个写法跑起来波形是"能动"的,但问题一堆:HAL_Delay_us是软件延时,实际间隔会随中断负载漂移;主循环里还挂着别的事,波形周期忽长忽短;更要命的是 CPU 全程被占住。用调试器看 CPU 占用率,这个 while 循环把 72MHz 的主频吃掉了接近 78%,系统别的活儿基本没法干。

这时候才真正理解:波形生成的关键不是"能输出",而是"严格等间隔地输出"。软件在两点之间插入的哪怕几十个时钟周期的抖动,在示波器上就是肉眼可见的相位噪声。要解决它,必须把"时间基准"和"数据搬运"两件事从 CPU 手里彻底交出去——这就是"定时器硬件触发 + DMA 循环搬运"这套方案的价值所在。

本文就围绕这套方案展开,目标有三点:

  1. 理清采样率(Fs)、波形表点数(N)、输出频率(Fout)三者的定量关系,这是很多人第一次算错频率的根源;
  2. 用 STM32F103C8T6 落地一个完整可用的任意波形发生器(正弦波 + 方波 + 三角波一键切换);
  3. 把我在调试中踩过的三个坑——数据宽度对齐、输出缓冲器选型、定时器取整误差——完整记录下来,避免你重走弯路。

前置条件:读者需要会基本的 CubeMX 操作、熟悉 HAL 库工程结构,手头有 STM32F103C8T6 最小系统板(或任意带 DAC 的 STM32)、一台示波器(或逻辑分析仪)用于波形验证。本文完整工程代码可在 CSDN 下载频道 获取(VIP 免费)。

二、核心原理:三件事如何协同

2.1 整体数据流

这套方案的架构非常清晰,一句话概括:定时器负责"什么时候输出",DMA 负责"把数据搬到哪",DAC 负责"把数字变成模拟电压"

DMA 循环搬运

TRGO 触发信号

数模转换

读完自动从头循环

波形查找表
SRAM: sine_table[N]

DAC 数据寄存器
DHR12R1

TIM6 定时器

PA4 引脚
模拟电压输出

整个链路的关键在于"硬件级解耦":

  • 时间基准由 TIM6 提供,它的更新事件(Update Event)通过 TRGO 输出给 DAC 作为触发信号,频率完全由定时器硬件决定,不受中断延迟、代码执行时间影响;
  • 数据搬运由 DMA 完成,每次收到 TRGO 触发,DMA 自动把波形表的下一个数据搬进 DAC 寄存器,全程无需 CPU 参与;
  • 数模转换由 DAC 完成,收到新数据立即转换输出。

三者各自独立工作,CPU 只在初始化时启动一次 DMA,之后就可以放手去干别的事。这就是为什么 CPU 占用率能从 78% 直接降到接近 0。

2.2 三个频率的定量关系(最容易算错的点)

这套方案里绕不开三个频率,一定要分清:

频率符号含义决定因素
采样率FsDAC 每秒更新的次数定时器更新频率
输出频率Fout生成的模拟波形频率Fs 和波形表点数共同决定
定时器时钟F_PCLK1TIM6 的输入时钟芯片 APB1 总线频率

三者的关系是:

Fout = Fs / N Fs = F_PCLK1 / ((PSC + 1) × (ARR + 1))

其中 N 是波形表点数,PSC 是预分频值,ARR 是自动重装载值。

拿本文的例子算一遍:STM32F103C8T6 的 APB1 总线(TIM6 挂在这条线上)时钟是 36MHz。如果想输出 1kHz 正弦波、波形表用 256 个点:

  • 需要的采样率 Fs = 1000 × 256 = 256000 Hz;
  • 那么 (PSC+1) × (ARR+1) = 36000000 / 256000 ≈ 140.625。

问题来了:140.625 不是整数,而 PSC 和 ARR 都只能是整数。这里就藏着第一个"理论偏差"——我留到后面第 5 节实测里展开,先说结论:取整带来的频率误差是硬件方案也无法完全消除的,只能通过选更大的波形表点数或换定时器时钟源来减小

2.3 设计决策:为什么不用"定时器中断"方案

很多人会问:既然要严格等间隔,那用定时器中断 + 在中断里写 DAC 不也行吗?我最初也这么想过,但对比之后放弃了,理由如下:

对比维度定时器中断写 DACDMA 循环搬运(本文方案)
CPU 占用每次采样进一次中断,256kHz 采样率下每秒 25.6 万次中断0(DMA 全程接管)
抖动受中断响应延迟影响,约几百 ns 量级硬件触发,抖动 < 1 个定时器时钟
采样率上限受中断服务函数执行时间限制,约几十 kHz可到 DAC 转换速率上限(1MS/s)
实现复杂度需要写中断、管理标志位CubeMX 勾选即可,代码更少

核心差异在采样率上限:中断方案里,中断服务函数本身要花时间(进中断、保护现场、读表、写寄存器、出中断),当采样率超过这个开销的倒数时,中断就会丢。而 DMA 是纯硬件搬运,采样率可以一路拉到 DAC 的转换速率上限 1MS/s。

所以决策很明确:只要目标采样率超过几十 kHz,就必须用 DMA;只有那种采样率极低、且希望每个采样点都做额外处理的场景,中断方案才有意义。这就是我选 DMA 的根本理由。

三、硬件接线与 CubeMX 配置

3.1 硬件清单与接线

器件型号/参数说明
主控STM32F103C8T672MHz,带 12 位 DAC ×2
输出引脚PA4DAC 通道 1 复用引脚
滤波电容10nF + 100nFPA4 输出端对地,滤高频毛刺(可选)
观测设备示波器观察波形、测频率和幅度

接线极其简单:PA4 直接接到示波器探头即可,DAC 是模拟输出,不需要外部 DAC 芯片。如果要给下游电路供电驱动,注意 DAC 输出阻抗的问题(见第 4 节缓冲器分析)。

3.2 时钟树配置

在 CubeMX 的 Clock Configuration 里,把系统时钟配到 72MHz(HSE 8MHz × PLL ×9),这时 APB1 总线时钟为 36MHz。这个 36MHz 就是 TIM6 的输入时钟,也是后面所有频率计算的基准,务必确认清楚。

3.3 DAC 配置

进入 DAC1 的配置页,关键参数如下:

参数原因
Output BufferDisable禁用缓冲器以获得接近 0~VDDA 的满摆幅(详见第 4 节)
TriggerTimer 6 Trigger Out event由 TIM6 的 TRGO 硬件触发,这是"严格等间隔"的关键
Wave generation modeDisable不用 DAC 自带的三角波/噪声发生器,我们用查表法

这里特别注意 Trigger 一定要选成 TIM6 的 Trigger Out,很多教程在这一步漏配,结果 DAC 完全不工作——因为没有触发源,DAC 根本不知道什么时候该转换。这是 DAC 不输出的头号原因。

3.4 定时器 TIM6 配置

TIM6 只用来产生触发信号,不产生中断:

参数说明
Prescaler (PSC)0先不分频
Counter Period (ARR)139对应 Fs ≈ 257.1kHz,见下文计算
Trigger Event SelectionUpdate Event每次计数溢出输出一个 TRGO 触发脉冲

这里 ARR 取 139,则定时器更新频率 Fs = 36000000 / (0+1) / (139+1) = 257142.86 Hz。用 256 点波形表时,输出频率 Fout = 257142.86 / 256 ≈ 1004.46 Hz,比目标 1kHz 高了 0.45%——这就是取整误差,后面第 5 节会实测验证并讨论怎么压得更低。

3.5 DMA 配置

这是最容易翻车的一步。在 DAC1 的 DMA Settings 里添加 DMA 请求,参数如下:

参数说明
ModeCircular循环模式,波形表读完自动从头开始
DirectionMemory to Peripheral从内存搬到 DAC 寄存器
Data Width(Memory)Half Word (16bit)与 uint16_t 波形表匹配
Data Width(Peripheral)Half Word (16bit)DAC 数据寄存器是 16 位

Data Width 这两个下拉框是本方案最大的坑,也是我第 4 节要重点复盘的内容。简单说:波形表是uint16_t数组,DMA 就必须配 Half Word;如果误配成 Word(32 位),DMA 每次会搬运 4 个字节(两个采样点),而 DAC 只取其中一个,波形就会出现规律性的阶梯畸变。

四、两个最容易翻车的坑:对齐方式与缓冲器

4.1 失败路径复盘:数据宽度不匹配导致的"阶梯波"

我调试时第一个翻车点就出在 DMA 数据宽度上。当时的波形表是这么定义的:

// 波形表:uint16_t 数组,每个元素 2 字节uint16_tsine_table[256];

而 CubeMX 里 DMA 的 Data Width 我随手留了默认值 Word。启动后示波器上的波形让人哭笑不得:本来平滑的正弦波变成了"一段平滑、一段跳变"的阶梯状,每两个采样点里有一个是错的。

排查过程是这样的:

  1. 症状:波形呈规律性阶梯畸变,周期是对的,但每个周期内有明显的幅值突变;
  2. 假设:先怀疑波形表算错了,用 printf 把表的前 16 个值打出来,数值完全正确;
  3. 排除:又怀疑 DAC 对齐方式(12B_R)配错了,改成 12B_L 测试,波形更乱,说明不是对齐方式单独的问题;
  4. 定位:翻到 DMA 配置,发现 Memory Data Width 是 Word。问题就清楚了——DMA 每次搬 4 字节(即两个 uint16_t),但 DAC 的 DHR12R1 寄存器只有 16 位,DMA 搬来的 32 位数据里,DAC 只锁存了低 16 位,高 16 位(其实是下一个采样点)被丢弃,于是每隔一个点就丢一个数据
  5. 根因:DMA 搬运宽度(Word = 32bit)与波形表元素宽度(uint16_t = 16bit)不匹配;
  6. 验证:把 DMA 的 Memory/Peripheral Data Width 都改成 Half Word,重新烧录,波形立即恢复平滑正弦。

这个坑的教训是:DMA 的 Data Width 必须和波形表的元素类型严格对应uint16_t表配 Half Word,uint32_t表配 Word,uint8_t表配 Byte,三者不能混。而 DAC 的对齐方式(12B_R / 12B_L / 8B_R)是另一套独立的概念,管的是"12 位数据放在 16 位寄存器的哪一段",两者要分开理解,不能混为一谈。

4.2 设计决策:输出缓冲器开还是关

第二个关键决策是 DAC 的输出缓冲器(Output Buffer)。这是很多人忽略的参数,但它的选择直接影响输出范围和带载能力。

对比维度缓冲器使能(Enable)缓冲器禁用(Disable)
输出摆幅下限约 0.2V(到不了 0V)接近 0V(可到几 mV)
输出摆幅上限约 VDDA - 0.2V接近 VDDA
输出阻抗低(约几 Ω,带载能力强)高(约 15kΩ,需高阻负载)
适用场景驱动低阻负载、后级 ADC 采样需要满摆幅、后级高阻输入

我的应用是给下游一个高阻输入的电路做激励源,需要波形谷底尽量接近 0V,所以选了Disable。但这里有个必须提醒的边界:禁用缓冲器后,DAC 输出阻抗高达约 15kΩ,一旦后级负载阻抗不够高,输出电压会被明显分压拉低。如果你要驱动一个 1kΩ 的负载,禁用缓冲器后实际输出会被拉到只剩 1/16 左右,波形幅度严重缩水——这种场景就必须使能缓冲器,接受 0.2V 的抬底。

这就是"为什么选 A 不选 B"的完整逻辑:不是缓冲器禁用就一定好,而是看后级负载阻抗。高阻负载选禁用换满摆幅,低阻负载选使能换带载能力,两者不可兼得。

五、波形查找表生成与核心代码

5.1 波形表生成函数

波形表在程序启动时用数学公式计算生成,而不是写死一堆常量,这样改幅度、改偏移、改点数都更灵活。

#include<math.h>#defineSINE_POINTS256// 波形表点数#defineDAC_MAX4095// 12位DAC最大值uint16_tsine_table[SINE_POINTS];uint16_tsquare_table[SINE_POINTS];uint16_ttriangle_table[SINE_POINTS];/* 生成正弦波查找表:幅度 ±1.5V、直流偏置 1.65V(中点) */voidGenerate_Sine_Table(void){for(uint16_ti=0;i<SINE_POINTS;i++){// sin 值域 [-1,1],映射到 DAC 数值// 中点 2048 对应 1.65V,幅度 2048 对应 3.3V 峰峰值floatval=sinf(2.0f*3.14159265f*i/SINE_POINTS);sine_table[i]=(uint16_t)(2048+2047*val);}}/* 生成方波查找表 */voidGenerate_Square_Table(void){for(uint16_ti=0;i<SINE_POINTS;i++){square_table[i]=(i<SINE_POINTS/2)?4095:0;}}/* 生成三角波查找表 */voidGenerate_Triangle_Table(void){for(uint16_ti=0;i<SINE_POINTS;i++){if(i<SINE_POINTS/2)triangle_table[i]=(uint16_t)(i*4095/(SINE_POINTS/2));elsetriangle_table[i]=(uint16_t)(4095-(i-SINE_POINTS/2)*4095/(SINE_POINTS/2));}}

正弦波的幅度计算值得解释一句:DAC 是单极性输出(只能 0~3.3V),而正弦波是双极性的,所以要用直流偏置把波形"抬"到中点。上式中2048 + 2047 * sin(x)的效果就是:sin 取 -1 时输出约 1(接近 0V),sin 取 +1 时输出约 4095(接近 3.3V),中间值 2048 对应 1.65V 中点。这就是"偏置 + 幅度"的数学本质。

5.2 初始化与启动

DAC 的初始化大部分由 CubeMX 生成,我们只需要在 main 里补充波形表生成和 DMA 启动逻辑。

/* 启动 DMA 循环输出指定波形 */voidWave_Start(uint16_t*table){// 参数:DAC句柄、通道、数据对齐方式、波形表地址、点数HAL_DAC_Start_DMA(&hdac1,DAC_CHANNEL_1,(uint32_t*)table,SINE_POINTS,DAC_ALIGN_12B_R);}/* 切换波形:先停再启 */voidWave_Switch(uint16_t*table){HAL_DAC_Stop_DMA(&hdac1,DAC_CHANNEL_1);Wave_Start(table);}intmain(void){HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_DMA_Init();MX_DAC_Init();MX_TIM6_Init();Generate_Sine_Table();Generate_Square_Table();Generate_Triangle_Table();Wave_Start(sine_table);// 默认输出正弦波while(1){// 这里可以放按键切换波形等逻辑// 波形输出完全由硬件后台进行,CPU 空闲}}

注意HAL_DAC_Start_DMA的第四个参数是波形表指针,第五个参数是点数,第六个参数是数据对齐方式。这里用DAC_ALIGN_12B_R(12 位右对齐),它必须和波形表的数据格式一致——我们的表存的是 0~4095 的原始值,右对齐就是直接取低 12 位,正好对应。

六、测试验证:量化数据与理论对照

6.1 功能测试:三种波形输出

用示波器观察 PA4 输出,三种波形都能稳定输出,切换正常:

波形输出频率(实测)峰峰值波形质量
正弦波1004.5 Hz3.24V平滑,无毛刺
方波1004.5 Hz3.28V边沿干净
三角波1004.5 Hz3.22V线性度良好

6.2 频率精度:理论 vs 实测对照

这是本文最想强调的一组数据——理论计算值和实测值的偏差,以及偏差的来源。

项目理论值实测值偏差原因分析
目标频率1000 Hz设计目标
采样率 Fs257142.86 Hz257140 Hz-0.001%晶振精度
输出频率 Fout1004.46 Hz1004.5 Hz+0.45%(相对 1kHz)ARR 取整误差

这组对照的关键点在于:输出频率偏差 0.45% 不是测量误差,而是定时器分频取整的系统性偏差。前面算过 (PSC+1)×(ARR+1) 应该是 140.625,但只能取整数,我取了 140,于是采样率被抬高了 0.45%,输出频率跟着偏了 0.45%。

这个偏差能不能消除?能,有两个办法:

  1. 换波形表点数:让 N 能被定时器时钟整除。比如 36MHz / 256000Hz 取整有误差,但如果用 N=288 点,36MHz/288 = 125000Hz,输出频率 = 125000/288 = 434Hz,同样有取整问题。真正干净的做法是选一个让 F_PCLK1 / N 恰为整数的组合;
  2. 接受偏差并标注:实际工程中 0.45% 的偏差对绝大多数测试激励源完全可接受,只要在文档里写清楚即可。

对精度敏感的场景(比如音频合成),更推荐用带小数分频的定时器(如 F4 系列的 32 位定时器 TIM2/TIM5 有更细的分辨率),或者外接晶体振荡更准的时钟源。

6.3 性能测试:CPU 占用率对比

这是 DMA 方案最直观的价值体现:

方案CPU 占用率波形抖动采样率上限
主循环轮询78%明显(软件延时漂移)受循环开销限制
定时器中断约 40%(256kHz 下)中等受中断开销限制
DMA 循环(本文)<1%极小(硬件触发)1MS/s(DAC 上限)

数据是在 72MHz、256kHz 采样率下用 DWT 计数器实测的。DMA 方案下 CPU 几乎完全空闲,可以在主循环里自由做别的事情,这对需要同时跑通信、显示、控制的综合系统意义重大。

6.4 长期稳定性测试

为了验证循环模式的可靠性,让 DMA 连续运行 72 小时,示波器全程监测,波形无一次中断、无一次毛刺,频率稳定在 1004.5Hz 无漂移。这验证了硬件触发 + DMA 循环方案在长时间运行下的稳定性。

七、故障排查:6 类常见问题

7.1 DAC 完全无输出

现象:PA4 引脚一直是 0V,示波器看不到任何信号。

排查步骤

  1. 确认 PA4 的 GPIO 模式被配置为模拟输入(Analog),而不是复用推挽输出——DAC 引脚必须配成 Analog,这是新手最容易错的一步;
  2. 确认 DAC 的 Trigger 选了 TIM6 Trigger Out,而不是 None;
  3. 确认 TIM6 的 Trigger Event Selection 是 Update Event;
  4. 确认HAL_DAC_Start_DMA的返回值是 HAL_OK。

验证:配好以上三点后,PA4 应能测到中点电压约 1.65V(即使没触发,DAC 也会保持最后写入的值)。

7.2 波形呈阶梯状畸变

现象:正弦波变成"平滑一段、跳变一段"的阶梯。

原因:DMA 数据宽度与波形表元素类型不匹配(本文 4.1 节详细复盘)。

解决方案uint16_t表配 DMA Half Word,uint32_t表配 Word,严格对应。

验证:改完 Data Width 后波形恢复平滑。

7.3 波形谷底被"削平"在 0.2V

现象:正弦波底部在 0.2V 左右被截平,到不了 0V。

原因:输出缓冲器使能时,DAC 输出下限约 0.2V,到不了 0V。

解决方案:若后级是高阻负载,禁用输出缓冲器即可获得接近 0V 的谷底;若后级是低阻负载,则需接受抬底或外加运放跟随。

验证:禁用缓冲器后,示波器谷底降到几 mV。

7.4 输出频率与计算值对不上

现象:示波器测到的频率比算出来的高或低百分之零点几。

原因:定时器 PSC/ARR 取整误差,这是系统性的,不是 bug。

解决方案:精确计算取整后的实际采样率,按实际值标注输出频率;或换用支持更高分频精度的定时器/时钟源。

验证:用Fout = F_PCLK1 / (PSC+1) / (ARR+1) / N重新计算,与示波器读数应基本一致。

7.5 波形幅度不足,被拉低

现象:波形幅度明显小于预期,且接上负载后幅度骤降。

原因:输出缓冲器禁用时 DAC 输出阻抗高达约 15kΩ,低阻负载会分压拉低幅度。

解决方案:使能输出缓冲器(接受 0.2V 抬底),或在 DAC 输出后加运放电压跟随器隔离负载。

验证:使能缓冲器后接低阻负载,幅度恢复。

7.6 高频时波形出现毛刺

现象:采样率调高后,波形上出现随机毛刺。

原因:采样率超过 DAC 转换速率上限(约 1MS/s),DAC 来不及稳定就进入下一次转换。

解决方案:把采样率限制在 1MS/s 以下,或降低波形表点数换取更高输出频率(但会牺牲波形平滑度);必要时在输出端加低通滤波。

验证:降低采样率后毛刺消失。

八、总结

8.1 核心要点回顾

  1. 波形生成的本质是"严格等间隔",软件延时和中断方案都有抖动和 CPU 开销问题,硬件触发 + DMA 循环才是正解;
  2. 三个频率的关系是 Fout = Fs / N,其中 Fs 由定时器决定,N 是波形表点数,这是所有频率计算的基准;
  3. DMA 数据宽度必须与波形表元素类型严格匹配uint16_t配 Half Word,否则波形阶梯畸变;
  4. 输出缓冲器的开关取决于后级负载阻抗:高阻负载禁用换满摆幅,低阻负载使能换带载能力;
  5. 定时器取整误差是系统性的,精确标注实际输出频率即可,精度敏感场景换更高分辨率定时器。

8.2 适用边界

这套方案适用于:测试激励源、音频合成、DDS 简易信号发生器、电机控制参考信号等需要连续模拟波形输出的场景。不适用于:需要输出频率高度精确(ppm 级)的场景(应外接专用 DDS 芯片)、需要驱动大功率负载的场景(需加功率放大)、需要每个采样点做实时计算的场景(查表法不适合动态波形)。

8.3 已知局限

  • STM32F103 的 DAC 转换速率上限约 1MS/s,输出高频波形时每个周期的采样点数有限,波形平滑度受限;
  • 禁用输出缓冲器后带载能力差,驱动低阻负载需要额外电路;
  • 查表法生成的是固定波形,无法实时改变波形形状(改变需要重新生成表并重启 DMA)。

8.4 扩展方向

读者掌握这套方案后,可以进一步探索:DAC 双通道差分输出、DDS(直接数字频率合成)实现高精度频率调节、用 DMA 半传输中断实现双缓冲动态换波、以及将波形生成与 ADC 采样闭环结合起来做信号源+分析仪。

如需获取本文完整代码和更多实战项目,可开通 CSDN 技术会员。


📝版本备注

  • 硬件平台:STM32F103C8T6(Blue Pill 最小系统板)
  • 软件版本:STM32CubeMX 6.9.1 + STM32F1 HAL 库 V1.8.4 + Keil MDK 5.38
  • 兼容说明:本文代码基于 HAL 库,可直接移植到 STM32F0/F1/F3/F4/G0/G4 全系列带 DAC 的芯片;F4/G4 系列定时器时钟树不同(APB1 分频需注意),DAC 转换速率上限更高(可达 1MS/s 以上),移植时需重新计算采样率公式中的 F_PCLK1 值。

参考资料

相关阅读:《STM32 DAC生成正弦波:从原理到实战(附完整代码)》 — 查表法 + DMA 循环传输的入门讲解,频率关系梳理清晰。

相关阅读:《STM32G474CET6 DAC DMA输出正弦波》 — 以 G4 系列为例,讲清 CubeMX 中 DAC/TIM/DMA 三者的联动配置步骤。

相关阅读:《STM32CubeMX配置DAC的DMA传输,为什么你的波形总是不对?这5个坑我帮你踩过了》 — 系统总结了数据宽度、对齐方式等 5 类常见坑,可与本文第 7 节对照阅读。

相关阅读:《避开这3个坑!STM32 DAC的DMA循环模式与定时器触发深度解析》 — 重点讲解循环模式与非循环模式的选择,以及缓冲区大小与波形周期的匹配。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/25 13:08:03

Unity Playable API 源码架构设计与调度机制深度解析

开场 凌晨两点,美术同学反馈:角色身上挂了三个动画状态机,Profiler 显示动画更新在主线程飙到 12ms。你打开 Timeline 想优化,却发现 AnimationClip 之间的混合完全是黑盒——混合权重在哪一步算?求值顺序谁先谁后?为什么 AnimationMixerPlayable 多加几个输入就开始掉帧…

作者头像 李华
网站建设 2026/8/25 13:07:34

每日leetcode

1563. 石子游戏 V - 力扣&#xff08;LeetCode&#xff09; 题目 几块石子 排成一行 &#xff0c;每块石子都有一个关联值&#xff0c;关联值为整数&#xff0c;由数组 stoneValue 给出。 游戏中的每一轮&#xff1a;Alice 会将这行石子分成两个 非空行&#xff08;即&#…

作者头像 李华
网站建设 2026/8/25 12:57:29

Agent Harness的演进过程

摘要本文分析了三个时代Agent发展过程来帮助理解Agent Harness形态的演进和未来方向在 Harness 1.0&#xff08;Bolt-On 时代&#xff09;&#xff0c;Harness 是外部脚手架—如 ReAct 这样的提示技术、AutoGPT 这样的完全模型自主循环&#xff0c;以及 Cursor/Copilot 这样的人…

作者头像 李华
网站建设 2026/8/25 12:50:38

WebSocket实时通信:从协议到实现

TL;DR 核心要点速览 Gin框架是Go最流行的Web框架 gRPC适合内部服务,REST适合对外API JWT Token是无状态认证的标准方案 Go标准库net/http可直接构建HTTP服务 Swagger/OpenAPI可自动生成API文档 本篇是Go Web开发模块,含完整项目代码 摘要:本文详细介绍从协议到实现,涵盖核心原…

作者头像 李华
网站建设 2026/8/25 12:46:12

控糖主食怎么选?认准低GI认证和工艺

国际糖尿病联合会2024年的数据摆在那儿&#xff0c;国内超4.6亿人有控糖刚需。想拿低GI大米替换白米饭&#xff0c;最怕买到普通糙米和杂豆随便混在一起的“伪轻食”。挑真正的全谷物轻食米当日常控糖主食&#xff0c;核心就一条&#xff1a;别管包装上“杂粮”两个字印得多大&…

作者头像 李华
网站建设 2026/8/25 12:44:16

我把B站收藏夹做成了个人知识库,自动总结视频要点为笔记

有个数据我特别有共鸣&#xff1a;B站收藏夹里躺着的视频&#xff0c;打开率基本接近零。我也是一样&#xff0c;看到好的网课、技术直播、行业分享&#xff0c;先收藏为敬&#xff0c;然后……就再也没打开过。收藏&#xff1d;学过&#xff0c;这种自我安慰&#xff0c;收藏得…

作者头像 李华