
1. 项目概述与核心价值最近在整理手头的几个嵌入式项目发现无论是驱动电机、控制LED亮度还是生成特定波形都绕不开两个核心的模拟信号输出技术PWM和DAC。很多刚接触STM32的朋友包括我当年都是从点灯开始然后学着用PWM调个呼吸灯就觉得“模拟输出”不过如此。但真到了需要精准电压控制、生成复杂音频或波形的时候才发现PWM和DAC的选择与使用里面门道不少。这个练习项目就是把我自己从入门到踩坑再到稳定应用的过程梳理一遍重点不是复现一个简单例程而是搞懂为什么要这么配置以及在实际调试中如何用一把示波器看透信号的本质。简单说PWM脉冲宽度调制是通过数字引脚输出一系列方波通过改变方波中高电平的占空比来“模拟”一个平均电压。而DAC数模转换器则是芯片内部直接将数字代码转换成真实的、连续变化的模拟电压。两者都能输出“模拟量”但原理、精度、应用场景和成本天差地别。这个练习的目标就是基于一块常见的STM32F103C8T6核心板亲手配置出PWM和DAC输出并用示波器观察、分析它们的波形特性理解从寄存器配置到物理引脚上电压变化的完整链条。无论你是想驱动舵机、制作可调电源还是播放一段音频这个基础练习都能给你打下坚实的硬件认知。2. 硬件平台与核心外设解析2.1 STM32F103C8T6的定时器与DAC资源盘点工欲善其事必先利其器。我手头这块“蓝色小药丸”Blue Pill核心板主控是STM32F103C8T6属于ARM Cortex-M3内核。选择它是因为资源典型、资料丰富且成本极低。我们先来盘一下它身上与本次练习相关的“家当”。首先是定时器TIM这是产生PWM的发动机。STM32F103的定时器功能强大且种类多高级定时器TIM1, TIM8功能最全支持互补输出、死区插入、刹车功能常用于电机控制和电源应用。本次我们用不到这么复杂的功能。通用定时器TIM2, TIM3, TIM4, TIM5本次练习的主力。它们都支持标准的PWM输出模式足够我们使用。需要注意的是TIM2和TIM5是32位定时器其他是16位在设置自动重装载值ARR时范围不同。基本定时器TIM6, TIM7主要用于单纯的定时中断没有输出引脚不能直接产生PWM。对于PWM输出我们重点关注通用定时器的输出比较通道。每个通用定时器通常有4个独立的通道CH1-CH4每个通道都可以独立配置为PWM模式并映射到特定的GPIO引脚上。例如TIM3的通道1CH1对应PA6引脚通道2CH2对应PA7引脚这在芯片数据手册的“复用功能映射”章节有详细说明。其次是DAC。STM32F103C8T6内部集成了2个12位的DAC通道。请注意不是所有STM32都有DACF103系列是有的这是一大优势。它的关键参数分辨率12位。意味着输出模拟电压可以被分为 2^12 4096 个等级。参考电压通常与VDDA模拟电源电压挂钩一般接3.3V。这意味着当数字输入为4095时输出电压理论上为3.3V。输出缓冲器可以启用或禁用。启用后驱动能力强但带宽和压摆率受限禁用后输出阻抗高但能获得更好的频率响应。对于输出直流或低频信号建议启用。注意一定要确保VDDA和VSSA模拟地被正确供电并良好滤波这是DAC输出稳定、噪声低的物理基础。很多情况下需要将VDDA连接到3.3V并通过一个磁珠或0欧电阻与数字电源VDD隔离并用1uF和100nF电容就近退耦。2.2 工具链与调试环境搭建代码编写我选择使用STM32CubeIDE。它是ST官方推出的免费集成开发环境基于Eclipse集成了CubeMX图形化配置工具、代码编辑、编译和调试功能。用它的CubeMX来初始化外设能避免大量繁琐的寄存器操作让我们更专注于逻辑。硬件连接上除了核心板你需要ST-Link V2调试器用于下载程序和在线调试。连接SWDIO、SWCLK、GND和3.3V四根线即可。数字示波器这是本次练习的“眼睛”。不需要很高端一个带宽50MHz以上、双通道的示波器就完全够用。关键是要会用。我们将用它测量PWM的频率、占空比、上升时间以及DAC输出的电压值、纹波和建立时间。万用表辅助测量静态电压。跳线帽和杜邦线用于连接。软件层面的准备在CubeIDE中新建工程选择正确的芯片型号。在Pinout Configuration视图里我们会先配置时钟树通常使用外部8MHz晶振倍频到72MHz系统时钟然后配置定时器和DAC。调试时学会使用实时变量观察和逻辑分析仪功能如果调试器支持。虽然不如真实示波器直观但可以方便地查看变量值的变化。3. PWM输出配置与深度实践3.1 定时器PWM模式原理与CubeMX配置PWM的本质是定时器在自动重装载值ARR和比较值CCR这两个寄存器控制下周期性翻转输出引脚电平的结果。ARR决定了PWM波的周期频率CCR决定了高电平的脉宽占空比。在CubeMX中配置TIM3的通道1PA6输出PWM步骤如下在Timers-TIM3下将Clock Source设为Internal Clock。在Channel1下拉菜单中选择PWM Generation CH1。转到Parameter Settings选项卡进行关键参数配置Prescaler预分频器PSC设为71。因为系统时钟是72MHzPSC1后定时器时钟为 72MHz / (711) 1MHz。这个值决定了定时器计数的“快慢”。Counter ModeUp向上计数模式。这是最常用的模式。Counter Period自动重装载值ARR设为999。这样PWM的周期就是 (ARR1) / 定时器时钟 (9991) / 1MHz 1ms即频率为1kHz。Pulse脉冲宽度即初始CCR值设为500。这样初始占空比就是 CCR / (ARR1) 500 / 1000 50%。CH PolarityHigh。表示默认输出高电平当计数器值小于CCR时输出有效电平高。配置完成后生成代码。CubeMX会自动帮我们生成GPIO和定时器的初始化代码MX_TIM3_Init()。3.2 动态调整PWM占空比与频率的代码实现初始化完成后PWM并不会自动启动。我们需要在main.c的用户代码区添加启动和调整代码。// 启动TIM3的PWM输出 HAL_TIM_PWM_Start(htim3, TIM_CHANNEL_1); // 在主循环或某个函数中动态修改占空比 // 将占空比修改为75% __HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_1, 750); // CCR设置为750 占空比750/100075% // 动态修改PWM频率通过改变ARR值 // 将频率提高到2kHz周期0.5ms __HAL_TIM_SET_AUTORELOAD(htim3, 499); // ARR设置为499 新频率 1MHz / (4991) 2kHz // 注意改变ARR后如果CCR值大于新的ARR输出可能会异常通常需要同步调整CCR。 __HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_1, 250); // 将占空比维持在50% CCR250这里有一个非常重要的实操心得直接使用__HAL_TIM_SET_COMPARE和__HAL_TIM_SET_AUTORELOAD这些宏来修改CCR和ARR其效果是立即更新的在下一个更新事件生效。但对于高级应用比如需要无毛刺地同步改变频率和占空比可能需要配置为Preload模式并使用__HAL_TIM_SET_AUTORELOAD和__HAL_TIM_SET_COMPARE后再通过__HAL_TIM_UPDATE_GENERATION来同时生效。3.3 示波器实测与波形分析要点将示波器探头地线夹在板子的GND探头尖端接触PA6引脚。上电运行程序。测量基础参数频率使用示波器的自动测量功能或测量一个完整周期的时间T频率f1/T。验证是否为设定的1kHz。占空比测量高电平时间与周期的比值。验证修改CCR后占空比是否准确变化。幅值测量高电平电压应该是接近3.3V单片机IO口电压低电平接近0V。观察细节上升/下降时间将示波器时基调小放大波形边沿。STM32 GPIO的翻转速度很快上升时间通常在纳秒级。这个参数会影响高频PWM的波形质量。抖动将波形稳定显示观察周期或脉宽是否有微小的随机变化。高质量的时钟源和稳定的电源可以减小抖动。一个关键技巧测量PWM的“模拟效果”。用示波器的探头同时接上PA6引脚和一个简单的RC低通滤波器例如一个1kΩ电阻串联到PA6电阻另一端接一个10uF电容到地从电容两端测量。你会发现原本的方波变成了一个平滑的直流电压并且这个直流电压值随着PWM占空比的变化而线性变化。这就是PWM实现数模转换的原理通过示波器你可以直观地看到滤波后的电压纹波大小这决定了PWM作为“廉价DAC”的输出质量。注意事项示波器探头有输入电容通常10pF左右这个电容会和你的电路产生影响。在测量高频信号或高阻抗节点时建议使用探头上的x10衰减档位这时输入阻抗更高通常10MΩ输入电容更小通常15pF对电路影响小。4. DAC输出配置与性能探究4.1 DAC初始化与双缓冲机制详解在CubeMX中配置DAC通道1PA4在Analog-DAC下勾选OUT1 Configuration。在Parameter Settings中关键配置有Output Buffer选择Enable。如前所述启用输出缓冲以获得更强的带负载能力除非你需要输出高频信号。Trigger选择None。表示我们不使用硬件触发来启动转换而是通过软件直接写数据寄存器。Wave generation选择None。我们先进行静态电压输出后续可以尝试噪声波或三角波生成。生成代码后在main.c中启动DAC并输出电压。// 启动DAC通道1 HAL_DAC_Start(hdac, DAC_CHANNEL_1); // 设置DAC输出一个固定电压例如输出满量程的一半 // 数字值 (期望电压 / 参考电压) * 4095 // 假设期望输出1.65V3.3V的一半则数字值 (1.65 / 3.3) * 4095 2047.5 ≈ 2048 HAL_DAC_SetValue(hdac, DAC_CHANNEL_1, DAC_ALIGN_12B_R, 2048); // 动态改变输出电压模拟一个锯齿波 for(uint16_t i 0; i 4096; i) { HAL_DAC_SetValue(hdac, DAC_CHANNEL_1, DAC_ALIGN_12B_R, i); HAL_Delay(1); // 延时1ms控制锯齿波斜率 }这里解释一下DAC_ALIGN_12B_R。DAC的数据寄存器是32位的我们的12位数据可以左对齐或右对齐存放。12B_R表示12位右对齐即数据放在寄存器的低12位。这是最直观的方式。STM32的DAC还支持双缓冲机制即有一个后台的预装载寄存器。你可以在一个缓冲区写入下一个要转换的数据而当前转换不受影响在下次触发事件时预装载值会自动载入主寄存器。这对于生成连续、无毛刺的波形至关重要但在软件单次触发模式下我们暂不涉及。4.2 DAC静态特性与动态特性测试用示波器观察PA4引脚的输出。静态精度测试设置DAC输出一个固定码值例如2048理论输出1.65V。用示波器的直流耦合档位并打开高分辨率采集模式如果支持测量其平均电压。用万用表的直流电压档进行更精确的测量。记录实测电压与理论电压的差值这就是积分非线性误差的体现。对于大多数应用STM32内置DAC的精度已经足够。动态响应测试运行上面的锯齿波代码。将示波器时基调到合适位置你会看到一个阶梯状的波形。因为我们是每1ms更新一次数值所以波形是由许多小台阶组成的。重点关注建立时间当DAC数字输入码值发生一个满量程跳变如从0跳到4095时输出模拟电压达到最终值附近一个特定误差带如±0.5LSB所需的时间。这反映了DAC的输出速度。你可以写一个代码让输出在0和满量程之间快速切换用示波器测量上升/下降边沿。观察毛刺在码值变化时特别是中间码跳变时如从0x7FF跳到0x800由于内部开关的不完全匹配可能会产生短暂的电压尖峰称为“毛刺”。这是DAC动态性能的重要指标。4.3 基于DAC的简易波形合成实践我们可以更进一步用DAC合成一个正弦波。这需要提前计算好一个正弦周期内的采样点。// 生成一个正弦波表一个周期取64个点 #define SIN_WAVE_TABLE_SIZE 64 uint16_t sin_wave_table[SIN_WAVE_TABLE_SIZE]; void generate_sin_table(void) { for(int i 0; i SIN_WAVE_TABLE_SIZE; i) { // 计算正弦值范围[-1, 1] float sin_val sinf(2 * 3.1415926f * i / SIN_WAVE_TABLE_SIZE); // 将正弦值偏移并缩放到[0, 4095]范围 // 公式 值 (sin_val 1) / 2 * 4095 sin_wave_table[i] (uint16_t)((sin_val 1.0f) * 2047.5f); } } // 在主循环中循环输出正弦波表 int wave_index 0; while(1) { HAL_DAC_SetValue(hdac, DAC_CHANNEL_1, DAC_ALIGN_12B_R, sin_wave_table[wave_index]); wave_index (wave_index 1) % SIN_WAVE_TABLE_SIZE; // 这里的延时决定了正弦波的频率 // 频率 1 / (SIN_WAVE_TABLE_SIZE * 延时时间) // 例如延时50us则频率 1 / (64 * 0.00005) ≈ 312.5Hz delay_us(50); // 你需要实现一个微秒级延时函数 }用示波器观察这个正弦波你会看到它是一个阶梯状的正弦。台阶的多少由SIN_WAVE_TABLE_SIZE决定台阶之间的平滑度由后续的低通滤波器决定。这就是数字音频播放的基本原理。实操心得软件循环输出波形会占用大量CPU时间且定时不精确。在实际项目中应该使用DMA直接存储器访问来将波形数据自动搬运到DAC数据寄存器同时配合一个定时器触发DAC转换。这样CPU只需设置一次就能输出高精度、不间断的波形。这是DAC应用的高级玩法也是发挥其性能的关键。5. PWM与DAC的关键差异与选型指南经过上面的动手实践我们用示波器亲眼看到了PWM是方波DAC是相对平滑的电压。现在我们来系统性地对比一下以后做项目该选哪个。特性维度PWMDAC (如STM32内置12位DAC)分析与选型建议本质数字脉冲信号真正的模拟电压信号PWM是“模拟效果”DAC是“真模拟”。硬件成本极低仅需数字IO较高需集成DAC或外接芯片对成本敏感且对信号质量要求不高的场景PWM是首选。输出精度取决于定时器分辨率16位和滤波效果取决于DAC位数如12位和基准源对于静态电压设定DAC的精度和稳定性通常远优于PWM滤波。12位DAC有4096级而PWM经过滤波后纹波和响应速度会限制其有效分辨率。动态响应极快。改变占空比下一个周期立即生效。较慢。受建立时间、压摆率限制。需要快速响应的闭环控制如开关电源PWM是唯一选择。DAC更适合设定静态工作点或输出低频波形。输出能力驱动能力强GPIO的拉灌电流可直接驱动LED、MOSFET栅极。驱动能力弱通常为mA级需加运放缓冲才能驱动负载。驱动功率器件选PWM。驱动高阻抗输入如运放、ADC参考端选DAC。滤波需求必须外接模拟低通滤波器才能得到平滑直流。芯片内部已处理输出本身是平滑的虽有高频噪声。PWM方案需额外设计滤波器R、C、运放增加了电路复杂度和相位滞后。典型应用LED调光、电机调速、开关电源、舵机控制。音频输出、精密电压基准、波形发生器、传感器偏置电压。应用场景区分明显。生成复杂模拟波形正弦波、音频必须用DAC。个人经验总结在我的项目中我遵循一个简单原则凡是需要“开关控制”的都用PWM凡是需要“真实电压”的都用DAC。例如控制电机的速度我用PWM给一个运算放大器提供可调的参考电压我用DAC。有时候也会组合使用比如用DAC设定一个电流限值用PWM控制功率开关。6. 进阶应用与联合调试实战6.1 用定时器触发ADC同步采样DAC输出一个经典的自我验证循环是用DAC输出一个波形同时用ADC去采集这个波形看看单片机“读”回来的和自己“写”出去的是否一致。这里的关键是同步。我们使用定时器来同时触发DAC的转换和ADC的采样。配置在CubeMX中配置一个定时器如TIM2产生一个固定频率的更新事件UEV。然后将DAC的触发源设置为该定时器Trigger选择TIM2 TRGO。同时配置ADC的触发源也为Timer 2 Trigger Out event。原理TIM2每次溢出会产生一个触发信号。这个信号同时启动DAC的数据转换从数据寄存器转到输出和ADC的采样转换。这样就实现了精确的同步避免了软件延迟带来的相位差。代码你需要用DMA将ADC的结果搬运到内存数组。然后可以将这个数组的数据通过串口发送到电脑用工具如Python的Matplotlib绘图与DAC输出的理论波形进行对比。这个练习能让你深刻理解STM32内部外设联动的强大之处也是构建精密测量系统的基础。6.2 PWM实现DAC功能PWMDAC的精度评估当芯片没有DAC或者DAC通道不够时可以用PWM加低通滤波器来模拟DAC常被称为PWMDAC。我们来评估一下它的实际性能。设计滤波器假设PWM频率为1kHz周期1ms。要得到一个平滑的直流我们需要滤除这个1kHz的基波及其谐波。根据RC低通滤波器截止频率公式f_c 1 / (2πRC)。如果我们希望衰减1kHz的信号可以将截止频率设为10Hz。选择R10kΩ则C 1 / (2π * 10 * 1000) ≈ 15.9uF。选择一个标准的22uF电解电容。搭建电路从PWM引脚串联一个10kΩ电阻然后接一个22uF电容到地从电容两端测量输出电压。测试用示波器观察滤波后的电压。纹波你会看到直流电压上叠加了一个很小的三角波纹波这就是PWM频率未能被完全滤除的残余。纹波电压的峰峰值V_ripple ≈ Vcc * (T_pwm / (R*C))其中T_pwm是PWM周期。代入我们的参数理论纹波约15mV。实测值会受到电容等效串联电阻ESR的影响。响应速度突然改变PWM占空比观察输出电压上升到新值的时间。这个建立时间很长大约为τ R*C 10k * 22u 0.22秒。这意味着PWMDAC的动态响应非常慢。结论PWMDAC成本低但存在纹波、响应慢、输出阻抗高受电阻R影响和精度依赖电源稳定性等缺点。它只适用于对动态性能要求极低、对精度要求不高的场合比如简单的LED调光或风扇调速。任何需要快速、精确电压的场合都应使用真正的DAC。6.3 示波器高级功能在信号分析中的应用工欲善其事必先利其器。用好示波器能让调试事半功倍。测量PWM占空比和频率使用示波器的自动测量功能是最快的方法。确保测量统计功能打开可以观察平均值、最大值、最小值、标准差评估信号的稳定性。观察DAC建立时间使用示波器的单次触发模式。设置边沿触发在代码中让DAC输出一个满量程阶跃。捕获到这个跳变后使用光标功能测量从跳变开始到电压稳定在最终值±0.5LSB范围内的时间。分析信号噪声测量DAC输出一个固定电压时的波形。将示波器输入耦合设为交流AC垂直档位调小如10mV/div时基调大。你可以看到输出上的噪声。使用FFT快速傅里叶变换功能可以将时域噪声转换到频域看看噪声主要分布在哪些频率有助于定位干扰源如开关电源噪声、时钟耦合等。捕获偶发毛刺使用毛刺触发或脉宽触发模式。如果你怀疑DAC在码值变化时会有窄脉冲毛刺可以设置触发条件为脉宽小于一定值如100ns的脉冲这样一旦出现毛刺示波器就能捕获并定格它。记录与导出数据很多示波器支持将波形数据保存为CSV格式。你可以将DAC输出的一个完整波形周期数据导出在Excel或Python中进行更详细的分析比如计算总谐波失真THD。对于PWM滤波后的波形导出数据可以直观地看到纹波的大小和形状。调试不是盲目的试错而是基于测量的科学分析。示波器就是工程师的眼睛学会这些高级功能能让你更快地定位问题、验证设计。