恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

STM32F103 DAC输出正弦波:定时器触发+DMA搬运实战

  • 首页
  • 资讯中心
  • /
  • STM32F103 DAC输出正弦波:定时器触发+DMA搬运实战

相关资讯

FT230XS 串口转 USB 驱动:从枚举失败到稳定通信的完整落地 2026/10/19 13:54:41
三模型协作写论文:从选题到定稿的7天完整流程 2026/10/19 13:54:41
基于LLM的叙事Agent:多模块架构实现稳定长文故事生成 2026/10/19 13:54:41

最新资讯

词云AI如何驱动呼叫中心从资源驱动走向AI驱动
内存乱序访问:高效CPU背后的并发陷阱与修复指南
端侧多模态实时对话系统开发实战:9B模型的流式架构与性能调优
工业级地图可视化大屏模板:支持GIS接口对接与动态刷新
企业级AI视频管理平台源码级重构与低代码交付实战
ISATAP隧道处理全解析:原理、配置与排障实战

今日推荐

OpenCowork 实测:支持本地文件、飞书机器人的 Windows AI 助手(只需配置 Token)
GPT-6 Sol 与 Luna 怎么选?Chat、Work、Codex 版本差异与 TaoToken 接入实测
openclaw.net 集成 ElBruno.MempalaceNet 记忆系统:把 MCP 配置改到 TaoToken 的完整实践

本周热门

基于GMM的语音识别系统原理与MATLAB实现详解
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

本月精选

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)

STM32F103 DAC输出正弦波:定时器触发+DMA搬运实战

