现场常见 ISP 能读 ID、程序也能下载,串口波特率却整体差 8 倍,电池设备还偶发上电不启动。结论先给:ATmega168 的时钟是 CKSEL 选源、CKDIV8/CLKPR 分频、OSCCAL 校准、SUT 定启动延时、BOD 管低压复位五段串联,按手册四张表对着配,就不会出现"下载成功但不工作"。
新板子焊好,ISP 能读出 ID,程序也能下载,串口波特率却整体偏了 8 倍,延时函数跟着一起错;实验室用 USB 供电一切正常,装到电池设备上偶尔上电不启动。这类"程序没错、就是不对劲"的问题,十有八九出在 AVR 的时钟链上。结论先给:ATmega168 的时钟是 CKSEL 选源、CKDIV8/CLKPR 分频、OSCCAL 校准、SUT 定启动延时、BOD 管低压复位这五段串联,任何一段配错,现象都像软件 bug。
ATmega48/88/168 的时钟源全部由熔丝位 CKSEL3..0 决定,数据手册 Table 4 给了完整编码。真正需要工程师拿主意的,是三个晶振档位之间的差别:
| 时钟选项 | CKSEL3..0 | 手册要点 | 适合场景 |
|---|---|---|---|
| 低功耗晶振 | 1111~1000 | XTAL2 摆幅低、功耗最低,不能驱动其它时钟输入 | 电池节点、通用 8MHz 晶振 |
| 满振幅晶振 | 0111~0110 | 满幅输出,可驱动外部时钟输入,仅 2.7~5.5V 可用 | 需要对外供时钟、噪声环境 |
| 低频晶振 | 0101~0100 | 32.768kHz 表用振荡器 | 走时、RTC 计时 |
| 内部 128kHz RC | 0011 | 片内低功耗振荡器,标称 128kHz | 极低占空比待机唤醒 |
| 校准内部 RC | 0010 | 8MHz,出厂已标定,无需外部器件 | 省晶振、精度要求 ±1% 以内 |
| 外部时钟 | 0000 | 由外部有源时钟直接驱动 XTAL1 | 板级统一时钟 |
| 保留 | 0001 | 手册标注保留,不要使用 | — |
低功耗晶振是默认推荐:功耗最小,代价是不能拿它去驱动板上别的芯片,噪声环境下也更脆弱。满振幅晶振摆幅到满幅,可以给外部器件当时钟源,但手册限定它只在 Vcc = 2.7~5.5V 可用,1.8V 的电池系统就得换档。两个档位的外部电容 C1、C2 必须相等,手册给的推荐范围是 12~22pF,具体值还要算上 PCB 杂散电容和晶振的负载电容。
还有一个容易忽略的前提:16MHz 不是任何电压都能跑。ATmega48/88/168 在 4.5~5.5V 下才保证 16MHz,2.7~5.5V 下最高 8MHz;V 版本在 2.4~5.5V 下最高 8MHz,1.8~5.5V 下只有 2MHz。先把供电和频率对上,再谈晶振选型,否则熔丝按 16MHz 配好,电池一掉压就等着跑飞。
很多"新片子上电就慢吞吞"的板子其实没坏:ATmega48/88/168 出厂时 CKSEL = 0010(校准内部 RC)、SUT = 10、CKDIV8 已编程,系统时钟就是 8MHz 再 8 分频后的 1MHz。CKDIV8 决定 CLKPS 的初始值——未编程是 0000,已编程是 0011,也就是上电即 8 分频。
运行中改频率走 CLKPR,分频因子共九档。写这个寄存器有硬性时序:先把 CLKPCE 置 1 且其余位写 0,然后在随后的 4 个时钟周期内写入 CLKPS 并同时清 CLKPCE,CLKPCE 会在 4 个周期后由硬件自动清除。这 4 个周期内必须关中断,否则一次定时器中断就能把写时序打断,分频值写不进去。
| CLKPS3..0 | 分频因子 | 8MHz 源下的系统时钟 | 典型用途 |
|---|---|---|---|
| 0000 | 1 | 8 MHz | 全速运行 |
| 0001 | 2 | 4 MHz | 降功耗起步档 |
| 0010 | 4 | 2 MHz | 低速采集 |
| 0011 | 8 | 1 MHz | 出厂默认 |
| 0100 / 0101 | 16 / 32 | 500 / 250 kHz | 传感器轮询 |
| 0110 / 0111 | 64 / 128 | 125 / 62.5 kHz | 低占空比待机 |
| 1000 | 256 | 31.25 kHz | 极限省电 |
改分频之前还有一条约束:如果选的时钟源超过了器件在该电压下的最大频率,一定要编程 CKDIV8 或软件分频,手册原文是"必须保证所得时钟符合器件的频率要求"。
校准内部 RC 在 3V、25℃ 下的标称频率是 8.0MHz,复位时硬件把校准字节自动装进 OSCCAL 寄存器,标称精度 ±1%。可调范围是 7.3~8.1MHz:CAL7 选择低端还是高端频率段,CAL6..0 在段内调节,每加 1 频率增加不到 2%。手册特别提醒,这个振荡器同时给 EEPROM 和 Flash 的写操作定时,要写 Flash 或 EEPROM 就不要把频率校到 8.8MHz 以上,否则写操作会失败。这条在产线做 OSCCAL 微调时最容易被忽略。
复位源消失后芯片并不会立刻跑程序,而是先等一个由看门狗振荡器计时的 tTOUT,周期数由 SUT 与 CKSEL 共同决定。手册 Table 5 给的典型值是:4K 个周期在 5V 下约 4.1ms,8K 个周期约 65ms(3V 下分别是 4.3ms 和 69ms)。
| SUT1..0 | 内部 RC 启动时间 | 复位后额外延时 | 说明 |
|---|---|---|---|
| 00 | 6 CK | 14CK | 电源快速上升,且有 BOD 兜底 |
| 01 | 6 CK | 14CK + 4.1ms | 电源快速上升,但无 BOD |
| 10 | 6 CK | 14CK + 65ms | 电源缓慢上升,出厂默认 |
| 11 | — | 保留 | 不要使用 |
这张表的用法是反的:先量电源上升时间,再选延时。上升快的板子选 14CK 就够;电池供电、大电容缓升的板子必须选 14CK + 65ms。如果为了省电把 BOD 关了又不留超时延时,手册直接写"不推荐"——延时期间 MCU 并不监控实际电压,没有 BOD 兜底就没有任何东西保证 Vcc 已经够高。
另外两条细节:RSTDISBL 熔丝被编程后,启动时间会强制增加到 14CK + 4.1ms,目的是留出进入编程模式的时间;用外部有源时钟时,工作频率突变超过 2% 会产生异常,要改频率就让 MCU 保持在复位状态再改。
BOD 的触发电平由 BODLEVEL2..0 三个熔丝位决定:
| BODLEVEL2..0 | 触发电平 | 说明 |
|---|---|---|
| 111 | BOD 禁用 | 靠外部复位芯片或上电时序兜底 |
| 110 | 1.8V | 1.8V 供电系统 |
| 101 | 2.7V | 3.3V 系统,留出约 0.6V 余量 |
| 100 | 4.3V | 5V 系统 |
| 011~000 | 保留 | 不要使用 |
BOD 比较器带约 50mV 的典型迟滞,Vcc 跌破 VBOT- 立即复位,回到 VBOT+ 以上再等一个 tTOUT 才恢复。要注意 BOD 是"常开"的:一旦使能,在各种休眠模式下都继续工作并消耗电流,深睡时这部分电流占比很高。电池节点要么选 1.8V 档并接受这点电流,要么彻底关掉 BOD 改用外部复位芯片,两头都想占通常两头都不讨好。
复位原因靠 MCUSR 区分:WDRF 看门狗、BORF 掉电、EXTRF 外部复位、PORF 上电(PORF 只能写 0 清除)。正确做法是在初始化最前面把 MCUSR 读进变量再清零,后面慢慢判断;如果先清零再读,一次叠加复位就把原因冲掉了。
熔丝位这一块有几个坑必须记住:熔丝位读回 0 表示"已编程",和很多人的直觉相反;芯片擦除不会改变熔丝位;LB1 锁定位一旦编程,熔丝位就被锁死,所以永远先写熔丝位、最后再上锁定位。SPIEN 出厂已编程,串行编程模式下这个位不可访问;EESAVE 决定芯片擦除时 EEPROM 内容是否保留;WDTON 编程后看门狗变成"总是使能",只能改溢出时间、不能关。
改分频和取复位原因这两件事都要注意时序与顺序,下面这段代码是可直接用的最小实现:
#include <avr/io.h>
#include <avr/interrupt.h>
/* 把系统时钟从出厂 1MHz 切到 8MHz:只改分频,不改时钟源 */
void clock_init_8m(void)
{
uint8_t sreg = SREG;
cli(); /* 4 个周期窗口内不能被打断 */
CLKPR = (1 << CLKPCE); /* 只置 CLKPCE,其余位写 0 */
CLKPR = 0x00; /* CLKPS = 0000,分频因子 1 */
SREG = sreg;
}
/* 复位原因要在初始化最前面取走,再清零 */
uint8_t reset_cause(void)
{
uint8_t flags = MCUSR;
MCUSR = 0x00;
/* bit3 WDRF, bit2 BORF, bit1 EXTRF, bit0 PORF */
return flags;
}
| 现象 | 优先怀疑 | 快速核实 |
|---|---|---|
| ISP 能读 ID,程序不跑 | SUT 延时太短或 BOD 档位与供电不匹配 | 示波器看 Vcc 上升时间,对照 SUT 表 |
| 串口波特率整体差固定倍数 | CKDIV8 或 CLKPR 分频没改 | 读 CLKPR,确认 CLKPS 实际值 |
| 波特率小幅漂移、温度一变就错 | 内部 RC 未标定或温漂 | 改外部晶振,或重新标定 OSCCAL |
| 改完熔丝再也连不上 | RSTDISBL 或 SPIEN 被改动、锁定位已上 | 用高压并行编程器恢复 |
| 电池设备深睡电流偏大 | BOD、看门狗常开 | 逐项关断后用万用表对比 |
| 16MHz 下偶发跑飞 | 供电低于 4.5V | 降频或改用 V 版的电压/频率档 |
一句话总结:AVR 的时钟问题几乎都能归结成一句话——先看供电允许多高频率,再选时钟源和分频,最后用 SUT 和 BOD 把上电这段"没人管"的时间兜住。四张表对着配,熔丝位按"先功能、后锁定"的顺序写,新板子就不会再出现"下载成功但不工作"的怪现象。
还想接着看:外部晶振电路与负载电容怎么算、STM32 时钟失效检测 CSS 怎么配、LVD/AVD 低压检测与安全关机。
本文基于 Atmel ATmega48/88/168 中文数据手册(Rev. 2545A–AVR–09/03)的时钟系统、系统控制与复位、熔丝位三章整理优化。
沧州艾诺威电子 — 国家高新技术企业,20+项国家专利
嵌入式系统开发 · 物联网方案 · AI智能硬件 · 一站式交付
电话:13930711029 | 邮箱:tech@czinv.com | 24小时内响应