1. 项目概述与核心需求
手头有个项目,需要测试一个水下LED灯用的灯珠阵列。这个阵列由10颗红色CREE XP-E2(每颗1W,75流明)和5颗暖白色Cree XM-L(每颗3W,220流明)组成,密密麻麻地挤在一块直径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计数到255(8位模式),然后复位回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引脚到地之间接一个100nF(0.1uF)的电容C1。这个电容至关重要,它能滤除电位器滑动时产生的噪声和来自数字电路的干扰,保证ADC采样值的稳定,避免PWM输出因输入噪声而抖动。
2.3 电路连接与电源设计
整个电路极其简洁:
- 电源(J1):直接接入5V直流电源。ATtiny13V的工作电压范围是1.8-5.5V,5V是常见且稳定的选择。
- PWM输出(J2, J3):我将Timer0的PWM输出引脚(PB0, 芯片第5脚)同时引到了两个排针上,方便同时连接示波器观察波形和驱动后续的LED驱动电路。
- ADC输入:占空比电位器R1的滑动端接PCINT1(ADC1, 芯片第7脚)。频率电位器R2的滑动端接PCINT0(ADC0, 芯片第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个理论频率档位:
- N=1: 9.6MHz / (1 * 256) = 37.5 kHz
- N=8: 9.6MHz / (8 * 256) = 4.6875 kHz
- N=64: 9.6MHz / (64 * 256) = 586.0 Hz
- N=256: 9.6MHz / (256 * 256) = 146.48 Hz
- N=1024: 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%(但注意,当OCR0A=255时,有些模式下输出可能不翻转,我们通常使用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,即芯片的VCC(5V)。这意味着电位器滑动端的电压范围是0-5V,对应ADC读数0-1023。这种设置最简单,但要求电源电压稳定。如果VCC波动,ADC读数和实际的PWM占空比都会随之漂移。对于精度要求极高的场合,可以考虑使用芯片内部的1.1V基准,但需要重新设计电位器的分压电路。
3.3 主程序逻辑与优化
主程序是一个简单的超级循环(super loop):
- 初始化:配置ADC(使能、选择参考源、设置预分频降低ADC时钟以提升精度),配置Timer0为快速PWM模式(WGM01:0=3, COM0A1:0=2表示非反向输出),设置初始预分频和占空比。
- 循环主体:
- 读取两个ADC值。为了抑制噪声,可以进行软件滤波,比如连续采样4次或8次然后取平均值。
- 根据频率ADC值,判断预分频系数N是否需要改变。如果改变,则更新TCCR0B寄存器。
- 根据占空比ADC值,计算并更新OCR0A寄存器。
- 加入适当的延时(例如几十毫秒)。这个延时决定了你旋转电位器时,PWM参数更新的“响应速度”。太短会浪费CPU资源且可能使ADC采样不稳定,太长则感觉操作迟钝。实测50-100ms的延时手感不错。
心得:在ADC采样函数中,启动转换后一定要等待转换完成,而不是依赖延时。检查ADSC位是否清零是最可靠的方法。此外,第一次ADC转换结果往往不准,可以在初始化后进行一次丢弃的读取。
4. 实测验证、数据对比与误差分析
电路在面包板上搭建完成后,我用一台LabNation的智能示波器进行了实测。将PWM输出接入示波器,缓慢旋转两个电位器,观察频率和占空比的变化。
4.1 频率实测数据与理论对比
| 预分频系数 N | 理论频率 (Hz) | 实测频率 (Hz) | 相对误差 |
|---|---|---|---|
| 1 | 37,500 | 38,100 | +1.6% |
| 8 | 4,687.5 | 4,730 | +0.9% |
| 64 | 586.0 | 594 | +1.4% |
| 256 | 146.48 | 148 | +1.0% |
| 1024 | 36.62 | 37.1 | +1.3% |
可以看到,实测频率普遍略高于理论值,误差在1-1.6%之间。这是完全正常且可接受的。主要原因在于ATtiny13的内部RC振荡器精度。虽然出厂时经过校准,但其典型精度为±10%,在-40°C到+85°C的温度范围内漂移可达±10%。我们测得的误差远小于此,说明这片芯片的振荡器还算比较准。对于PWM调光、电机控制等应用,这个级别的误差毫无影响。如果需要精确的频率,必须外接晶振。
4.2 占空比范围与线性度测试
旋转多圈电位器,观察占空比从最小到最大的变化:
- 最小占空比:当ADC值接近0时,OCR0A被设置为0。在快速PWM模式下,输出保持常低。但实际由于ADC零漂和噪声,最小能稳定维持的占空比约为0.4%(对应OCR0A=1)。
- 最大占空比:当ADC值接近1023时,OCR0A被设置为254。此时输出高电平时间为254/256 ≈ 99.6%。为什么不是100%?因为如果设置OCR0A=255,在快速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/8=1.2MHz,所有频率都会变成理论值的1/8。
- 检查代码配置:确认Timer0是否正确配置为快速PWM模式(
TCCR0A |= (1<<WGM01) | (1<<WGM00);),以及输出模式是否正确(TCCR0A |= (1<<COM0A1);用于非反向输出)。 - 检查引脚连接:确认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只能输出一路PWM(PB0)。如果你需要两路同步的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阵列的需求。它证明了,对于许多嵌入式应用来说,解决方案未必需要复杂的芯片和电路,深刻理解一颗简单单片机的特性,并将其发挥到极致,往往就能得到优雅而有效的设计。