Para resumir todas las ideas, veamos cómo hacer un blink mínimo en un ATtiny84 funcionando a 20Mhz.
Lo primero, monta el chip en una protoboard, llevando el pin1 a 5V, el pin14 a tierra, los pin2 y pin3 a tierra con sendos condensadores de 22pF, y conectados entre si con un cristal a 20Mhz (o la frecuencia que quieras, por encima de 3Mhz). Para hacer el blink, conecta un LED al pin8, y llévalo a tierra con una resistencia razonable (200 Ohm o así.)
Una imagen aquí.
Ahora, programa los fuses del microcontrolador para que funcione bien a esta frecuencia de reloj. Esto se logra con
avrdude, usando el comando:
avrdude -c arduinoISP -p t84 -U lfuse:w:0xff:m -U hfuse:w:0xdc:m -U efuse:w:0xff:m
Es posible que tengas que cambiar el tipo de programador, por ejemplo,
-c usbasp si usas el usbasp, etc. Esta elección de fuses prepara el micro para funcionar con el cristal, y activa del brown-out detector a un nivel razonable.
Ahora vamos a por el programa del blink en si mismo:
#include <inttypes.h>
#include <avr/io.h>
#include <util/delay.h>
#include <avr/eeprom.h>
#include <avr/pgmspace.h>
#include <avr/interrupt.h>
#include <string.h>
int main ()
{
DDRA |= (1 << 5); // Pin 5 como salida.
for (;;)
{
PORTA |= (1 << 5); // Enciende pin 8.
_delay_ms(250);
PORTA &= ~(1 << 5); // Apaga pin 8.
_delay_ms(250);
}
}
La parte de main() en este código es bastante clara, si uno se lee la
documentación de ATtiny84: se está configurando el bit 5 del puerto A, que corresponde al pin 8 del chip. Se usan los registros DDRA (Data Direction puerto A) para configurarlo como salida, y el registro PORTA para encender y apagar el LED. La macro
_delay_ms está definida en el fichero de cabecera
util/delay.h y es precisa y fácil de usar.
Una cosa que llama mucho la atención al pasar del ensamblador al C es que en este programa no se hace mención de los vectores de interrupción. ¡Y al menos necesitamos el vector de reset! ¿Cómo resuelve avr-gcc este problema? Parece que tenemos dos problemas:
- Queremos que nuestro código main() y demás subrutinas no sobreescriban los vectores de interrupción, sino que empiecen más adelante que ellos.
- Queremos almacenar en los dos primeros bytes de la flash un salto relativo a main().
Las versiones modernas de avr-gcc se ocupan directamente de esto, como veremos
en la parte siguiente, sobre desensamblado de nuestro programa. Por ahora, podemos confiar en que gcc se encargará adecuadamente del trabajo sucio.
Por tanto, vamos a compilar el código fuente y subirlo. Para compilar,
avr-gcc -c main.c -o main.o -Os -mmcu=attiny84 -D__AVR_ATtiny84__ -DF_CPU=20000000L
La mayor parte de esto no es muy misteriosa. Las dos definiciones podrían haberse incluido en el fichero fuente:
#define __AVR_ATtiny84__ // Modelo de mcu.
#define F_CPU= 20000000L // Velocidad del reloj.
Observa que si tu reloj va a otra frecuencia, debes cambiarlo aquí. Por ejemplo, a 8Mhz deberías usar F_CPU=8000000L.
La optimación -Os es fundamental para que algunos de los ficheros de cabecera funcionen, así que no es conveniente saltársela.
Una vez que tenemos el fichero
main.o generado por el compilador, podemos generar un fichero .elf con el programa:
avr-gcc main.o -o main.elf -Os -mmcu=attiny84 -D__AVR_ATtiny84__ -DF_CPU=20000000L
Las flags del compilador son idénticas, pero esta vez sólo se hace el linkado.
Ahora extraemos el fichero .eep, que contiene la información a subir a la eeprom: en nuestro caso, no hay ninguna que subir:
avr-objcopy -j .eeprom --set-section-flags=.eeprom='alloc,load' --change-section-lma .eeprom=0 -O ihex main.elf main.eep
Y la parte a subir a la flash de nuestro programa, que en este caso es todo:
avr-objcopy -O ihex -R .eeprom main.elf main.hex
El fichero
main.hex es el que subiremos usando avrdude. Antes de hacerlo, de todos modos, es buena idea saber cuánto estamos usando de memoria, por si nos hemos pasado. Para ello, ejecutamos:
avr-size --mcu=attiny84 -C --format=avr main.elf
La salida es el clásico mensaje al que estamos aconstumbrados en el software de Arduino:
AVR Memory Usage
----------------
Device: attiny84
Program: 96 bytes (1.2% Full)
(.text + .data + .bootloader)
Data: 0 bytes (0.0% Full)
(.data + .bss + .noinit)
Por supuesto, nuestro minimalista blink apenas ocupa memoria. Para subir el programa a la flash, invocamos a avrudude:
avrdude -c arduinoISP -p t84 -B 9500 -e -U flash:w:"main.hex"
La opción -B 9500 ralentiza el envío, porque en mi caso lo hago a través de cables largos, y a grandes velocidades hay errores de transmisión. Si tu programamdor va rápido, puedes ahorrarte esta línea.
Y con esto tu attiny está listo para correr el programa. El proceso puede parecer muy largo, pero creando una macro, o un makefile (más adelante publicaré el mío), todo el procedimiento es muy rápido y adaptable a otros proyectos.