attiny85 bootloader usb microcontroller embedded arduino
ATtiny85 USB Bootloader: Microcontroller Programming
Guía completa para configurar un bootloader USB en ATtiny85. Aprende a programar microcontroladores sin programador externo usando Micronucleus.
Por Jesus Velez
ATtiny85 USB Bootloader: Microcontroller Programming
El ATtiny85 es un microcontrolador compacto y poderoso de Atmel. Con un bootloader USB, podemos programarlo directamente desde el puerto USB sin necesidad de un programador externo. Esta guía te mostrará cómo configurar Micronucleus bootloader.
¿Qué es un Bootloader USB?
Un bootloader es un pequeño programa que se ejecuta al inicio del microcontrolador y permite cargar nuevo firmware. En el caso del ATtiny85:
- 🔌 Programación USB: Sin necesidad de programador ISP
- ⚡ Rápido y fácil: Drag & drop programming
- 💰 Económico: Reduce costos de hardware
- 🔄 Actualizaciones: Firmware updates en campo
Hardware Requerido
Componentes Básicos
ATtiny85 Digispark Clone:
- ATtiny85 microcontroller
- USB connector (micro USB)
- Regulador de voltaje 5V → 3.3V
- Cristal de 16.5MHz (opcional)
- LEDs de estado
- Resistencias pull-up
Esquema de Conexión
ATtiny85 Pinout (DIP-8):
┌─────────────┐
RESET │1 U 8│ VCC
PB3 │2 7│ PB2 (SCK)
PB4 │3 6│ PB1 (MISO)
GND │4 5│ PB0 (MOSI)
└─────────────┘
USB Connection:
- PB3 (Pin 2) → USB D- (via 68Ω resistor)
- PB4 (Pin 3) → USB D+ (via 68Ω resistor)
- PB4 también conectado a VCC via 1.5kΩ resistor (pull-up)
Instalación del Bootloader
1. Configuración del Hardware
// Configuración de fuses para ATtiny85
// Low Fuse: 0xE1 (16MHz PLL clock)
// High Fuse: 0xDD (permite bootloader)
// Extended Fuse: 0xFE
// Comando avrdude para configurar fuses:
avrdude -c usbtiny -p attiny85 -U lfuse:w:0xe1:m -U hfuse:w:0xdd:m -U efuse:w:0xfe:m
2. Flash del Bootloader Micronucleus
# Descargar Micronucleus
git clone https://github.com/micronucleus/micronucleus.git
cd micronucleus
# Compilar para ATtiny85
cd firmware/configuration/t85_default
make
# Flash bootloader (requiere programador ISP)
avrdude -c usbtiny -p attiny85 -U flash:w:main.hex:i
3. Verificación del Bootloader
# El bootloader debe aparecer como dispositivo USB
lsusb
# Debe mostrar: "ID 16d0:0753 MCS Digistump DigiSpark"
# Test con micronucleus utility
./micronucleus --run test.hex
Programación con Arduino IDE
1. Configuración del Arduino IDE
// Agregar URL de board manager (Preferencias):
http://digistump.com/package_digistump_index.json
// Instalar "Digistump AVR Boards" desde Board Manager
2. Ejemplo: Blink LED
// blink.ino - Parpadeo de LED en ATtiny85
#define LED_PIN 1 // PB1 (Pin 6)
void setup() {
pinMode(LED_PIN, OUTPUT);
}
void loop() {
digitalWrite(LED_PIN, HIGH);
delay(500);
digitalWrite(LED_PIN, LOW);
delay(500);
}
3. Upload Process
1. Código → Verificar/Compilar
2. Código → Subir
3. Conectar ATtiny85 cuando aparezca el mensaje
4. Esperar confirmación de upload exitoso
Ejemplo Avanzado: Sensor de Temperatura USB
Hardware Setup
// ATtiny85 Temperature Sensor with USB communication
// PB1 → Status LED
// PB2 → Temperature sensor (ADC)
// PB3/PB4 → USB communication
#include <DigiUSB.h>
#include <OneWire.h>
#include <DallasTemperature.h>
// Pin definitions
#define LED_PIN 1
#define TEMP_SENSOR_PIN 2
#define USB_POWER_PIN 0
// Temperature sensor setup
OneWire oneWire(TEMP_SENSOR_PIN);
DallasTemperature tempSensor(&oneWire);
// Variables
float temperature = 0.0;
unsigned long lastReading = 0;
const unsigned long READING_INTERVAL = 2000; // 2 seconds
void setup() {
// Initialize pins
pinMode(LED_PIN, OUTPUT);
pinMode(USB_POWER_PIN, OUTPUT);
digitalWrite(USB_POWER_PIN, HIGH); // Enable USB power
// Initialize USB communication
DigiUSB.begin();
// Initialize temperature sensor
tempSensor.begin();
// Startup indication
for (int i = 0; i < 3; i++) {
digitalWrite(LED_PIN, HIGH);
delay(200);
digitalWrite(LED_PIN, LOW);
delay(200);
}
}
void loop() {
// Handle USB communication
DigiUSB.refresh();
// Read temperature periodically
if (millis() - lastReading >= READING_INTERVAL) {
readTemperature();
sendTemperatureData();
lastReading = millis();
// Flash LED to indicate reading
digitalWrite(LED_PIN, HIGH);
delay(50);
digitalWrite(LED_PIN, LOW);
}
// Handle USB commands
if (DigiUSB.available()) {
handleUSBCommand();
}
delay(10); // Small delay to prevent overwhelming USB
}
void readTemperature() {
tempSensor.requestTemperatures();
temperature = tempSensor.getTempCByIndex(0);
// Validate reading
if (temperature == DEVICE_DISCONNECTED_C) {
temperature = -999.0; // Error value
}
}
void sendTemperatureData() {
// Send JSON-formatted data
DigiUSB.print("{\"temperature\":");
DigiUSB.print(temperature, 2);
DigiUSB.print(",\"timestamp\":");
DigiUSB.print(millis());
DigiUSB.println("}");
}
void handleUSBCommand() {
String command = DigiUSB.readString();
command.trim();
if (command == "GET_TEMP") {
sendTemperatureData();
}
else if (command == "LED_ON") {
digitalWrite(LED_PIN, HIGH);
DigiUSB.println("LED ON");
}
else if (command == "LED_OFF") {
digitalWrite(LED_PIN, LOW);
DigiUSB.println("LED OFF");
}
else if (command == "STATUS") {
DigiUSB.print("ATtiny85 Temperature Sensor - Running: ");
DigiUSB.print(millis() / 1000);
DigiUSB.println(" seconds");
}
else {
DigiUSB.println("Unknown command");
}
}
Cliente Python para Comunicación USB
#!/usr/bin/env python3
# attiny85_client.py - Cliente USB para ATtiny85
import serial
import json
import time
import argparse
from datetime import datetime
class ATtiny85Client:
def __init__(self, port="/dev/cu.usbmodem*", baudrate=9600):
self.port = port
self.baudrate = baudrate
self.connection = None
def connect(self):
"""Conectar al ATtiny85"""
try:
self.connection = serial.Serial(self.port, self.baudrate, timeout=1)
time.sleep(2) # Esperar inicialización
print(f"Conectado a ATtiny85 en {self.port}")
return True
except Exception as e:
print(f"Error conectando: {e}")
return False
def disconnect(self):
"""Desconectar del ATtiny85"""
if self.connection:
self.connection.close()
print("Desconectado")
def send_command(self, command):
"""Enviar comando al ATtiny85"""
if not self.connection:
print("No hay conexión")
return None
try:
self.connection.write(f"{command}\n".encode())
time.sleep(0.1)
response = ""
while self.connection.in_waiting:
response += self.connection.read().decode()
return response.strip()
except Exception as e:
print(f"Error enviando comando: {e}")
return None
def get_temperature(self):
"""Obtener lectura de temperatura"""
response = self.send_command("GET_TEMP")
if response:
try:
data = json.loads(response)
return data.get("temperature", None)
except json.JSONDecodeError:
return None
return None
def get_status(self):
"""Obtener status del dispositivo"""
return self.send_command("STATUS")
def control_led(self, state):
"""Controlar LED (True=ON, False=OFF)"""
command = "LED_ON" if state else "LED_OFF"
return self.send_command(command)
def monitor_temperature(self, interval=2, duration=60):
"""Monitorear temperatura continuamente"""
print(f"Monitoreando temperatura por {duration} segundos...")
start_time = time.time()
try:
while time.time() - start_time < duration:
temp = self.get_temperature()
if temp is not None:
timestamp = datetime.now().strftime("%H:%M:%S")
print(f"[{timestamp}] Temperatura: {temp:.2f}°C")
else:
print("Error leyendo temperatura")
time.sleep(interval)
except KeyboardInterrupt:
print("\nMonitoreo detenido por usuario")
def main():
parser = argparse.ArgumentParser(description="Cliente ATtiny85 USB")
parser.add_argument("--port", default="/dev/cu.usbmodem*", help="Puerto serie")
parser.add_argument("--command", choices=["temp", "status", "led_on", "led_off", "monitor"],
help="Comando a ejecutar")
parser.add_argument("--monitor-time", type=int, default=60,
help="Tiempo de monitoreo en segundos")
args = parser.parse_args()
# Crear cliente
client = ATtiny85Client(args.port)
if not client.connect():
return
try:
if args.command == "temp":
temp = client.get_temperature()
if temp is not None:
print(f"Temperatura: {temp:.2f}°C")
else:
print("Error leyendo temperatura")
elif args.command == "status":
status = client.get_status()
print(f"Status: {status}")
elif args.command == "led_on":
response = client.control_led(True)
print(f"LED encendido: {response}")
elif args.command == "led_off":
response = client.control_led(False)
print(f"LED apagado: {response}")
elif args.command == "monitor":
client.monitor_temperature(duration=args.monitor_time)
else:
print("Comandos disponibles:")
print(" --command temp - Leer temperatura")
print(" --command status - Estado del dispositivo")
print(" --command led_on - Encender LED")
print(" --command led_off - Apagar LED")
print(" --command monitor - Monitoreo continuo")
finally:
client.disconnect()
if __name__ == "__main__":
main()
Herramientas de Desarrollo
Makefile para Compilación
# Makefile para ATtiny85 projects
MCU = attiny85
F_CPU = 16500000UL
CC = avr-gcc
OBJCOPY = avr-objcopy
AVRDUDE = avrdude
# Compiler flags
CFLAGS = -mmcu=$(MCU) -DF_CPU=$(F_CPU) -Os -Wall -Wextra
# Project files
TARGET = main
SOURCES = main.c
OBJECTS = $(SOURCES:.c=.o)
# Default target
all: $(TARGET).hex
# Compile
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
# Link
$(TARGET).elf: $(OBJECTS)
$(CC) $(CFLAGS) $(OBJECTS) -o $@
# Create hex file
$(TARGET).hex: $(TARGET).elf
$(OBJCOPY) -O ihex $< $@
# Upload via bootloader
upload: $(TARGET).hex
micronucleus --run $(TARGET).hex
# Clean
clean:
rm -f *.o *.elf *.hex
# Flash bootloader (requires ISP programmer)
flash-bootloader:
$(AVRDUDE) -c usbtiny -p $(MCU) -U flash:w:bootloader.hex:i
# Set fuses
fuses:
$(AVRDUDE) -c usbtiny -p $(MCU) -U lfuse:w:0xe1:m -U hfuse:w:0xdd:m -U efuse:w:0xfe:m
.PHONY: all upload clean flash-bootloader fuses
Script de Test Automatizado
#!/bin/bash
# test_attiny85.sh - Script de test para ATtiny85
echo "=== ATtiny85 USB Bootloader Test ==="
# Verificar si el dispositivo está conectado
if lsusb | grep -q "16d0:0753"; then
echo "✅ ATtiny85 detectado"
else
echo "❌ ATtiny85 no detectado"
echo "Conecta el dispositivo e intenta nuevamente"
exit 1
fi
# Compilar test program
echo "🔨 Compilando test program..."
make clean && make
if [ $? -eq 0 ]; then
echo "✅ Compilación exitosa"
else
echo "❌ Error en compilación"
exit 1
fi
# Upload test program
echo "📤 Subiendo test program..."
make upload
if [ $? -eq 0 ]; then
echo "✅ Upload exitoso"
else
echo "❌ Error en upload"
exit 1
fi
# Wait for device to restart
echo "⏳ Esperando reinicio del dispositivo..."
sleep 3
# Test USB communication
echo "🔄 Probando comunicación USB..."
python3 attiny85_client.py --command status
if [ $? -eq 0 ]; then
echo "✅ Comunicación USB exitosa"
else
echo "❌ Error en comunicación USB"
exit 1
fi
echo "🎉 Todos los tests pasaron correctamente!"
Troubleshooting Common Issues
1. Bootloader no se detecta
# Verificar fuses
avrdude -c usbtiny -p attiny85 -U lfuse:r:-:h -U hfuse:r:-:h -U efuse:r:-:h
# Re-flash bootloader si es necesario
avrdude -c usbtiny -p attiny85 -U flash:w:micronucleus.hex:i
2. Upload timeouts
# Usar timeout más largo
micronucleus --run --timeout 60 firmware.hex
# Verificar drivers USB
# En Windows: instalar libusb drivers
# En macOS: puede requerir desactivar System Integrity Protection temporalmente
3. Debugging USB Issues
// Debug USB communication
void debug_usb() {
DigiUSB.println("USB Debug Mode");
DigiUSB.print("Free RAM: ");
DigiUSB.println(freeRam());
DigiUSB.print("Uptime: ");
DigiUSB.println(millis());
}
int freeRam() {
extern int __heap_start, *__brkval;
int v;
return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval);
}
Optimización y Mejores Prácticas
1. Memory Management
// Optimizar uso de memoria en ATtiny85 (8KB flash, 512B RAM)
#include <avr/pgmspace.h>
// Strings en PROGMEM
const char status_msg[] PROGMEM = "ATtiny85 Ready";
void print_status() {
char buffer[32];
strcpy_P(buffer, status_msg);
DigiUSB.println(buffer);
}
2. Power Management
#include <avr/sleep.h>
#include <avr/power.h>
void enter_sleep_mode() {
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
power_all_disable(); // Disable all peripherals
sleep_enable();
sleep_cpu();
// Wake up here
sleep_disable();
power_all_enable(); // Re-enable peripherals
}
3. Watchdog Timer
#include <avr/wdt.h>
void setup() {
// Enable watchdog timer (8 seconds)
wdt_enable(WDTO_8S);
// Your setup code
DigiUSB.begin();
}
void loop() {
// Reset watchdog
wdt_reset();
// Your main code
// ...
}
Conclusión
El bootloader USB para ATtiny85 ofrece:
- ✅ Programación sin hardware adicional mediante USB
- ✅ Desarrollo rápido con Arduino IDE
- ✅ Comunicación bidireccional con el host
- ✅ Ideal para prototipos y proyectos embedded pequeños
- ✅ Costo efectivo para producción de bajo volumen
Aplicaciones Comunes
- Sensores USB: Temperatura, humedad, luz
- Actuadores simples: LEDs, servos, relays
- Interfaces HID: Keyboards, mice personalizados
- Data loggers: Pequeños sistemas de monitoreo
- Wearables: Dispositivos portátiles conectados
Próximos Pasos
- Implementar comunicación I2C/SPI
- Crear dispositivos HID personalizados
- Desarrollar bootloader personalizado
- Integrar con sistemas IoT
- Optimizar consumo de energía para aplicaciones battery-powered
¡El ATtiny85 con bootloader USB es perfecto para proyectos embedded pequeños y eficientes!