发布时间:2026/10/19 13:54:41
STM32F103 DAC输出正弦波:定时器触发+DMA搬运实战 简介这份资源面向嵌入式开发初学者与STM32学习者围绕STM32F103ZET6内置12位DAC实现音频输出这一实验场景帮助读者理解数字信号转模拟信号、WAV转C数组、定时器与DMA驱动播放等关键环节并涉及外接功放与扬声器的硬件连接思路。压缩包共181个文件约5.54MB以44个h头文件、39个c源文件为核心配合25个o目标文件、25个d依赖文件及24个crf等编译中间产物另含3个wav音频素材、3个txt说明、Keil工程文件uvprojx、uvoptx、sct、axf、hex与批处理脚本构成可直接编译下载的完整工程。目前已有4862人学习下载适合作为DAC音频实验的参考模板读者可据此快速搭建工程、对照源码理解DAC配置与音频数据播放流程并在此基础上扩展更复杂的嵌入式音频应用。1. 从一块“哑巴”开发板到能唱歌的DAC这个实验到底在做什么手头有块 STM32F103 的最小系统板跑过点灯、串口打印、PWM 调光但总觉得少了点“能感知”的东西。直到把示波器探头夹在某个引脚上看到正弦波从一堆方波里慢慢浮出来才意识到 DAC 这个外设一直被忽略了。这个实验做的事情很纯粹用 STM32F103 自带的 12 位 DAC 通道把预先算好的数字采样值按固定时间间隔喂进去让 PA4 或 PA5 引脚输出连续变化的模拟电压接上耳机或者小功放就能听到声音。它适合已经会配置 GPIO 和定时器、想搞明白“数字音频到底怎么从单片机里出来”的嵌入式开发者。不需要外部音频解码芯片也不需要文件系统核心就是定时器触发 DMA 搬运 DAC 转换这条链路。搞通之后做简易信号发生器、语音提示模块、甚至低采样率的 WAV 播放器都是在这个骨架上加东西。2. 硬件链路与工程结构DAC 出声之前需要理清的几个连接2.1 为什么选 PA4/PA5 而不是随便找个引脚STM32F103 的 DAC 输出通道是固定映射到 PA4通道 1和 PA5通道 2的这不是软件可以改的。很多新手翻原理图时看到 PA4 旁边标着 SPI1_NSS 或者 ADC12_IN4就以为要避开其实只要把 DAC 使能了这个引脚的数字功能会自动让位给模拟输出。我一般会在 CubeMX 里先把 PA4 配成 DAC_OUT1然后检查一下有没有其他外设占着这个脚。如果用的是那种带运放跟随的板子PA4 后面可能已经接了电压跟随器输出阻抗低直接接耳机问题不大如果是裸引脚最好加一个隔直电容再接负载否则直流分量会让耳机偏磁。这一点在实验里经常被忽略现象就是声音出来了但很小或者耳机里有“咔”的一声。2.2 时钟树和定时器触发的关系DAC 要输出音频核心是“等间隔更新”。STM32F103 的 DAC 支持多种触发源TIM2、TIM4、TIM5、TIM6、TIM7 的 TRGO 事件都能用。我一般选 TIM6因为它基本定时器没有输出引脚不会跟其他外设抢资源。假设要输出 8kHz 采样率的音频定时器就要每 125 微秒触发一次。系统时钟 72MHz预分频器设成 72-1计数器周期设成 125-1这样更新事件频率正好是 72MHz / 72 / 125 8kHz。这个计算看着简单但实际配置时经常有人把预分频和周期搞反结果出来的声音要么快放要么慢放。配置完之后在 DAC 的触发源里选 TIM6_TRGODMA 请求也一并使能剩下的就是往缓冲区里填数据了。2.3 工程目录里哪些文件真正在干活一个典型的 Keil 或 STM32CubeIDE 工程跟这个实验相关的文件其实就几个。main.c里放初始化和主循环dac.c和tim.c是 CubeMX 生成的配置代码真正需要自己写的是一个采样数据数组和 DMA 启动语句。如果用的是标准外设库那就要手动写DAC_InitTypeDef和TIM_TimeBaseInitTypeDef再开 DMA 通道。我习惯把采样数据单独放在一个audio_data.h里用const uint16_t存这样编译器会把它放到 Flash 里不占 RAM。下面是一个最小工程的目录示意Project/ ├── Core/ │ ├── Inc/ │ │ ├── main.h │ │ ├── dac.h │ │ ├── tim.h │ │ └── audio_data.h // 采样表 │ └── Src/ │ ├── main.c │ ├── dac.c │ ├── tim.c │ └── stm32f1xx_it.c ├── Drivers/ │ └── STM32F1xx_HAL_Driver/ └── MDK-ARM/ // 或 STM32CubeIDE 工程文件这个结构里audio_data.h是唯一跟“音频内容”有关的东西其他都是通道配置。改声音就是改这个数组改采样率就是改定时器参数分工很清晰。3. 核心代码拆解从采样表到模拟波形的完整链路3.1 生成正弦采样表的两种做法DAC 输出正弦波第一步是有一张表。最简单的是在代码里用sin()算但浮点运算在 F103 上跑起来慢而且每次启动都算一遍没必要。我一般用 Python 或者 Octave 离线算好存成 C 数组。比如要输出 1kHz 正弦波采样率 8kHz那一个周期就是 8 个点。但 8 个点太粗糙听感上谐波很重所以实际会取 32 或 64 个点然后让定时器按 8kHz 触发这样输出频率就是 8000 / 64 125Hz。想要 1kHz要么提高采样率到 64kHz要么减少点数。F103 的 DAC 建立时间大约 1 微秒64kHz 完全没问题。下面是一个 32 点正弦表的生成代码# 生成 32 点正弦采样表12 位 DAC范围 0-4095 import math points 32 for i in range(points): # 加 2048 偏置是因为 DAC 输出范围是 0-3.3V正弦要居中 value int(2048 2047 * math.sin(2 * math.pi * i / points)) print(f0x{value:03X}, , end) if (i 1) % 8 0: print()这段代码输出的是 12 位右对齐的数据直接填进uint16_t数组就行。注意偏置 2048 和幅度 2047 的搭配保证不会溢出到 0 以下或 4095 以上。如果想让声音大一点可以把幅度调到 2000 左右留一点余量因为 DAC 在接近轨到轨的时候线性度会变差。3.2 DAC DMA 定时器的初始化顺序初始化顺序错了DAC 要么不出波形要么只输出一个直流电平。正确的顺序是先开时钟再配 GPIO 为模拟模式然后配 DMA再配 DAC最后配定时器并启动。下面是一段基于 HAL 库的初始化代码// 1. 使能时钟 __HAL_RCC_DAC_CLK_ENABLE(); __HAL_RCC_DMA1_CLK_ENABLE(); __HAL_RCC_TIM6_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); // 2. PA4 配成模拟模式 GPIO_InitTypeDef gpio {0}; gpio.Pin GPIO_PIN_4; gpio.Mode GPIO_MODE_ANALOG; HAL_GPIO_Init(GPIOA, gpio); // 3. DMA 配置从内存搬到 DAC 数据寄存器 hdma_dac.Instance DMA1_Channel3; // DAC_CH1 对应 DMA1_Channel3 hdma_dac.Init.Direction DMA_MEMORY_TO_PERIPH; hdma_dac.Init.PeriphInc DMA_PINC_DISABLE; hdma_dac.Init.MemInc DMA_MINC_ENABLE; hdma_dac.Init.PeriphDataAlignment DMA_PDATAALIGN_HALFWORD; hdma_dac.Init.MemDataAlignment DMA_MDATAALIGN_HALFWORD; hdma_dac.Init.Mode DMA_CIRCULAR; // 循环模式音频连续输出 hdma_dac.Init.Priority DMA_PRIORITY_HIGH; HAL_DMA_Init(hdma_dac); __HAL_LINKDMA(hdac, DMA_Handle1, hdma_dac); // 4. DAC 配置触发源选 TIM6 TRGO hdac.Instance DAC; HAL_DAC_Init(hdac); DAC_ChannelConfTypeDef dac_ch {0}; dac_ch.DAC_Trigger DAC_TRIGGER_T6_TRGO; dac_ch.DAC_OutputBuffer DAC_OUTPUTBUFFER_ENABLE; HAL_DAC_ConfigChannel(hdac, dac_ch, DAC_CHANNEL_1); // 5. 启动 DMA 和 DAC HAL_DAC_Start_DMA(hdac, DAC_CHANNEL_1, (uint32_t *)sine_table, 32, DAC_ALIGN_12B_R); // 6. 定时器配置72 分频125 周期 - 8kHz htim6.Instance TIM6; htim6.Init.Prescaler 72 - 1; htim6.Init.Period 125 - 1; HAL_TIM_Base_Init(htim6); HAL_TIM_Base_Start(htim6);这段代码里几个关键点DMA 必须配成循环模式否则放完一遍就停了HAL_DAC_Start_DMA的最后一个参数是数据对齐方式12 位右对齐对应DAC_ALIGN_12B_R定时器的Period决定采样间隔改这个值就能改音调。如果发现没有波形先查 DMA 通道有没有选对F103 的 DAC 通道 1 固定用 DMA1_Channel3通道 2 用 DMA1_Channel4这个在参考手册里有表选错了不会报错但就是不动。3.3 主循环里到底还要不要做事DMA 和定时器都跑起来之后主循环其实是空的。CPU 只需要在初始化完成后进入while(1)剩下的搬运和转换全是硬件自动完成。这也是这个实验适合入门的原因没有中断嵌套没有实时任务调度纯粹是外设之间的配合。如果想做更复杂的东西比如按键切歌那就在主循环里轮询按键然后重新调用HAL_DAC_Start_DMA换一张表。注意换表之前要先HAL_DAC_Stop_DMA否则新旧数据会混在一起听起来像爆音。4. 避坑与排查DAC 不出声时先查这五个地方4.1 现象示波器上只有一条直线没有波形先看 DAC 的输出缓冲使能了没有。F103 的 DAC 输出缓冲默认是关闭的关闭状态下输出阻抗很高带不动负载示波器探头接上去可能直接把它拉平。在DAC_ChannelConfTypeDef里把DAC_OutputBuffer设成DAC_OUTPUTBUFFER_ENABLE输出能力会强很多。如果还是直线检查 DMA 有没有真正启动可以在HAL_DAC_Start_DMA后面加一句读 DMA 使能位的代码确认EN位是 1。4.2 现象有波形但频率不对要么快一倍要么慢一半这是定时器参数算错了。常见错误是把预分频值直接写成 72 而不是 72-1HAL 库的Prescaler是“分频系数减一”写 72 实际是 73 分频。另一个坑是Period写成了 125 而不是 124导致实际频率是 72MHz / 72 / 126 ≈ 7.94kHz跟预期的 8kHz 差了一点。听感上可能不明显但用示波器测周期就能看出来。我一般会在初始化之后用调试器看一眼 TIM6 的 ARR 和 PSC 寄存器确认跟手算的一致。4.3 现象声音断断续续或者放一小段就停了DMA 模式没设成循环。DMA_CIRCULAR和DMA_NORMAL的区别就是前者搬完一圈自动回到开头继续搬后者搬完就停。音频必须用循环模式。另外检查一下 DMA 的计数器有没有被意外清零如果开了 DMA 传输完成中断但没处理可能会进中断后卡住。最简单的做法是不开中断纯轮询标志位也不开就让 DMA 自己跑。4.4 现象接上耳机有很响的“嗡嗡”声这是直流偏置导致的。DAC 输出的正弦波是叠加在 1.65V 直流上的直接接耳机直流分量会让音圈偏离中心位置轻则声音小重则发热。解决方法是加一个隔直电容通常用 10uF 到 100uF 的电解电容正极接 DAC 输出负极接耳机。如果板子上已经有电容但还有嗡嗡声可能是电源纹波耦合进来了在 DAC 输出和地之间并一个 100nF 的瓷片电容能滤掉一部分高频噪声。4.5 现象换了一张采样表声音变成刺耳的噪声采样表的长度和 DMA 配置的缓冲区长度不一致。HAL_DAC_Start_DMA的第四个参数是“要搬运的数据个数”如果表是 64 点但这里写 32那只会搬前一半波形就断了。另外如果新表的点数跟定时器周期不匹配比如定时器还是按 8kHz 触发但表变成了 16 点输出频率就会翻倍。换表的时候要同时改 DMA 长度和定时器周期这两个是绑定的。5. 二次开发可以往哪走从固定正弦到可交互音频5.1 把采样表换成 WAV 文件片段固定正弦波听久了会腻下一步自然是放真实声音。F103 的 Flash 有 64KB 或 128KB放一段 8kHz、8 位的单声道 WAV 可以存十几秒。做法是用工具把 WAV 转成 C 数组每个采样点取 8 位无符号数然后在 DMA 搬运之前左移 4 位变成 12 位因为 DAC 是 12 位的。注意 WAV 文件头要去掉只取数据段。下面是一个转换脚本的片段import wave with wave.open(voice.wav, rb) as w: frames w.readframes(w.getnframes()) # 假设是 8 位单声道直接取字节 for i, b in enumerate(frames): dac_val b 4 # 8 位转 12 位 print(f0x{dac_val:03X}, , end) if (i 1) % 12 0: print()这段代码把 8 位采样值映射到 12 位 DAC 范围。如果原始 WAV 是 16 位的就要取高 12 位右移 4 位再送 DAC。采样率也要对应调整定时器8kHz 的 WAV 就用 8kHz 的触发频率否则声音会变调。5.2 用按键或串口动态切换波形在主循环里加一个按键扫描按一下切换一张表正弦、方波、三角波轮流来。方波表很简单前一半填 4095后一半填 0。三角波就是线性递增再递减。切换的时候先HAL_DAC_Stop_DMA改HAL_DAC_Start_DMA的缓冲区指针和长度再启动。如果通过串口接收命令可以用一个简单的状态机解析收到 s 就切正弦q 就切方波。注意串口中断的优先级不要设得比定时器高否则可能会打断 DMA 请求导致波形抖动。5.3 提高采样率的边界在哪里F103 的 DAC 最高转换率在数据手册里写的是 1MHz但实际用 DMA 搬运时受限于总线和 Flash 读取速度稳定跑到 200kHz 左右问题不大。再高的话DMA 搬运会跟 CPU 取指抢总线波形开始出现台阶。如果想做 16kHz 采样率的音频一个周期 64 点输出频率就是 250Hz这个范围做人声提示音足够了。想放音乐要么降低采样率到 8kHz 并接受音质损失要么换带 I2S 的型号。这个实验的价值在于把 DAC 的基本链路跑通后面换芯片或者加外部 codec思路是一样的。5.4 一个容易忽略的细节DAC 输出后的滤波DAC 输出的是阶梯波每个采样点保持一个定时器周期频谱里除了基波还有大量高频谐波。如果直接接功放这些高频会听起来像“沙沙”声。简单做法是在输出端加一个 RC 低通滤波器截止频率设在采样率的一半左右。比如 8kHz 采样截止频率设 4kHz用 1kΩ 电阻和 100nF 电容组成的一阶滤波就够了。这个滤波器要放在隔直电容之前否则电容的容抗会跟电阻分压改变截止频率。我一般会在洞洞板上先搭一个试听确认效果再画到 PCB 上。从那以后我每次调 DAC 输出都强制先用示波器看静态直流电平确认在 1.65V 附近再启动 DMA不然直接上耳机很容易被直流偏置吓一跳。希望帮到你。本文还有配套的精品资源点击获取

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号