1. 项目概述与核心需求手头有个项目需要测试一个水下LED灯用的灯珠阵列。这个阵列由10颗红色CREE XP-E2每颗1W75流明和5颗暖白色Cree XM-L每颗3W220流明组成密密麻麻地挤在一块直径53mm、高35mm的铝块上。我的核心任务是搞清楚在不同PWM占空比下这些LED的电压、功率以及铝块的温升情况。最终的水下灯项目打算用ATtiny44来驱动所以测试用的PWM信号特性得和它匹配。市面上现成的信号发生器要么太贵要么功能过剩操作复杂。我需要的是一个纯粹的、可调的PWM信号源频率和占空比都能手动平滑调节最好还能直接插在面包板上和被测电路对接。翻遍了零件盒ATtiny13这颗仅有8个引脚、价格低廉的8位单片机进入了视线。它内置了硬件PWM模块配合两个电位器理论上完全能实现我的需求。于是一个基于ATtiny13的简易PWM信号发生器方案就这么定下来了。整个电路算上电源和输出核心部分真的只需要4个元件非常适合快速搭建和验证。2. 硬件设计与元件选型解析2.1 核心控制器ATtiny13的潜力挖掘选择ATtiny13首要原因是其极致的性价比和够用的性能。它虽然只有1KB的Flash和64字节的SRAM但驱动一个PWM发生器绰绰有余。我选用的是ATtiny13V-10PU后缀“V”代表其工作电压可以低至1.8V“10”表示在5V供电时最高可运行于10MHz。为了获得更精准的PWM频率我通过熔丝位配置关闭了系统时钟预分频器让芯片直接使用内部校准的9.6MHz RC振荡器作为系统时钟。这样做的好处是频率固定无需外部晶振进一步简化了电路。它的Timer0定时器支持多种PWM模式。我选择了快速PWM模式因为在这种模式下计数器从0计数到2558位模式然后复位回0产生一个固定的、频率由预分频值和计数器上限决定的锯齿波。占空比通过比较匹配寄存器OCR0A来设置当计数器值小于OCR0A时输出高电平大于等于时输出低电平逻辑清晰易于计算和控制。2.2 频率与占空比输入电位器的选择与权衡用户交互的核心是两个电位器分别用于调节频率和占空比。占空比调节电位器R1最初我随手找了一个Pollin的微型可调电阻Trimpot。在实际测试中发现问题它的调节旋钮行程很短稍微拧动一点ADC读取的值就变化很大导致占空比调节非常“跳”无法进行精细微调。这对于需要精确观察LED在不同占空比下性能变化的实验来说是致命的。因此我将其更换为一个多圈精密电位器。这种电位器通过一个螺杆驱动电阻丝需要旋转很多圈才能走完全程实现了极高的分辨率可以非常平滑、精确地设定占空比。电阻值我选择了2.2kΩ这是一个在功耗和ADC输入阻抗间取得平衡的值。频率调节电位器R2频率调节的逻辑不同。PWM频率并非连续可调而是由定时器预分频系数N决定的几个固定档位1, 8, 64, 256, 1024。因此电位器在这里的作用是将ADC的0-1023读数映射到这5个档位上。对调节分辨率的要求不高只要能把整个ADC范围大致均匀分成5个区间即可。所以我选用了一个普通的10kΩ单圈电位器完全够用。注意电位器的另一端接VCC滑动端接单片机ADC引脚。务必在ADC引脚到地之间接一个100nF0.1uF的电容C1。这个电容至关重要它能滤除电位器滑动时产生的噪声和来自数字电路的干扰保证ADC采样值的稳定避免PWM输出因输入噪声而抖动。2.3 电路连接与电源设计整个电路极其简洁电源J1直接接入5V直流电源。ATtiny13V的工作电压范围是1.8-5.5V5V是常见且稳定的选择。PWM输出J2 J3我将Timer0的PWM输出引脚PB0 芯片第5脚同时引到了两个排针上方便同时连接示波器观察波形和驱动后续的LED驱动电路。ADC输入占空比电位器R1的滑动端接PCINT1ADC1 芯片第7脚。频率电位器R2的滑动端接PCINT0ADC0 芯片第6脚。两个电位器的另外两端分别接VCC和GND。滤波电容除了前面提到的ADC引脚滤波电容C1在芯片的VCC和GND之间还应就近放置一个100nF的陶瓷去耦电容以吸收电源线上的高频噪声确保单片机稳定运行。3. 软件原理与代码实现详解3.1 PWM频率生成机制ATtiny13的Timer0在快速PWM模式下的频率计算公式是理解整个项目的关键F(PWM) F(CPU) / (N * 256)F(CPU) 我们的系统时钟配置为9.6 MHz。N 定时器预分频系数可选值为1, 8, 64, 256, 1024。256 因为计数器是8位从0计数到255共256个时钟周期为一个完整的PWM周期。根据这个公式我们可以计算出5个理论频率档位N1: 9.6MHz / (1 * 256) 37.5 kHzN8: 9.6MHz / (8 * 256) 4.6875 kHzN64: 9.6MHz / (64 * 256) 586.0 HzN256: 9.6MHz / (256 * 256) 146.48 HzN1024: 9.6MHz / (1024 * 256) 36.62 Hz这覆盖了从超高频可用于简单的D类音频或非常快的LED调光到低频可用于电机调速或呼吸灯的常用范围。在代码中我们需要根据ADC1频率电位器的采样值来映射到这5个N值。我的策略是将ADC的0-1023范围等分成5个区间。// 读取频率选择ADC值 uint16_t adc_freq readADC(ADC_FREQ_PIN); // 假设值在0-1023 // 映射到预分频枚举值 typedef enum { PRESCALE_1 1, PRESCALE_8 2, PRESCALE_64 3, PRESCALE_256 4, PRESCALE_1024 5 } prescale_t; prescale_t get_prescale(uint16_t adc_val) { if (adc_val 205) return PRESCALE_1; // 区间 0-204 else if (adc_val 410) return PRESCALE_8; // 区间 205-409 else if (adc_val 615) return PRESCALE_64; // 区间 410-614 else if (adc_val 820) return PRESCALE_256; // 区间 615-819 else return PRESCALE_1024; // 区间 820-1023 }然后在Timer0初始化或更新时根据get_prescale的返回值配置TCCR0B寄存器中的CS02, CS01, CS00位以设定对应的预分频值。3.2 占空比设置与ADC采样占空比由OCR0A寄存器的值决定范围是0-255。0代表0%占空比常低255在快速PWM模式下通常代表100%但注意当OCR0A255时有些模式下输出可能不翻转我们通常使用0-254来对应0%~100%。我们读取ADC0占空比电位器的值并将其从0-1023线性映射到0-254。// 读取占空比ADC值 uint16_t adc_duty readADC(ADC_DUTY_PIN); // 线性映射到0-254。使用32位运算避免溢出。 uint8_t duty_value (uint8_t)((adc_duty * 254UL) / 1023UL); // 设置PWM占空比 OCR0A duty_value;这里有一个重要的实操细节ADC参考电压我选择了AVCC即芯片的VCC5V。这意味着电位器滑动端的电压范围是0-5V对应ADC读数0-1023。这种设置最简单但要求电源电压稳定。如果VCC波动ADC读数和实际的PWM占空比都会随之漂移。对于精度要求极高的场合可以考虑使用芯片内部的1.1V基准但需要重新设计电位器的分压电路。3.3 主程序逻辑与优化主程序是一个简单的超级循环super loop初始化配置ADC使能、选择参考源、设置预分频降低ADC时钟以提升精度配置Timer0为快速PWM模式WGM01:03 COM0A1:02表示非反向输出设置初始预分频和占空比。循环主体读取两个ADC值。为了抑制噪声可以进行软件滤波比如连续采样4次或8次然后取平均值。根据频率ADC值判断预分频系数N是否需要改变。如果改变则更新TCCR0B寄存器。根据占空比ADC值计算并更新OCR0A寄存器。加入适当的延时例如几十毫秒。这个延时决定了你旋转电位器时PWM参数更新的“响应速度”。太短会浪费CPU资源且可能使ADC采样不稳定太长则感觉操作迟钝。实测50-100ms的延时手感不错。心得在ADC采样函数中启动转换后一定要等待转换完成而不是依赖延时。检查ADSC位是否清零是最可靠的方法。此外第一次ADC转换结果往往不准可以在初始化后进行一次丢弃的读取。4. 实测验证、数据对比与误差分析电路在面包板上搭建完成后我用一台LabNation的智能示波器进行了实测。将PWM输出接入示波器缓慢旋转两个电位器观察频率和占空比的变化。4.1 频率实测数据与理论对比预分频系数 N理论频率 (Hz)实测频率 (Hz)相对误差137,50038,1001.6%84,687.54,7300.9%64586.05941.4%256146.481481.0%102436.6237.11.3%可以看到实测频率普遍略高于理论值误差在1-1.6%之间。这是完全正常且可接受的。主要原因在于ATtiny13的内部RC振荡器精度。虽然出厂时经过校准但其典型精度为±10%在-40°C到85°C的温度范围内漂移可达±10%。我们测得的误差远小于此说明这片芯片的振荡器还算比较准。对于PWM调光、电机控制等应用这个级别的误差毫无影响。如果需要精确的频率必须外接晶振。4.2 占空比范围与线性度测试旋转多圈电位器观察占空比从最小到最大的变化最小占空比当ADC值接近0时OCR0A被设置为0。在快速PWM模式下输出保持常低。但实际由于ADC零漂和噪声最小能稳定维持的占空比约为0.4%对应OCR0A1。最大占空比当ADC值接近1023时OCR0A被设置为254。此时输出高电平时间为254/256 ≈ 99.6%。为什么不是100%因为如果设置OCR0A255在快速PWM模式下根据数据手册输出比较匹配的行为可能特殊可能常高或产生一个极窄的低脉冲。为了获得规整的、可预测的PWM波我们通常避免使用255这个值。因此99.6%是实际可用的最大占空比对于绝大多数应用已完全足够。线性度由于ADC是10位PWM分辨率是8位且我们做了线性映射所以整个调节过程线性度非常好。旋转电位器时示波器上显示的占空比百分比是均匀变化的。4.3 波形质量观察在37.5kHz的高频下方波的上升/下降沿依然清晰锐利没有明显的振铃或圆角这得益于ATtiny13的IO口驱动能力和面包板较短的连线。在低频下波形更是完美。输出直接驱动一个LED进行测试亮度变化平滑无闪烁感在高于100Hz的频率下。5. 常见问题、调试技巧与扩展思路5.1 搭建与调试中可能遇到的问题无输出或输出常高/常低检查电源和接地用万用表测量VCC和GND之间是否为稳定的5V。检查熔丝位确认是否已正确编程特别是关闭了时钟分频CKDIV8。如果这个熔丝位使能系统时钟会是9.6MHz/81.2MHz所有频率都会变成理论值的1/8。检查代码配置确认Timer0是否正确配置为快速PWM模式TCCR0A | (1WGM01) | (1WGM00);以及输出模式是否正确TCCR0A | (1COM0A1);用于非反向输出。检查引脚连接确认PWM输出是否连接到了PB0芯片第5脚。PWM频率不准首要怀疑内部RC振荡器精度。这是固有特性除非更换为外部晶振否则只能接受。检查预分频系数设置代码确保映射逻辑正确写入TCCR0B寄存器的值无误。占空比调节不线性或跳变ADC滤波电容检查ADC输入引脚到地之间的100nF电容是否焊接/插接良好。这是稳定读数的关键。软件滤波在代码中增加ADC采样平均算法例如连续采样4次取平均能有效抑制毛刺。电源噪声确保电源干净。可以尝试用电池供电测试排除开关电源噪声的影响。电位器质量劣质电位器在调节时会有接触噪声导致ADC值跳动。更换为质量好的多圈电位器能极大改善。高频下如37kHz系统不稳定检查去耦电容。在芯片的VCC和GND引脚之间尽可能靠近引脚的地方并联一个100nF陶瓷电容和一个10uF的电解电容为芯片提供瞬时电流。5.2 项目扩展与变体这个简易PWM发生器是一个很好的起点你可以根据需求轻松修改增加频率档位ATtiny13的Timer0也支持相位修正PWM等模式频率计算公式不同F F_CPU / (N * 510)可以得到另一组中间频率。可以通过增加一个拨码开关或按钮让用户在“快速PWM”和“相位修正PWM”模式间切换获得更多频率选择。增加输出通道ATtiny13的Timer0只能输出一路PWMPB0。如果你需要两路同步的PWM例如控制RGB灯中的两个颜色可以考虑使用Timer0的另一种模式让PB1也输出PWM但两路频率相同占空比独立可调。或者升级到ATtiny25/45/85它们有更多的IO和PWM通道。加入数字显示配合一个简单的OLED屏或数码管可以实时显示当前的频率和占空比值更加直观。这需要用到I2C或SPI通信代码会复杂一些。改为固定频率/占空比输出如果用于生产测试可以去掉电位器在代码中固定频率和占空比做成一个超小体积的专用PWM信号模块。提高PWM分辨率ATtiny13的PWM是8位256级。通过启用定时器溢出中断在中断中动态修改OCR0A值可以实现“脉冲密度调制”或更高分辨率的PWM效果但这会消耗大量CPU资源且频率会降低。5.3 从面包板到成品如果这个发生器好用打算长期使用可以考虑将其制作成一个小型PCB模块。在KiCad工程中我已经画好了原理图和PCB。PCB设计时要注意将滤波电容和去耦电容尽量靠近芯片引脚。PWM输出走线可以稍宽以减少输出阻抗。为电源输入和PWM输出设计标准的连接器如XH2.54排针。甚至可以集成一个5V稳压芯片如AMS1117-5.0和USB接口实现USB供电。这个基于ATtiny13的PWM发生器以其极简的设计、低廉的成本和可靠的表现完美地解决了我测试LED阵列的需求。它证明了对于许多嵌入式应用来说解决方案未必需要复杂的芯片和电路深刻理解一颗简单单片机的特性并将其发挥到极致往往就能得到优雅而有效的设计。