1. 从Arduino到ATtiny85:为什么我们需要这颗“小钢炮”?
如果你玩过Arduino Uno或者Nano,肯定对它们那丰富的引脚和相对宽裕的Flash空间习以为常。但有没有遇到过这样的场景:你只想做一个简单的LED呼吸灯、一个温度传感器数据记录器,或者一个简单的红外遥控发射器,用Arduino Uno来做,感觉就像用牛刀杀鸡,不仅体积大、成本高,而且功耗也完全不在一个量级。这时候,你就需要认识一下今天的主角——ATtiny85。
ATtiny85是Microchip(原Atmel)公司生产的一款8位AVR微控制器。它有多“tiny”?我们拿数据说话:它只有8个引脚(其中5个或6个可用作I/O),8KB的Flash程序存储器,512字节的SRAM,512字节的EEPROM。跟Arduino Uno的32KB Flash和2KB SRAM比起来,它确实是个“小不点”。但正是这种极致的精简,让它成为了嵌入式项目中的“小钢炮”。它的核心优势非常明显:超低功耗(睡眠模式下电流可低至微安级)、超小体积(有DIP-8和SOIC-8两种封装,后者只有指甲盖大小)、极低成本(单价通常只有Arduino主控芯片的几分之一)。
那么,问题来了。我们习惯了用Arduino IDE那种“一键上传”的便捷开发方式,而ATtiny85原生的开发环境是AVR-GCC和AVRdude,需要配置熔丝位、编写Makefile,门槛不低。有没有可能让我们用熟悉的Arduino代码(那些digitalWrite、analogRead、Serial.print)来驱动这颗小芯片呢?答案是肯定的,而且这个过程比你想象的要简单。本文将手把手带你完成两件事:第一,搭建一个用Arduino Uno作为编程器(ISP)来给ATtiny85烧录程序的硬件和软件环境;第二,深入理解在资源受限的ATtiny85上编写Arduino代码时,需要注意的那些“坑”和优化技巧。无论你手头是方便插拔的DIP-8封装,还是需要焊接到板子上的SOIC-8封装,本文的流程都适用。
2. 硬件准备与连接:打造你的专属“微型编程工坊”
要给ATtiny85烧录程序,你需要一个“编程器”。最经济实惠的方案,就是利用你手头已有的Arduino Uno(或类似的AVR板卡,如Nano)来充当这个角色。这个模式在AVR领域被称为“ISP”(In-System Programming),即在线系统编程。Arduino Uno上的主控芯片ATmega328P本身就可以被配置成一个ISP编程器,通过SPI接口向目标芯片(我们的ATtiny85)发送机器码。
2.1 物料清单与核心原理
你需要准备以下硬件:
- 一台Arduino Uno(作为ISP编程器)。
- 一颗ATtiny85芯片(DIP-8或SOIC-8封装均可)。
- 一块无焊面包板(用于DIP封装测试)或一个SOIC-8转DIP-8的适配座(用于焊接好的SOIC芯片编程)。
- 若干杜邦线(公对公)。
- 一个10μF的电解电容或钽电容(可选,但强烈推荐,用于在编程时稳定Arduino Uno的复位信号)。
- 对于SOIC封装:你还需要一个对应的编程夹具或已经将芯片焊接在带有ISP接口的自制PCB上。
其核心连接原理基于SPI总线。在ISP编程中,编程器(Master)通过三根信号线控制目标芯片(Slave):
- MOSI (Master Out Slave In):编程器输出,目标芯片输入。用于发送指令和数据。
- MISO (Master In Slave Out):目标芯片输出,编程器输入。用于读取目标芯片的状态和数据。
- SCK (Serial Clock):时钟信号,由编程器产生,同步数据传输。
- 除此之外,还需要RESET线来控制目标芯片进入编程模式,以及共地(GND)和电源(VCC,通常5V或3.3V)。
ATtiny85的引脚排列(以DIP-8为例,俯视,半圆缺口朝上):
- Pin 1: /RESET (PCINT5)
- Pin 2: (ADC3) PB3
- Pin 3: (ADC2) PB4
- Pin 4: GND
- Pin 5: (MOSI) PB0
- Pin 6: (MISO) PB1
- Pin 7: (SCK) PB2
- Pin 8: VCC
2.2 两种封装的详细接线图
对于DIP-8封装的ATtiny85(使用面包板):这是最简单的情况。将ATtiny85插入面包板,注意方向(缺口朝上)。接线如下:
| Arduino Uno 引脚 | 连接到 ATtiny85 引脚 | 信号线 |
|---|---|---|
| D10 | Pin 1 (RESET) | RESET |
| D11 | Pin 5 (PB0) | MOSI |
| D12 | Pin 6 (PB1) | MISO |
| D13 | Pin 7 (PB2) | SCK |
| 5V | Pin 8 (VCC) | VCC |
| GND | Pin 4 (GND) | GND |
重要提示:在Arduino Uno的RESET引脚和GND之间,接上一个10μF电容的正极(正极接RESET,负极接GND)。这个电容的作用是防止Arduino Uno在编程过程中被自身串口信号意外复位,导致编程失败。这是很多新手容易忽略但至关重要的一步。
对于SOIC-8封装的ATtiny85:SOIC芯片通常已经焊接在定制的小板子(比如流行的“Digispark”开发板核心)上。你需要找到板上引出的ISP接口(通常是6个排针:VCC、GND、RESET、SCK、MISO、MOSI)。连接逻辑与DIP封装完全一致,只是物理接口变成了排针。如果你是自己焊接的裸芯片,那么强烈建议你制作或购买一个SOIC-8转DIP-8的编程座,将芯片放入座中,再按照DIP封装的接线方式连接,这样最稳妥。
连接好硬件后,ATtiny85的VCC和GND就由Arduino Uno供电了。至此,硬件“编程工坊”搭建完毕。
3. 软件环境配置:让Arduino IDE认识ATtiny85
硬件就绪后,我们需要在软件层面让Arduino IDE支持ATtiny85这颗“非官方”芯片。
3.1 安装ATtiny核心支持包
Arduino IDE默认的板卡列表里没有ATtiny85。我们需要通过“开发板管理器”添加第三方核心。
- 打开Arduino IDE,进入“文件” -> “首选项”。
- 在“附加开发板管理器网址”中,输入以下URL(如果你已有其他URL,用逗号分隔):
https://raw.githubusercontent.com/damellis/attiny/ide-1.6.x-boards-manager/package_damellis_attiny_index.json这是由David A. Mellis维护的ATtiny核心,经典且稳定。 - 点击“好”保存,然后打开“工具” -> “开发板” -> “开发板管理器”。
- 在搜索框中输入“attiny”,你会找到“attiny by David A. Mellis”。点击并选择安装最新版本(通常是1.0.2或更高)。
- 安装完成后,关闭开发板管理器。现在,在“工具” -> “开发板”列表中,你应该能看到“ATtiny”系列了。
3.2 将Arduino Uno配置为ISP编程器
接下来,我们需要把连接着ATtiny85的这台Arduino Uno刷写成“Arduino as ISP”固件。
- 用USB线将这台作为编程器的Arduino Uno连接到电脑。
- 在Arduino IDE中,选择开发板为“Arduino Uno”(编程器本身)。
- 选择正确的端口。
- 打开示例代码:“文件” -> “示例” -> “11. ArduinoISP” -> “ArduinoISP”。
- 在代码中,有一个重要的配置项。为了获得更稳定的编程体验,建议找到
#define USE_OLD_STYLE_WIRING这一行(大约在第100行)。取消它的注释(即删除行首的//)。这会让编程器使用更传统的接线方式,兼容性更好。 - 将此代码上传到你的Arduino Uno。上传成功后,这块Uno就变成了一个专用于给其他AVR芯片烧录程序的ISP编程器。
3.3 配置烧录参数与熔丝位
现在,我们来配置如何给目标芯片ATtiny85烧录程序。
保持Arduino Uno通过USB连接电脑。
在Arduino IDE中,“工具”菜单下,进行如下关键设置:
- 开发板:选择“ATtiny25/45/85”。
- 处理器:选择“ATtiny85”。
- 时钟:这是至关重要的一步!ATtiny85内部默认使用1MHz时钟。但为了与Arduino库函数(特别是
delay()和串口软件模拟)的时序兼容,我们通常将其设置为内部8MHz。请选择“Internal 8 MHz”。(如果你使用外部晶振,则选择对应的频率)。 - 编程器:选择“Arduino as ISP”。
- 端口:选择你的Arduino Uno所在的COM端口。
烧录引导程序(Bootloader): 在Arduino语境下,对于ATtiny85,“烧录引导程序”这个操作的实际作用是配置芯片的熔丝位(Fuses),而不是真的烧录一个Bootloader(ATtiny85通常没有空间放Bootloader)。熔丝位决定了芯片的核心配置,如时钟源、启动延时、BOD(掉电检测)电平等。 点击“工具” -> “烧录引导程序”。IDE会通过我们刚才刷写的“Arduino as ISP”编程器,向ATtiny85写入一组与“Internal 8 MHz”时钟匹配的熔丝位配置。 此时,观察Arduino Uno上的指示灯(通常Pin 13的LED)会快速闪烁,表示正在通信。如果一切顺利,IDE底部状态栏会显示“烧录引导程序 完成”。如果报错(如
avrdude: stk500_getsync() attempt X of 10: not in sync),请立即检查:- 所有接线是否牢固,尤其是RESET线。
- 10μF电容是否接在Uno的RESET和GND之间。
- ATtiny85的VCC和GND是否接反。
- 在“烧录引导程序”前,是否正确选择了“编程器”为“Arduino as ISP”。
软件环境至此全部配置完成。你的Arduino IDE已经准备好向ATtiny85编译和上传Arduino风格的代码了。
4. 编写你的第一个ATtiny85“Arduino”程序
环境搭好了,我们来点个灯吧!这是嵌入式世界的“Hello World”。我们将让ATtiny85的PB1引脚(对应Arduino引脚号1,也是物理Pin 6)上的LED闪烁。
4.1 引脚映射:跨越封装的数字游戏
这是从Arduino过渡到ATtiny85的第一个挑战。ATtiny85的物理引脚和Arduino IDE中使用的数字引脚号不是一一对应的,我们需要一张映射表。
基于我们选择的“ATtiny25/45/85”核心,常用的映射关系如下(以芯片内部端口PBx为准):
| 芯片端口 | 物理引脚 (DIP-8) | Arduino IDE 引脚编号 | 模拟输入编号 | 特殊功能 |
|---|---|---|---|---|
| PB0 | 5 | 0 | A0 | MOSI, DI, SDA |
| PB1 | 6 | 1 | A1 | MISO, DO |
| PB2 | 7 | 2 | A2 | SCK, SCL, ADC1 |
| PB3 | 2 | 3 | A3 | ADC3, /RESET (若禁用复位) |
| PB4 | 3 | 4 | A2 | ADC2 |
| PB5 | 1 | 5 | A0 | /RESET (默认), 或作为I/O使用 |
注意:PB5(物理Pin 1)默认是复位引脚(/RESET)。如果你想把它当作普通IO引脚(Pin 5)使用,必须在烧录引导程序前,在“工具” -> “处理器”下选择“ATtiny85 (internal 8 MHz clock, disable BOD, enable EEPROM,reset disabled)”。这会改变熔丝位,永久禁用其复位功能,使其变为普通IO。一旦禁用,你将无法再通过此引脚用ISP方式编程,必须使用高压并行编程器(HVPP)才能恢复,新手请谨慎操作!
在我们的闪烁LED例子中,我们将LED正极通过一个220Ω限流电阻接到PB1(Arduino Pin 1),负极接GND。
4.2 代码编写与上传
在Arduino IDE中新建一个草图,输入以下代码:
void setup() { pinMode(1, OUTPUT); // 将ATtiny85的PB1(Arduino Pin 1)设置为输出 } void loop() { digitalWrite(1, HIGH); // LED亮 delay(1000); // 等待1000毫秒 digitalWrite(1, LOW); // LED灭 delay(1000); // 等待1000毫秒 }代码非常简单,和标准的Arduino程序一模一样。这就是我们想要的效果——用熟悉的语法控制新硬件。
点击左上角的“上传”按钮。注意:这里不是普通的“上传”,而是“通过编程器上传”。IDE会执行以下步骤:
- 编译代码,将Arduino风格的C++代码编译成ATtiny85可执行的机器码(
.hex文件)。 - 调用avrdude工具,通过我们指定的“Arduino as ISP”编程器,将编译好的
.hex文件烧录到ATtiny85的Flash存储器中。 - 烧录完成后,avrdude会发送一个命令让ATtiny85复位并开始运行新程序。
观察你的ATtiny85电路,连接在Pin 6(PB1)上的LED应该开始以1秒的间隔稳定闪烁了!恭喜你,你已经成功用Arduino的方式驾驭了这颗微型芯片。
5. 深入核心:ATtiny85上Arduino库的局限性与优化实战
让LED闪烁只是第一步。当你试图把更复杂的Arduino项目移植到ATtiny85上时,会立刻撞上它的资源墙。8KB的Flash和512字节的RAM是极其宝贵的,你必须成为一个“内存侦探”和“代码裁缝”。
5.1 内存与Flash空间的精细化管理
RAM(512字节):这是变量、数组、字符串常量和函数调用栈的生存空间。一个int变量占2字节,一个float占4字节,一个10个字符的String对象轻松占用几十字节。RAM耗尽会导致程序行为异常、崩溃,且编译器不一定报错。
实战技巧1:使用
F()宏存放字符串。Serial.print("Hello, ATtiny85!");这行代码会将字符串“Hello, ATtiny85!”放入RAM。在ATtiny85上这是奢侈的。正确的做法是:Serial.print(F("Hello, ATtiny85!"));F()宏将字符串常量存放在Flash(程序存储器)中,仅在需要时调入RAM,能节省大量空间。实战技巧2:避免使用
String类。Arduino的String类动态分配内存,非常方便但开销大,且容易产生内存碎片。在ATtiny85上,应使用C语言风格的字符数组(char array[])和相关的字符串处理函数(如strcpy,strcat,atoi)。实战技巧3:使用局部变量,慎用全局变量。全局变量在整个程序生命周期都存在,而局部变量在函数退出后释放。在满足功能的前提下,尽量使用局部变量。
Flash(8KB):存放你的程序代码和用F()宏存储的常量。代码体积优化是关键。
- 实战技巧4:选择必要的库,并了解其体积。
SoftwareSerial库很有用,但会占用约1.5KB的Flash。如果只是输出调试信息,可以考虑更轻量的TinyDebugSerial或直接操作寄存器进行位操作。 - 实战技巧5:在IDE中查看编译结果。每次编译后,IDE底部控制台会输出类似信息:
时刻关注这两个百分比,确保它们不会接近100%。项目使用了 2342 字节(28%)的程序存储空间。最大为 8192 字节。 全局变量使用了 45 字节(8%)的动态内存,剩下 467 字节用于局部变量。最大为 512 字节。
5.2 模拟输入与PWM输出的特殊配置
ATtiny85有一个10位精度的ADC(模数转换器),但引脚有限。
- 模拟读取:使用
analogRead(Ax),其中x是模拟引脚编号(见4.1的映射表)。例如,读取PB4(物理Pin 3)的模拟值:int sensorValue = analogRead(A2);。 - PWM输出:ATtiny85支持PWM的引脚是PB0(Pin 5)和PB1(Pin 6)。使用
analogWrite(pin, value)即可,其中value是0-255之间的值。PB4(Pin 3)不支持硬件PWM,如果需要更多PWM引脚,需要使用软件模拟,但这会消耗CPU周期和Flash空间。
5.3 “串口”通信:软件模拟的智慧
ATtiny85没有硬件UART,因此Serial对象在默认核心中是不可用的。我们需要使用SoftwareSerial库来模拟。
#include <SoftwareSerial.h> // RX, TX。 注意:在ATtiny85上,只能使用支持PCINT的引脚作为RX。 SoftwareSerial mySerial(3, 4); // 使用PB3(Pin 2)作RX, PB4(Pin 3)作TX void setup() { mySerial.begin(9600); } void loop() { mySerial.println(F("Hello from ATtiny85!")); delay(1000); }重要限制:
SoftwareSerial的RX引脚必须支持引脚变化中断(PCINT)。在ATtiny85上,只有PB1、PB3、PB4支持作为RX。PB0和PB2不支持。同时,软件串口会占用大量CPU时间,在高波特率(如115200)下可能不稳定,且会显著增加代码体积。对于仅输出的调试,有时直接使用TinyDebugSerial(它仅使用TX引脚)是更节省资源的选择。
6. 高级话题:功耗优化与睡眠模式实战
ATtiny85的真正威力在于其超低功耗特性,非常适合电池供电的长期监测项目。实现低功耗的关键是让芯片在不工作时进入睡眠模式。
6.1 配置时钟与关闭外设
首先,在代码层面进行优化:
- 降低时钟频率:如果不是必须,可以在“烧录引导程序”时选择更低的时钟(如内部1MHz)。功耗与频率大致成正比。
- 禁用未用的外设:ADC、模拟比较器、看门狗等在不需要时应该关闭。
// 在setup()中关闭ADC以省电 ADCSRA &= ~(1 << ADEN); // 关闭模拟比较器 ACSR |= (1 << ACD);
6.2 实现深度睡眠
我们需要使用<avr/sleep.h>和<avr/power.h>库。
#include <avr/sleep.h> #include <avr/power.h> #include <avr/wdt.h> // 看门狗定时器,可用于唤醒 void setup() { pinMode(1, OUTPUT); // 配置看门狗定时器中断唤醒(可选) // 这里以外部中断唤醒为例 attachInterrupt(0, wakeUp, LOW); // 使用INT0(PB2, Arduino Pin 2)低电平唤醒 } void loop() { digitalWrite(1, HIGH); delay(100); digitalWrite(1, LOW); // 进入睡眠前的准备工作 sleep_enable(); // 使能睡眠功能 set_sleep_mode(SLEEP_MODE_PWR_DOWN); // 设置最省电的掉电模式 power_all_disable(); // 关闭所有外设电源 sleep_cpu(); // 进入睡眠!程序在此暂停 // 被中断唤醒后,程序从这里继续执行 sleep_disable(); // 禁用睡眠 power_all_enable(); // 重新开启需要的模块(如ADC) // ... 执行唤醒后的任务 delay(1000); // 模拟一些工作 } void wakeUp() { // 中断服务程序。这里可以什么都不做,唤醒由硬件处理。 }在SLEEP_MODE_PWR_DOWN模式下,ATtiny85的电流消耗可以降到1微安以下,这意味着一颗纽扣电池可以支撑数年。唤醒方式可以是外部引脚中断、看门狗定时器中断等。
6.3 实测功耗与电池寿命估算
使用万用表电流档串联在电池和ATtiny85的VCC之间进行测量。确保测量时,编程器的USB线已断开,系统由电池独立供电。
- 运行模式(8MHz, LED闪烁):电流可能在5-10mA量级。
- 空闲模式(
SLEEP_MODE_IDLE):关闭CPU但保持外设,电流在mA级别。 - 掉电模式(
SLEEP_MODE_PWR_DOWN):如上述代码,电流<1μA。
假设我们使用一颗容量为240mAh的CR2032纽扣电池,项目99%的时间在掉电睡眠(0.001mA),1%的时间在活跃工作(5mA)。 平均电流 ≈ (0.001mA * 0.99) + (5mA * 0.01) ≈ 0.05099 mA 理论续航 ≈ 240 mAh / 0.05099 mA ≈ 4700小时 ≈196天。 这就是ATtiny85在低功耗项目中的巨大优势。
7. 从原型到产品:DIP与SOIC的最终抉择
当你完成了面包板上的原型验证,接下来就是考虑如何将项目产品化或固化,这就涉及到封装的选择。
DIP-8封装:
- 优点:引脚粗壮,可以直接插在面包板或IC座上,非常适合原型开发、实验和学生教学。焊接和拆卸都非常容易,甚至可以不焊接。
- 缺点:体积大,不适用于对空间有严格要求的最终产品。引脚也更容易在反复插拔中损坏。
SOIC-8封装:
- 优点:体积非常小巧(大约5mm x 6mm),是最终产品、小型化设备的首选。采用表面贴装技术(SMT),可以自动化生产,成本更低。
- 缺点:无法直接在面包板上使用,必须焊接在PCB上。焊接需要一定的技巧(可以使用热风枪或尖头烙铁配合助焊膏),对于新手有挑战。调试时需要借助专用的测试座或焊出引线。
我的实战建议:
- 开发阶段:毫不犹豫地使用DIP封装的ATtiny85。配合一个8脚的IC座,你可以随时拔插芯片,烧录、测试、修改代码无比方便。多买几颗DIP芯片,成本也不高。
- 小批量生产或最终作品:使用SOIC封装。你可以设计一块简单的PCB,将ATtiny85、必要的电阻电容、传感器接口和电源接口都集成上去。在PCB上预留一个6Pin的ISP编程接口(VCC, GND, RESET, SCK, MISO, MOSI),这样即使焊接成SOIC,仍然可以通过这个接口对芯片进行编程和调试。
- 编程夹具:如果经常需要烧录SOIC芯片,投资一个SOIC-8测试夹或编程座是值得的。它能像夹子一样夹住芯片的引脚,另一端引出杜邦线,省去了焊接引线的麻烦。
无论是选择可插拔的DIP进行快速迭代,还是选择微型的SOIC打造精致作品,ATtiny85都能让你用最熟悉的Arduino思维方式,去探索嵌入式世界中那片高效、低功耗的广阔天地。它就像一把精巧的瑞士军刀,虽然功能不像重型机械那样繁多,但在其擅长的领域内,它无比锋利、可靠且顺手。