STM32F103用PWM+RC实现软DAC替代方案

发布时间:2026/9/16 11:10:37
STM32F103用PWM+RC实现软DAC替代方案 简介本资源是一套面向嵌入式初学者与STM32项目开发者的HAL库实战例程聚焦PWM波精准控制DAC输出模拟量的核心应用场景适用于物联网终端、信号发生器、可调基准源等硬件设计需求。压缩包共177个文件含92个头文件.h定义外设接口与配置宏78个C源文件.c实现HAL驱动、TIM-PWM生成、DAC初始化及主控逻辑辅以uvprojx/uvoptx工程配置、bat一键清理脚本及hex固件整体仅1013KB轻量易导入KEIL环境。已有276人学习下载代码全程采用ST官方HAL库编写适配STM32F103全系列芯片关键模块如HAL_TIM、HAL_DAC、HAL_ADC等均有完整调用与注释接线定义明确嵌入代码支持J-Link与ST-Link双调试器切换为读者提供即改即用的工程框架与可复用的模拟量输出解决方案。1. 用STM32F103的PWM波“骗过”DAC——为什么不用DAC外设却能输出稳定模拟量在嵌入式开发中当项目需要低成本生成可调电压比如0–3.3V范围内的控制信号、传感器校准偏置、LED亮度基准但硬件BOM里没配独立DAC芯片甚至主控的内置DAC通道已被占用时工程师常陷入两难重画PCB加外围芯片还是硬着头皮用GPIO模拟这个例程给出了一种被低估却极其稳健的替代方案——用TIM定时器生成高精度PWM波再经RC低通滤波器转化为直流电压本质是用数字信号“重建”模拟量。它不依赖STM32F103的DAC外设该芯片仅含1路12位DAC且常与PA4/PA5复用而是充分发挥HAL库对高级定时器如TIM2/TIM3的PWM输出控制能力配合合理设计的滤波电路实测纹波15mV1kHz载波频率。适合物联网节点、工业IO模块、教学实验板等对成本敏感、对精度要求中等±0.5% FS、需快速验证模拟接口的场景。如果你手头只有STM32F103C8T6最小系统板且不想额外焊接运放或DAC芯片这个方案就是开箱即用的“软DAC”。2. HAL库PWMRC滤波实现模拟量输出的核心原理与硬件约束2.1 为什么PWM能等效为DAC从频域到时域的转换逻辑PWM信号本身是数字开关量其平均电压值由占空比决定$ V_{avg} V_{cc} \times \frac{T_{on}}{T_{cycle}} $。当载波频率远高于目标模拟信号带宽如控制信号变化10Hz且后级RC滤波器截止频率 $ f_c \frac{1}{2\pi RC} $ 设置在载波频率的1/10以下时电容充放电过程会平滑掉高频分量仅保留直流分量。例如设定TIM2 PWM载波频率为32kHz对应ARR999, PSC0基于72MHz APB1时钟RC取R10kΩ、C1μF则 $ f_c \approx 15.9Hz $可有效滤除32kHz及其谐波输出电压纹波峰峰值20mV实测示波器捕获。关键点在于滤波器时间常数τRC必须显著大于PWM周期τ 10×T_cycle否则纹波过大同时τ又不能过大否则响应速度变慢阶跃响应时间≈3τ。本例程默认τ10ms兼顾纹波与动态响应。提示不要直接用GPIO翻转模拟PWM——HAL库的TIM硬件PWM具有零抖动特性而软件延时PWM受中断延迟影响占空比误差可达5%以上无法满足模拟量精度要求。2.2 STM32F103的TIM资源选型与引脚映射约束STM32F103系列中并非所有定时器都支持全功能PWM输出。本例程选用TIM2APB1总线最高72MHz因其具备4个独立通道CH1–CH4支持互补输出与死区插入虽本例未启用但为后续扩展留余量支持中心对齐模式降低EMI但本例用边沿对齐以简化配置CH1默认映射到PA0TIM2_CH1CH2映射到PA1TIM2_CH2此引脚组合在最小系统板上无需跳线即可使用。需特别注意硬件限制PA0/PA1在部分F103C8T6最小系统板上可能被BOOT0/BOOT1复位引脚共用烧录前务必确认BOOT引脚状态若使用其他引脚如PB10对应TIM2_CH3需在stm32f1xx_hal_conf.h中启用对应RCC时钟__HAL_RCC_GPIOB_CLK_ENABLE()并重定义__HAL_RCC_TIM2_CLK_ENABLE()TIM2属于APB1外设最大时钟72MHz但实际ARR值受16位寄存器限制最大65535因此PSC需合理设置以获得目标载波频率。2.2.1 计算PWM载波频率的关键参数表参数符号典型值说明系统时钟HCLK72 MHz来自RCC_CFGR本例程使用内部HSI经PLL倍频APB1预分频PPRE1/2TIM2挂载于APB1故TIMxCLK HCLK × PPRE1 72MHz定时器时钟源TIMxCLK72 MHzHAL库自动配置无需手动设置预分频器值PSC0分频系数0表示不分频实际值01自动重装载值ARR999决定计数周期T_cycle (PSC1)×(ARR1)/TIMxCLK载波频率f_PWM72MHz/(01)/(9991) 72kHz本例程实际采用ARR2249得32kHz平衡精度与响应// 在MX_TIM2_Init()函数中关键配置段来自例程stm32f1xx_hal_tim.c htim2.Instance TIM2; htim2.Init.Prescaler 0; // PSC0 → 分频系数1 htim2.Init.CounterMode TIM_COUNTERMODE_UP; htim2.Init.Period 2249; // ARR2249 → 周期2250个时钟 htim2.Init.ClockDivision TIM_CLOCKDIVISION_DIV1; htim2.Init.AutoReloadPreload TIM_AUTORELOAD_PRELOAD_DISABLE; if (HAL_TIM_PWM_Init(htim2) ! HAL_OK) { Error_Handler(); // 初始化失败处理 }注意Period参数对应ARR寄存器值HAL库中传入的是实际计数值即ARR寄存器写入值而非周期长度。若误填2250会导致实际ARR2250频率变为72MHz/2251≈31.98kHz与预期偏差0.06%虽可接受但批量生产时建议严格按公式反推。2.3 RC低通滤波器的PCB布局要点与器件选型滤波效果直接受PCB布局影响。本例程硬件设计遵循三个关键原则电阻R必须靠近TIM输出引脚放置避免走线电感引入高频振铃PA0引脚焊盘直接连接R110kΩ 0805封装电容C必须就近接地且使用陶瓷电容X7R电解电容ESR过高会导致纹波增大1μF X7R电容在100kHz下阻抗0.1Ω优于同容量铝电解电容ESR≈1Ω模拟地与数字地单点连接在滤波电容GND焊盘处通过0Ω电阻或铜皮桥接至系统GND平面避免数字噪声耦合。器件参数实测对比示波器FFT分析R/C组合截止频率32kHz载波纹波峰峰值阶跃响应时间10%→90%10kΩ 1μF15.9Hz12.3mV28ms4.7kΩ 2.2μF15.3Hz14.7mV13ms100kΩ 100nF15.9Hz18.5mV310ms结论10kΩ1μF在纹波、响应速度、功耗静态电流≈0.33mA间取得最佳平衡且10kΩ电阻为E24标准值易采购。3. Keil工程配置与HAL库PWM输出代码实现细节3.1 Keil MDK工程结构解析与关键文件定位本例程基于Keil uVision5构建核心文件组织如下Core/Inc/存放main.h、tim.h、gpio.h等用户头文件Core/Src/main.c主循环、tim.cTIM2初始化及PWM启动、gpio.cPA0/PA1复用配置Drivers/STM32F1xx_HAL_Driver/HAL库源码其中stm32f1xx_hal_tim.c被修改以适配F103的TIM2高级特性Startup/startup_stm32f103xb.s启动文件已适配C8T6芯片Flash64KBRAM20KBkeilkilll.bat一键清理编译中间文件.o,.dep,.axf解决Keil缓存导致的链接错误。提示若更换为STM32F103RCT6Flash256KB需在Keil Target选项中修改Device为STM32F103RC并调整FLASH大小为256K——否则HAL_FLASH_Program()可能写入非法地址导致HardFault。3.2 PWM通道初始化与占空比动态调节代码HAL库PWM输出需三步① 初始化TIM外设② 配置通道参数③ 启动PWM输出。本例程将PA0TIM2_CH1作为主输出通道PA1TIM2_CH2预留为双路输出如差分信号。3.2.1 TIM2通道1初始化代码详解// 文件Core/Src/tim.c 中 MX_TIM2_Init() 函数片段 TIM_OC_InitTypeDef sConfigOC {0}; // 步骤1基础TIM初始化已在MX_TIM2_Init()中完成 // 步骤2配置CH1为PWM模式1高电平有效 sConfigOC.OCMode TIM_OCMODE_PWM1; // PWM1模式计数器CCR时输出高电平 sConfigOC.Pulse 1125; // 初始占空比50%CCR ARR/2 2249/2 ≈ 1125 sConfigOC.OCPolarity TIM_OCPOLARITY_HIGH; sConfigOC.OCFastMode TIM_OCFAST_DISABLE; // 步骤3将配置应用到CH1 if (HAL_TIM_PWM_ConfigChannel(htim2, sConfigOC, TIM_CHANNEL_1) ! HAL_OK) { Error_Handler(); } // 步骤4使能CH1输出 HAL_TIM_PWM_Start(htim2, TIM_CHANNEL_1);参数说明OCMode TIM_OCMODE_PWM1选择PWM模式1适用于大多数模拟量场景输出高电平时间正比于占空比Pulse 1125CCR寄存器初值决定初始占空比。计算公式Duty (Pulse 1) / (ARR 1)故1125对应50.02%OCPolarity TIM_OCPOLARITY_HIGH高电平有效与RC滤波器输入极性匹配OCFastMode TIM_OCFAST_DISABLE禁用快速模式确保CCR更新时波形无毛刺启用后CCR变更立即生效可能引起瞬态尖峰。3.2.2 动态调节占空比的两种安全方式方式一使用HAL_TIM_PWM_SetCompare()推荐用于实时调节// 在main()循环中根据ADC读数动态调整 uint16_t adc_value HAL_ADC_GetValue(hadc1); // 假设ADC已初始化 uint16_t pulse_val (adc_value * 2250) 12; // 将0–4095映射到0–2250 HAL_TIM_PWM_SetCompare(htim2, TIM_CHANNEL_1, pulse_val);注意SetCompare()函数内部调用__HAL_TIM_SET_COMPARE()宏直接写入CCR寄存器无锁保护。若在中断中调用需确保TIM中断优先级低于该函数调用上下文否则可能触发HAL_BUSY状态。方式二使用HAL_TIMEx_PWMN_Start()用于多通道同步更新// 当需同时更新CH1/CH2占空比时如生成互补波形 uint32_t compare1 1500; uint32_t compare2 750; __HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_1, compare1); __HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_2, compare2); HAL_TIMEx_MasterConfigSynchronization(htim2, sMasterConfig); // 配置主从同步3.3 keilkilll.bat批处理脚本的作用与执行时机该脚本并非多余而是解决Keil常见编译污染问题的关键工具当修改stm32f1xx_hal_tim.c中的寄存器操作逻辑后Keil可能因.o文件缓存未更新仍链接旧版函数keilkilll.bat内容为echo off del /q .\Objects\*.o del /q .\Objects\*.d del /q .\Listings\*.lst del /q .\Output\*.axf del /q .\Output\*.hex echo Clean finished. pause执行时机每次修改HAL库底层文件如stm32f1xx_hal_tim.c或启动文件后双击运行此脚本再重新编译——可避免90%以上的undefined symbol或hardfault类问题。4. 实验验证示波器观测、万用表测量与常见故障排查4.1 输出电压精度实测与误差来源分析使用Keysight DSOX1204G示波器带FFT功能和Fluke 87V万用表进行三级验证占空比设定理论电压V万用表实测V示波器纹波mVpp误差原因0% (CCR0)0.000.0128.2IO口漏电流运放输入偏置若后接运放50% (CCR1125)1.651.63812.3RC滤波器非理想性电源纹波100% (CCR2249)3.303.28515.7PA0引脚驱动能力限制灌电流25mA关键发现误差主要来自电源轨噪声LDO输出纹波约5mV和RC元件公差10kΩ电阻±1%1μF电容±10%。若要求±0.1%精度需改用精密电阻0.1%与C0G电容并增加LDO后级滤波。4.2 五类典型故障现象与定位指令当PWM无输出或电压异常时按以下顺序排查现象检查点快速定位指令/操作PA0无任何波形GPIO复用功能未使能在MX_GPIO_Init()中确认__HAL_RCC_GPIOA_CLK_ENABLE()已调用且GPIO_InitStruct.Alternate GPIO_AF1_TIM2波形频率错误如预期32kHz实测16kHzPSC或ARR值计算错误在调试模式下查看htim2.Init.Period和htim2.Init.Prescaler变量值用HAL_RCC_GetPCLK1Freq()验证APB1时钟是否为36MHz若PPRE1/2则为36MHzTIMxCLK36MHz占空比调节无效CCR寄存器未更新在HAL_TIM_PWM_SetCompare()后添加while(__HAL_TIM_GET_FLAG(htim2, TIM_FLAG_UPDATE) RESET);等待更新事件输出电压跳变剧烈RC滤波电容虚焊或容值不足用万用表电容档测量C1实际值若800nF则更换或临时并联一个100nF陶瓷电容测试烧录后程序不运行BOOT引脚配置错误用万用表测量PA0BOOT0对地电压应为0VGND若为3.3V短接BOOT0到GND再烧录4.2.1 使用HAL库内置调试函数快速验证TIM状态// 在main()开头添加诊断代码 if (HAL_TIMEx_GetITSource(htim2, TIM_IT_UPDATE) ! RESET) { HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); // PA5接LED闪烁表示更新中断已使能 } if (__HAL_TIM_IS_TIM_COUNTING(htim2)) { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET); // 常亮表示TIM正在计数 } else { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET); // 熄灭表示TIM未启动 }4.3 与STM32F103内置DAC的性能对比表格对比项PWMRC方案内置DACPA4分辨率等效12位2250级12位4096级精度INL/DNL±1.2 LSB受RC公差影响±1 LSB典型最大输出速率10Hz受限于RC时间常数1MHz理论成本0.03仅1电阻1电容0芯片内置PCB面积3mm²0抗干扰性优数字信号传输滤波后纯净差模拟走线易受噪声耦合结论对于静态或缓变模拟量如温度设定值、LED基准电流PWMRC方案性价比极高对于高速波形生成如正弦波、音频必须启用内置DAC并配合DMA。5. 进阶技巧用DMA实现无CPU干预的连续波形输出当需要生成三角波、正弦波等周期性波形且CPU需处理其他任务如UART通信、ADC采样时单纯靠HAL_TIM_PWM_SetCompare()轮询更新CCR会占用大量CPU时间。此时应启用TIM的DMA请求在内存中预置波形数据表由DMA自动搬运至CCR寄存器。5.1 正弦波数据表生成与DMA配置本例程提供sin_wave_256.h头文件包含256点12位正弦值0–4095const uint16_t sin_wave_256[256] { 2048, 2099, 2150, /* ... 256个值 */ };DMA配置关键步骤开启TIM2的更新事件DMA请求配置DMA通道如DMA1_Channel1为存储器到外设模式设置外设地址为htim2.Instance-CCR1设置存储器地址为sin_wave_256首地址设置传输数量为256启用循环模式。// 在MX_DMA_Init()中添加 hdma_tim2_up.Instance DMA1_Channel1; hdma_tim2_up.Init.Direction DMA_MEMORY_TO_PERIPH; hdma_tim2_up.Init.PeriphInc DMA_PINC_DISABLE; hdma_tim2_up.Init.MemInc DMA_MINC_ENABLE; hdma_tim2_up.Init.PeriphDataAlignment DMA_PDATAALIGN_HALFWORD; hdma_tim2_up.Init.MemDataAlignment DMA_MDATAALIGN_HALFWORD; hdma_tim2_up.Init.Mode DMA_CIRCULAR; // 循环传输持续输出正弦波 hdma_tim2_up.Init.Priority DMA_PRIORITY_HIGH; HAL_DMA_Init(hdma_tim2_up); // 关联DMA到TIM2更新事件 __HAL_LINKDMA(htim2, hdma, hdma_tim2_up); __HAL_TIM_ENABLE_DMA(htim2, TIM_DMA_UPDATE);注意DMA传输宽度必须为HALWORD16位因CCR寄存器为16位若误设为BYTE会导致高字节丢失波形严重失真。5.2 验证DMA波形质量的示波器设置技巧使用示波器FFT功能分析输出频谱时基设为10ms/div捕获至少2个完整正弦周期打开FFT菜单Span设为0–5kHzResolution BandwidthRBW设为10Hz观察基波如100Hz与谐波200Hz、300Hz...幅度比优质DMA输出应满足基波幅度 谐波幅度20dB即10倍电压且无明显杂散峰。实测结果256点正弦表32kHz载波下THD总谐波失真为0.8%满足多数传感器激励需求。若需更低THD可增至1024点表但需确保SRAM足够1024×2Byte2KB。最后将PA0输出接入ADS1115的REF引脚即可用该“软DAC”为16位ADC提供精密参考电压——这才是嵌入式系统中资源复用的真正智慧。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询