简介:面向嵌入式初学者与小型控制项目开发者的ATtiny13开发板资料包,围绕这款8引脚AVR超小型单片机(1KB闪存、64B SRAM、128B EEPROM、内置8MHz RC振荡器)组织内容,可用于智能家居节点、物联网终端与教学实验等低功耗微型控制场景。包内共237个文件,以C语言源码、汇编与工程构建文件(.c、.mak、.prj、.hex)、编译中间产物(.o、.lst、.lis、.cof)以及2份PDF为主,另有若干工程目录与压缩子包,涵盖LED闪烁、按键检测、定时器、PWM调光、ADC采样等实例,便于对照原理图逐项验证。其中开发板原理图说明电源管理、编程接口与扩展电路,中文数据手册覆盖寄存器、指令集与应用注意事项,ISP及ArduinoISP下载方式也有相应工程可供参考。已有280人学习下载,适合希望从点亮第一个LED到独立完成小型嵌入式设计的读者系统查阅。
1. 一块 1KB 闪存的 8 脚芯片,凭什么还值得单独打一版板子
去年帮朋友改一个小夜灯方案,要求是插在 5V 充电头上就能跑、BOM 成本压到一块多、板子塞进直径 25 毫米的圆壳里。选型绕了一圈,能同时满足「8 引脚封装」「内置振荡器不用外挂晶振」「5.5V 以内宽压供电」「单价几毛」这几条的,选项其实不多。ATtiny13 就是其中之一:1KB 闪存、64 字节 SRAM、64 字节 EEPROM,出厂默认 1.2MHz 内部 RC 时钟,8 个脚里 6 个能当 IO。
它的能力边界得先摆明白。没有硬件串口,没有硬件 SPI/I2C 主机,SRAM 少到连一个 64 字节的接收缓冲都放不下。所以拿它做通信网关、做屏幕驱动、做需要浮点运算的场合,基本是自找麻烦。它真正的甜区是替代 555 定时器、替代一堆分立逻辑门、做单键开关机、做 PWM 调光、做低占空比的传感器唤醒采集——一句话,做那些「逻辑简单但要极小极便宜」的活。
后面这条路径按「先看清资源边界 → 再搭烧录链路 → 写裸机代码 → 排掉那批反复出现的坑」铺开,新手上手能照着复现,熟手能对着参数和边界判断手上这活到底该不该用它。
2. 引脚、时钟和 64 字节 SRAM:ATtiny13 的资源到底紧到什么程度
很多人第一次拿到 ATtiny13 开发板,看原理图上标着 PB0 到 PB5 六个 IO,就默认这六个都能随便用。真跑起来才发现:有一个脚改完配置就再也烧不进去,有一个脚被 ISP 占着,还有两个脚做 ADC 的时候会跟别的东西打架。这一章先把这几个硬约束说死,后面写代码才不至于边写边返工。
2.1 8 个引脚里真正能给你用的有几个
DIP-8 封装的 ATtiny13 引脚排列是固定的:1 脚 PB5/RESET,2 脚 PB3,3 脚 PB4,4 脚 GND,5 脚 PB0/MOSI,6 脚 PB1/MISO,7 脚 PB2/SCK,8 脚 VCC。SOIC-8 封装编号一致,只是封装形式不同。
PB5 是第一个陷阱。它出厂就是复位脚,内部带上拉,正常状态下不能当普通 IO 用。想把它变成 PB5 数字 IO 或者 ADC0 通道,必须烧写RSTDISBL熔丝位。这个位一旦编程,常规的 ISP 烧录通道就彻底关闭了——因为 ISP 进编程模式靠的正是把 RESET 拉低到 12V 以下再发命令,复位功能没了,ISP 也就没了。恢复只能靠高压串行编程(HVSP)那套专门的时序和 12V 电源,普通 USBasp 干不了这活。所以除非板子上预留了 HVSP 排针,否则不建议第一次就动这个位。
PB3 和 PB4 的隐藏身份是晶振脚。默认内部 RC 时钟配置下,它们就是普通 IO,可以接 LED、接按键、接继电器。但只要把熔丝位改成外部晶振模式,这两个脚立刻被 XTAL1/XTAL2 占用,再想当 IO 用就没门了。8 脚封装本来就缺脚,把仅剩的两个换成晶振,实际可用的 IO 会掉到 4 个,这个账要提前算。
PB0、PB1、PB2 是 ISP 三根线:MOSI、MISO、SCK。烧录的时候它们被编程器占着,烧完之后完全释放,可以正常当 IO。但要留个心眼——如果这三个脚上外接了强上拉或者强下拉(比如直接驱动继电器线圈),烧录时编程器可能拉不动,表现为「有时候能烧有时候烧不进」。
PB2 是唯一的外部中断脚(INT0)。做按键唤醒、做旋转编码器计数,基本只能落在它身上。PB1 和 PB0 分别是定时器 0 的 OC0B 和 OC0A 输出,做硬件 PWM 只能从这两个脚出。
把 ADC 通道也一起理清:ADC0 对应 PB5,ADC1 对应 PB2,ADC2 对应 PB4,ADC3 对应 PB3。注意 ADC0 和复位脚是同一个物理脚,ADC1 和 SCK 是同一个物理脚。所以「用 PB2 做 ADC 采集 + 同时用 PB2 做 ISP 烧录」这件事不冲突,但「一边烧录一边采」就别想了。
汇总成一张表:
| 物理脚 | 端口 | 默认功能 | 复用能力 | 使用代价 |
|---|---|---|---|---|
| 1 | PB5 | RESET | ADC0、数字 IO | 需烧 RSTDISBL,烧完 ISP 失效 |
| 2 | PB3 | 数字 IO | ADC3、XTAL1 | 改外部晶振后不可用 |
| 3 | PB4 | 数字 IO | ADC2、XTAL2 | 改外部晶振后不可用 |
| 4 | GND | 地 | — | — |
| 5 | PB0 | 数字 IO | MOSI、OC0A | 烧录时被占用 |
| 6 | PB1 | 数字 IO | MISO、OC0B | 烧录时被占用 |
| 7 | PB2 | 数字 IO | SCK、ADC1、INT0 | 烧录时被占用 |
| 8 | VCC | 电源 | — | — |
实际做产品的时候,我一般把 PB5 保留成复位脚不动,PB3/PB4 当普通 IO 用,PB0/PB1 留给 PWM 输出,PB2 留给按键中断或者 ADC。这样六个脚里能用满五个,剩下一个当保险。
2.2 内部 RC 时钟与 CKDIV8:为什么延时总差 8 倍
ATtiny13A 出厂默认的低熔丝位是0x6A。这个值的含义是:时钟源选内部 9.6MHz RC 振荡器,同时CKDIV8分频器处于使能状态,9.6MHz 被除以 8,系统时钟实际跑在 1.2MHz。
这就解释了新手最常撞上的那个现象:代码里写_delay_ms(1000),拿秒表一掐,实际延迟了 8 秒。原因不是_delay_ms不准,而是编译时的F_CPU宏写的是 9600000,芯片实际只跑 1200000,所有基于时钟周期的延时、波特率、PWM 频率全部按 8 倍比例缩短或拉长。
要解决只有两条路。一条是把F_CPU老老实实写成 1200000UL,接受 1.2MHz 的低速运行;另一条是把低熔丝位改成0x7A,关掉CKDIV8,让系统时钟跑满 9.6MHz。改熔丝位之后必须断电重新上电才生效,这一点经常被忽略——改完直接烧程序,程序还是按旧时钟跑,人会以为是熔丝没写进去。
内部 RC 的精度得说清楚。出厂校准条件是 25℃、3V 供电,标称偏差在 ±10% 这个量级。温度每变化几十度、供电电压每变化 1V,频率都会跟着漂。所以:
- 做按键消抖、做 PWM 调光、做几秒量级的定时,完全够用;
- 做软件模拟串口、做红外接收解码这类对时序敏感的活,基本会翻车;
- 做精确计时(比如做时钟),必须外挂 32.768kHz 晶振或者用外部时钟源。
有些人看到「支持最高 20MHz」就想超频。ATtiny13A 的速度等级是按供电电压分档的:1.8V 时最高 4MHz,2.7V 时最高 9.6MHz,4.5V 以上才能跑 20MHz。3.3V 供电下强行设 20MHz,芯片能起来,但高温或者电压跌落时会随机死机,这种玄学问题排查起来能耗掉一整天。
2.3 64 字节 SRAM 与 1KB 闪存的分配账
64 字节 SRAM 是什么概念?一个float占 4 字节,一个uint16_t占 2 字节,函数调用一次压栈要吃掉 2 到 4 字节(返回地址加保存的寄存器)。稍微写复杂一点,栈就能把全局变量区踩穿,表现是变量莫名其妙变成 0 或者随机值——这就是典型的栈溢出,而且不会报错,纯靠现象猜。
几条硬性习惯,照着做能少掉很多头发:
第一,不要用printf和任何浮点运算。printf光格式化那套代码就能吃掉几百字节 flash,运行时还要几十字节栈,1KB 闪存的芯片根本扛不住。需要输出调试信息的时候,用「点亮某个 LED 表示走到哪一步」这种最原始的办法。
第二,能用uint8_t就别用int。AVR 是 8 位机,int是 16 位,所有算术都要走两步。更重要的是 RAM 占用直接翻倍。
第三,缓冲区能省则省。想做一个 16 个采样的滑动平均?16 个uint8_t就是 16 字节,占掉 SRAM 的四分之一。要么改成 8 个采样,要么改成「指数加权平均」,只存一个累加值。
第四,函数调用层级不要太深。每层调用至少吃 2 字节栈,嵌套四五层就是十几个字节没了。
代码体积这边,给几个实测参考值(avr-gcc -Os编译,不含库):
| 功能组合 | 大致 flash 占用 |
|---|---|
纯 GPIO 闪灯 +_delay_ms | 150 ~ 250 字节 |
| 加定时器 PWM 呼吸灯 | 300 ~ 400 字节 |
| 再加一路 ADC 采样 + 简单滤波 | 500 ~ 700 字节 |
| 再加掉电睡眠 + 看门狗唤醒 | 700 ~ 900 字节 |
| 软件 I2C 从机 + 上述全部 | 接近或超过 1024 字节 |
最后一行不是危言耸听。软件模拟 I2C 从机要处理起始条件、地址匹配、ACK、时钟拉伸,代码量很容易上 300 字节。1KB 闪存看着还有点余量,实际写完一编译就爆。
编译完顺手查一下占用,是个必做动作:
# 编译完立刻看 flash / RAM 占用,别等烧进去才发现爆了 avr-size --format=avr --mcu=attiny13a firmware.elf输出里Program那一行是 flash,Data那一行是 RAM。经验值是 flash 用到 90% 以上就开始出现「加一行代码就编译不过」的诡异现象,RAM 用到 70% 以上就要警惕栈溢出。留 10% 余量是底线,不是浪费。
3. 搭一条可靠的 ATtiny13 烧录链路:ISP 接线、熔丝位与 avrdude 命令
资源账算清楚,接下来就是把这颗芯片跟电脑连起来。ATtiny13 没有 USB、没有 bootloader,唯一的入口就是 ISP。这条链路只要有一根线接反、一个时钟参数没设对,就会表现为「编程器识别不到芯片」,而且报错信息极其含糊,不给任何方向。这一章把接线、命令序列和 Arduino IDE 的挂载方式一次讲全。
3.1 用 USBasp 或另一块开发板当 ISP 的接线对照表
ISP 接口标准是 6 根线:MISO、MOSI、SCK、RESET、VCC、GND。USBasp 那一头是标准的 10 针或 6 针排针,ATtiny13 这一头要按物理引脚对应接过去。
对照表:
| ISP 信号 | USBasp 6 针定义 | ATtiny13 物理脚 | 端口 |
|---|---|---|---|
| MOSI | 第 4 针 | 5 | PB0 |
| MISO | 第 1 针 | 6 | PB1 |
| SCK | 第 3 针 | 7 | PB2 |
| RESET | 第 5 针 | 1 | PB5 |
| VCC | 第 2 针 | 8 | VCC |
| GND | 第 6 针 | 4 | GND |
最容易接反的是 MOSI 和 MISO。名字看起来对称,实际是一进一出,接反了编程器能识别到「有东西在线」,但每次读签名都读回0x000000。判断方法很简单:读签名读不到就先把这两根对调试一次。
供电方式要选一种,不要两边同时供。USBasp 板上通常有个跳线帽,短接表示由编程器给目标板供电(3.3V 或 5V),断开表示目标板自己供电。如果目标板已经有独立电源,跳线帽必须断开,否则两路电源顶在一起,轻则编程器保护,重则把芯片的 VCC 脚打穿。
目标板 VCC 上如果有大电容,烧录时容易失败。USBasp 的供电能力有限(通常 100mA 以内),如果板子上挂了一颗 100μF 以上的电解电容,上电瞬间会把电压拉低,编程器还没进入编程模式就被判定为「目标无响应」。解决办法是烧录时把电容焊掉,或者用外部稳压电源单独供电,只把 GND 和 RESET 跟编程器共起来。
如果手边没有 USBasp,用一块 Arduino 板子刷成 ISP 也能干活。常见做法是把 Arduino 板烧上ArduinoISP示例程序,然后把它的 D11/D12/D13/D10 分别接到目标芯片的 MOSI/MISO/SCK/RESET。这种方案的好处是不用额外买件,坏处是 ISP 时钟比较慢,烧 1KB 的芯片要等几秒钟,而且有些核心包的烧录脚本对 ArduinoISP 的兼容性一般。
注意:不管用哪种编程器,目标芯片的 VCC 一定要先量一下。很多人接线接对了、命令敲对了,就是烧不进去,最后发现是目标板上的稳压芯片坏了,实际只有 1.2V。万用表这一步不能省。
3.2 avrdude 读芯片、改熔丝位、烧写 hex 的完整命令序列
avrdude是 AVR 生态里通用的烧录工具,绝大多数图形界面工具底层调的都是它。直接敲命令虽然原始,但每一步做了什么清清楚楚,出问题好定位。
第一步,先只读不写,确认链路通:
# -c 指定编程器型号,-p 指定芯片型号 # -B 8 把 ISP 时钟周期拉长到 8 微秒(约 125kHz) # -n 表示 dry run,只读不写,用来验证接线 avrdude -c usbasp -p t13 -B 8 -n命令跑通会打印芯片签名1e 90 07,这是 ATtiny13 的固件签名。读不到签名就说明接线、供电或者 ISP 时钟三者有问题,先解决这个再往下走。
第二步,把当前熔丝位读出来并抄在纸上:
# -U 后面跟的是「存储区:操作:数据:格式」 # lfuse:r:-:h 表示读低熔丝位,输出到标准输出,十六进制格式 avrdude -c usbasp -p t13 -B 8 -U lfuse:r:-:h -U hfuse:r:-:h出厂芯片应该读回0x6A和0xFF。这两个值必须记下来,因为改坏了要靠它恢复。
第三步,按需改时钟。把系统时钟从 1.2MHz 提到 9.6MHz:
# 把低熔丝位从 0x6A 改成 0x7A,即关闭 CKDIV8 分频 avrdude -c usbasp -p t13 -B 8 -U lfuse:w:0x7A:m:w:表示写,:m表示立即模式(直接给数值)。写完必须断电再上电,新的时钟配置才生效。
如果要改成 4.8MHz 内部 RC,低熔丝位用0x79;如果要改成 128kHz 超低功耗内部 RC,用0x6B。这三个值是第三方核心包板级定义里最常见的三档。
第四步,烧写编译好的 hex 文件:
# -U flash:w:...:i 表示写 flash,格式是 Intel HEX avrdude -c usbasp -p t13 -B 8 -U flash:w:firmware.hex:i烧完 avrdude 会自动回读校验,输出里出现avrdude done. Thank you.才算成功。如果出现verification error,说明读到的不等于写入的,通常是供电不稳或者 ISP 线太长(超过 20 厘米就开始不稳定)。
第五步,EEPROM 内容单独备份。ATtiny13 的 EEPROM 是独立存储区,烧 flash 的时候不会碰它:
# 把 EEPROM 全部内容读出来存成 hex,方便后面比对 avrdude -c usbasp -p t13 -B 8 -U eeprom:r:eeprom_backup.hex:i参数说明汇总一下:
-c usbasp:编程器类型。用 ArduinoISP 就写-c arduino,用别的编程器要换成对应的名字。-p t13:芯片型号。ATtiny13A 也写t13,avrdude 内部把它当attiny13a处理。-B 8:ISP 时钟周期,单位微秒。数值越大时钟越慢。芯片主频 1.2MHz 时至少要-B 8,主频改到 9.6MHz 之后可以降到-B 2加快烧录速度。-n:dry run,只执行不写入。-U 区:操作:数据:格式:区可以是flash、eeprom、lfuse、hfuse、lock;操作是r(读)或w(写);格式是i(Intel HEX)、h(十六进制)、m(立即数)、b(二进制)。
3.3 在 Arduino IDE 里挂第三方核心,写第一段闪灯程序
不想碰命令行的人可以走 Arduino 这条路。官方 IDE 本身不带 ATtiny13 的板级定义,需要挂第三方核心。
流程是:打开 IDE 的「首选项」,在「附加开发板管理器网址」里加一条第三方 ATtiny 核心的索引地址(具体地址按你选的核心包来,不同维护者各有一份),然后在「开发板管理器」里搜 ATtiny 安装。装完之后在「工具 → 开发板」里能选到 ATtiny13/13A。
关键设置有三项:
- Chip:选 ATtiny13A。
- Clock:选 9.6MHz internal osc.(前提是熔丝位已经改成 0x7A;如果没改就选 1.2MHz internal osc.)。
- Programmer:选 USBasp 或 Arduino as ISP。
这三项必须跟芯片实际状态一致。选错了不会报错,只会表现为「程序烧进去了但不跑」。
第一段程序,物理脚 6(PB1)接一颗 LED 到 GND:
// 目标:ATtiny13A,9.6MHz 内部 RC,PB1 驱动 LED 闪烁 // 核心包里的引脚编号 0~5 对应 PB0~PB5,所以这里的 1 就是 PB1 void setup() { pinMode(1, OUTPUT); // 把 PB1 配置为推挽输出 } void loop() { digitalWrite(1, HIGH); // PB1 输出高电平,LED 点亮 delay(500); digitalWrite(1, LOW); // PB1 输出低电平,LED 熄灭 delay(500); }逻辑说明:pinMode内部操作的是DDRB寄存器,把对应位置 1;digitalWrite操作的是PORTB寄存器。delay(500)依赖核心包根据你选的时钟自动定义的F_CPU宏,所以时钟选项必须选对,否则 500 毫秒会变成 4 秒。
烧录时点「项目 → 使用编程器上传」,IDE 会调用 avrdude 完成擦除、写入、校验三步。第一次烧录如果报program enable失败,把 IDE 的「烧录器」设置里那个「上传速度」调慢一点(对应命令行就是加大-B值)。
LED 串联电阻取值:5V 供电、普通红色 LED 压降约 1.8V、目标电流 5mA,算下来是(5 - 1.8) / 0.005 = 640Ω,取 680Ω 或者 1kΩ 都行。ATtiny13 单个 IO 的最大灌电流和拉电流都是 40mA,但整颗芯片所有 IO 加起来不能超过 200mA,所以不要同时驱动太多负载。
4. 在 1KB 里写裸机代码:GPIO、PWM、ADC 与掉电睡眠
Arduino 核心包省事,但它本身要吃掉一部分 flash 和 RAM。1KB 的芯片上,核心包占掉 200 到 400 字节之后,留给业务逻辑的空间就很紧张了。真要做产品,直接写裸机寄存器代码更划算——代码小、时序可控、出了问题能直接对着数据手册定位。这一章给三段可以直接抄的裸机代码。
4.1 定时器 0 做 PWM 呼吸灯:OCR0A 与预分频怎么定
ATtiny13 只有一个 8 位定时器(定时器 0),它同时承担延时、PWM 输出、溢出中断三个职责。PWM 频率由三个量决定:系统时钟、预分频系数、计数上限。
计算公式是:f_pwm = f_cpu / (预分频 × 256)(快速 PWM 模式下计数上限固定 255)。
9.6MHz 系统时钟,预分频选 8:9.6e6 / (8 × 256) = 4687.5Hz。这个频率人耳听不到,LED 上看不到闪烁,是比较理想的取值。如果预分频选 1,频率是 37.5kHz,对 LED 来说没必要,反而增加开关损耗;选 64 的话频率掉到 586Hz,摄像头拍出来会有横纹。
#include <avr/io.h> #include <util/delay.h> int main(void) { // PB0 配置为输出(它同时是 OC0A,硬件 PWM 输出脚) DDRB |= (1 << PB0); // TCCR0A: COM0A1 <p> <a href="https://download.csdn.net/download/jkd_405/9943455" style="color:#ec7500;font-size:14px;"> 本文还有配套的精品资源,点击获取 </a> <img alt="menu-r.4af5f7ec.gif" src="https://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif" style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;"> </p>