SlideShare una empresa de Scribd logo
Qué son los microcontroladores y para qué sirven
Respondiendo a la primera parte, un microcontrolador (µC o MCU para abreviar) es un
circuito integrado programable capaz de llevar a cabo una determinada tarea. El tipo de
tarea vendría a ser la segunda parte.
Si alguien nos preguntara qué es lo que hace una computadora personal, le
responderíamos de todo, según el programa que le instalemos. De igual modo, un
microcontrolador, como un “micro computador" que es, puede hacer casi de todo (dentro
de sus posibilidades, claro está), según el programa grabado en su memoria.
La analogía de un microcontrolador con una computadora va más allá de su programación.
Los microcontroladores son circuitos integrados que encierran en un solo chip un CPU
(unidad central de procesamiento), las memorias ram y rom, los diversos periféricos
especiales y los puertos de entrada/salida.

Diagrama de bloques de un microcontrolador
Si echamos un vistazo a nuestro alrededor, podremos notar que estamos cada vez más
rodeados de los microcontroladores: los periféricos de nuestras computadoras, como las
impresoras, teclados, ratones, monitores y demás, tienen incorporados uno o más
microcontroladores.
Los electrodomésticos, cada vez más modernos, como equipos de sonido, televisores LCD,
reproductores de DVD, etc., sin duda tienen microcontroladores que guían sus funciones.
Los aparatos de entretenimiento como los reproductores de MP3 y MP4 portátiles también
tienen microcontroladores en su parte cerebral.
Los teléfonos celulares, las cámaras digitales, etc., etc. De hecho, los microcontroladores se
han infiltrado en todos los campos de la vida moderna, desde los pasatiempos hasta la
industria robótica, de telecomunicaciones, automovilística... En fin, ¿todavía quieres saber
para qué sirven?

Características de los AVR
Los productos estrella de Atmel son sus microcontroladores AVR. Comparado con otros
microcontroladores, en distintos modelos por supuesto, pueden tener memoria de
programa flash reprogramable, capacidad ISP (In System Programming), puertos
configurables como E/S pin a pin, interfaces de comunicación serial RS232 e I2C, módulos
generadores de onda PWM, etc.
Yo pienso que una de las razones por las que la gente novel no empieza por los AVR es su
set de 130 instrucciones; una cantidad que los haría desistir.
Este hándicap inicial se invierte cuando se utiliza un compilador de alto nivel, ya que los
AVR fueron diseñados para un óptimo trabajo con el lenguaje C. Por si fuera poco, la gente
del software libre ha desarrollado el poderoso compilador AVR GCC, el cual está disponible
en sus versiones para Windows y Linux. Así que, si de herramientas para desarrollar
proyectos se trata, los AVR toman la delantera y se convierten en serios competidores de
los actuales monarcas de Microchip.
Las características de cada AVR son descritas con todo detalle en el capítulo Arquitectura
interna de los AVR.

Microcontroladores con Arquitectura Harvard
Como todos los microcontroladores modernos, los AVR fueron diseñados con arquitectura
Harvard. Con esta estructura los microcontroladores AVR disponen de dos memorias, una
que contiene el programa y otra para almacenar los datos. De este modo el CPU puede
tener acceso simultáneo a ambas memorias utilizando buses diferentes. Más
específicamente, el CPU puede leer la siguiente instrucción de programa mientras está
procesando los datos de la instrucción actual.

Arquitectura Harvard de un microcontrolador
Antiguamente los microcontroladores tenían una arquitectura Von Neumann. Como se ve
en el diagrama de abajo, estos microcontroladores usaban una memoria única que
constituía tanto el segmento de memoria de programa como el de datos. Con un solo bus
de comunicación entre dicha memoria y el procesador no es posible realizar diversos
accesos a la vez.

Arquitectura Von Neumann de un microcontrolador

Microcontroladores con Instrucciones RISC
RISC es sigla de Reduced Instruction Set Computer. También es una característica propia
de los microcontroladores actuales como los AVR. Estos microcontroladores cuentan con
instrucciones sencillas y en un número mínimo. En muchos casos ello permite que la
programación en ensamblador sea una labor cómoda y esté al alcance de todos.
Sin embargo, cuando se desarrollan proyectos mucho más complejos, el uso del lenguaje
ensamblador se torna cada vez más engorroso. Entonces se prefiere optar por los
compiladores de alto nivel, para los cuales un set CISC no es obstáculo.
CISC significa Complex Instruction Set Computer y era un distintivo de los primeros
microcontroladores que aparecieron en el mundo, los cuales estaban inspirados en los
procesadores de los grandes computadores de la época. Es complejo porque consta de
muchas instrucciones, complicadas y difíciles de recordar a la hora de programar en
lenguaje ensamblador. Además, al crecer el número de instrucciones también crecerán los
códigos de las instrucciones, lo cual deriva en una mella en la eficiencia del
microcontrolador.

Características de los AVR
Algunas de las características y recursos generales y comunes a casi todos los AVR son:
Están fabricados con tecnología CMOS. Aunque los dispositivos CMOS son más lentos que
los TTL, son ideales para los microcontroladores porque requieren de menor consumo de
energía. Es posible implementar sistemas que solo se alimenten de baterías corrientes. La
tecnología CMOS, como sabemos, también significa que los transistores, al ser mucho
menos, ocupan mucho menor espacio en el chip.
Memorias de programa (FLASH o ROM), memoria de datos estática (SRAM) y memoria
EEPROM internas.
Puertos de E/S bidireccionales configurables independientemente pin por pin.
Suministro de alta corriente en los puertos de E/S.
Timer‟s. Temporizadores de alta precisión o contadores de pulsos externos. También
funcionan como generadores de ondas PWM (Pulse Width Modulation), particularmente
útiles para controlar la velocidad de los motores DC.
WatchDog. Monitoriza que el AVR funcione adecuadamente a lo que se esperaba y no se
cuelgue.
ISP (In System Programming). Permite realizar la programación del AVR utilizando una
interface serial con muy pocos pines.
Fuses y Lock bits, permiten establecer un determinado modo de funcionamiento del AVR,
como el tipo de oscilador que utilizará o si el código grabado podrá o no ser leído después
de la programación.
Otros recursos, más avanzados, son específicos a cada familia de AVR y pueden ser:
Conversores Analógico-Digital, ADC. Para recibir señales del mundo analógico.
Módulos SPI. Para la comunicación con dispositivos que utilizan el bus SPI.
Módulos TWI. Para la comunicación con dispositivos que utilizan el bus I2C.
USART, Transmisor Receptor Síncrono Asíncrono Universal. Para comunicarse mediante los
protocolos RS232 con cualquier dispositivo que también lo soporte. Por ejemplo, podemos
conectar nuestro AVR al puerto serie del PC o a cualquier otro microcontrolador con USART.
Módulo Comparador Analógico. Nos puede ahorrar un OP-AMP y algo más.
Módulo CAN. Para facilitarle al AVR su conexión con otros microcontroladores en una
pequeña red LAN con un protocolo robusto para trabajar en condiciones extremas.
Módulo USB. Casi todos los dispositivos digitales modernos presentan interface USB. Con
esto podemos diseñar sistemas que no tengan nada que envidiarles.
Etc., etc.

Clasificación de los AVR. ¿Con qué AVR empezar?
Cuando la gente va a trabajar con una nueva familia de microcontroladores como los AVR
revisa primero su clasificación para saber con cuál puede empezar. Normalmente esta
gente ha tenido alguna experiencia previa con otros microcontroladores como los PIC y
suele pensar que para programar los AVR primero hay que empezar por lo más básico así
como el PIC16F84 en el mundo de los PICs. y seguir avanzando con el PIC16F877 y luego
con el PIC18F4550 y así... Bueno, no los puedo culpar, pues yo mismo pensaba así :) Aquí
descubriremos por qué esa idea es errónea.
Pero volviendo al tema, en el mundo de los AVR hay 4 familias de ellos: los tinyAVR,
los megaAVR, losXMEGA y los AVR32.
A la vez, puede haber varias decenas de AVR dentro de cada familia, pero las diferencias
entre ellos son cada vez menores, como tener algunos pines de E/S más o menos, tener
algo de memoria más o menos, tener un Timer más o menos, emplear otro tipo de
memoria, y demás detalles de ese tipo.

Clasificación de los AVR (fuente: atmel.com).

Los tinyAVR
Son los "tiny toons" de los AVR, son los microcontroladores de Atmel con menos recursos
de memoria y periféricos posibles. Sin embargo, son muy veloces, alcanzando a operar a
20 MIPS (millones de instrucciones por segundo). Así que tampoco los deberíamos
subestimar y no se te vaya ocurrir equipararlos con un PIC16F84 o similar. Es
tranquilamente factible usar un tinyAVR para conectarse al puerto USB de una
computadora con todo el firmware implementado a nivel software. Por ejemplo,Limor
Fried uso un ATtiny2313 para su programador USBtinyISP. ¿Crees que un PIC16F84A
podría hacer eso? Muy difícil, ¿verdad? Yo no conozco aplicaciones similares ni siquiera con
un PIC16F877A (Debe ser mi ignorancia).
Inicialmente todos eran muy pequeños en tamaño, por lo general de 8 pines, pero
últimamente están apareciendo modelos que llegan a los 20 pines. Lo que varía muy poco
es su set de cerca de 130 instrucciones, que es el mismo set base de
los megaAVR y XMEGA. Es decir, que sean los más limitados no significa que sean los más
fáciles de programar.

Los megaAVR.
Popular e inapropiadamente conocidos como ATmegas, son los microcontroladores de 8 bits
más sobresalientes de Atmel. Detesto hacer estas comparaciones pero creo que son un
buen referente para conocer de forma rápida los AVR. Pero bueno, yo diría que en general
los megaAVR son como losPIC18 de Microchip. Claro que habrá diferencias a favor y en
contra: los PIC18 tienen mayor variedad de ejemplares (personalmente me confunde más)
y los megaAVR trabajan un poco más veloz, y etc., etc... No voy a abrir un debate aquí.
Todos los megaAVR tienen más de 130 instrucciones, la mayoría de 16 bits, y pueden llegar
a velocidades de 20 MIPS. Sus memorias flash pueden alcanzar 256 kB para almacenas
hasta 128k instrucciones y sus memorias RAM pueden alojar hasta 4 kB de datos
temporales.
En general, en ellos se pueden encontrar casi todos los recursos hardware buscados en un
microcontrolador de 8 bits, por eso se suele tomar de aquí algunos modelos como punto de
partida de aprendizaje.
Ahora bien, dentro de la familia de los megaAVR todavía debemos distinguir dos grupos:
Los clásicos megaAVR, que ahora Atmel denomina los viejos AVR, como los ejemplares
cuyos nombres empiezan con AT90S (AT90S8535, AT90S2313, etc.) e incluso los no tan
antiguos ATmega8, ATmega16, ATmega32, ... y demás.
Los nuevos megaAVR. Bueno, eso de "nuevos" lo puse yo, por si acaso, solo para darles
cierta distinción. No es que estos AVR hagan la gran revolución por el hecho de que puedan
trabajar hasta a 20 MIPS en lugar de los 16 MIPS de sus antecesores o porque sus Timers
puedan generar 2 canales de ondas PWM cada uno.
En cursomicros.com trabajamos con los nuevos megaAVR, en especial con los de la
serie ATmegaXX8 yATmegaXX4, que son los megaAVR más potentes que se pueden
conseguir en empaques DIP de 28 y 40 pines respectivamente. Los otros megaAVR son
más grandes en cuanto a numero de pines y por tanto solo vienen en empaques TQFP,
QFN, etc. Sus periféricos son básicamente los mismos, solo suelen diferenciarse por tener
más puertos de E/S.
No digo que lo viejos AVR no sirvan para nada. Si bien es cierto que pueden tener
pequeños bugs (como cualquier microcontrolador de cualquier otra marca), los miles de
proyectos disponibles enAVRfreaks.net e incluso los primeros Robots de Dale
Heatherington demuestran que se pueden hacer maravillas con ellos. Yo solo digo que si
podemos elegir entre un buen producto y otro un poco mejor, ¿por qué no tomar el mejor?

Los XMEGA
Son microcontroladores de 8 bits pero con injertos de un típico microcontrolador de 16 bits
que elevan su performance hasta equipararse con los verdaderos microcontroladores de 16
bits como algunos dsPIC de Microchip. Solo que a diferencia de ellos, ningún Xmega viene
en empaque DIP, lo cual dificulta su empleo para fines de experimentación de un
principiante.
Los AVR XMEGA alcanzan velocidades de hasta 33 MHz pero solo operan a tensiones de
hasta 3.3V. Su memoria de programa flash llega a 384 kB. Debido a su renovada
arquitectura presentan muchas instrucciones nuevas pero su set básico sigue siendo
compatible con las 130 instrucciones de losmegaAVR y tinyAVR.

Los AVR32
Son los microcontroladores AVR de 32 bits. Aquí hay dos grupos: los de la serie UC3 y los
de la serieAP7. Son AVRs que para los noveles diseñadores deberían ser descartados como
punto de partida.

Otros Microcontroladores
Hay muchas marcas de microcontroladores en el mercado. De ellas solo mencionaré las que
creo más populares. A veces un mismo tipo de microcontrolador lo suelen proveer diversos
fabricantes, por lo que ésta no es una clasificación estrictamente metódica
En esta presentación las descripciones se hacen teniendo en cuenta solo a los
microcontroladores de 8 bits. En este sentido, salvo el caso peculiar de los Basic Stamp,
personalmente no encuentro diferencias notables en el hardware interno de cada
microcontrolador que me hagan optar por uno u otro para un proyecto en específico. No
puedo decir lo mismo sobre la disponibilidad de herramientas de desarrollo software y
hardware.

Los Microcontroladores PICmicro o PIC de Microchip
Sin lugar a dudas, son los microcontroladores que han fascinado al mundo en los últimos
años. Su facilidad de uso, comodidad y rapidez en el desarrollo de aplicaciones, abundante
información y libre disposición de herramientas software proporcionada por Microchip le
han permitido ganar terreno rápidamente en el mercado de los microcontroladores a nivel
mundial, hasta convertirse en los microcontroladores más vendidos en la actualidad.
Los buenos resultados que le dieron a Microchip la estrategia de proveer libremente a los
usuarios de muchas herramientas software para el desarrollo de proyectos con sus
productos hicieron que los otros fabricantes de microcontroladores también la adoptaran,
aunque parece que la ventaja de Microchip en el mercado está ya marcada y tal vez se
acentúe más en el futuro.

Por qué empezar con los PICS
Por su fácil adquisición. Se pueden conseguir en casi cualquier tienda de electrónica.
Por su pequeño set de instrucciones, que no logra ser igualado por ningún otro
microcontrolador. Es casi mágica la forma cómo se pueden implementar fácilmente casi
cualquier algoritmo de programa con solo sus 35 instrucciones básicas.
Por su bajo costo. Los PICs son tal vez los microcontroladores más baratos con las
características que poseen.
Por su fácil aprendizaje. Los PICs cuentan con el menor conjunto de instrucciones, y no por
ello menos eficientes, que los convierten de lejos en los de mejor aprendizaje.
Por la disponibilidad de herramientas. Las herramientas de hardware y software son de
amplio alcance. Eso nos permitirá empezar muy pronto con la experimentación sin la
preocupación por mayores recursos.

Los Microcontroladores de Freescale
Hasta no hace muchos años Motorola era uno de los fabricantes de microcontroladores con
mayores ventas en el mundo. En esos tiempos el trabajo con microcontroladores era una
actividad casi exclusiva de los considerados gurúes de la microelectrónica y que contaban
con suficientes medios para acceder a las herramientas necesarias. Lo cierto es que con el
tiempo Motorola empezó a perder su liderazgo y ha preferido ceder la franquicia a
Freescale.
Freescale continúa con la producción de microcontroladores basados en la arquitectura los
viejos productos de Motorola y dotándoles de todo el arsenal tecnológico de la actualidad.
Salvo el prestigio legado no tienen nada nuevo en su hardware que no se pueda hallar en
otros microcontroladores.

Los Microcontroladores 8051 de Intel
Intel era otro de los gigantes de los microcontroladores y µPs. Sus productos más
conocidos eran los famosos 8051, 80151 y 80251, pero actualmente ya no tiene interés en
fabricarlos. En su lugar, fueron otras compañías, como Atmel, Philips, Infineon, Dallas,
entre otros, las que tomaron la posta y fabrican algunas partes compatibles. Cabe
mencionar que, salvo raras excepciones (como los PICs), el resto de los microcontroladores
fueron inspirados en la arquitectura de estos procesadores de Intel.
Por lo demás, no tiene caso especificar sus características porque no hay diferencias
grandes respecto de los otros productos. En este sentido, no se puede afirmar qué marca
de microcontrolador es mejor o peor. Es decir, si tomamos un microcontrolador cualquiera,
siempre podremos encontrar un modelo de otro fabricante que pueda sustituirlo en una
determinada aplicación.

Los Módulos Basic Stamp de Parallax
Los Basic Stamp nos son una nueva familia de microcontroladores; son módulos montados
sobre otros microcontroladores. Cuentan con un microcontrolador, un circuito oscilador, el
circuito de interface con el puerto serie de la computadora, una memoria externa para
almacenar el programa y un regulador de tensión; todo en una pequeña tarjeta directa y/o
fácilmente conectable a las computadoras. Una vez cargado el programa, el módulo está
listo para ser insertado en el circuito de aplicación, incluso si está armado en un simple
breadboard.
Los programas se desarrollan íntegramente en un lenguaje Basic adaptado. El programa se
carga en la EEPROM serial y el microcontrolador del Basic Stamp tiene que interpretarlo.
Constitución de un módulo Basic stamp
Constitución de un módulo Basic Stamp
Por ejemplo, el BS2sx mostrado arriba cuenta con un microcontrolador que está pre
programado específicamente para trabajar como intérprete, esto es, para leer las
sentencias de comando de la EEPROM serial, decodificarlas y ejecutar las instrucciones que
representan. El microcontrolador no se puede reprogramar, viene así de fábrica.
Aunque el intérprete opera a toda su potencia, la mayor parte del tiempo la "desperdicia"
leyendo la EEPROM serial y decodificando sus comandos. Por tanto, el campo de aplicación
de los Basic Stamp es más bien de carácter didáctico y de entrenamiento; no son para
grandes proyectos.
Actualmente solo hay tres familias de Basic Stamp, cada una con muy pocas variantes,
referidas básicamente a la velocidad de operación, capacidad de memoria y cantidad de
pines de I/O. En realidad, el tercer grupo está formado por los Javelin Stamp, que
interpretan código Java en vez de Basic.
Si después de todo aún tuvieras interés por estos módulos, los circuitos de hardware y las
herramientas software están a libre disposición en la web de la
firma http://www.parallax.com/.

Programadores de microcontroladores AVR
Conseguir las herramientas software siempre es la tarea más fácil comparada con la parte
hardware. Podemos incluso trabajar con las versiones demo de los programas y ver por
nosotros mismos cuál nos resultará más conveniente. Pero con el hardware no podemos
darnos el lujo de ir probando los que queramos. Así que debemos informarnos bien antes
de gastar dinero en una compra inadecuada y/o de perder tiempo construyendo un
programador que más tarde nos pueda decepcionar.
Pero como en ninguna otra marca de microcontrolador que haya conocido, los
programadores de AVR son tan diversos que mucha gente novata se estanca buscando un
buen programador por no saber exactamente lo que quiere, gente que mientras más busca
en Internet, más opciones y términos nuevos encuentra que al finalmente queda más
confundida. Así que si eres nuevo en esto, no pienses que la lectura de este extenso
capítulo puede frenar tu progreso. Todo lo contrario, despacio se llega lejos.

Esquema de la programación de un AVR.
Para evaluar a priori la utilidad y calidad de un producto tenemos que comparar sus
características. En el caso de un programador de AVR nosotros vamos a considerar
su interface con la PC, su interface con el microcontrolador y el software de computadora
con el que lo utilizaremos. La siguiente tabla nos muestra los programadores más
reconocidos. Por supuesto que existen muchos otros, pero como veremos luego, la mayoría
de ellos son variaciones o derivados de estos.
Tabla Programadores de AVR

Programador

Interface con
Interface con el AVR
la PC

Software

Boot Loader

USB,
COM

USART, USB,…

AVRDUDE,
AVROSP, FLIP,...

USBasp

USB

SPI y TPI

AVRDUDE

USBtinyISP

USB

SPI

AVRDUDE, Studio 6

AVR Doper

USB

SPI y HVSP

AVRDUDE, Studio 6

HVProg

COM

SPI, HVSP y HVPP

AVRDUDE, Studio 6
Tabla Programadores de AVR

Programador

Interface con
Interface con el AVR
la PC

Software

AVRminiProg

USB

SPI, JTAG, HVSP y HVPP

AVRDUDE, Studio 6

ArduinoISP

USB

SPI

AVRDUDE

FTDI

USB

SPI

AVRDUDE

SI-Prog

COM

SPI

AVRDUDE

DASA

COM

SPI

AVRDUDE

BSD

LPT

SPI

AVRDUDE

AVR910

COM

SPI

AVRDUDE, AVROSP

AVRISP

COM

SPI

AVRDUDE, Studio 6

STK500

COM

SPI, HVSP, y HVPP

AVRDUDE, Studio 6

AVRISP MkII

USB

SPI, PDI y TPI

AVRDUDE, Studio 6

STK600

USB

SPI, PDI, TPI, TPI-HV, JTAG,
aWire, HVSP y HVPP

AVRDUDE, Studio 6

AVR Dragon

USB

SPI, PDI, JTAG, aWire, HVSP y
HVPP

AVRDUDE, Studio 6

JTAGICE MkII

USB

SPI, PDI, JTAG y aWire

AVRDUDE, Studio 6

AVR ONE!

USB

SPI, PDI, JTAG y aWire

AVRDUDE, Studio 6

Si te preguntas por qué en nuestra tabla de comparación no hemos considerado la cantidad
o el tipo de microcontroladores que cada programador soporta, es porque está indicado de
forma implícita. Es decir, eso depende de la interface de programación del AVR. Por
ejemplo, si vamos a trabajar con un AVR XMEGA, debemos saber que ellos tienen interface
de programación PDI y por tanto en ese caso necesitaremos un programador como
el AVRISP MkII. Simple, ¿verdad? En principio sí, pero los AVR pueden tener más de una
interface de programación, unas con más alcance que otras. Enseguida las explicamos.
Programación Serial y Paralela
La interface entre el programador y el microcontrolador puede ser Serial y Paralela. En la
tabla de arriba la Interface paralela figura como HVPP. Todas las otras interfaces
desde SPI hasta aWire son seriales.
Las líneas de las interfaces seriales varían según el protocolo. En cambio la interface
paralela es única y consta de 16 líneas sin contar los pines de alimentación del AVR. Se
deduce fácilmente que este modo solo está disponible en los AVR de bastantes pines, más
de 14 pines, si queremos establecer una regla.

Programación de Alto Voltaje HVPP y HVSP
Antes hablamos de la dicotomía serie-paralela. Ahora describiremos los modos de
programación en alto y bajo voltaje así como de la relación entre todos ellos.
El alto voltaje es un modo de programación adicional que solo está disponible en los AVR de
las familias tinyAVR y megaAVR. Es un modo cuya interface de programación involucra el
pin de RESET, pues allí donde se aplica el voltaje de 12V. Es el único pin diseñado para
soportar ese nivel de tensión.
La programación de alto voltaje ofrece algunas opciones que no se pueden encontrar en la
programación de bajo voltaje como programar los fuses SPIEN (para activar o desactivar la
programación serial) y RSTDISBL (para rehabilitar el pin RESET). En la programación de
bajo voltaje el AVR trabaja con su alimentación habitual de 5V mientras que el pin de
RESET permanece en 0V. Este modo tiene algunas limitaciones como impedirnos el acceso
al fuse SPIEN, para bien o para mal.
Advertencia: en los algunos AVR (los ATmegaXX8 de nuestro curso) el pin de RESET puede
ser programado por el fuse RSTDISBL para que trabaje como un pin de puerto más, o sea,
para que lo usemos para sacar o leer datos por él, aunque el precio a pagar puede ser muy
alto. A eso se llamadeshabilitar el pin RESET. Podemos hacerlo desde la programación de
alto o bajo voltaje, pero para volver a habilitarlo el único camino será la programación de
alto voltaje. Me pregunto si puedes descubrir el porqué.
La programación serial puede ser de alto o bajo voltaje. En cambio, la programación
paralela (PP) es siempre de alto voltaje. Por eso es casi redundante su denominación
de HVPP (High Voltage Parallel Programming). La sigla HVPP aparece en el manual
de Atmel Studio 6 solo una vez. Antes me desagradaba pero ahora me empieza a gustar.
Las líneas de la interface paralela son 16 sin contar los pines de alimentación del AVR. De
aquí concluimos que este modo solo está disponible en los AVR de bastantes pines, más de
14 pines, si queremos establecer una regla.
Líneas de interface de la programación paralela, HVPP.
La programación serial de alto voltaje está presente en los tinyAVR que cuentan con 14
pines o menos. Si uno de estos AVR tiene interface de programación SPI entones su
programación serial de alto voltaje se denomina HVSP (High Voltage Serial Programming).
Igualmente parece poco apropiado limitar el calificativo porque los tinyAVR cuya interface
de programación es TPI también soportan el alto voltaje. Debe ser porque son muy
poquitos :-(.
Lo anterior no significa que la programación HVSP vaya por el puerto SPI. Los pines de
interface en este caso son otros, tal como se ve en la siguiente imagen.

Líneas de interface de la programación serial de alto voltaje, HVSP.
Serial, paralelo, alto, bajo, más o menos de 14 pines. Sé que todo esto suena algo
enredado sobre todo si los modos e interfaces también se cruzan, pero se puede resumir y
entender fácilmente así: “En principio todo AVR debe tener al menos una programación
serial de bajo voltaje. Adicionalmente puede tener una programación de alto voltaje, que
debe ser paralela si el AVR tiene más de 14 pines o serial si el AVR tiene 14 pines o
menos”.
El pin RESET es determinante en el modo de operación del AVR.
Si aplicamos 12 V (vale entre 11.5V y 12.5V) al pin RESET, el AVR entra en modo de
programación de alto voltaje (serial o paralela, según el AVR).
Si aplicamos 5 V al pin RESET, el AVR trabaja en operación normal, o sea, empieza a
ejecutar su programa.
Si mantenemos el pin RESET en 0V, el AVR permanece en estado de RESET, es decir, no
hace nada y se queda esperando a que el pin RESET valga 5V para iniciar su operación
normal o esperando a que lleguen las instrucciones para entrar en modo de programación
de bajo voltaje.
Si ponemos el pin RESET en 0 V y luego iniciamos la secuencia de programación, uno de
cuyos paso incluye aplicar algunos pulsos al mismo pin RESET, entonces el AVR entra en
modo de programación de bajo voltaje (serial, obviamente).
Aquí hay una lección importante que debemos aprender. Algunos AVR tienen la capacidad
de multiplexar la función del pin de RESET mediante el fuse RSTDISBL. Si dejamos este
fuse con su valor de fábrica (sin programar), el pin RESET servirá para resetear el AVR, o
sea, con su función habitual. Pero si lo programamos, el pin RESET trabajará como un pin
de puerto convencional. Es útil si queremos ampliar nuestras líneas de E/S, pero observa
que ello impedirá que el AVR pueda volver a entrar en modo de programación de bajo
voltaje. La única señal que le hará recordar al pin RESET su función primigenia es la tensión
de 12 V, es decir, necesitaremos de un programador de alto voltaje.

Interfaces de programación
Aquí estudiaremos las interfaces SPI, TPI, PDI, JTAG y aWire, que podemos juntarlas a las
de alto voltaje HVSP y HVPP examinadas previamente. A todas se les suele también
denominar modos de programación.

Interface de programación SPI
Al ser esta la principal interface de programación de los AVR tinyAVR y megaAVR, vamos a
explicarlo largo y tendido, así que hecha esa salvedad espero que no te me desanimes.
Bueno, empezaremos por distinguir entre los términos ISP y SPI.
ISP es la sigla de In System Programming y hace referencia al tipo de programación
realizada estando el AVR en su circuito de aplicación, es decir, no es necesario quitarlo de
allí y colocarlo en el hardware del programador. Alguna gente le suele llamar también ICSP,
por In Circuit Serial Programming, que viene a significar lo mismo, pero se usa más con
otros microcontroladores como los PICs. Además ese término es marca registrada de
Microchip.
SPI es la sigla de Serial Port Interface, que es un bus de comunicación serial, así como lo
son los buses RS232, I2C o USB. Los microcontroladores suelen tener un módulo hardware
también llamado SPI que facilita las comunicaciones con otros dispositivos usando este
protocolo. En los AVR ese mismo módulo SPI sirve también como su interface de
programación.
Técnicamente hablando todas las interfaces de programación permiten programar el AVR
estando en su circuito de aplicación (In System), así que todos los programadores podrían
merecer el calificativo de ISP. Debemos tener eso en cuenta porque hasta en la
documentación de Atmel es frecuente hablar de programación ISP como sinónimo de la
interface SPI.
Las comunicaciones por el puerto SPI se llevan a cabo en una relación maestro-esclavo. En
este caso el maestro es el programador y el esclavo es el dispositivo programado (AVR).
Las transferencias de datos requieren de 4 líneas de interface: MISO, MOSI, SCK y SS. Para
la programación del AVR sin embargo se omite la línea SS. En su lugar se usa el pin de
RESET y en algunos casos hasta una señal de reloj para AVR puede ser necesaria. En este
momento no vamos a profundizar más sobre el control del bus SPI, pero sí vamos a
explicar la función de sus pines para que al menos sepamos cómo conectarlos cualquiera
que sea el AVR o el programador que usemos.

Líneas de interface de la programación SPI.
MISO. Master Input Slave Output. Es la línea por donde el maestro recibe datos del esclavo,
es decir, en el maestro el pin MISO es entrada y en el esclavo es salida.
MOSI. Master Output Slave Input. Es la línea por donde al maestro envía datos al esclavo,
es decir, en el maestro el pin MOSI es salida y en el esclavo es entrada.
SCK. Serial Clock. Es la señal de reloj. En la comunicación SPI el reloj está siempre a cargo
del maestro así que el pin SCK es salida en el maestro y entrada en el esclavo. Con esta
señal se establece la velocidad de transferencia de datos. Según el diseño del puerto SPI de
los AVR, esta velocidad debe ser como mucho igual a la cuarta parte de la frecuencia del
procesador. Por ejemplo, si un AVR trabaja con un XTAL de 8MHz, la frecuencia de SCK no
debe superar los 2MHz.
RESET. Esta señal no forma parte del bus SPI pero siempre debe haber una línea con el
mismo nombre que sale del programador y va al pin RESET del AVR a programar para
iniciar el modo de programación. Encuentras más información sobre la función de este pin
en la secciónProgramación de alto voltaje HVSP y HVPP.
XTAL1. Esta señal es necesaria solo si el AVR está configurado para trabajar con reloj
externo. De fábrica no lo está. En un AVR nuevo los fuses de reloj seleccionan el oscilador
RC interno, así que la primera programación no necesita esta señal. Pero desde el principio
lo común es reprogramar los fuses de reloj para seleccionar una fuente de reloj externa,
como un XTAL o resonador cerámico. Solo en esos casos debemos usar la señal XTAL1.
SS significa Slave Select. En principio esta señal sirve para que el maestro seleccione el
esclavo con el que entablará la comunicación, pues en una red SPI puede haber varios
esclavos. Sin embargo, no participa en la programación porque el AVR target da por hecho
que es el esclavo elegido desde el momento en que entra en modo de programación.
VCC y GND. Naturalmente son los pines de alimentación del AVR. La señal VCC suele a
veces aparecer con el nombre de VTG, por Voltage of Target.
Hay dos tipos de conectores para la interface de programación SPI. El grande es de 10
pines llamadoISP10PIN. Es un conector antiguo que todavía se encuentra en la
placa STK500 e incluso en la STK600pero solo por compatibilidad. Aunque parece mejor
para el diseño de PCB, Atmel nos recomienda dejarlo de lado y que procuremos usar el
conector de 6 pines ISP6PIN, que debiera estar presente en todo hardware para AVR
moderno.
Conectores ISP6PIN e ISP10PIN de la interface de programación SPI.

Conectores ISP6PIN e ISP10PIN de la interface de programación SPI.

Interface de programación TPI
TPI quiere decir Tiny Programming Interface es decir es una interface de programación de
los "tiny" AVR. Con eso podríamos pensar que está presente todos los tinyAVR, pero no. La
mayoría de lostinyAVR utilizan las dos interfaces de programación SPI y HVSP. La
interface TPI solo está disponible en los ATtiny4, ATtiny5, ATtiny9, ATtiny10, ATtiny20,
ATtiny28 y ATtiny40.
Por otro lado, la documentación de Atmel y algunos autores dicen que esta interface queda
destinada a los tinyAVR de 6 pines pero los ATtiny20, ATtiny28 y ATtiny40 echan por tierra
esa teoría. Ellos tienen como 20 pines y sin embargo también se programan vía TPI.
Como sea, la interface TPI utiliza dos líneas de comunicación llamadas TPIDATA y TPICLK,
para datos y reloj, evidentemente. Para la programación también es necesaria la
intervención del pin de RESET y según el nivel de tensión que se le aplique
el tinyAVR puede entrar en dos modos de programación:
Si se aplica 12V al pin RESET, el tinyAVR entra en modo de programación de alto voltaje.
No tiene una sigla o abreviatura propia, así que en la primera tabla presentada le llamé
simplemente TPI-HV. En este modo podemos habilitar y deshabilitar el pin de RESET para
que trabaje como el pin GPIO PB3.
Si el programador no puede entregar 12V, debe mantener el pin de RESET en 0V para
iniciar el modo de programación convencional (de bajo voltaje). También en este caso
tendremos acceso al fuseRSTDISBL para configurar el pin RESET como pin PB3, pero ya no
podremos devolverle su función de pin de RESET a menos recurramos a la programación de
alto voltaje.

Líneas de interface de la programación TPI.
El conector que usa la interface TPI es el mismo conector ISP6PIN visto arriba, solo que con
diferentes nombres de señal.

Conector de la interface de programación TPI.

Interface de programación PDI
Como su nombre indica PDI (Program and Debug Interface) es la interface de dos líneas
diseñada por Atmel con fines de programación y depuración exclusivamente para sus AVR
de la familia XMEGA. Adicionalmente los XMEGA tienen la interface JTAG, también con
propósitos de programación y depuración. Eso es todo; ellos no tienen interface aWire ni
mucho menos SPI ni TPI.
Los XMEGA también poseen fuses y lock bits para establecer diferentes formas de
operación y niveles de seguridad, incluso tienen la capacidad de poder configurar su pin de
RESET mediante el fuseRSTDISBL, pero ellos lo hacen todo mediante su interface PDI y no
necesitan programación de alto voltaje.
Las transferencias de datos en una comunicación PDI se rigen por un protocolo muy similar
al delUSART trabajando en modo síncrono. La señal de datos, llamada DATA o PDI_DATA,
es bidireccional y utiliza un pin dedicado del XMEGA; en tanto que la señal de
reloj, CLOCK o PDI_CLK, es siempre controlada por el programador y está multiplexado con
el pin RESET.
Líneas de la interface de programación y depuración PDI.
El conector que usa la interface PDI también es el mismo conector ISP6PIN, pero con
diferentes nombres de señal. Por eso el programador AVRISP MkII utiliza el mismo conector
para las interfaces SPI, PDI y TPI.

Interface de programación JTAG
JTAG también es una interface de programación y depuración presente en
los AVR32, XMEGA y en losmegaAVR de 40 pines o más. A diferencia de las
interfaces PDI y aWire, su principal modo de operación siempre ha sido la depuración,
quedando su función de programador como recurso auxiliar.
El conjunto de los pines de la interface JTAG se conoce como TAP, por Test Access Port. De
ellos los 4 pines que participan en la programación del AVR son:
TMS. Test Mode Select. Indica el estado el controlador Test.
TCK. Test Clock. Es la señal de reloj.
TDI. Test Data Input. Es la línea de entrada para las instrucciones de datos o de comando.
TDO. Test Data Output. La línea por donde el AVR envía los datos seriales.

Líneas de la interface de programación y depuración JTAG.
El conector de la interface JTAG según el estándar IEEE incluye también dos pines de
RESET: nSRSTque serviría para producir un reset software y nTRST para un reset del
controlador TAP. Ninguno se usa como tal en el AVR. En su lugar el módulo TAP puede
regresar a su estado inicial manteniendo la líneaTMS durante 5 periodos de reloj. A veces
se usa la señal nTRST pero aplicada al pin RESET del AVR para un RESET convencional. Eso
es útil no directamente para llevar al AVR al modo de programación sino como una forma
alterna de limpiar el bit JTD del registro MCUCR. Eso junto con el fuse JTAGENprogramado
son condiciones para que se abra el puerto JTAG, ya sea para programación o depuración.

Conector de la interface de programación y depuración JTAG.

Interface de programación aWire
Es la interface de programación y depuración constituida por un solo cable que tienen
los AVR32 de pocos pines, aunque eso parezca exagerado pues como mínimo los AVR32
tienen 48 pines. Lo que sí es seguro es que no está disponible en ningún AVR de 8 bits.
A pesar de su requerimiento mínimo, como interface de depuración ofrece el mismo alcance
que JTAG.
La única señal, DATA, es bidireccional y en el AVR32 corresponde al pin de RESET.

Líneas de la interface de programación y depuración aWire.
Conector de la interface de programación y depuración aWire.

Boot Loader
Pongo el Boot Loader en el primer lugar de la lista porque quiero que le demos especial
consideración.
Quienes empezamos programando los PIC adquirimos la costumbre de trabajar con un
programador. Utilizar un Boot Loader en ese mundo era una alternativa superflua o un
camino para los aventureros. Es comprensible entonces que al migrar a los AVR nos
preguntemos ¿y ahora qué programador vamos a utilizar? Tenemos muchas páginas
después de ésta donde hallar una respuesta. Pero antes de pensar en un programador
deberíamos saber que en el mundo de los AVR el Boot Loader es la mejor opción. Al
menos es mi conclusión personal.
Pero, ¿qué es un Boot Loader? Es un programa de microcontrolador que le permite
auto-programarse. Mediante el Boot Loader el AVR recibe su nuevo programa
directamente de la computadora sin intermediación de ningún dispositivo programador
y lo graba en su memoriaFLASH. La transferencia se realiza por cualquier interface
disponible, siendo las más comunes los puertos serial y USB. La siguiente figura nos
ayudará a entender su mecanismo de funcionamiento.

Esquema de la auto-programación de un AVR usando un Boot Loader.
La memoria FLASH, mostrada en color naranja, es donde se almacena el programa del
AVR, o sea, el firmware. En tiempo de ejecución el CPU lee cada una de las
instrucciones de esta memoria, las decodifica y las lleva a cabo. Aunque aparece
dividida en dos secciones, físicamente la memoria FLASH es un bloque compacto y el
firmware puede ocupar parte o todo el espacio disponible.
Anteriormente lo habitual era tener un solo firmware en el microcontrolador, que
realizaba la tarea de usuario como controlar dispositivos externos, comunicarse con la
computadora, etc. Ahora lo llamaríamos firmware de aplicación.
Siendo el Boot Loader un firmware también, significa que podemos tener dos
firmwares en el AVR. El firmware de aplicación va en la Sección de Aplicación y el
firmware de Boot Loader, en laSección de Boot Loader. ¿Cómo sabrá el AVR qué código
ejecutar?
Cada vez que se inicia el programa de un AVR, esto es tras un RESET, el CPU empieza
por ejecutar el Boot Loader –si existe– para ver si la computadora le está enviando
datos que representan un nuevo firmware de aplicación. De ser así, recibe los datos y
los graba en la sección de Aplicación, operando en lo que podemos llamar modo de
auto-programación. Pero si no encuentra nada, el CPU pasa a ejecutar el firmware de
Aplicación; operación en modo normal, por así decirlo.
Así como la memoria FLASH el Boot Loader también permite programar la memoria
EEPROM y el Byte de Lock bits. Además puede tener acceso de lectura a
los fuses aunque no los puede modificar.
Cuando trabajamos con la auto-programación dejamos de lado ese término para
introducir otros dos. El proceso de cargar un firmware de aplicación en el AVR
mediante el Boot Loader se denomina upload o subir. Y como el Boot Loader también
permite leer el firmware, a dicho proceso se denomina download o bajar.

Ventajas y desventajas
El uso de un Boot Loader tiene sus pros y contras respecto de un programador
convencional.
El espacio que el código del Boot Loader ocupa en la memoria FLASH lo descarta como
opción de la mayoría de los microcontroladores pequeños. Generalmente un Boot
Loader puede abarcar entre 1 kB y 2 kB de memoria. Para los tinyAVR cuya FLASH
anda por estos rangos no tendría sentido. En cambio para los megaAVR cuyas
memorias llegan hasta los 256 kB, el Boot Loader apenas si se deja notar.
El código del Boot Loader debería ser compilado o ensamblado para cada tipo de
microcontrolador. Es raramente compatible por ejemplo un Boot Loader para un
ATmega16 que para un ATmega32, a pesar de pertenecer a la misma serie. El primer
requisito de compatibilidad es que los AVR tengan el mismo tamaño de memoria
FLASH puesto que el Boot Loader se aloja en su parte final.
El Boot Loader no puede programar los fuses. A veces esto puede ser una pena pero
en general será una bendición.
Existen algunos microcontroladores que ya traen un Boot Loader de fábrica, pero en su
gran mayoría vienen vacíos. El Boot Loader lo tenemos que grabar como cualquier otro
programa, con un dispositivo programador convencional. Si nos vamos a dedicar a la
programación masiva de microcontroladores, nos puede resultar poco práctico grabar
el Boot Loader primero y subir elfirmware de aplicación después. Sin duda en esos
casos sería mejor usar un programador para grabar el firmware de
aplicación directamente.
Por otro lado, en los procesos de aprendizaje y de desarrollo de proyectos, donde
modificamos constantemente el firmware de aplicación, el uso del Boot Loader es la
mejor opción. Aquí nos basta con un microcontrolador para experimentar con
diferentes programas y en diferentes circuitos. Para no exagerar y asumiendo la
realidad de los microcontroladores que quemaremos en camino podemos preparar el
Boot Loader en varios de ellos. Éste será el paso más difícil pero bien habrá valido la
pena porque después solo necesitaremos de un clic para subir un nuevo firmware sin
ni siquiera tener que desconectar nuestro circuito de aplicación.

Boot Loader AVR109 y Butterfly
La variedad de Boot Loaders es tan amplia como la de los programadores mismos. Los
hay con diferente interface, con diferente protocolo, propietarios o de código abierto, y
para diferentes series de AVR. Aquí nos ocuparemos brevemente de los más
reconocidos.
AVR109 y Butterfly son términos sinónimos para identificar el mismo tipo de Boot
Loader que se presenta en la nota de aplicación AVR109 de Atmel. Este Boot Loader
está escrito en C para los compiladores AVR IAR C y AVR GCC. Su código es de libre
disposición así que podemos compilarlo para cualquiera de nuestros AVR con sección
de Boot Loader. Utiliza el puerto serie para conectarse con la computadora. En una
laptop un conversor USB-UART funciona bastante bien.
La computadora por su parte deberá correr un software que entienda el
protocolo AVR109. El más sobresaliente es AVR OSP, descrito en la nota de
aplicación AVR911 de Atmel. Su nombre significa AVR Open Source Programmer, así
que también es libre. Pero lo mejor de todo es que trabaja con los archivos XML de
dispositivo originales y actuales de Atmel, es decir, soporta todos los AVR con Boot
Loader. Abajo tenemos una versión para Windows de AVR OSP editada por Mike
Henning.
Entono del programa AVR-OSP II.
Butterfly para ser más precisos es una adaptación del Boot Loader AVR109 hecha por
Atmel para su mini tarjeta de desarrollo del mismo nombre. La tarjeta AVR
Butterfly está basada en un ATmega169, el cual además del Boot Loader se puede
programar mediante las interfaces SPI yJTAG. Esta tarjeta ofrecía características muy
útiles a un bajo precio, por eso Joe Pardue apostó por ella al escribir su eBook C
Programming for Microcontrollers, pero actualmente su preferencia ha sido desplazada
por el Arduino.
Anverso y reverso de la tarjeta de desarrollo AVR Butterfly (fuente: atmel.com).

Boot Loader de Arduino
El Arduino es, sin duda, el sistema de desarrollo del momento. Su tarjeta se puede
reducir a un megaAVR y un conversor USB-UART. Siendo así de simple es
sorprendente el éxito que ha alcanzado. Mucho se debe al framework de su lenguaje
de programación pero en cuanto al hardware contribuyó el hecho de que el conversor
USB-UART se conecte a la computadora para el data logging así como para recibir el
nuevo firmware de aplicación del megaAVR.

Tarjeta del Arduino.
Al igual que la tarjeta AVR Butterfly, el Arduino también posee un
conector ISP6PIN para la programación SPI de su megaAVR. Es solo una alternativa,
pues el principal medio de su [auto] programación sigue siendo el Boot Loader. El Boot
Loader del Arduino está basado en el protocolo de programación STK500 1.x.
El IDE del Arduino permite subir al megaAVR el código del firmware automáticamente
después de compilar el programa tan solo con presionar el botón Upload (ver la figura
de abajo). El usuario no tiene que saber dónde se encuentra el archivo hex generado
ni mucho menos conocer de los protocolos que operan detrás del escenario. ¡Genial!

Entorno de Desarrollo Integrado del Arduino.
Empero a nosotros nos interesará saber que detrás de esta ventana corre AVRDUDE
luego de presionar Upload. AVRDUDE es un programa open source que soporta casi
todos los programadores de AVR. Como no tiene un entorno propio se han hecho
muchos intentos de darle uno; aunque todos quedaron incompletos. Geir
Lunde escribió una aplicación Windows para AVRDUDE que permite usarlo para subir o
cargar el archivo hex al AVR del Arduino. Por lo visto arriba esto parecería innecesario,
pero resulta sumamente útil cuando trabajamos fuera del entorno del Arduino. El
programa se llama Xloader y lo puedes encontrar en versión más reciente en su
web russemotto.com.

Entorno del programa Xloader.
El Boot Loader del Arduino está hecho para los AVR de sus tarjetas: ATmega328,
ATmega2560 o ATmega32U4, por ejemplo. La buena noticia es que es fácilmente
recompilable para cualquiermegaAVR con soporte de Boot Loader, en especial de las
series ATmegaXX4 y ATmegaXX8, que son los usados en cursomicros.com. También es
posible compilarlo para los XMEGA e incluso para los tinyAVR pero eso requeriría de
edición extra. Para los AVR con periférico USB se usa el Boot Loader
llamado Caterina escrito por Dean Camera usando su librería LUFA.
Hay una característica en el Boot Loader del Arduino que lo convierte en mi favorito y
es que su ejecución es íntegramente controlada por el software de la computadora.
Con otros Boot Loaders como el del AVR109 suele ser necesario presionar algún botón
en el circuito del AVR aparte del botón RESET para entrar en modo de autoprogramación. En el Arduino el reset se genera mediante el conversor USB-UART. A
decir verdad en algunas variaciones del Arduino sí se requiere presionar algún botón
pero como todo el código es abierto lo podemos modificar a placer.
Los códigos de Boot Loader de Arduino están en la
subcarpeta hardwarearduinobootloaders.

Programador FTDI
Los módulos FTDI que estudiaremos son los mismos puentes USB-UART que usaremos
intensivamente para las comunicaciones de nuestro AVR con la computadora. Son los
comercialmente conocidos como conversores USB a Puerto Serie, solo que ahora los
veremos en su desempeño como programadores de AVR.
Bueno, en realidad no hay ningún programador llamado como tal. Denominamos así a
los módulos de comunicación basados en el transceiver FT232R o similar. Este chip es
un conversor USB-UART fabricado por FTDI chip. De ahí su apelativo, aunque ello no
quita la posibilidad de usar este método con un transceiver de otra marca, como por
ejemplo el CP2102 de Silicon Labso el MCP2200 de Microchip, o un conversor
personalizado basado en un microcontrolador, como el USB Serial Light apoyado por el
Arduino. De hecho dicen que el MCP2200 es en realidad un PIC pre programado.
Los módulos FTDI están difundidos en dos formas: Como un conversor USB - Puerto
Serie y como un conversor USB-UART. Dos ejemplos del primer grupo son los
módulos EVAL232R y USB-to-RS232, de la misma FTDI Chips, los cuales ofrecen a la
salida un conector DB9 con los 9 pines completos, con los voltajes y niveles lógicos
del Estándar RS232. Se comportan como auténticos puertos serie. Muy buenos.
Además los circuitos de estos y otros productos similares están disponibles en su web.
Conversores USB-UART

Los conversores USB-Puerto Serie EVAL232R y USB-to-RS232, de FTDI Chips.
Por otro lado, quienes trabajamos con microcontroladores utilizamos el puerto serie
para intercambiar con la computadora datos que no tienen que viajar en los niveles y
voltajes RS232. Tampoco solemos emplear las 9 señales disponibles. Muchas de ellas
solo son un legado de los antiguos módems.
Es por eso que varias empresas como Adafruit o Sparkfun comercializan conversores
USB-UART. Estos son como la tarjeta EVAL232R pero sin el transceiver MAX232,
encargado de la conversión de los niveles de voltaje. En lugar del DB9 poseen un
conector lineal que solo brinda las señales útiles para nosotros. Para decirlo de otra
forma, son pequeñas tarjetas que básicamente unen las líneas del FT232RL a un
conector USB por un lado y a un conector plano por el otro. En las imágenes
mostradas abajo puedes ver que casi no tienen más elementos que esos. Por eso a
veces se les llama también cables FTDI. En inglés un término muy extendido
es Breakout. Así aparece identificado en los circuitos de las prácticas de
cursomicross.com.
conversores USB-UART
Los conversores USB-UART FTDI Friend, FTDI Basic y USB Serial Light
El conversor USB Serial Light es una tarjeta basada en un microcontrolador
ATmega8U2 programado para emular un puerto serie. El AVR funciona también como
el FT232RL aunque a nivel software los drivers con que trabaja no tienen el prestigio
de FTDI chips.
Los tres adaptadores son muy parecidos. Lo que nos interesa a fin de cuentas son las 6
señales de salida que proveen. Las 5 primeras son las
mismas: GND, CTS, VDD, TXD y RXD (los nombres cambian un poco pero son las
mismas). La sexta señal es DTR en el FDTI Basic y USB Serial Lightpero RTS en el FTDI
Friend. Esa es la diferencia crucial. Como puente de comunicación para transferencias
de mensajes con la computadora funcionan igual pues allí solo intervienen TXD y RXD
además de VDD y GND claro está.
Sin embargo, como circuito para programar un AVR ya sea mediante el Boot Loader
del Arduino o por la interface SPI, sí debemos considerar esa diferencia.

Módulo FTDI como programador de interface SPI
Mediante el chip FT232RL podemos emular cualquier programador de puerto serie de
los llamados bit bang como el programador SI-Prog o los programadores DASA. Puesto
que elFT232RL puede manejar niveles de tensión TTL, la conexión con el
microcontrolador sería directa, sin resistencias, ni diodos zéner ni transistores.
Pero si vamos a trabajar con un adaptador como alguno de los tres mencionados
arriba, debemos considerar dos puntos. Primero, que el software de grabación
AVRDUDE asume que a la salida del puerto serie las señales tienen polaridad según
el estándar RS232, esto es, invertidas respecto de las señales TTL. Y segundo, que los
programadores seriales como SI-Progo DASA usan las señales DTR y RTS al mismo
tiempo, lo cual deriva en un escollo puesto que los adaptadores FTDI Basic y USB
Serial Light solo usan DTR, en tanto que FTDI Friend o usa DTR oRTS pero muy
difícilmente las dos a la vez.
Una alternativa para resolver estas dos contrariedades sería usar un conversor que nos
brinde todos los pines del puerto serie necesarios, como por ejemplo el UM232R o
el UM232H, mostrados abajo.
conversores USB-UART

Los conversores USB-UART UM232R y UM232H, de FTDI Chips.
Afortunadamente también existe una solución software. Para ello debemos abrir en un
editor de texto el archivo avrdude.conf que usa AVRDUDE. Se halla en el mismo
directorio donde resideavrdude.exe, por ejemplo en C:WinAVR-20100110bin,
suponiendo que lo instalamos con las opciones por defecto de WinAVR.
Entre otras cosas el archivo avrdude.conf contiene las descripciones e interfaces de
todos los programadores que soporta. Lo que haremos será añadir las características
correspondientes de nuestro nuevo programador que solo usa las señales de los
adaptadores FTDI Basic o FTDI Friend y con las polaridades debidas. Podemos incluirlo
en cualquier parte pero para mantener las categorías lo pondremos entre los
programadores seriales bit bang. Como ves en el extracto mostrado abajo, yo lo puse
en el segundo lugar del grupo, entre ponyser y siprog. Es todo el contenido con fondo
gris.

#
# some ultra cheap programmers use bitbanging on the
# serialport.
#
# PC - DB9 - Pins for RS232:
#
# GND 5 -- |O
#

| O| <- 9 RI

# DTR 4 <- |O |
#

| O| <- 8 CTS

# TXD 3 <- |O |
#

| O| -> 7 RTS

# RXD 2 -> |O |
#

| O| <- 6 DSR

# DCD 1 -> |O
#
# Using RXD is currently not supported.
# Using RI is not supported under Win32 but is supported under Posix.

# serial ponyprog design (dasa2 in uisp)
# reset=!txd sck=rts mosi=dtr miso=cts

programmer
id="ponyser";
desc="design ponyprog serial, reset=!txd sck=rts mosi=dtr miso=cts";
type=serbb;
reset=~3;
sck=7;
mosi=4;
miso=8;
;

# programador basado en un adaptador FTDI
# reset=dtr sck=cts mosi=txd miso=rxd

programmer
id="ftdi";
desc="programador ftdi, reset=DTR sck=CTS mosi=TXD miso=RXD";
type=ft245r;
reset=4;# DTR
sck=3;# CTS
mosi=0;# TXD
miso=1;# RXD
;

# Same as above, different name

# reset=!txd sck=rts mosi=dtr miso=cts

programmer
id="siprog";
desc="Lancos SI-Prog <http://www.lancos.com/siprogsch.html>";
type=serbb;
Extracto del archivo avrdude.conf modificado.
En principio, a cada señal de la interface SPI, mosi, miso, sck y reset, le deberíamos
asignar un número entre 1 y 9 que son los que tendría el conector DB9. Pero como
dice el comentario (todo lo mostrado en azul oscuro es comentario), AVRDUDE no
puede controlar la señal RXD de forma normal. Al no poder utilizarla, cualquiera de
nosotros se habría dado por vencido, pero Doshwalo hizo. No sé cómo lo descubrió
pero lo hizo. En su esquema no emplea la numeración del conector DB9 ni la polaridad
invertida que se esperaría. No voy a quitarle más el crédito así que si deseas ver cómo
funciona de fondo te recomiendo que lo leas en su sitio web. Los que llevan prisa solo
copien y peguen lo indicado. Guarden los cambios hechos en el archivo avrdude.conf y
cierren el editor.
Ahora ya podemos usar nuestro conversor FTDI Basic u otro similar para programar
cualquier AVR. Un ejemplo sería así.

>avrdude -c ftdi -p atmega324p -P ft0 –U flash:w:main.hex:i-B 1
Por si no quedó clara la interface entre el módulo FTDI y el AVR, abajo tenemos el
circuito a utilizar. Como se ve, la alimentación del AVR proviene del mismo módulo
FTDI. No es recomendable ponerle una fuente propia. Si el AVR no tiene el pin AVCC
pues no pasa nada, y si tiene varias señales VCC o GND, se conectan todas. El circuito
del XTAL no será necesario si el AVR está configurado para operar con su reloj interno.
De fábrica viene así. Este módulo FTDI es perfectamente compatible con el FTDI
Basic y USB Serial Light. Para el FTDI Friend debemos aclarar que el pin RTS se debe
re direccionar para unirse con la señal DTR. Incluye un pequeñísimo jumper en la parte
posterior para realizar esta maniobra. Otra opción sería modificar de nuevo el
archivo avrdude.conf como lo hicimos arriba.

Esquema para programar un AVR con un Módulo FTDI.

Módulo FTDI como programador Boot Loader de Arduino
Una tarjeta Arduino es en última instancia un módulo FTDI unido a un AVR. Pero no
están unidos como aparece justo en la figura de arriba. En el Arduino el módulo FTDI
le sirve al AVR en su función original de comunicarse con la computadora como puerto
serie virtual, ya sea para intercambiar simples mensajes de texto o para cargar el
nuevo firmware de aplicación mediante el Boot Loader del AVR. En el primer caso las
líneas TXD y RXD se conectan con sus homólogos en el AVR de forma cruzada. No hay
novedad en ese aspecto, así que ni ponemos un circuito.
Por otro lado, cuando se usa el módulo FTDI para cargar el nuevo programa del AVR
interviene además de TXD y RXD la señal DTR, cuya función es resetear el AVR para
que reinicie desde elBoot Loader. Para conocer los detalles de este proceso puedes leer
la sección programador Boot Loader. El circuito en este caso queda como se ve abajo.
Valen las mismas sugerencias indicadas para el circuito anterior sobre la fuente de
alimentación y la conexión de los pines relacionados a ella. Aquí el AVR siempre
trabaja con un XTAL de 16MHz.

Esquema para programar un AVR con un Módulo FTDI usando el Boot Loader del
Arduino.
El Boot Loader le permite al AVR auto grabar en su memoria FLASH el programa que
recibe por elmódulo FTDI (auto-programación). Pero para que esto sea posible primero
el AVR debe tener grabado el programa del Boot Loader. Esto será sencillo si usamos
el módulo FTDI como programador de interface SPI, estudiado antes.
Luego el trabajo será más que sencillo. El circuito mostrado arriba es un "Arduino
desnudo". Parecerá precario pero con el Boot Loader debidamente cargado será
completamente funcional. Podremos usarlo desde su entorno natural (mostrado abajo)
o desde un programa alternativo como XLoader.
Entorno de Desarrollo Integrado del Arduino.
El programa XLoader es particularmente útil para quienes programamos el AVR desde
otras plataformas como de los compiladores IAR AVR C, CodeVisionAVR o AVR
GCC alojado en Atmel Studio 6. De esa forma trabajaremos en todas las prácticas de
cursomicros.com.
Entorno del programa Xloader.

Programador SI-Prog o PonySer
Los programadores de puerto serie o paralelo se están convirtiendo en herramientas
obsoletas por la casi extinción de esos puertos en las computadoras. Si necesitamos
armar un programador similar aunque sea provisionalmente (en un breadboard) será
para grabar el AVR que servirá de cerebro de un programador USB. Estamos hablando
de programadores que no llevan un AVR en su circuito. De esos tenemos varios otros y
los veremos luego. Ahora examinaremos los programadores conocidos como de bit
bang por la manipulación bit a bit de los datos desde la computadora. No serán los
más veloces pero sí los más fáciles de armar.
Para muchos que reconocen, a veces hasta con cierto bochorno, haber empezado con
los PIC antes que otro microcontrolador, es casi inevitable preguntar si habrá algún
programador de AVR parecido al entrañable JDM. Un sencillo pero fiable y
cómodamente portable programador de puerto serie que no necesitaba de
alimentación externa.
El programador más cercano es el también conocido en la comunidad PIC. Estamos
hablando del viejo superviviente SI-Prog, de Claudio Lanconelli, quizá más conocido
por su softwarePonyProg. Este programador usa la interface SPI para grabar todos
los megaAVR y tinyAVR que la soportan. El circuito del SI-Prog lo podemos dividir en
dos etapas: la que controla las líneas de programación (parte inferior) y la que
conforma la fuente de alimentación (parte superior). He puesto un conector ISP de 6
pines estándar en vez del conector SIL que figura en el circuito original y he ignorado
la etapa de los zócalos para los AVR que allí se incluían. Puedes encontrar los circuitos
originales y el software PonyProg que los controla en la web del autor lancos.com.
Circuito del programador SI-Prog.
La etapa de alimentación del SI-Prog parte de los 3 diodos 1N4148 que toman
corriente de las líneas del puerto serie y recargan el capacitor C3. Si el jumper está
cerrado como se ve en la figura también participa el capacitor C4 para acumular en la
entrada del LM2936Z-5 hasta 12V tensión. El chip LM2936Z-5 es un regulador parecido
al 7805 que a su salida entrega una tensión de 5V, aunque no deberíamos
reemplazarlo pues el 7805 no es LDO. De este modo el circuito obtiene su alimentación
del puerto serie. Pero si vamos a usar una fuente de alimentación externa debemos
mover el jumper a la otra posición. En este caso sí podemos pensar en un 7805 como
alternativa.
Si quitamos toda la etapa de alimentación y nos quedamos con las líneas básicas de
programación, el circuito se reduce a su forma más difundida. Ahora el
programador SI-Prog es netamente ISP porque su fuente de alimentación VCC la
obtendrá del circuito del AVR programado.
Circuito reducido del programador SI-Prog.

Programador SI-Prog reducido comercializado por deccanrobots.
El circuito resultante sigue siendo igual de fiable que el original. Es bastante bueno
aunque no podemos decir lo mismo de PonyProg. Como la mayoría de los programas
con entorno gráfico para Windows, este software ha quedado desactualizado. Soporta
pocos dispositivos, la mayoría antiguos modelos, y difícilmente funcionará en los
sistemas operativos actuales.
Entorno de trabajo del programador PonyProg.
Dada la popularidad del programador SI-Prog, se pueden encontrar en Internet
algunos otros softwares con entorno gráfico que lo soportan. Podría citar una lista de
ellos, con características tan diversas como sus limitaciones, pero sería en vano, un
desperdicio de tiempo tratando de explicar a quiénes les puede convenir tal o cual
programa. Mejor es usar sugerir un programa que a todos nos servirá sin importar el
hardware ni sistema operativo de nuestra computadora. Este programa universal es
AVRDUDE: el mejor software programador que existe, a pesar de no tener entorno
gráfico GUI completo.
En la siguiente captura, obtenida al escribir avrdude –c x, AVRDUDE nos muestra la
lista de todos los programadores que soporta. Los 5 primeros son programadores bit
bang de puerto serie. ElSI-Prog aparece identificado como siprog, pero también es
reconocido por su alias ponyser. Por eso también se le conoce así.
Programadores seriales bit bang soportados por AVRDUDE.
De hecho, y como podremos comprobar después, todos los programadores bit bang de
puerto serie tienen la misma estructura de circuito. Esto a veces puede hacer confusa
su identificación. Por ejemplo, es frecuente encontrar pequeñas variaciones en un
programador SI-Prog que hacen que se vea como un programador DASA3, y viceversa.
Sucede mucho en las versiones comerciales porque no suelen identificarse con los
nombres aquí presentados.
Pero si vamos a trabajar con AVRDUDE, poco interesa la procedencia de nuestro
programador serial y tampoco es imprescindible saber cómo se llama. Todo lo que
necesitamos es conocer la relación de las señales MISO, MOSI, SCK y RST con los
pines del conector DB9. Con estas correspondencias podemos identificarlo fácilmente.
Por ejemplo, la imagen que tenemos abajo confirma que en el programador SIProg o PonySer la interface es MISO-CTS, MOSI-DTR, SCK-RTSy RST-TXD.
Pines de interface del programador SI-Prog o PonySer.
A modo de ejemplo, si queremos grabar un archivo main.hex en la memoria FLASH de
un ATmega128P con AVRDUDE estando nuestro programador conectado al puerto serie
COM1, podríamos escribir:
>avrdude –c siprog –P com1 –p atmega128p –U flash:w:main.hex:i
O de la siguiente forma, puesto que ponyser es alias de siprog.
>avrdude –c ponyser –P com1 –p atmega128p –U flash:w:main.hex:i
A falta de un puerto serie en las computadoras actuales se han difundido bastante
losconversores USB a Puerto Serie, casi todos basados en la familia del
transceiver FT232RL, pero sobre su uso el manual del AVRDUDE no garantiza nada y
hasta recomienda no usarlo como puerto serie virtual. Si tienes un módulo de este tipo
es recomendable que leas la página delprogramador FTDI.

Programadores DASA
Existen 3 programadores de esta serie: DASA,DASA2 y DASA3. Son programadores
que no tienen alimentación propia ni derivada del puerto serie y tampoco una señal de
reloj para el AVR en caso de que éste trabajara con oscilador externo. Empezaremos
por describir el último.
No, no ha habido un error al poner la imagen. El circuito que ves abajo corresponde al
programador DASA3. Es muy parecido al circuito reducido del programador SI-Prog.
Serían idénticos de no ser por el diodo 1N4148, que en muchas ocasiones también
suele aparecer en elSI-Prog, y porque la conexión de los pines TXD yDTR en el
conector DB9 está permutada.

Circuito del programador DASA3.
El circuito del programador DASA2 es más parecido aun. De hecho podríamos decir
que es el mismo SI-Prog reducido. Supongo que por eso no es considerado por el
programa AVRDUDE. Poner su circuito por tanto sería desperdiciar espacio.
La primera versión de esta familia era el programador DASA. Es el programador serial
más simple de todos. Yo creo en el adagio kiss pero este circuito no me da mucha
confianza. Me atrevería a armarlo solo como programador provisional. Observa que
nuevamente ha cambiado la conexión de las líneas en el conector DB9.

Circuito del programador DASA.
El programador DASA (fuente: make.larsi.org).
Podríamos seguir mostrando otros programadores de puerto serie pero solo
descubriríamos que son derivados o variaciones de los programadores SIProg y DASA3. Podemos incluso hacer nuestras propias adaptaciones que puedan ser
controladas por AVRDUDE. Tanto su nombre como la conexión de señales en el
conector DB9 deben ser añadidas en el archivo avrdude.conf, que utiliza AVRDUDE.
Más adelante veremos cómo hacer esto pero para un programador que utiliza un
conversor USB-UART.
Debido a su parecido, son muy recurrentes las metidas de pata al confundir los
programadoresDASA entre ellos y con el SI-Prog. Antes de usar un programador de
estos debemos cerciorarnos en su identificación viendo los pines de interface que usa.
La siguiente imagen por ejemplo nos resalta las interfaces de los
programadores DASA y DASA3. Observa que no aparece DASA2. Si tuviéramos uno,
podemos invocarlo como siprog o ponyser.
Programadores DASA soportados por AVRDUDE.
A modo de ejemplo, si queremos grabar con un DASA3 un archivo main.hex en la
memoria FLASH de un ATmega128P con AVRDUDE estando nuestro programador
conectado al puerto serie COM1, podríamos escribir:
>avrdude –c dasa3 –P com1 –p atmega128p –U flash:w:main.hex:i
O, si usamos un DASA.
>avrdude –c dasa –P com1 –p atmega128p –U flash:w:main.hex:i
O, si usamos un DASA2.
>avrdude –c siprog –P com1 –p atmega128p –U flash:w:main.hex:i
A falta de un puerto serie en las computadoras actuales se han difundido bastante
losconversores USB a Puerto Serie, casi todos basados en la familia del
transceiver FT232RL, pero sobre su uso el manual del AVRDUDE no garantiza nada y
hasta recomienda no usarlo como puerto serie virtual. Si tienes un módulo de este tipo
es recomendable que leas la página delprogramador FTDI.

Programador USBasp
USBasp deber por mucho el programador USB para AVR más vendido y también el más
construido en casa debido a la libre disponibilidad de su hardware, firmware y la
amplia gama de software de computadora que lo soporta. Su popularidad se ha
acrecentado además por la flexibilidad de su circuito que permite usarlo con el
firmware de otros programadores USB comoAVR Doper, AVRminiProg o USBtinyISP.
Todo esto lo explicaremos de a poco. Así que empecemos por mostrar sus principales
características entres pros y contras.
Programador usbasp

Programadores USBasp.
Soporta las interfaces de programación SPI y TPI. Con SPI podemos programar todos
losmegaAVR y la gran mayoría de los tinyAVR. Con TPI cubrimos el pequeño restante
de los tinyAVR. Recordemos que la programación TPI también puede realizarse en alto
voltaje, pero ese modo no es manejado por USBasp.
Fue diseñado por Thomas Fischl utilizando la librería V-USB de Christian
Starkjohann que administra todo el protocolo de comunicación USB a nivel software,
de modo que no es preciso disponer de un microcontrolador con USB incorporado.
Inicialmente el firmware está escrito y compilado para el ATmega8 y ATmega88 pero
puede adaptarse para funcionar en cualquier AVR siempre que tenga la memoria y los
pines suficientes.
Puesto que la librería V-USB trabaja en bit banging (bit a bit), las transferencias de
datos se dan a la mínima velocidad del protocolo USB, esto es, 1.5 Mb/s (modo Low
Speed). A pesar de ello la velocidad de programación alcanza los 5 kB/s. Es bastante
rápido.
Usa un jumper para activar la alimentación Vcc al AVR target y otro para “frenar”
cuando sea necesario la velocidad de programación. Mover jumpers cada vez que
vamos a programar un AVR podría resultar hasta frustrante en especial si tenemos
planeado usar el programador activamente.
No tiene un software de computadora con entorno a lo Windows que lo maneje a
plenitud. AVRDUDE lo controla estupendamente pero su uso desde la línea de
comandos con frecuencia termina por ahuyentar a los principiantes. Muchos han
intentado darle a AVRDUDE un entorno grafico que permita programar el AVR con un
par de clics: avrdude-gui, SinaProg, AVR Burn-O-Mat, son algunos ejemplos. Para mí
todos están incompletos en especial por las ventanas de configuración de los fuses.
Inicialmente eso me disgustaba pero luego vi que los fuses no los programaba más
que una vez. El resto del tiempo solo programaba la memoria flash, para lo cual
efectivamente bastan con un par de clics.
AVRDUDE corre sin ningún inconveniente en Mac OS X y Linux. En Windows requiere la
instalación del driver open source para USB libusb. No tendría que resaltar esto de no
ser porque libusb se lleva mal con los drivers USB de Jungo que emplea Atmel Studio
6 para sus programadores y depuradores hardware, como AVRISP mkII, AVR Dragon,
etc. En estos casos se recomienda emplear la librería libusb en modo filtro, el cual
lamentablemente no está del todo acabado.
Parece que al final la balanza no favorece mucho que digamos el lado de los pros.
Siendo el programador USBasp de hardware y software open source, tal vez pienses
que en vez de criticar debería hacer mi aporte para remediar sus supuestas
deficiencias. De hecho lo pensé al principio pero luego comprendí por qué otros no lo
habían hecho antes. ¿Por qué?
Si echamos un vistazo al sitio web oficial de USBasp, quizá nos sorprenda encontrar
además del diseño original las distintas adaptaciones que hicieron muchos usuarios.
¿Por qué tantas versiones?, ¿Serán igualmente fiables?, ¿Cuál me conviene construir?
No es fácil responder a estas preguntas para alguien que pretende ser imparcial. Así
que en vez de dar un sí, no, éste o aquél, vamos a hacer un pequeño viaje para
conocer las características del circuito de este programador y comprender por qué le
hicieron algunas variaciones.
Empecemos por el circuito original del USBasp.

Programador USBasp original.
Circuito del programador USBasp original.

La interface USB
El AVR es un dispositivo esclavo en la red USB que debe estar atento a todos los datos
que le envía la computadora. Esto se consigue usando la interrupción INT0 para
detectar los cambios de nivel en la línea D+. Se puede editar fácilmente el firmware
del AVR para conectar las líneasD+ y D- a otros pines del AVR (siempre que
pertenezcan al mismo puerto), pero la conexión deD+ al pin INT0 debe permanecer.
En realidad, según la librería de USB Virtual V-USB que usa el firmware y
las interrupciones de cambio de pin del ATmega88, es posible incluso pasar por alto
esta conexión pero con una edición un poco más avanzada.
La resistencia de 2.2k presenta el programador a la computadora como dispositivo Low
Speed(1.5MHz). Si estuviera atada al pin D+ la computadora trataría de reconocerlo
como dispositivoFull Speed (12 MHz).
Es regla general que las transferencias de datos a altas velocidades no se pueden
realizar a niveles de tensión muy altos. Por eso el USB fue diseñado para que sus
líneas de datos D+ y D-manejen señales diferenciales de 0 y 3.3V, no los niveles TTL
de 0 y 5V. Si en D+ hay 3.3V en D- debe haber 0V y viceversa. Los microcontroladores
con USB hardware tienen pines preparados para entender estos niveles incluso si están
alimentados por Vcc = 5V. Pero como el USBasp no lleva uno de esos, tiene que usar
diodos zéner. Nota que si fueran de 3.3V, estaríamos en el borde del rango que puede
entender el AVR. 3.6 V está bien. Eso junto con las resistencias de 68 Ω dan los niveles
de tensión e impedancias que el estándar USB puede aceptar.

Interface USB del programador USBasp.
Uno de los diseños que me llamó la atención rápidamente por su PCB compacto (solo
tiene 3 discretos puentes) era el de J.A. de Groot. Pero me desilusioné al percatarme
de que le había quitado los diodos zéner. Yo no habría hecho eso, pues la
especificación del USB es bastante estricta. Si bien es cierto que era así en las
primeras del programador, no he visto otros proyectos con USB emulado que omitan
los diodos zéner.
Programador USBasp modificado por J.A. de Groot.

La interface SPI
En el circuito del USBasp original las líneas de la interface SPI van directamente al
conector. Puesto que el AVR programador siempre trabaja a 5 V, es necesario que el
AVR target (a programar) también lo haga o, de lo contrario, no se podrán entender.
Hasta allí no parece haber nada novedoso, ¿verdad?

Interface SPI del programador USBasp.
Sin embargo, varias personas decidieron interponer elementos en las líneas del bus
SPI pensando en los AVR alimentados por 3.3 V o, en general, por voltajes inferiores a
5 V. No estamos hablando de los XMEGA ni de los AVR32 que son los que trabajan a
3.3 V de forma exclusiva. Además ellos no se programan por la interface SPI. Nos
referimos a los tinyAVR ymegaAVR que como sabemos pueden trabajar entre 1.8V y
5.5 (esos son valores críticos, no significa que tengamos que llegar a esos extremos).
En algunos rediseños como el de Fisch und Fischl GmbH y Thomas Pfeifer se han
puesto resistencias con el propósito adicional de prevenir cortocircuitos. En otros
diseños como Pawel Szramowski o yuki-lab.jp han ido más lejos y le han colocado un
buffer como el 74HC125 o el 74HC541. En cualquiera de esos casos el jumperSupply
Target debe permanecer abierto durante la programación dejando que AVR target
trabaje con su propia alimentación (inferior a 5V).
Programador usbasp

Interface SPI del programador USBasp.

Los jumpers
Hay tres jumpers en el programador USBasp:
Jumpers del programador USBasp.
Slow SCK. Sirve para reducir velocidad de programación del USBasp. Por defecto,
cuando este jumper está abierto, la velocidad del bus SPI se establece a Fsck =
375kHz (frecuencia de la señal SCK). En la gran mayoría de los casos se cumplirá con
el requisito de ser menor a la cuarta parte de la frecuencia del procesador, es
decir, Fsck < F_CPU/4, y no habrá problemas pues en general nuestro F_CPU vale la
frecuencia del XTAL usado. Una de las excepciones se da cuando el AVR es nuevo,
donde los fuses de reloj configuran la frecuencia F_CPU = 1MHz, esto es, la octava
parte de la frecuencia del oscilador RC interno del AVR. En ese caso la condición
375kHz < 1MHz/4 no es cierta y por tanto será necesario disminuir el valor de Fsck.
El programador USBasp ofrece dos formas de reducir el valor de Fsck: por la vía
hardware, cerrando el jumper Slow SCK, y por la vía software, desde el programa
como AVRDUDE. Si el jumper está cerrado, el bus SPI se configura con Fsck = 8kHz.
Esta frecuencia no solo cumplirá la exigencia de Fsck < F_CPU/4, sino que puede
reducir demasiado la velocidad de programación del AVR. La buena noticia es que si
programamos los fuses de reloj para que la frecuencia F_CPU sea superior a 1.5MHz,
en las siguientes ocasiones ya no habrá necesidad de reducir el valor de Fsck.
Self Programming es el jumper de auto-programación. Los programadores USB suelen
tener un jumper similar para permitir la actualización automática de su firmware, pero
este no es el caso. En el USBasp la actualización o la primera grabación del firmware
es manual. Si queremos hacerlo, debemos grabar el AVR del USBasp con otro
programador. Solo en esa ocasión se debe cerrar el jumper Self Programming. El resto
del tiempo debe estar abierto.
Supply Target. Este jumper sí lo usaremos con frecuencia. Si está cerrado se conectan
las líneas Vcc del programador USBasp y del circuito de aplicación (target). Aunque eso
te parezca obvio, debes notar que si el circuito target tiene su propia alimentación
activada, el jumper cerrado podría dejar el puerto USB dañado. El USB en este caso
está configurado para brindar alimentación [al USBasp y al circuito target inclusive];
no para recibirla. Así que antes de cerrar el jumper, es recomendable apagar la
alimentación del circuito target. En ese caso podemos incluso mantener el jumper
cerrado todo el tiempo; así funcionaba el USBasp en sus primeras versiones.
Dejar el jumper abierto y permitir que el circuito target trabaje con su propia
alimentación es útil cuando ésta tiene un valor inferior a 5 V. Recordemos que esto
requiere la adaptación de las líneas del bus SPI con buffers o al menos con resistencias
en serie. Para más información ver la interface SPI.

Los diodos LED
Son elementos básicamente decorativos. El LED verde se enciende cuando apenas se
conecta el programador al puerto USB de la computadora. El LED rojo se enciende
durante el proceso de grabación del AVR. Desde el firmware es posible reubicarlos así
como editar su función. Yo por ejemplo modifiqué el código para que el LED verde se
encienda tras la correcta enumeración del programador, o sea cuando la computadora
lo haya reconocido correctamente. También Limor Fried hizo lo mismo con su
programador USBtinyISP. En el circuito original las resistencias de cada LED son de 1k.
Eso por supuesto depende del tipo de LED. Para mis diodos LED yo tomé resistencias
de 330 Ω.
Diodos LED del programador USBasp.

Los conectores
Si por su tamaño el programador necesitará un cable para conectarse a la
computadora, se usará un conector de recepción de tipo B estándar (ese que tiene
forma de cajita y es el más usado) o micro (demasiado pequeño). Aunque los
conectores mini ya no están contemplados en especificación 3.0 del USB, todavía se
encuentran en el mercado y son muy populares en dispositivos portátiles.
Pero si el programador es muy pequeño se puede conectar directamente a la
computadora usando un conector de tipo A macho. Es el conector que usaron por
ejemplo Fabio Baltieri y Sven Hedin en sus adaptaciones.
Programador usbasp

Adaptaciones del USBasp de Fabio Baltieri (izquierda) y Sven Hedin (derecha).
Los dos diseños se ven muy parecidos, uno más presentable que el otro, pero la
principal diferencia está en el conector ISP. ¿Por qué un conector tiene 6 pines y el
otro 10?
En el USBasp original el conector es un ISP10PIN, de 10 pines. Además de las líneas
de programación propias de la interface SPI (MISO, MOSI, SCK, RST) y de
alimentación (VCC y GND), están conectadas las líneas de transmisión TXD y
recepción RXD del USART. El USART sirve para comunicarse con la computadora por el
puerto serie. El autor, Thomas Fischl, la puso para la depuración del programador, es
decir, para quienes deseen contribuir al desarrollo y mejora de su firmware. Para los
usuarios finales esta interface está por defecto desactivada y TXD y RXDson “líneas
muertas”, de modo que pueden prescindir de ellas. Sin las líneas del USART basta con
un conector de 6 pines para el programador.

Líneas del USART en el conector ISP10PIN pines del USBasp.
El conector de 10 pines del USBasp original es compatible con el hardware antiguo de
los AVR. Atmel todavía lo incluye en algunos de sus productos como sus
placas STK500 y STK600 pero solo con fines de compatibilidad. Al final siempre nos
insta a usar el conector de 6 pines. Así lo han entendido los diseñadores de
programadores similares como AVR Doper o USBtinyISP. Parece que somos muy pocos
los que seguimos el mismo camino con el USBasp hecho íntegramente en versión
through hole. Recuerdo haber visto también el programador de yuki-lab.jp con un
conector ISP6PIN.

Los zócalos
Para ser sincero, yo era de los que venían del mundo de los PIC y tenía la casi viciosa
costumbre de utilizar un programador con zócalo donde colocar el PIC para después
retirarlo y llevarlo al circuito de aplicación. Con los AVR me deshice definitivamente de
esa práctica. Luego entenderás por qué.
Programador usbasp

Programadores USBasp con zócalos.
Un turco de nickname gevv armó un USBasp con zócalo ZIF para los AVR de 8, 20, 28
y 40 pines. Adicionalmente cuenta con un conector ISP de 10 pines para programar
AVRs fuera del zócalo; es el de la imagen derecha. Es uno de mis anhelos llegar algún
día a Turquía, pero no imaginaba que el idioma me resultara tan complicado. No
entendí nada de su web. Ni siquiera sé si acepta los AVR de 20 pines antiguos o de los
nuevos. Haré un mayor esfuerzo en otra oportunidad. Por ahora no tengo interés en su
programador.
El programador de la imagen izquierda es la adaptación de Matthias Görner utilizando
zócalos ordinarios de 28 y 40 pines. No tiene conector ISP para programar los AVR que
no quepan en sus zócalos.
Si al igual que a mí, no te gusta ninguno de los dos programadores mostrados, puedes
rehacer el tuyo propio según tus preferencias. Con el firmware y el circuito a tu
disposición solo necesitas conocer dos cosas:
Las señales del programador MOSI, MISO, SCK y RST deben ir a los pines del AVR con
el mismo nombre. Se da por hecho la conexión de todos los pines de
alimentación GND, VCC y, si existe, también AVCC. Puedes encontrar información
adicional en la sección interface de programaciónSPI.
El modo de programación es una de las formas de trabajo del AVR, y para que trabaje
el AVR necesita de una señal de reloj que puede ser interno o venir del exterior.
Cuando el AVR es nuevo su reloj es el circuito RC interno. Después lo programamos
para que su reloj sea externo, normalmente de un XTAL.
Los dos programadores descritos antes utilizan un XTAL para el AVR target. Otra forma
sería generar una onda cuadrada periódica en el AVR del programador y aplicarla al
pinXTAL1 del AVR target. Esta señal es compatible con cualquiera de las fuentes de
reloj externas del AVR. Cuando programamos los fuses de reloj en realidad estamos
optimizando el circuito de reloj interno del AVR para alguno de los elementos externos
entre XTAL, resonador cerámico, circuito RC o simplemente una señal cuadrada en el
pin XTAL1; no significa que una configuración de reloj impedirá el funcionamiento del
AVR con la otra fuente de reloj externa, solo que quizá no será la mejor opción.
Un programador con zócalos puede ser realmente útil cuando nos dedicamos a la
programación en serie de microcontroladores. Las pocas veces que tuve que hacerlo
fue utilizando microcontroladores de 28 o 40 pines (comprándolos en cantidades, los
microcontroladores pequeños cuestan casi lo mismo que los medianos). Así que al
empezar a trabajar con los AVR supuse que si quizá, tal vez, alguna vez,… necesitaba
de un programador con zócalo, sería con un ZIF para los AVR de 28 y 40 pines. En el
hardware puse un ATmega88 SMD para reducir el tamaño de la PCB y en el firmware
configuré el Timer2 para generar una onda cuadrada de 2MHz como reloj del AVR
target.
Programador usbasp

Programador USBasp con zócalo ZIF.
El resultado es como lo que ves arriba. Para mí quedó muy bonito pero te confieso que
de todos mis programadores es el que menos uso. Me costó casi lo mismo que
un Arduino. Así que antes que cometas el posible mismo error, te recomiendo que te
compres eso: un Arduino. Con ello tendrás un microcontrolador, un programador, un
conversor USB-UART (puerto serie para tu laptop) y hasta una fuente de alimentación
de 5V y 500mA.

Programador USBtinyISP
El programador USBtinyISP fue desarrollado porLimor Fried a partir del
programador USBtiny deDick Streefland. Como su nombre deja suponer, está basado
en un microcontrolador tinyAVR, el ATtiny2313, y solo ofrece la interface de
programación SPI (ISP por antonomasia). Describamos algunas de sus características:
Puede programar todos los megaAVR ytinyAVR con interface SPI, excepto los que
cuentan con memoria superior a 64K, que son los familiares del ATmega128 y
ATmega256.
Utiliza buffers para grabar microcontroladores AVR que operan a Vcc < 5V.
Su alimentación proviene del puerto USB. También puede suministrar alimentación de
5V al circuito target, siempre que no exceda de los 100mA. Este tope se puede
extender editando el firmware.
Tiene una aceptable velocidad de programación de 1kB/s. Para tener una referencia
práctica, los programas de cursomicros son de 2kB en promedio, así que grabarlos en
el AVR tomaría cerca de 2 segundos cada uno.
Se puede usar desde AVRDUDE o desde Atmel Studio 6.
En resumen es bastante parecido al USBasp, salvo por la primera limitación y por el
costo. Este programador no está disponible comercialmente en versión ensamblada. Lo
podemos comprar desde adafruit.com solo como un kit. La ventaja de este kit es que
el microcontrolador ATtiny2313 ya viene pre-programado. Eso te puede ahorrar
bastante tiempo y esfuerzo.
Programador usbasp

Vistas exterior e interior del programador USBtinyISP (fuente: ladyada.net).
Las líneas de la interface de programación SPI del USBtinyISP se reúnen en los dos
conectores estándar: el cada vez menos usado de 10 pines y el recomendado por
Atmel de 6 pines.
El jumper OUTPWR tiene las mismas funciones que en el programador USBasp:
Brindar alimentación (con el jumper cerrado) de 5V al circuito target si éste no la
tiene, o
Permitir (con el jumper abierto) que el circuito target trabaje con su propia
alimentación, lo cual es deseable cuando su nivel es diferente de 5 V.
Actualmente el USBtinyISP se encuentra en su versión 2.0. La diferencia notoria
respecto de la versión 1.0, aparte de algunos pequeños ajustes en el firmware, está en
el buffer 74AHC125Ncolocado en las líneas de programación MISO, MOSI, SCK y RST,
del hardware. Además de aislante, este CI sirve de puente entre las señales TTL del
AVR programador y los niveles del AVR programado, que pueden ser de 0-5 V o bajar
hasta 0-2 V. El buffer 74AHC125N se alimenta del pin Vcc del circuito target.
En el circuito de la versión 1.0 en lugar de los buffers las líneas de programación se
aislaban con resistencias de 1.5k. En teoría, también permiten la comunicación entre
dos microcontroladores que operan a diferente nivel de Vcc, pero algunos dicen que en
la práctica no es una interface muy fiable.

Circuito del programador USBtinyISP, versión 2.0.
Circuito del programador USBtinyISP, versión 1.0.

Construcción de un USBtinyISP
El programador USBtinyISP es open source. Todos los archivos están disponibles en la
web de su autora, www.ladyada.net. Como no pasa por muchas actualizaciones aquí
están algunas réplicas, actualizadas a noviembre de 2012.
Circuito y PCB en Eagle. (Versión 2.0)
Firmware del microcontrolador ATtiny2313. (Versión 2.0)
Circuito y PCB en Eagle. (Versión 1.0)
Firmware del microcontrolador ATtiny2313. (Versión 1.0)
El firmware compilado es main.hex y se encuentra en la carpeta spi.
Para construir cualquier programador de AVR con interface USB se sigue el mismo
procedimiento, el cual se divide en dos etapas:
Elaborar la placa de circuito impreso, PCB, y soldar todos los componentes. En lo
posible usamos un zócalo en vez del AVR del programador. Aquí terminaríamos si el
programador no se basara en un microcontrolador. De hecho, si compraste el kit de
ensamblaje deadafruit, coloca el ATtiny2313 en el zócalo y ya puedes empezar a usar
tu programadorUSBtinyISP. Quienes estén armando su programador con componentes
propios, deberán enfrentarse a la siguiente parte.
Programar el AVR del programador usando otro programador. Esta fase puede ser la
más tediosa. Si al inicio nos cuesta conseguir ese otro programador, después nos
quebrará la paciencia usarlo con AVRDUDE. Si ya estás acostumbrado a usar otros
programadores como el USBasp, entonces esto será pan comido.
Puedes seguir el procedimiento totalmente ilustrado por la misma Limor Fried en la
páginawww.ladyada.net/make/usbtinyisp/make.html de su web. Desafortunadamente
Limor solo guía durante la primera etapa. Y lo que para muchos puede caer como
baldazo de agua fría, nos pide que no busquemos ayuda sobre la grabación del
firmware en su foro. ¿ ? Debe ser porque la respuesta involucra múltiples formas o
para animarnos a comprar el kit, donde el AVR ya viene pre-programado.

Uso del USBtinyISP
Al conectar el programador el LED verde se enciende si su enumeración se llevó a cabo
con éxito.
El programador USBtinyISP puede ser controlado por AVRDUDE o desde Atmel Studio
6. Su nombre para ser invocado desde AVRDUDE es usbtiny. Por ejemplo, para
programar la memoria FLASH de un ATmega324P con el archivo main.hex podríamos
ejecutar:

>avrdude –c usbtiny –p atmega324p –U flash:w:main.hex:i
El LED rojo se enciende durante el proceso de grabación.
La instalación de WinAVR trae consigo los drivers de libusb que usa AVRDUDE. Sin
embargo, Limor sugiere usar los drivers modificados [por ella]. Hay dos versiones:
el driver para Windows de 32 bits y el driver para Windows de 64 bits.
Los programadores que pueden ser controlados por Atmel Studio 6 son los que utilizan
los protocolos de programación estándar de Atmel; el STK500, por ejemplo. Pero ese
no es el caso del USBtinyISP. En vez de implementar el protocolo STK500 en el
firmware del microcontrolador, cosa que no hubiera sido posible debido a su poca
memoria, de Limor Fried decidió escribir una aplicación que hace la conversión del
protocolo en la computadora. Para decirlo burdamente, se trata de un parche que
engaña a Atmel Studio 6 haciéndole creer que el USBtinyISP es un programador de
protocolo STK500v2.
Funcionando como programador STK500 el USBtinyISP tiene dos restricciones
destacables respecto del original:
No siempre puede programar el byte de calibración. Este byte permite ajustar la
frecuencia del oscilador RC interno del AVR cuando se usa como reloj del sistema. Creo
que nos es relevante puesto que lo habitual es usar el AVR con XTAL. Además la
calibración del circuito RC interno también se puede hacer en tiempo de ejecución, es
decir, podríamos añadir un par de líneas en el programa del microcontrolador para
solucionar el problema.
Sin duda la principal diferencia entre un programador original de Atmel y cualquiera de
sus clones que trabajan emulando un puerto USB es la velocidad de programación.
El USBtinyISPacepta los comandos para cambiar la velocidad de programación pero no
los obedece. ElUSBtinyISP sigue con su tope de 1kB/s.
Para que nuestro USBtinyISP emule un STK500 debemos seguir dos pasos:
Instalar la aplicación com0com para generar dos puertos series virtuales en nuestra
computadora. Este software es open source y lo puedes descargarla
desdehttp://com0com.sourceforge.net.
Luego debemos instalar el software puente USBtiny500 de Limor Fried.

Programador AVR Doper
La inexistencia de un periférico USB en los microcontroladores AVR de empaque DIP
junto con la arquitectura y compatibilidad en el set de instrucciones han alentado a
algunas personas a desarrollar librerías de USB software. Son librerías optimizadas a
nivel ensamblador que manejan las rutinas mínimamente necesarias del protocolo
USB. Tenemos por ejemplo, la librería de Igor Cesko explicada en la nota de
aplicaciónAVR309 de Atmel. Otra librería, con mejor soporte y actualización, es VUSB (acrónimo de Virtual USB), de Christian Starkjohann. Es la misma librería que
utilizó Thomas Fischl para su programador USBasp.
IMAGINO que cuando Christian vio el trabajo de Thomas pensó “yo puedo hacerlo
mejor” y decidió crear el programador AVR Doper.

Programador AVR Doper en su circuito original.
AVR Doper es un proyecto Open Source tanto en hardware y software. Muchas de sus
características son también apreciables en otros programadores: se alimenta desde el
puerto USB, posee buffers en las líneas de programación para programar dispositivos
que trabajan con voltajes inferiores a 5V y hasta cuenta con un jumper como forma
alternativa de reducir la frecuencia del reloj SCK (velocidad de programación) similar
al USBasp.
El firmware del programador AVR Doper está basado en el protocolo de
programación STK500v2de Atmel y como tal puede programar todos los megaAVR y la
mayoría de los tinyAVR. El protocolo original maneja las tres interfaces de
programación: SPI, HVSP y HVPP, de los cuales AVR Doper acepta los dos primeros.
Utiliza un conector de 10 pines para la programación SPI que, con algunas variaciones,
todavía es compatible con el estándar de Atmel. Para la programación de alto voltaje
dispone de dos zócalos para los [tiny] AVR de 8 y 14 pines. Esta
interface HVSP también sale al exterior mediante un conector libre de 20 pines.
Pero lo más importante –creo yo– es que, como todo programador STK500, puede ser
controlado desde Atmel Studio 6. Adiós insufrible línea de comandos de AVRDUDE y a
sus incompletas máscaras como SinaProg. Esto nos facilitará el trabajo ahorrándonos
tiempo.
¡Wow! Hubiera conocido este programador antes de construir mi USBasp dirán
algunos. La buena noticia para ellos es que pueden convertir su USBasp en un AVR
Doper tan solo cambiándole el firmware.
Claro que el circuito del USBasp no da para la programación de alto voltaje. Así que
está funcionalidad no estará disponible. Para mí es una limitación inapreciable.
Observa que si renunciamos a la programación HVSP, podemos deshacernos del
circuito elevador de tensión, de los dos zócalos in-situ y de ese enorme conector de 20
pines, que es el mismo que usó Tobias Hammer en su programador HVProg. Él lo hizo
justificadamente, pues empleaba todos los pines. Pero no entiendo por qué Christian
Starkjohann le siguió el paso. Ni siquiera es un conector estándar. En fin, el caso es
que al final nos quedará un circuito muy similar al del USBasp o del mismo USBtinyISP.
Incluso podemos notar los tres jumpers que, de hecho, Christian los puso ex profeso
por compatibilidad con el USBasp.
Circuito original del programador AVR Doper (fuente: obdev.at).
AVR Doper no es el primer programador que presentamos. Su descripción la hacemos
asumiendo que ya conocemos los programadores tratados previamente en especial
el USBasp. Quienes lo estudiaron comprenderán fácilmente el funcionamiento del AVR
Doper. Quizá les sorprenda un poco encontrar dos señales de reloj en el circuito, pero
también este punto ya lo explicamos anteriormente. La señal SCK es el reloj del
bus SPI; es la que marca la velocidad de transferencia de datos entre el AVR
programador y el AVR programado. La señal TSCK es el reloj de sistema del AVR
programado (target, de ahí la T); es la que establece su velocidad de trabajo. En otros
programadores no hay esta señal porque se asume que AVR target trabaja con su
propio reloj, generalmente basado en un XTAL.
Al igual que pasó con el programador USBasp, tras el éxito del AVR Doper muchas
personas hicieron sus propias adaptaciones. A continuación, algunas de ellas.
Las siguientes imágenes son del rediseño de Robert. Casi no hay diferencia con el
original pero sí se nota que tiene los componentes mejor ordenados. El circuito es el
mismo.
Programador usbasp

Programador AVR Doper con PCB mejorado por Robert.
También hay quienes prefieren tener su programador en una caja pequeña. AVR tiny
Doper es un rediseño hardware que prescinde de los zócalos de 8 y 14 pines pero
como el AVR es de empaque SMD incorpora un conector ISP6PIN para la actualización
del firmware. Parece que al autor tampoco le gustaba el gigantesco conector de 20
pines.

Programador AVR tiny Doper SE de www.s-engel.de.
Por supuesto, no faltan quienes modifican el programador quitándole la funcionalidad
que no usarán. Domen Ipavec se deshizo de todos los componentes que permiten la
programaciónHVSP y en el espacio ganado le añadió diodos dobles en casi todas las
señales como protección ESD. Similar característica posee el programador AVRISP
mkII. Ni los empaques SMD le ayudaron a darle un mejor aspecto a su placa.
Programador AVR Doper adaptado por Domen Ipavec.
En realidad tenemos hasta tres formas de construir un AVR Doper: (1) construirlo con
su circuito original, (2) montarlo en el circuito metaboard y (3) implementarlo en el
hardware de un USBasp. Puesto que la disposición de las líneas que van o salen del
ATmega8 es diferente en cada caso, el autor ha escrito el firmware usando macros que
permiten compilarlo para uno u otro circuito. Pero no te preocupes, no es necesario
que nosotros compilemos el programa. Christian también ha publicado todos dos
archivos hex, listos para grabar en el ATmega8.
El programador AVR Doper ha sido corregido y mejorado a lo largo de tres años. La
última actualización es de noviembre de 2008 y la puedes descargar desde su web o
desde aquí.

Uso del AVR Doper
El programador AVR Doper posee un jumper USB HID que establece dos modos de
operación. Este jumper se debe mover antes de conectar el programador a la
computadora.
Si el jumper USB HID está cerrado, el AVR Doper inicializará como un dispositivo HID.
En este caso solo podremos usarlo desde AVRDUDE. Podemos invocarlo
con stk500v2 o sus alias (avrispmkII o avrispv2). También será necesario
especificar avrdoper en parámetro –P. Por ejemplo, para programar la flash de un
ATmega168p con el programa main.hex podemos escribir.

>avrdude –c stk500v2 –P avrdoper –p atmega168p –U flash:w:main.hex:i
Si dejamos el jumper USB HID abierto, el AVR Doper trabajará en modo CDC. Ahora el
programador aparecerá como un dispositivo conectado a un puerto serie COM virtual.
Debemos especificar ese puerto COM al configurar el software programador. En la
siguiente línea por ejemplo repetimos el ejercicio anterior considerando que se ha
creado el puerto COM1:

>avrdude –c stk500v2 –P com1 –p atmega168p –U flash:w:main.hex:i
El trabajo desde Atmel Studio 6 se reduce a algunos clics.
Debemos aclarar que según el autor, Christian Starkjohann, el modo CDC presenta
algunas incongruencias con la especificación 1.1 del USB que pueden afectar el
desempeño del programador o simplemente dejarlo irreconocible. Aunque la mayoría
de las computadoras de ahora soportan las especificaciones 2.0 y hasta 3.0 del USB,
Christian nos recomienda usarlo solo en modo HID.

Programador HVProg
Este programador fue hecho por Tobias Hammertomando como referencia el circuito
de la placaSTK500. Su característica más distintiva y sobresaliente está en su nombre.
No estoy bromeando. Las letras HV vienen de alto voltaje. Éste junto con
el AVRminiProg son los únicos programadores open source que conozco con soporte
para las interfaces de programaciónHVSP y HVSP. Comercialmente tenemos al
programador AVR Dragon y las tarjetas de desarrollo STK500 y STK600. Naturalmente,
aparte del alto voltaje HVProg maneja el modoSPI. Todas las interfaces están basadas
en el protocolo STK500, de modo que además de AVRDUDE lo podemos controlar
con Atmel Studio 6.
La programación de alto voltaje, como dice Dean Camera, sirve para resucitar el AVR
cuando por algún accidente hemos mal configurado los fuses RSTDISBL o SPIEN,
quitándole la forma de ser reprogramado por la interface serial SPI o TPI de bajo
voltaje. Claro que puede haber ocasiones donde adrede se programen esos fuses.
Afortunadamente, a diferencia de otros microcontroladores como los PIC, en los AVR la
programación de los fuses va separada de la programación de la memoria flash. En la
práctica, grabar los fuses es una tarea muy esporádica. Por ejemplo, para
experimentar con todas lasprácticas de cursomicros, puede bastar con grabar los fuses
una vez. Luego ni los tocamos y solo nos dedicamos a trabajar con la memoria flash.
De modo que las probabilidades de meter la pata son realmente muy bajas.
Si trabajamos con los cuidados debidos quizá nunca tengamos que recurrir a un
programador de alto voltaje. Pero si por algún motivo necesitamos uno,
el HVProg puede ser una buena opción.
Programador HVProg (versión 0.5)
Arriba se muestra la versión más reciente del programador HVProg. Es un rediseño
hecho porKlaus Leidinger. Al describir su hardware vamos a encontrar muchos
aspectos desalentadores:
Se conecta a la computadora por el puerto serie. Usa un MAX232 para la conversión
deniveles RS232.
Utiliza una fuente de alimentación de 15 V. En otros programadores como el AVR
Doper la alimentación es de 5 V y generan el alto voltaje en el circuito.
Para la programación SPI usa un conector ISP de 10 pines estándar. Para las
programaciones HVSP y HVPP usa un conector libre de 20 pines.
Usa un jumper para habilitar la programación de alto voltaje.

Circuito del programador HVProg (versión 0.5)
La primera versión del HVProg utilizaba 5 conectores para las interfaces de
programación: los 2 conectores estándar ISP6PIN e ISP10PIN para la
programación SPI; un conector de 8 pines para la programación HVSP y otros 2
conectores de 10 pines para la programación HVPP. No sé si te parezcan muchos pero
a mí me gustaba. Al menos están el conector ISP de 6 pines que recomienda Atmel y
los dos conectores de HVPP son compatibles con los que lleva la tarjeta de
desarrollo STK500.
Al parecer Crazy Horse (no sé cómo se llama en realidad) pensaba lo mismo que yo. Él
tomó como referencia el primer diseño y lo modificó usando componentes SMD. Aparte
de los 5 conectores descritos le añadió otro ISP6PIN para la actualización del firmware.

Programador HVProg adaptado por Crazy Horse.
Circuito del programador HVProg (versión de Crazy Horse)

Programador AVRminiProg
El programador AVRISP mkII es la siguiente generación de los programadores que
usan el protocolo del de la tarjeta STK500. Se conecta al puerto USB de la
computadora directamente (sin conversores USB-UART) y puede programar todos los
AVR de 8 bits por las tres interfaces SPI, TPI yPDI. Como su protocolo también está
disponible en la web de Atmel, no tardaron en aparecer sus denominados clones.
Hay dos tipos de clones del programador AVRISP mkII: los que usan un AVR con USB
hardware y los que usan un AVR con USB emulado a nivel software. Los AVR con USB
incorporado siempre serán mejor, permitiendo igualar la performance del AVRISP
mkII original, pero como aún no vienen en empaque DIP muchos prefieren el segundo
grupo. Nosotros los veremos después.
El programador AVRminiProg fue desarrollado por Simon Qian usando también la
librería de USB virtual V-USB de Christian Starkjohann. Más que un clon del AVRISP
mkII también puede emular a los programadores JTAGICE mkII y AVR Dragon. La
compatibilidad de su firmware le permite comunicarse con Atmel Studio 6. A juzgar por
todo eso es sencillamente fenomenal, el mejor programador open source que se puede
encontrar. Siendo tan prometedor, sin embargo, no se puede entender por qué su
autor olvidó su web http://www.simonqian.com.
En realidad el programador de Simon Qian tiene más de una
versión. AVRminiProg como su nombre revela es la versión más limitada. Solo soporta
las interfaces de programación SPI yJTAG. Su circuito es una ampliación del
programador USBasp de modo que su firmware se puede recompilar para él. Esto es
podemos convertir un programador USBasp en un AVRminiProg tan solo con cambiar
de firmware.

Programador AVRminiProg.
A estas alturas del capítulo, después de estudiar los
programadores USBasp, USBtinyISP y AVR Doper, ya resultaría hasta tedioso estar
analizando el circuito del programador AVRminiProg. Creo que lo único que cambia son
los nombres y valores de algunos elementos.
Circuito del programador AVRminiProg.

La Tarjeta de Desarrollo STK500 y el Programador AVRISP
Antes que un simple programador STK500 es la tarjeta de desarrollo de Atmel
destinada a sustinyAVR y megaAVR de empaque DIP, o quizá relegada a ellos. Se
conecta a la computadora por el puerto serie, tiene zócalos hasta para los AVR más
antiguos, botones, diodos LED, conectores DIL para todos los puertos y, el punto que
nos trae aquí, lleva incorporado un programador
Tarjeta de desarrollo STK500 (fuente: atmel.com)
En la siguiente imagen se distinguen las principales partes de la tarjeta STK500.

Partes de la tarjeta de desarrollo STK500.
No he etiquetado los zócalos porque su identificación es inmediata ¿o no? Obviamente
allí se colocan los AVR de prueba. Parece que hubiera duplicados pero no. Sucede que
no todos los AVR son compatibles en pines por más que sean del mismo tamaño. Por
ejemplo, el patillaje de un ATmegaXX4 difiere de un AT90S8515, siendo ambos de 40
pines (este último es un modelo antiguo y debe ir en el zócalo inferior). También sus
colores de fondo parecen resultado del mal gusto pero tienen un significado que
veremos luego.
Cada puerto de los AVR tiene como a lo sumo 8 pines. Todos ellos van unidos a los
5 conectores de puertos. Con los AVR de los zócalos pequeños, por supuesto, quedarán
conectores libres.
Los 8 botones o pulsadores alineados al costado izquierdo están enlazados al conector
etiquetado como Botones. Mediante él podemos usar un conector para unir los 8
botones a cualquiera de los puertos del AVR. Del mismo modo, los 8 diodos LED
ubicados al lado de cada botón están unidos al conector LEDs. Así que también
podemos acoplarlos a cualquiera de los puertos del AVR. En la siguiente imagen por
ejemplo, los 8 botones se unen al puerto A y los diodos LED, al puerto D. si queremos
conexiones aisladas tendríamos que usar conectores de un cable.

Conexión de los puertos a los botones y LEDs (fuente: atmel.com).
El circuito de Alimentación se basa en un regulador que puede recibir entre 10V y 15V
por su conector externo. A su salida puede entregar hasta 500mA.
El bloque etiquetado con USART del AVR enlaza los pines TXD y RXD de los AVR que
los tienen al conector DB9. El circuito, que ya incluye la conversión de niveles entre
RS232 y TTL, permite el intercambio de datos entre el AVR y la computadora, para
control o data logging.
Y así llegamos al bloque de nuestro mayor interés: el Programador. Los principales
elementos de su circuito son dos microcontroladores AVR. El primero se encarga de
administrar las interfaces y el protocolo de programación mientras que el segundo le
asiste en el Boot Loader. El segundo se conecta a la computadora y se encarga de
revisar la versión del firmware en el primer AVR para actualizarlo si fuera necesario. La
actualización es un proceso que se realiza automáticamente desde el entorno de Atmel
Studio 6. De ese modo el programador estará al día para reconocer los AVR más
recientes. El firmware actual es de versión 2.x, denominadoSTK500v2, que tanto se
menciona en este capítulo.
El programador de la tarjeta STK500 soporta tres interfaces de
programación: SPI, HVSP y HVPP. Como sabemos, estas interfaces utilizan diferentes
pines del AVR (puedes regresar a revisar si deseas). Por eso existen varios conectores
del programador en el bloque enmarcado de azul. Las programaciones de alto voltaje
son recursos auxiliares que raramente usaremos a diferencia de la interface SPI. En la
tarjeta hay dos conectores para este medio: uno de 10 pines ISP10PINsolo para
programar dispositivos fuera de la tarjeta y el otro de 6 pines llamado ISP6PIN para
programar los AVR de los zócalos, además de los externos, obviamente.
En la imagen de la izquierda el conector ISP6PIN está dirigido al conector de
base roja para programar cualquier AVR que esté en un zócalo de base roja también.
Si lo conectamos al conector de base azul programaremos el AVR que esté en algún
zócalo azul, y lo mismo con el conector de base verde. En la imagen derecha se
muestra cómo tomar el conector ISP6PIN para programar un AVR de un circuito
externo.
Programador usbasp

Uso del conector ISP6PIN para programar AVRs en la tarjeta y fuera de ella.
Pregunta: ¿qué sucede si a la tarjeta STK500 le quitamos todos los elementos dejando
solamente el circuito del programador con los conectores ISP6PIN e ISP10PIN?
Obtenemos algo muy cercano al programador AVRISP.
Programador AVRISP Mature (fuente: atmel.com).
Ya sé. Imagino que estarás pensando dónde está el otro conector. Pues está allí
adentro. Debe ser que dos cables colgando no se ven bien. Por eso solo aparece uno a
la vez. Si queremos cambiarlo debemos abrir la caja y poner otro cable al otro
conector. La siguiente figura nos ilustra esta maniobra.

Interior del programador AVRISP Mature (fuente: atmel.com).

Programador AVRISP MkII
Hay demasiada información en este capítulo que podría terminar consumiendo tiempo
de oro de algunos lectores mientras se deciden cuál comprar o construir. Si aceptan un
consejo, yo les sugiero comprar el programador AVRISP mkII, por solo $35. Es lo
mejor que se puede conseguir a bajo precio. Permite programar todos los AVR de 8
bits, entre tinyAVR, megaAVR y XMEGA, por sus principales interfaces. Tendrá la
limitación de no manejar la programación de alto voltaje, pero como ese recurso es
prescindible en la gran mayoría de los casos, el AVRISP mkII sigue siendo la
herramienta a elegir.
Programador AVRISP mkII original.
Éstas son las principales características con que Atmel nos lo presenta:
Es compatible con Atmel Studio 6.
Suporta las interfaces de programación SPI, PDI y TPI.
Se alimenta del puerto USB, no necesita de alimentación externa.
Ofrece actualización automática para reconocer los AVR más recientes.
Puede programar AVRs que trabajan a tensiones entre 1.6V y 5.5V.
Permite regular la velocidad de programación SPI desde 50Hz a 8MHz.
Permite programar las memorias FLASH y EEPROM del AVR.
Permite programar los fuses y locks bits del AVR.
Es compatible con estándar USB 2.0 en modo full speed o 12 Mbps.
Ofrece protección de corto circuito.
Ahora bien, si antes que comprar prefieres construir tus propias herramientas para
ahorrar algo de dinero o para aprender hasta de esa experiencia, puedes elegir entre
uno de los denominados clones del AVRISP mkII.
Hasta antes de la aparición de la librería LUFA, descrita después, todos los clones de
los programadores con puerto USB de Atmel usaban AVRs que emulaban el puerto USB
a nivel software. Tal es el caso por ejemplo del programador AVRminiProg o del clon
hecho por Gerhard Schmidt en base a la herramienta USB AVR Lab de Christian Ulrich.
Son programadores que usan componentes discretos de fácil adquisición para
emprender su más pronta construcción, aunque presentan notables restricciones
respecto del original.
Clon del programador AVRISP mkII de Gerhard Schmidt.

Circuito del clon del programador AVRISP mkII de Gerhard Schmidt.
La historia empieza a cambiar desde que Dean Camera escribió su cada vez más
famoso proyecto LUFA, consistente en una librería USB para todos los
microcontroladores AVR (de 8 y de 32 bits) con módulo USB hardware incorporado. La
librería abarca las operaciones del AVR en modo Host, Device así como Dual role.
También soporta las clases CDC, HID, Mass Storage y Audio.
El proyecto LUFA incluye varios ejemplos demo para probar la funcionalidad de la
librería. Uno de esos demos es un clon del programador AVR ISP MkII de Atmel. Este
clon no tiene un hardware especifico. Dean escribió el firmware de forma genérica de
modo que cualquiera pudiera adaptarlo a su propio hardware basándose en la
configuración de pines del programa.
El proyecto LUFA es open source y está liberado bajo licencia MIT que permite su uso
para fines no comerciales y también comerciales. Esto ha sido aprovechado por
muchas empresas para vender sus propias versiones del programador AVR ISP
MkII Clone. Por eso si buscamos en Internet vamos a encontrar muchísimas variantes.
Las versiones comerciales (que cuestan unos $10 menos que el original) suelen ofrecer
el archivo HEX como firmware de actualización aunque no el código fuente modificado
y menos el circuito.
Programador avr doper

Clones del programador AVRISP mkII ofertadas por Brave kit y Olimex.
Puesto que estos programadores se basan en un AVR con USB hardware, su
performance puede llegar a igualar al original en sus principales aspectos. Así que no
vamos a citar sus características para no repetir la lista presentada arriba. Más bien
vamos a dedicarnos a describir las diferencias, que han ayudado a simplificar el diseño
para reducir el costo y por ende su construcción.
Usa tres conectores para cada una de las interfaces de programación SPI, PDI y TPI.I.
El original usa un solo conector de 6 pines para todos.
Dependiendo de la configuración del firmware y de los drivers de la computadora,
algunos clones pueden tener dificultades en ser reconocidos por Atmel Studio 6.
Para programar los AVR que trabajan a tensiones menores a 5V, el circuito target debe
tener su propia alimentación y pasar algo de ella al regulador que usa el programador.
En el AVRISP mkII original el circuito target también trabaja con su propia
alimentación pero permanece siempre aislada del programador. No sé si eso sea
ventaja o inconveniente.
Lleva diferentes LEDs para indicar las operaciones y el estado del programador. El
original lleva un LED multicolor para todo eso, aparte de un LED verde interno que
refleja el tráfico USB.
No cuenta con protección de corto circuito como el original.
Las diferencias mostradas no son propias del clon del AVRISP mkII, pues como
dijimos, Dean Camera solo escribió el firmware. Dependerá del hardware que le
dotemos mejorar su funcionalidad. Sin embargo esa parece una tarea innecesaria. Con
el circuito mínimo basta. Pero, ¿cuál es ése? Los clones mostrados arriba son
comerciales así que difícilmente se develarán sus circuitos.
A decir verdad, no es difícil descubrirlo, como tampoco es difícil diseñar un circuito
personal. Pero como imagino que debes pensar que no es éste el momento de ponerse
retos ni somos los primeros interesados, vamos a valernos de algunos diseños
experimentales que se pueden encontrar en Internet.
.De los circuitos que he visto, me quedo con el USBTiny-MkII SLIM, de P. Kisielewski.
Él se basó en el diseño USBTiny-MkII de Tom Light quien es por así decirlo el “partner
oficial” de Dean Camera encargado de poner a prueba su firmware. Así que andamos
por buen camino. El diseño se ve compacto. Da ganas de construirlo ya. Desilusiónate
si estás pensando en una versión de agujero pasante, pues el AVR que lleva no viene
en empaque DIP.

El programador USBTiny-MkII SLIM.
P. Kisielewski trató inicialmente de usar el transceiver MAX3002 por su mayor
disponibilidad similar al circuito del USBTiny MkII PL pero descubrió que provocaba
algunos errores. Así que retomó el chip GTL2003 como puente de interface entre el
programador y los AVR que operan en todo el rango 1.8V y 5.5V.
Circuito del programador USBTiny-MkII SLIM.

Programador AVR910
Éste no es el nombre del programador propiamente hablando. Se le llama así porque
aparece en la nota de aplicación AVR910 de Atmel. En ese documento y en su
firmware aparece simplemente como programador de AVR ISP. No lo denominamos así
para no confundirlo con el programador AVRISP, que usa el protocolo STK500. El
programador AVR910 usa un protocolo mucho más simple. Poco robusto dicen
algunos.
El programador es proveído por Atmel como herramienta de bajo costo. Abajo tenemos
una imagen de su circuito original. Como se ve, utiliza la interface de
programación SPI, se conecta a la computadora por el puerto serie y usa arreglos con
transistores en vez de un MAX232 (o similar) para convertir los niveles RS232 a TTL. El
firmware del AT90S1200 también es de disposición pública.
Circuito original del programador AVR910 (fuente: atmel.com).
Por lo visto parece ser un diseño antiguo, dirán algunos. Y es cierto. Es un
programador algo desatendido por Atmel. Pese a que la nota de aplicación AVR910 fue
actualizada a 2008, el firmware del AVR se quedó con la revisión del año 2000. Es muy
difícil por tanto utilizarlo como está para que reconozca los AVR actuales como
los ATmegaXX8 y ATmegaXX4.
Sin embargo, sigue siendo muy útil estudiar el documento AVR910. Con lo aprendido
mucha gente ha mejorado el firmware del AVR por su cuenta y también han adaptado
el circuito para conectarlo al puerto USB de la computadora. Abajo tenemos una placa
reeditada para el ATtiny2313 por Vassilis Serasidis que usa el firmware de Klaus
Leidinger. Tampoco es que sea un ejemplo de diseño moderno. Se puede ver que el
conversor USB-UART es el transceiver FT232BM, un chip viejo que usaba un XTAL y se
valía de una memoria EEPROM externa para almacenar algunos datos del protocolo
USB. Ahora se usa el FT232RL.
Programador AVR910 con conversor USB-UART.

Circuito del programador AVR910 con conversor USB-UART.
El software recomendado para controlar este programador es AVR-OSP II.
Entono del programa AVR-OSP II.
Y, por supuesto, también podemos usar AVRDUDE. Por ejemplo.
>avrdude –c svr910 –P com1 –p atmega644p –U flash:w:main.hex:i

Programador ArduinoISP
Con el Arduino se han hecho casi todos los proyectos imaginables: con displays touch
screen, con comunicaciones inalámbricas, con motores BLDC, con sensores de todos
los tipos, etc., etc., Es válido entonces que nos preguntemos ¿no habrá alguien hecho
un programador de AVRs?
Tarjeta del Arduino.
Sí. Y más que eso. El proyecto se encuentra como parte de los ejemplos que el Arduino
trae por defecto. Así que ni siquiera tendremos que buscarlo afuera. Simplemente
vamos al menú Archivo
Ejemplos
ArduinoISP, luego presionamos el
botón Upload y esperamos a que termine de cargar el firmware, tal como se indica
abajo.
Programador arduino isp

Cargando el firmware de ArduinoISP en la tarjeta del Arduino.
Y eso es todo. Ahora nuestra tarjeta de Arduino ya es un programador de AVR. Pero,
¿cómo lo usamos?
Como dice el comentario del sketch, este proyecto emula el programador AVRISP con
el protocoloSTK500 en su version1.x. Es una versión algo antigua que Atmel Studio
6 tendrá problemas en reconocer. Si vamos al menú Herramientas
Programadores en el entorno de Arduino, encontraremos algunos programadores
entre ellos nuestro recién creado ArduinoISP. Esta opción nos permite usarlo pero solo
para grabar el archivo hex del Boot Loader. Podríamos engañar al Arduino
reemplazando ese archivo por el archivo hex de nuestras prácticas, pero tampoco es la
mejor forma. Es preferible hacerlo desde AVRDUDE directamente y no con el AVRDUDE
que trae Arduino sino con el que viene con WinAVR.
El sobrenombre de nuestro ArduinoISP en AVRDUDE es avrisp. En teoría stk500
también debería funcionar pero vaya uno a comprobarlo. Un ejemplo de uso sería así:

>avrdude –c svrisp –P com1 –p atmega644p –U flash:w:main.hex:i
Programador ArduinoISP

Conector ISP de 6 pines del megaAVR del Arduino.
Según el firmware del ArduinoISP, las líneas de programación SPI son los mismos
pines MOSI,MISO, SCK y SS del bus SPI. El pin SS desempeña la función de la
línea RESET. En el circuito parcial del Arduino mostrado arriba se ve que corresponden
a los pines 10, 11, 12 y 13 de su tarjeta.
Los AVR grandes tienen más pines de alimentación. En esos casos es recomendable
conectarlos todos. Si hay un pin AVCC también se debe conectar a VCC. Por otro lado,
el XTAL solo es necesario si el AVR trabaja con un reloj externo. Sobre esto puedes
leer más información en la sección Interface de Programación SPI.

Compiladores para microcontroladores AVR
Un microcontrolador ejecuta el programa que lleva grabado en su memoria FLASH. Ese
programa está guardado allí en formato de código máquina (a base de unos y ceros) y
nosotros lo podemos cambiar por otro programa que queramos (a eso se llama grabar
el microcontrolador). Pero antes tenemos que crear ese programa. Lo podemos hacer
de tres formas.
Escribiendo el código máquina directamente con unos y ceros o sus equivalentes en
números hexadecimales. Es un método factible pero que muy pocos se atreverían a
intentar. El programa escrito puede ser un simple archivo de texto (con extensión
.hex) que luego será decodificado por el software grabador de microcontroladores. Un
programa en código máquina luce más o menos así.
:020000020000FC
:1000000012E01EBF1FEF1DBF36D0F0E0E8E15BD06D
:10001000F0E0E8E458D0FFCF0D0D0A207777772E77
:10002000637572736F6D6963726F732E636F6D208A
:100030000D0A203D3D3D3D3D3D3D3D3D3D3D3D3D70
:100040003D3D3D3D3D3D20000D0D0A20546573743E
:10005000696E67207468652054574920696E74651D
:100060007266616365200D0A00000D48692074689E
:10007000657265210000B0E0A0EC1C9112601C9339
:100080001127B0E0A5EC1C9318E6B0E0A4EC1C939B
:1000900016E0B0E0A2EC1C9318E1B0E0A1EC1C93D8
:1000A0000895B0E0A0EC1C9115FFFBCFB0E0A6ECEA
:1000B0000C930895B0E0A0EC1C9117FFFBCFB0E0CB
:1000C000A6EC0C9108950591002311F0EADFFBCF17
:1000D0000895FF93EF93F0E0ECE03197F1F7EF91A3
:1000E000FF910895000000000000089508953AEF80
:1000F00011D03AEF0FD008953AEF0CC034E60AC0A1
:100100003EE108C030E106C035E004C032E002C084
:1001100031E000C0FF93EF93F1E0EAE43197F1F7AB
:0A012000EF91FF913A95B1F70895B1
:00000001FF
Escribiendo el programa en código ensamblador. El ensamblador es el lenguaje más
elemental de los microcontroladores y muchos aún lo usamos como recurso auxiliar.
Sin embargo, sigue siendo muy engorroso como método de desarrollo ya que si
queremos, por ejemplo, que el microcontrolador ejecute una simple división de
números puede tomar cerca de 100 líneas de código ensamblador. El programa escrito
en este lenguaje sigue siendo un archivo de texto (esta vez con extensión .asm) que
luego será convertido en código máquina por un software de computadora llamado
también ensamblador (AVRASM2en el caso de los microcontroladores AVR). A
continuación se lista un ejemplo de programa en ensamblador.
;//////////////////////////////////////////////////////////////
;/// FileName:

main.asm

;/// Author:

Shawn Jhonson

;/// Copyright:

(C) 2008-2008 Shawn Jhonson

;/// Date:

04 December 2008

;//////////////////////////////////////////////////////////////

.nolist
.include

"m48Pdef.inc"

.list

.def

arg

= R16

; argument for functions

.def

tmp

= R17

; for temporal operations

.def

status

= R18

;

.def

del

= R19

;

.def

AddressHigh

= R27

; X Pointer

.def

AddressLow

= R26

;

;//////////////////////////////////////////////////////////////
;

Code program

;//////////////////////////////////////////////////////////////
.cseg
.org0x0000; Reset vector address
Start:
; Setup Stack Pointer
ldi

tmp,high(RAMEND)

out

SPH, tmp

ldi

tmp,low(RAMEND)

out

SPL, tmp

rcall

usart_init

ldi

ZH,high(2*StrTest)

ldi

ZL,low(2*StrTest)

rcall

puts

ldi

ZH,high(2*StrType)

ldi

ZL,low(2*StrType)

rcall

puts

; Initialize the usart.

Main:
rjmp

Main

;//////////////////////////////////////////////////////////////////////
; Strings. Null ended strings
StrTest:
.db0x0D,0x0D,0x0A, " www.cursomicros.com "
.db0x0D,0x0A, " =================== ",0x00
StrType:
.db0x0D,0x0D,0x0A, " Testing the TWI int",0x0D,0x0A,0x00
StrHi:
.db0x0D, "Hi there!",0x00
;//////////////////////////////////////////////////////////////////////
.include

"usart.asm"

.include

"delays.asm"

.exit

Escribiendo el programa en un lenguaje de alto nivel. Los dos lenguajes más usados
son el C y el Basic. Con menos frecuencia se aprecia el Pascal. En este caso el código a
escribir es más comprensible para nosotros, los humanos. Por ejemplo, para una
división es suficiente con escribir una sentencia como a = b/c. El programa escrito en
este lenguaje también es un archivo de texto (ahora con extensión .c, .bas o .pas) que
luego será convertido en código ensamblador por el compilador y luego convertido de
código ensamblador en código maquina por el ensamblador del compilador. Abajo
tenemos un ejemplo de un programa escrito en lenguaje C.
/********************************************************************
* FileName:

main.c

* Purpose:

Comunicación básica por USART0

* Processor:

ATmel AVR con módulo TWI

* Compiler:

AVR IAR C y AVR GCC (WinAVR)

* Author:

Shawn Johnson. http://www.cursomicros.com.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*******************************************************************/

#include "avr_compiler.h"
#include "usart.h"

int main(void)
{
char name[20];
usart_init();// Inicializar USART0 @ 9600-8N1

puts("nr www.cursomicros.com ");
puts("nr =================== nr");

puts("nr - ¿Cuál es tu nombre?... nr - ");
scanf("%s", name);
printf(" - Gusto en conocerte, %s", name);

while(1);
}

Los compiladores son las herramientas de programación mas potentes que existen. En
resumen, el compilador traduce el programa escrito en su lenguaje (C, Basic, Pascal u
otro) en código ensamblador y luego generará los archivos de depuración (*.dbg,
*.cof, *.d31, etc.) necesarios y de ejecución (*.hex).
Al trabajar con un compilador el programador ya no tiene que preocuparse (o lo hace
muy poco) por los vericuetos del hardware ni por el ensamblador nativo de cada
microcontrolador. Esto simplifica de manera asombrosa el desarrollo de proyectos.
El compilador convierte el código fuente en el código máquina de cada
microcontrolador sin importar mucho cuál sea. Por ejemplo, un código escrito para un
ATmega128P podría ser fácilmente compilado para un ATxmega64A3U u otro y
viceversa. Incluso es posible adaptar el código para un microcontrolador de otra
marca, por ejemplo, de Freescale o Microchip. Eso se llama portabilidad.
Conocer un lenguaje de programación como el C es un mundo aparte y es raro que
una persona trate de aprenderlo al mismo tiempo que va conociendo el
microcontrolador. En cursomicros se ofrece un capítulo entero sobre el lenguaje
C aunque puede no ser suficiente dada su complejidad comparado con lenguajes como
el Basic.
A continuación se describen brevemente los principales compiladores para los
microcontroladores AVR. Para conocer más de su uso se presentan posteriormente los
tutoriales respectivos.
El Compilador CodeVisionAVR
El Compilador AVR IAR C
El Compilador AVR GCC
El Compilador Bascom AVR
El Compilador ImageCraft C
El Compilador mikroC for AVR

El Compilador CodeVisionAVR
CodeVisionAVR es un compilador desarrollado por Pavel Haiduc para los
microcontroladores AVR de 8 bits, desde los tinyAVR hasta los XMEGA. Su principal
ventaja es que provee librerías integradas para controlar sus periféricos internos y
también dispositivos externos como LCDs, GLCDs, RTCs, sensores de temperatura,
memorias SD, etc. En este sentido se le podría comparar con los compiladores C de
CCS o Mikroe para los PICmicro. Como lo veremos luego,CodeVisionAVR es el
compilador C para los AVR más fácil de usar, sin embargo, no llega a igualar la
eficiencia de los compiladores AVR IAR C o AVR GCC. Luego podremos comprobar algo
de esta diferencia.
La versión de evaluación de CodeVisionAVR permite usarlo en casi toda su
funcionalidad para fines no comerciales. Tiene ciertas limitaciones con algunas de sus
librerías (casi ni se nota) y no compila programas que superen los 4 kbytes de código
ejecutable. Sin embargo, es suficiente para la mayoría de los programas como las
prácticas de cursomicros.com. Así que la puedes descargar desde su
web www.hpinfotech.ro/html/download.htm o, más directo, haciendo clic aquí. Si al
final te convences de su performance puedes descargar la versión comercial y tendrás
que comprar la clave de activación.
El compilador CodeVisionAVR no tiene un simulador del programa creado. En su lugar
genera un buen archivo COFF que puede ser utilizado por otros programas como Atmel
Studio 6 o Proteus. De hecho, si a veces uso este compilador es por la cómoda
depuración del código fuente desde Proteus.
Yo hubiera querido que todas las programas de cursomicros.com también pudieran ser
compilados directamente con CodeVisionAVR como sucede con los compiladores AVR
IAR C y AVR GCC pero lamentablemente ciertas divergencias de sintaxis
de CodeVisionAVR lo hacían complicado y le quitaban la claridad y sencillez que he
querido hacer prevalecer en todos los programas del curso.
Entorno de trabajo del compilador CodeVisionAVR.

El Compilador AVR IAR C
AVR IAR C y AVR GCC son de lejos los mejores compiladores para los AVR. Entre ellos
hay ligeras diferencias. Hay algunos aspectos en que un compilador saca cierta ventaja
y otros aspectos en que el otro trabaja mejor. Si nos pusiéramos a compararlos punto
a punto, estoy casi seguro de que el veredicto final sería… un empate.
En cursomicros.com se da preferencia a AVR GCC pero todos los programas están
escritos para ser compilados también por AVR IAR C. Esto se consigue gracias al
archivo proporcionado por Atmel avr_compiler.h, al cual le añadí pequeñas macros. No
todas las prácticas están compiladas en AVR IAR C pero los programas que tomé al
azar compilaron perfectamente a la primera y sin errores. Bueno, a decir verdad tuve
un traspié en un programa que utilizaba la función itoa, ya que dicha función no es
parte del ANSI C y como estricto seguidor de este estándar, AVR IAR Cno la tiene
implementada. Fuera de esa anécdota no tuve ningún inconveniente.
El entorno de desarrollo de AVR IAR C se ve bastante sencillo para la potencia que
ostenta el compilador. Se llama IAR Embedded Workbench y también sirve de
plataforma para los otros compiladores de la empresa IAR Systems. Si bien es cierto
que AVR GCC se integra a Atmel Studio 6 para aprovechar sus utilidades para las
herramientas hardware de Atmel , el entorno de IAR cuenta con sus propias armas de
programación, simulación y depuración, incluyendo los drivers para AVR ICE200, AVR
JTAG ICE, AVR JTAGICE mkII, AVR Dragon y AVR ONE! Puedes ver una breve
descripción de estas herramientas en la presentación de Atmel Studio 6.
Entorno de desarrollo del compilador AVR IAR C.

El Compilador AVR GCC
Como su nombre lo revela, AVR GCC es un software derivado del compilador
libre GCC (Gnu C Compiler). Tiene por tanto todo el respaldo de la comunidad Open
Source. Mucha gente tiene la idea de que el software libre, por más bueno que sea,
nunca llegará a superar a los productos comerciales, además de la falta de soporte
técnico. Bueno, quizá eso sea cierto con algunos programas como el conocido
compilador SDCC donde muchos programadores pasan más tiempo descubriendo que
el bug es del compilador y no de su firmware. SDCC es un compilador de relativa
juventud destinado a múltiples microcontroladores, incluyendo los AVR, y tal vez esos
sean su principal punto débil. Ya sabes, "el que mucho abarca poco aprieta".
Por fortuna, el compilador AVR GCC ya está lo suficientemente evolucionado y
corregido. Es uno de los pocos programas que supera a sus contrapartes comerciales
como CodeVisionAVR, ImageCraftC, MikroC para AVR, Bascom AVR y otros. El único
compilador que le puede hacer frente es AVR IAR C. Éstas, por supuesto, son
apreciaciones personales basadas también en mi experiencia personal. Me gusta su
velocidad de compilación y su capacidad de generar el código más eficiente. No tiene
bugs y todos mis programas compilan limpiamente.
En el pasado el principal inconveniente del compilador AVR GCC era que no tenía un
IDE propio. Los programadores debían valerse de otros entornos como CodeBlocks o
Eclipse. Pero con el paso del tiempo el AVR Studio de Atmel lo fue incorporando cada
vez más hasta convertirlo en la actualidad en su compilador casi oficial para sus
microcontroladores AVR, todo gracias a su prestigio. Ahora ya no es necesario buscar
el compilador AVR GCC por separado, pues AVR Studio 5 y Atmel Studio 6 lo incluyen
en su paquete de instalación. De hecho, incluyen una versión del compilador que es
mejor (?) que la disponible yendo a la web de AVR GCC para Windows
(WinAVR) http://winavr.sourceforge.net.
Se van entendiendo, por tanto, las razones por que AVR GCC sea el principal
compilador utilizado en todas las prácticas de cursomicros.com. La plataforma elegida
es desde luego Atmel Studio 6, así que en ese capítulo se explica todo sobre cómo
trabajar con AVR GCC en versión Windows (WinAVR).
El compilador AVR GCC (WinAVR) corriendo en Atmel Studio 6.

El Compilador Bascom AVR
Bascom AVR es el compilador Basic para los microcontroladores AVR que sobresale
entre los de su clase, aunque, la verdad, no tiene mucha competencia. Soporta los
microcontroladores AVR de 8 bits: los tinyAVR, los megaAVR y los XMEGA. La sintaxis
de sus funciones tiene ciertas diferencias respecto de otros compiladores Basic como
MBasic o Proton Plus, pero en general es fácil de asimilar.
Bascom AVR ofrece aceptables librerías, incorpora un sencillo simulador, un terminal
serial y un muy buen software programador de AVRs que soporta casi todos
dispositivos conocidos como el USBasp, USB-ISP, PROGGY, FLIP, AVR ISP mkII (con la
interface ISP), KamProg for AVR, STK600, ARDUINO, etc., etc.
Su autor, Mark Alberts, desarrolló Bascom AVR como una adaptación de su compilador
Basic para los microcontroladores 8051. Actualmente lo mantiene desde su
empresa MCS electronics y ofrece un versión demo que la puedes descargar desde
de aquí. Es completamente funcional y solo tiene el limitante de generar códigos
ejecutables de hasta 4 kbytes.
Uso del Compilador CodeVisionAVR
CodeVisionAVR es un compilador desarrollado por Pavel Haiduc para los AVR de 8 bits,
desde los tinyAVR hasta los XMEGA. Su principal ventaja es que provee librerías
integradas para controlar sus recursos internos y también dispositivos externos como
LCDs, GLCDs, RTCs, sensores de temperatura, etc. En este sentido se le podría
comparar con los compiladores C de CCS o Mikroe para los PICmicro. Como lo
comprobaremos enseguida, CodeVisionAVR es el compilador C para los AVR más fácil
de usar, sin embargo, no llega a igualar la eficacia de los compiladores IAR AVR o AVR
GCC. También podremos comprobar algo de esta diferencia en la siguiente práctica.
La versión de evaluación de CodeVisionAVR permite usarlo en casi toda su
funcionalidad para fines no comerciales. Tiene ciertas limitaciones con algunas de sus
librerías (casi ni se nota) y no compila programas que superen los 4 kbytes de código
ejecutable. Para la presente práctica nos bastará esta versión, así que la puedes
descargar desde www.hpinfotech.ro/html/download.htmo haciendo clic aquí. Si al final
te convences de su performance puedes descargar la versión comercial y tendrás que
comprar la clave de activación.

Instalación de CodeVisionAVR
Su instalación no requiere ninguna salvedad. Es el clásico procedimiento de Next,
Next,…
Instalación de CodeVisionAVR.

Creación de un Proyecto
Al iniciar CodeVisionAVR por primera vez nos aparece un ide como el de la siguiente
figura.
Entorno de desarrollo IDE de CodeVisionAVR.
Muchos de los paneles mostrados pueden ser útiles para los principiantes. Nosotros las
cerramos luego. Por el momento las dejaremos como están y empezaremos sin más
por crear nuestro proyecto.
Vamos al menú File

New y escogemos la opción Project.
Luego nos surge una ventanita si queremos usar el asistente de creación de proyectos
CodeWizardAVR. Personalmente me aburre usar este asistente, aunque al inicio a
todos nos intriga saber qué cosas nos ofrece. Así que esta vez lo tomaremos haciendo
clic en Yes. Luego podrás recorrer por tu cuenta el camino al que lleva la opción No.

Naturalmente tomamos la opción de los megaAVR.

A continuación nos aparece la ventana donde podemos configurar los recursos del
microcontrolador. Empezamos por la pestaña Chip para seleccionar el AVR y establecer
su frecuencia de operación en Clock, tal como se indica en la siguiente figura.
La opción Crystal Oscillator Divider generará el código para que el prescaler del reloj
del sistema lo divida entre alguno de los valores allí indicados (desde 1 hasta 256). Por
ahora no nos interesa porque de todos modos borraremos dicho código, asumiendo
que configuramos ese prescaler con el fuse CKDIV8.
Si está activada la opción Check Reset source el asistente generará el código que
comprueba el origen de cada reset del AVR. Por el momento tampoco nos interesa
esto.
La opción Program Type debe quedar en Application, a menos que estemos
desarrollando un programa con Boot Loader.
Ahora pasamos a la pestaña USART0 ya que vamos a utilizar el puerto serie. Allí
activamos las casillas para habilitar los módulos del Transmisor y Receptor porque los
mensajes serán de ida y vuelta. No utilizaremos las interrupciones del USART. Las
demás opciones las dejamos con su valor por defecto, como ve en la siguiente figura.
Si deseas saber lo que significan estos parámetros puedes revisar el estándar RS232.
El USART es el único periférico de comunicación que utilizaremos en esta práctica, así
que ya podemos terminar con esta etapa y presionamos el botón Generate program,
save and exit, como se indica abajo.
Enseguida se nos presentan tres ventanas para nombrar los archivos del proyecto. Al
archivo de código fuente principal yo siempre le llamo main.c. Previamente debemos
ubicar una carpeta para estos archivos.

Los otros archivos corresponden al proyecto y al asistente que estamos usando. Este
proyecto se llama ledflasher3 así que le puse ese nombre a ambos archivos (no tienen
que ser iguales).
Curso micros
Por fin llegamos a entorno para editar el código en el editor que apenas se vislumbra.
Por eso vamos a cerrar los paneles indicados en la siguiente figura. Si luego te interesa
utilizarlos, podrás encontrarlos en el menú View.

Ahora tenemos al frente al editor mostrándonos el código que generó el asistente. La
mayor parte es “código basura” con inicialización redundante de los periféricos del
AVR. Lo único que vamos a rescatar de este código es el fragmento que configura
el USART0, mostrado abajo, y para que el programa luzca lo más parecido posible al
código original escrito para AVR IAR C oAVR GCC lo empaquetaremos en una función
llamada usart_init.
Después de realizar lo descrito y de borrar el resto del contenido debemos completar
nuestro código que como dije anteriormente corresponde a la práctica ledflasher3 y su
listado para CodeVisionAVR se muestra a continuación.

/******************************************************************************
* FileName: main.c
* Purpose: LED Flasher 3. Secuenciador de 3 efectos seleccionables vía USART
* Processor: ATmega164P
* Compiler: CodeVision AVR
* Author:

Shawn Johnson. http://www.cursomicros.com.

*

Basado en el led flasher 2 de Seiichi Inoue localizado en

*

http://hobby_elec.piclist.com/index.htm.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License: Se permiten el uso y la redistribución de este código con
*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y la nota de autor de arriba.

*****************************************************************************/

#include <io.h>.
#include <stdio.h>
#include <delay.h>

#define kbhit()

(UCSR0A & (1<<RXC0))

voidusart_init(void);

/******************************************************************************
Patrón que contiene el patrón de efecto del secuencial
led1 111111111111111111111111111111... Prendido 6 de 6 partes
led2 100100100100100100100100100100... Prendido 2 de 6 partes
led3 100000100000100000100000100000... Prendido 1 de 6 partes
- Cada bloque tiene 6 items
- Cada bloque se repite 100 veces
- Hay una pausa de 150 µs entre los ítems
- Hay 12/11 bloques en total
-> Cada barrido dura 6 * 100 * 150 * 12 = 1.08 segundos aprox.
******************************************************************************/

__flashcharPattern[]=
{
// Efecto 1. 12 bloques de 6 items
0x81,0x81,0x81,0x81,0x81,0x81,0xc3,0x42,0x42,0xc3,0x42,0x42,
0xe7,0x24,0x24,0x66,0x24,0x24,0x7e,0x18,0x18,0x3c,0x18,0x18,
0x3c,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,
0x3c,0x24,0x24,0x3c,0x24,0x24,0x7e,0x42,0x42,0x66,0x42,0x42,
0xe7,0x81,0x81,0xc3,0x81,0x81,0xc3,0x00,0x00,0x81,0x00,0x00,
0x81,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
// Efecto 2. 11 bloques de 6 items
0x80,0x80,0x80,0x80,0x80,0x80,0xc0,0x40,0x40,0xc0,0x40,0x40,
0xe0,0x20,0x20,0x60,0x20,0x20,0x70,0x10,0x10,0x30,0x10,0x10,
0x38,0x08,0x08,0x18,0x08,0x08,0x1c,0x04,0x04,0x0c,0x04,0x04,
0x0e,0x02,0x02,0x06,0x02,0x02,0x07,0x01,0x01,0x03,0x01,0x01,
0x03,0x00,0x00,0x01,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,
// Efecto 3. 11 bloques de 6 items
0x01,0x01,0x01,0x01,0x01,0x01,0x03,0x02,0x02,0x03,0x02,0x02,
0x07,0x04,0x04,0x06,0x04,0x04,0x0e,0x08,0x08,0x0c,0x08,0x08,
0x1c,0x10,0x10,0x18,0x10,0x10,0x38,0x20,0x20,0x30,0x20,0x20,
0x70,0x40,0x40,0x30,0x40,0x40,0xe0,0x80,0x80,0xc0,0x80,0x80,
0xc0,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00
};

/*****************************************************************************
* Main function
****************************************************************************/
voidmain(void)
{
unsignedinti,j,k,b,bi,ba;
charc,Effect;

DDRB=0xFF;// Setup PORTB for output

usart_init();// Initialize USART @ 9600 N 1

puts("nr Escoja un efecto ");
puts("nr (1) Barrido simétrico");
puts("nr (2) Barrido a la derecha");
puts("nr (3) Barrido a la izquierda");

Effect='1';// Por defecto iniciar con efecto 1

while(1)
{
start:
/* Establecer parámetros de barrido del efecto actual */
switch(Effect)
{
case'1':ba=0;b=12;break;
case'2':ba=6*12;b=11;break;
case'3':ba=6*(12+11);b=11;break;
}

/* Iniciar barrido del efecto actual */
for(i=0;i<b;i++)// Para barrer b bloques
{
for(j=0;j<100;j++)// Cada bloque se repite 100 veces
{
bi=ba+6*i;
for(k=0;k<6;k++)// Cada bloque tiene 6 items
{
PORTB=Pattern[bi+k];
//PORTB = pgm_read_byte(&(Pattern[bi+k]));

/* Este bloque sondea el puerto serie esperando recibir una
* opción válida para cambiar de efecto
*/
if(kbhit())// Si hay datos en el USART,..
{
c=getchar();// Leer dato
if((c<='3')&&(c>='1'))// Si es una opción válida,...
{
Effect=c;// Tomar opción
gotostart;// y reiniciar
}
}
delay_us(150);
}
}
}
}
}

voidusart_init(void)
{
// USART0 initialization
// Communication Parameters: 8 Data, 1 Stop, No Parity
// USART0 Receiver: On
// USART0 Transmitter: On
// USART0 Mode: Asynchronous
// USART0 Baud Rate: 9600 (Double Speed Mode)
UCSR0A=0x02;
UCSR0B=0x18;
UCSR0C=0x06;
UBRR0H=0x00;
UBRR0L=0x67;
}
Copia todo este código y pégalo en el editor de CodeVisionAVR.

Construir el Proyecto de CodeVisionAVR
Los botones de las barras de herramientas de CodeVisionAVR no me parecen nada
intuitivos porque después de tantos años apenas si logro distinguirlos. Para construir el
proyecto yo prefiero utilizar el menú Proyect
Build All.
Después de construir el proyecto (esto es compilar, enlazar, ensamblar y generar los
archivos de salida), tendremos la ventana que reporta los resultados del proceso. Si la
construcción fue exitosa esta ventana tendrá adicionalmente la pestaña Assembler con
la información de memoria utilizada, como se aprecia en la siguiente figura.
También el panel Code Navigator puede dar cuenta de la creación de los archivos de
salida como resultado de una construcción exitosa. De todos modos podemos examinar
la carpeta del proyecto para comprobar la existencia de los archivos de salida, como
el archivo HEX para grabar el AVR o el archivo COFF para las simulaciones en
programas como Proteus o Atmel Studio 6. El archivo HEX se encuentra dentro de la
carpeta Exe.
Si quieres que los archivos de salida no se “desparramen” entre tantas carpetas
(Exe, Linker,List y Obj) y que más bien vayan todos a una carpeta Debug como en AVR
GCC o AVR IAR C, que es como se acostumbra en los programas de cursomicros.com,
puedes hacer la reconfiguración en el menú Project
Configure
File
Output
directories. Escribes Debug en los campos respectivos como se ve en la siguiente
captura, aceptas con OK y luego podrás borrar las otras carpetas.
Simulación del Programa CodeVisionAVR
CodeVisionAVR no tiene un simulador integrado. Si queremos simular nuestro
programa tendremos que recurrir a un simulador externo como Proteus VSM o Atmel
Studio 6.
Para simular en Proteus creamos el diseño respectivo y cargamos el archivo HEX,
o archivo COFFsi queremos trabajar con una depuración paso a paso. Obviamente,
ahora no voy a entrar en detalles. Para eso puedes revisar el tema dedicado
a simulaciones con Proteus.

Quienes no tengan el privilegio de contar con Proteus, puedes utilizar el simulador
de Atmel Studio 6. Ahora el traspase del programa ya no es tan flexible como lo era en
el Studio 4 pero todavía es posible mediante el archivo COFF.
Así que abrimos Atmel Studio 6 y vamos al menú File
Debuggingcomo se indica en la siguiente toma.

Open

Open Object File for
En la siguiente ventana debemos ubicar el archivo COFF a simular. También dice que
AVR Studio creará un proyecto para la simulación, así que debemos darle un nombre y
especificar una carpeta donde se guardará el proyecto. El nombre no es relevante pero
igual le puseledflasher3. Es preferible que la carpeta sea la misma que contiene el
proyecto de CodeVisionAVR.
Luego seleccionamos el microcontrolador del proyecto.
Ahora podremos apreciar que el panel Solution Explorer de Atmel Studio 6 muestra los
archivos de depuración COFF y de código fuente main.c. Sin más inicia la simulación
presionando el botón indicado.
Aquí seleccionamos AVR simulador. Se habla con más amplitud de esto en esta
sección.

Lo que sigue es historia conocida para quienes conocen las simulaciones con Atmel
Studio 6. Si aún no eres de ellos, puedes revisar la sección simulación del programa
con Atmel Studio 6. Observa que el archivo COFF puede mostrar las variables ubicadas
en los registros de trabajo, a diferencia de la depuración con el archivo ELF.
Uso del Compilador AVR IAR C
IAR AVR C y AVR GCC son de lejos los mejores compiladores para los AVR. Entre ellos
hay ligeras diferencias. Hay algunos aspectos en que un compilador saca cierta ventaja
y otros aspectos en que el otro trabaja mejor. Si nos pusiéramos a compararlos punto
a punto, estoy casi seguro de que el veredicto final sería… un empate.

En cursomicros.com se da preferencia a AVR GCC(WinAVR) pero todos los programas
están codificados para ser compilados también por AVR IAR C. Esto se consigue gracias
al archivo proporcionado por Atmel avr_compiler.h, al cual le añadí pequeñas cosas.
No todas las prácticas están compiladas en AVR IAR C pero los programas que tomé al
azar compilaron perfectamente a la primera y sin errores. Bueno, a decir verdad tuve
un traspié en un programa que utilizaba la función itoa, ya que dicha función no es
parte del ANSI C y como estricto seguidor de este estándar, AVR IAR C no la tiene
implementada. Fuera de esa anécdota no tuve ningún inconveniente.
El entorno de desarrollo de AVR IAR C se ve bastante sencillo para la potencia que
ostenta el compilador. Se llama IAR Embedded Workbench y también sirve de
plataforma para los otros compiladores de la empresa IAR Systems. Si bien es cierto
que AVR GCC se integra a Atmel Studio 6 para aprovechar sus utilidades para las
herramientas hardware de Atmel, el entorno de IAR cuenta con sus propias armas de
programación, simulación y depuración, incluyendo los drivers para AVR ICE200, AVR
JTAG ICE, AVR JTAGICE mkII, AVR Dragon y AVR ONE! Puedes ver una breve
descripción de estas herramientas en la presentación de Atmel Studio 6.
Creando un Proyecto en IAR AVR
El desarrollo de programas en el IDE de AVR IAR C es muy similar a hacerlo en Atmel
Studio 6. Los programas deben integrar un proyecto, el proyecto debe pertenecer a
un espacio de trabajo, y un espacio de trabajo puede agrupar varios proyectos.
Un espacio de trabajo oWorkspace se podría equiparar con las Soluciones de Atmel
Studio 6.
Así que empezamos por crear nuestro Workspace o espacio de trabajo yendo al
menú File New
Workspace. Sin importar su contenido actual, el IDE de AVR IAR C
se limpiará y quedará como se muestra abajo.
Seguidamente pasamos a crear el proyecto seleccionando el menú Project
Create
New Project… Para los “pequeños” programas no hay mucha diferencia entre las
diversas opciones de proyectos en C (Empty Project, C++ o C). Sin embargo
tomaremos Empty Project. Si tienes varios compiladores instalados, debes
seleccionar AVR en el deplegable Tool Chain. Con la configuración como se muestra
abajo presionamos OK.
Luego tendremos que buscar una carpeta donde guardar el archivo del proyecto creado
y escribir su nombre. Como ves, yo le llamé ledflasher3.
Recién ahora podemos guardar el espacio de trabajo que creamos al inicio. Así que
vamos al menú File
Save Workspace. El archivo de Workspace también puede tener
cualquier nombre, pero yo le llamé otra vez ledflasher3. Si el espacio de trabajo va a
contener varios proyectos será preferible que este archivo se ubique en un directorio
superior. Como en este caso el espacio de trabajo tendrá un único proyecto, lo
guardaré en la misma carpeta del proyecto.

Luego podemos ver que el panel Workspace apenas si contiene el nombre del
proyecto. Aún no vemos dónde escribir el código del programa, pero eso viene
después.
Asumiendo que estamos creando un proyecto desde cero, pasamos a crear un archivo
en blanco que será donde se edite el código del programa. Para esto vamos al
menú File
New
File o hacemos clic en ese típico botón blanco.

El nuevo archivo aparece por ahora como Untitled1. Así que pasamos a guardarlo con
ese botón de diskette. Cuando los proyectos tienen más de un archivo de código fuente
al archivo principal se le suele llamar main.c. La siguiente imagen parece indicar que
éste es uno de esos proyectos.
Ahora por fin ya podemos editar el código del programa. En esta ocasión vamos a
reproducir el programa de la práctica ledflasher3. Por eso estaba insistiendo con ese
nombre ;) El código completo se muestra a continuación.

/******************************************************************************
* FileName: main.c
* Purpose: LED Flasher 3. Secuenciador de 3 efectos seleccionables vía USART
* Processor: ATmega164P
* Compiler: AVR IAR C y AVR GCC (WinAVR)
* Author:

Shawn Johnson. http://www.cursomicros.com.

*

Basado en el led flasher 2 de Seiichi Inoue localizado en

*

http://hobby_elec.piclist.com/index.htm.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License: Se permiten el uso y la redistribución de este código con
*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y la nota de autor de arriba.

*****************************************************************************/

#include "avr_compiler.h"
#include "usart.h"

/******************************************************************************
Patrón que contiene el patrón de efecto del secuencial
led1 111111111111111111111111111111... Prendido 6 de 6 partes
led2 100100100100100100100100100100... Prendido 2 de 6 partes
led3 100000100000100000100000100000... Prendido 1 de 6 partes
- Cada bloque tiene 6 items
- Cada bloque se repite 100 veces
- Hay una pausa de 150 µs entre los ítems
- Hay 12/11 bloques en total
-> Cada barrido dura 6 * 100 * 150 * 12 = 1.08 segundos aprox.
******************************************************************************/

PROGMEM const charPattern[]=
{
// Efecto 1. 12 bloques de 6 items
0x81,0x81,0x81,0x81,0x81,0x81,0xc3,0x42,0x42,0xc3,0x42,0x42,
0xe7,0x24,0x24,0x66,0x24,0x24,0x7e,0x18,0x18,0x3c,0x18,0x18,
0x3c,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,
0x3c,0x24,0x24,0x3c,0x24,0x24,0x7e,0x42,0x42,0x66,0x42,0x42,
0xe7,0x81,0x81,0xc3,0x81,0x81,0xc3,0x00,0x00,0x81,0x00,0x00,
0x81,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
// Efecto 2. 11 bloques de 6 items
0x80,0x80,0x80,0x80,0x80,0x80,0xc0,0x40,0x40,0xc0,0x40,0x40,
0xe0,0x20,0x20,0x60,0x20,0x20,0x70,0x10,0x10,0x30,0x10,0x10,
0x38,0x08,0x08,0x18,0x08,0x08,0x1c,0x04,0x04,0x0c,0x04,0x04,
0x0e,0x02,0x02,0x06,0x02,0x02,0x07,0x01,0x01,0x03,0x01,0x01,
0x03,0x00,0x00,0x01,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,
// Efecto 3. 11 bloques de 6 items
0x01,0x01,0x01,0x01,0x01,0x01,0x03,0x02,0x02,0x03,0x02,0x02,
0x07,0x04,0x04,0x06,0x04,0x04,0x0e,0x08,0x08,0x0c,0x08,0x08,
0x1c,0x10,0x10,0x18,0x10,0x10,0x38,0x20,0x20,0x30,0x20,0x20,
0x70,0x40,0x40,0x30,0x40,0x40,0xe0,0x80,0x80,0xc0,0x80,0x80,
0xc0,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00
};

/*****************************************************************************
* Main function
****************************************************************************/
intmain(void)
{
unsignedinti,j,k,b,bi,ba;
charc,Effect;
DDRB=0xFF;// Setup PORTB for output

usart_init();// Initialize USART @ 9600 N 1

puts("nr Escoja un efecto ");
puts("nr (1) Barrido simétrico");
puts("nr (2) Barrido a la derecha");
puts("nr (3) Barrido a la izquierda");

Effect='1';// Por defecto iniciar con efecto 1

while(1)
{
start:
/* Establecer parámetros de barrido del efecto actual */
switch(Effect)
{
case'1':ba=0;b=12;break;
case'2':ba=6*12;b=11;break;
case'3':ba=6*(12+11);b=11;break;
}

/* Iniciar barrido del efecto actual */
for(i=0;i<b;i++)// Para barrer b bloques
{
for(j=0;j<100;j++)// Cada bloque se repite 100 veces
{
bi=ba+6*i;
for(k=0;k<6;k++)// Cada bloque tiene 6 items
{
// PORTB = Pattern[bi+k];
PORTB=pgm_read_byte(&(Pattern[bi+k]));

/* Este bloque sondea el puerto serie esperando recibir una
* opción válida para cambiar de efecto
*/
if(kbhit())// Si hay datos en el USART,..
{
c=getchar();// Leer dato
if((c<='3')&&(c>='1'))// Si es una opción válida,...
{
Effect=c;// Tomar opción
gotostart;// y reiniciar
}
}
delay_us(150);
}
}
}
}
}
Puedes copiar todo el listado y pegarlo en el editor de AVR IAR C. Deberá quedar algo
así.

Observa que el panel Workspace aún no despliega el nombre de main.c. Ese archivo
todavía está flotando en el aire. Para que forme parte del proyecto tenemos que
incluirlo siguiendo el procedimiento que indica la siguiente captura.
Configurando el Proyecto AVR IAR C
El entorno IAR Embedded Workbench está diseñado para trabajar con los múltiples
compiladores de IAR Systems así que la configuración predeterminada del proyecto
puede variar entre una y otra Toolsuit. A continuación veremos los cambios básicos
que normalmente deberemos realizar a través de la ventana Opciones disponible en el
menú Project o en el menú emergente del archivo del proyecto como se aprecia abajo.
Aquí el primer paso es cambiar el microcontrolador.
Pasamos a las opciones de C/C++ Compiler y fijamos un nivel de optimización que
puede referirse al código más pequeño que deseamos obtener (size) o al más veloz en
ejecución (speed).
En algunos modos el proyecto genera por defecto el archivo de salida d90 para
la depuración del programa. Si en la pestaña Output del enlazador vemos que esto no
es así, deberemos modificar la configuración para que quede como se indica en la
imagen. Obviamente no todos los archivos de salida de llamarán ledflasher3. Solo
asegúrate de que tenga la extensión d90 (la escribes si fuera necesario) y de que su
formato sea ubrof 8 (forced). Observa que el enlazador también puede generar otros
archivos de depuración como COFF o ELF, pero no todos los formatos serán igualmente
válidos para todos los microcontroladores. Los archivos ELF de IAR, por ejemplo, solo
están disponibles para los 68HC11, 68HC12, 68HC16, ARM, M16C, MC80, M32C, R32C,
SH, y V850.
No sé por qué, pero normalmente el archivo HEX no se genera de forma automática.
Para ello debemos ubicarnos en la pestaña Extra Output y dejamos la configuración
mostrada en la siguiente imagen. Como en el caso anterior debes escribir la
extensión HEX si fuera necesario. De hecho, puedes intercambiar el orden y configurar
en la anterior ventana la generación del archivo HEX y en esta ventana la del
archivo d90.
Añadiendo Archivos o Librerías
Bueno, si se tratara de un programa más sencillo que constara solo de un archivo de
código fuente, ya podríamos construirlo. Pero aunque sea ésta una sencilla
demostración, quise realizarla con un programa promedio de cursomicros.com. Aquí
casi todas las prácticas utilizan el puerto serie para intercambiar datos con la
computadora y para ello se requiere de las librerías del USART respectivas
conformadas por los archivos usart.h y usart.c. Además será necesario incluir el
archivo avr_compiler.h, que se encarga de mantener la compatibilidad del código para
que funcione en los compiladores AVR IAR C y AVR GCC (WinAVR).
Así que ahora procedemos a incluir los archivos descritos. Los puedes encontrar en casi
cualquier práctica de cursomicros. Cuando los hayas localizado cópialos en la carpeta
de este proyecto, junto al archivo main.c, como se ve abajo.
Que los archivos señalados estén en la misma carpeta del proyecto no significa que ya
formen parte de él. Para indexarlos al proyecto hacemos lo que indica la siguiente
figura.
En el cuadro de diálogo que surge seleccionamos los archivos a añadir. Podemos
seleccionar varios archivos al mismo tiempo.
Ahora por fin el panel Workspace nos muestra todos los archivos del proyecto,
debidamente indexados.

Construyendo el Proyecto AVR IAR C
No queda más que construir el proyecto. Ya sabemos que en los compiladores C
construir implica compilar, enlazar, ensamblar y generar los archivos de salida.

Si la construcción resulta exitosa tendremos el siguiente reporte.
Los archivos de salida incluyen el archivo HEX para grabar el microcontrolador y el
archivo d90que se utiliza en programas depuradores como Proteus. Estos archivos se
encuentran en la carpeta Exe que a su vez está dentro de la carpeta Debug.
Simulando el Programa en AVR IAR C
El entorno de AVR IAR C incluye un buen simulador y un potente depurador que
trabaja incluso a nivel hardware llamado C-SPY. La siguiente captura muestra la
simulación en Proteus. El procedimiento es sencillo: solo creas un diseño en ISIS y en
el AVR cargas el archivo con extensión d90 generado por el compilador. Para una
información pormenorizada puedes ir aquí.
Para la simulación con C-SPY de IAR se debe primero reconfigurar la generación del
archivo d90 de acuerdo con lo mostrado en la siguiente figura. Ésta era la opción por
defecto que tuvimos que cambiar para que el archivo d90 sea aceptado por Proteus. La
opción Allow C-SPY specific extra output file nos permitirá poder obtener nuestro
archivo HEX de costumbre.
Después de reconstruir el proyecto podemos ir al menú Project
Download and
Debug y estaremos frente a la siguiente interface. Aparecerán varias nuevas opciones
en los menús y también nuevas barras de herramientas, entre ellas la que contiene los
ya clásicos comandos de simulación como step over, step into, etc.
Atmel Studio 6
Atmel Studio 6 es el Entorno de Desarrollo Integrado (IDE) de Atmel para el desarrollo
de proyectos con sus productos relacionados con sus microcontroladores.
Durante muchos años los IDEs de Atmel para sus microcontroladores se mantuvieron
separados en dos versiones principalmente. Por un lado estaba el AVR Studio, que
soportaba sus microcontroladores AVR de 8 bits, entre lostinyAVR, los megaAVR y
los XMEGA. Eventualmente aparecían nuevas versiones AVR Studio 4.xx pero era
esencialmente el mismo y con ese 4 tan inamovible como el nombre de una estación
radial. En tanto que por otro lado teníamos al AVR32 Studio, que era desde luego la
plataforma análoga pero para trabajar con los microcontroladores AVR de 32 bits.
Recién en 2011 Atmel decide lanzar su totalmente renovado Studio 5, esta vez
fusionando el soporte para sus microcontroladores emblema de 8 bits y de 32 bits, es
decir,
AVR Studio 5 = AVR Studio + AVR32 Studio
Al igual que la version 4 yo creía que ese 5 iba a perdurar por muchos años pero en
2012 Atmel vuelve a repotenciar su Studio dándoles cabida esta vez a sus
microcontroladores con procesador ARM de la serie Cortex-M,
denominados SAM3 y SAM4. Como era de esperarse, el nombre ya no va precedido por
la palabra AVR. Ahora se llama simplemente Atmel Studio 6. Sin emargo, al igual que
las versiones anteriores, los usuarios de los AVR lo suelen llamar simplemente Studio
6, a secas.
Entre las herramientas que incluye el nuevo Atmel Studio 6 nos deben interesar las
siguientes:
Un editor de códigos, para editar los programas. Como todo gran editor permite
mostrar los códigos fuente a colores, con números de línea, etc.
Un administrador de proyectos, que además de trabajar con programas en
ensamblador, le da un completo soporte a los compiladores ARM GCC, AVR32
GCC y AVR GCC. A diferencia de versiones anteriores ahora es menos flexible la
integración con los compiladores comerciales como CodeVisionAVR o ImageCraft AVR.
El ensamblador AVRASM, para trabajar con programas en ensamblador.
Los compiladores de software libre AVR32 GCC y AVR GCC en su versión para Windows
(WinAVR), para desarrollar programas en C para los AVR de 8 y 32 bits, como
los tinyAVR,megaAVR, XMEGA y AVR32. En versiones pasadas de Atmel Studio 6, este
compilador se debía instalar por separado.
El compilador ARM GCC, que es otro derivado de la colección GCC pero adaptado para
los microcontroladores ARM.
El simulador AVR Simulator, para simular los programas de los AVR tanto si están
escritos en lenguaje C o ensamblador. Todavía no está disponible un simulador para
los microcontroladores con procesador ARM.
El paquete Atmel Software Framework o ASF, que es un conjunto de más de 1000
proyectos escritos en lenguaje C para los AVR de 8 bits y para los ARM y AVR de 32
bits. Incluye librerías y APIs que facilitan la interface con todos los periféricos de los
microcontroladores, desde los puertos hasta los módulos USB o CAN.
Un completo sistema de ayuda integrado con servidor local. Había algunos problemas
con esta característica en el Studio 5 pero en Atmel Studio 6 las cosas andan muy
bien.
Atmel Studio 6 permite trabajar con más de 300 microcontroladores, entre AVR y
ARM.Atmel Studio 6 ya no soporta los siguientes dispositivos, que aún están
disponibles en el Studio 4, ATtiny11, ATtiny12, ATtiny15, ATtiny22, AT90S1200,
AT90S2313, AT90S2323, AT90S2343, AT90S4433, AT90S8515, AT90S8535,
ATmega323, ATmega161, ATmega163, ATmega103, ATmega165, ATmega169,
ATmega406, ATmega16HVA, ATmega16HVA2, ATmega64HVE, ATmega32U6,
AT90PWM2, AT90PWM3, AT90SCR100, AT86RF401.
Atmel Studio 6 también incluye las siguientes herramientas, las cuales son de uso
opcional porque requieren del hardware correspondiente. De todos modos, si tú
puedes conseguirlos o deseas saber más de ellos, puedes visitar la web de Atmel.
Los softwares de programación como AVR ISP MkII. Éste programa trabaja con el
dispositivo programador comercial del mismo nombre.

El programador AVR ISP mkII.
Los programadores y depuradores como AVR Dragon, JTAGICE3, JTAGICE MkII o AVR
ONE!La performance y potencia de estas herramientas siguen el orden de su
presentación. ICE significa In Circuit Emulator, y hace referencia a un sistema de
depuración en el mismo circuito y con el mismo microcontrolador. Obviamente deben
trabajar con sus propios adaptadores hardware, que se conectan a la computadora por
el puerto USB y al microcontrolador AVR vía una interface que puede
ser JTAG, PDI, debugWire o aWire, dependiendo del depurador y del microcontrolador
seleccionados. La interface más común para con el megaAVR es JTAG y está
conformada por los pines TMS, TCK, TDI y TDO del microcontrolador AVR. Esta
interface también permite la programación serial del AVR, es decir, el hardware es un
depurador y programador. No todos los AVR tienen soporte JTAG ICE (ejemplo los
tinyAVR).
programadores y depuradores AVRICE MkII y AVR ONE!.

Los programadores/depuradores AVRICE MkII (izquierda) y AVR ONE! (derecha).
El software depurador (AVR Dragon, JTAGICE3, JTAGICE MkII o AVR ONE!) puede
dirigir el programa del AVR en la misma aplicación paso a paso, como en cámara lenta
o en tiempo real, e ir visualizando los resultados de las operaciones a medida que se
van ejecutando. De ese modo se puede monitorizar y analizar «en vivo y en directo»
cómo van cambiando los recursos hardware del AVR, como el contenido de sus
memorias y de sus registros internos. Para quienes conozcan algo, es parecido a la
simulación de Proteus o del mismoAtmel Studio 6, pero esto será real.
Los elementos hardware de control y programación del AVR también suelen estar
disponibles en las tarjetas de desarrollo que provee ATmel, como las
tarjetas STK500,STK600 o EVKxxx. Las utilidades software correspondientes están
incluidas en Atmel Studio 6.
La tarjeta de desarrollo STK600.

Descarga de Atmel Studio 6 y WinAVR
Ahora que ya tienes una idea bien formada de lo que es y lo que puede hacer Atmel
Studio 6, puedes descargarlo libremente desde www.atmel.com. Puede pesar más de
700 MB, así que tendrás que esperar un poco. Si ya tienes este programa, pero en su
versión 5.x, será mejor que te actualices. Hay sustanciales diferencias entre la versión
5.x y las anteriores, en las cuales no perderé tiempo citándolas. Solo espero que luego
no te quejes si encuentras cosas que no te salen igual ;).
En la página de descarga podremos encontrar los siguientes paquetes.
El instalador completo (Full) de Atmel Studio 6. Como allí se indica, aquí se incluyen
los paquetes Microsoft Windows Framework .NET 4.0 y Visual Studio Shell (Isolated
Mode) 2010. Ambos son prerrequisitos para la instalación de Atmel Studio 6 y
probablemente ya los tengas instalados en tu PC. Si no estás seguro de ello o si
prefieres perder un poco más de tiempo en la descarga antes que averiguarlo, puedes
descargar este paquete completo. Recuerda que el instalador trae incluidos los
compiladores AVR GCC, AVR32 GCC y ARM GCC.
El instalador solo de Atmel Studio 6. Lo mismo que el anterior pero sin Microsoft
Windows Framework .NET 4.0 ni Visual Studio Shell (Isolated Mode) 2010.
La actualización de Atmel Software Framework (ASF). Contiene el paquete de los más
de los más de 1000 proyectos, ejemplos, librerías, etc. El instalador de Atmel Studio
6 ya lo trae incluido solo que quizá no en su versión más actual. Descarga este archivo
exe solo si quieres estar súper actualizado e instálalo luego de Atmel Studio 6.

Instalación de Atmel Studio 6 y AVR GCC - WinAVR
Requisitos de sistema
Instalación de MVS 2010
Instalación de drivers USB
Instalación de Atmel Studio

Requisitos de sistema
Windows XP (x86) con Service Pack 3 en todas las ediciones excepto Starter Edition.
Windows Vista (x86) con Service Pack 1 en todas las ediciones except Starter Edition.
Windows XP (x64) con Service Pack 2.
Windows Vista (x64) con Service Pack 1.
Windows 7 (x86 y x64).
Windows Server 2003 R2 (x86 y x64).
Computadora con procesador de 1.6GHz o superior.
1 GB de RAM para x86.
2 GB de RAM para x64.
512 MB de RAM adicionales si se trabaja en una PC virtual.
3GB de espacio disponible en el disco duro.
Disco duro de 5400 RPM o superior.
Tarjeta de vídeo con soporte DirectX 9 y resolución de 1024 x 768 o superiores.
Antes de instalar Atmel Studio 6 debemos desinstalar la versión Beta del Studio 5, si
es que lo teníamos. Y si tenemos instalados el AVR Studio 5 versión Release, AVR
Studio 4 o AVR32 Studio, los podemos conservar porque Atmel Studio 6 puede trabajar
en paralelo con ellos.
Bueno ahora sí empezamos la instalación como cualquier otro programa de Windows,
siguiendo las indicaciones presentadas. Aquí, algunos screenshots:

Lista de los requisitos software.
Antes de instalar Atmel Studio 6 propiamente se instalarán los paquetes listados. Para
quienes ya tenían instalados Microsoft .NET Framework 4.0 (se supone que Windows 7
Ultimate ya lo incluye) o Microsoft Visual Studio 2010 Shell, dichas opciones ya no
estarán disponibles y por tanto pasarás directamente a la Instalación de los Drivers
USB.
Curso micros
Curso micros
Instalación de Microsoft Visual Studio 2010 Shell
Luego vendrá la instalación de Microsoft Visual Studio 2010 Shell. Si tienes Internet y
deseas enviar a Microsoft información sobre tu experiencia con la instalación de Visual
Studio, puedes activar la casilla indicada.
Curso micros
Curso micros
Curso micros
Terminada esta parte nos dirán que Microsoft Visual Studio 2010 Shell se instaló con
éxito, a pesar de que pueden aparecer esas X en rojo indicando que aún no se ha
instalado la documentación respectiva (no es necesario para el desempeño de Atmel
Studio 6 pero si deseas la puedes descargar desde la web de Microsoft). También me
recomienda actualizar mi PC con los parches de seguridad más recientes de Windows.
Instalación de los Drivers USB de Atmel
Y ahora viene la instalación de los Drivers USB de Atmel. Atmel Studio 6 utiliza estos
drivers para manejar sus tarjetas hardware con interface USB. Si ya los tenías
instalados, digamos porque trabajaste con versiones anteriores del AVR Studio,
entonces este procedimiento actualizará los drivers con la versión presente. Estos
drivers USB de Atmel son compatibles con sus antecesores.
Curso micros
Curso micros
Curso micros
Por supuesto que estos drivers son confiables, son de los mejores.
Instalación de Atmel Studio 6
Y pensar que recién vamos a instalar Atmel Studio 6 propiamente (creo mi Windows se
ha instalado más rápido ;).
Curso micros
Curso micros
Entorno de Atmel Studio 6
Entorno de trabajo de Atmel Studio 6.
Ésa es la distribución por defecto de las ventanas de Atmel Studio 6. En primer plano
tenemos aStart Page o Página de Inicio, a la derecha está la ventana Solution
Explorer y abajo, la ventanaOutput. Hay más ventanas que iremos conociendo en el
camino pero mientras te familiarizas con el entorno puedes reacomodarlas según tu
preferencia. Para ello puedes arrastrarlas tomándolas por su barra de títulos y
colocarlas donde indica el guía o puedes dejarlas flotando. Esto último no suele ser
muy útil, aunque a mí me servirá para mostrar las capturas de pantalla más adelante

Reubicación de las ventanas del entorno de Atmel Studio 6.
Hacer estas reubicaciones es divertido sobre todo si tenemos en cuenta que podemos
regresar a la distribución inicial yendo al menú Window
Reset Window Layout. Y si
queremos volver a tener la página Start Page para ver a la mariquita de nuevo vamos
al menú View
Start Page. Nota que esta opción también tiene un icono en la barra
de herramientas. Es el menú View donde también puedes encontrar las otras ventanas
y no en el menú Window.
Antes que mostrar una típica descripción de cada uno de los elementos del entorno
de Atmel Studio 6 los iré presentando a medida que hagan su aparición y sean
requeridos.

Trabajando con Proyectos y Soluciones en C
A diferencia de los compiladores Basic, donde basta con crear un archivo BAS suelto y
luego compilarlo, los programas en C siempre forman parte de un proyecto.
Actualmente desarrollar proyectos en C con Atmel Studio 6 es más sencillo que en
versiones anteriores debido en gran parte a que está adaptado para trabajar
especialmente con los compiladores libres GCC AVR32 y AVR GCC (WinAVR).
Una Solución (Solution) es un conjunto formado por uno o varios proyectos, así que
todo proyecto debe pertenecer a alguna Solución.Atmel Studio 6 puede trabajar con
uno solo o con todos los proyectos de la Solución al mismo tiempo, pero solo puede
administrar una Solución a la vez.
Hay más consideraciones respecto a los Proyectos y las Soluciones, pero creo que será
mejor describirlas mientras creamos el Proyecto.

Creación de un Proyecto en C
Solo hay una forma de crear un proyecto, esto para simplificar las cosas.
Abierto Atmel Studio 6 vamos al menú File
Project deStart Page.

New Project o hacemos clic en New
Creando un proyecto desde Start Page.
De cualquier modo llegaremos al mismo asistente, donde empezamos por seguir lo que
indica la siguiente figura. El nombre y la ubicación del proyecto pueden ser los que
desees.
Elección del nombre y ubicación del proyecto.
Ten en cuenta que Atmel Studio 6 creará una nueva carpeta (con el nombre del
proyecto) dentro de la ubicación indicada. Observa que el nombre de la Solución es el
mismo que el del proyecto.
Todo proyecto debe pertenecer a alguna Solución, y como estamos creando el primer
proyecto,Atmel Studio 6 le asignará una Solución automáticamente. Cuando creemos
los siguientes proyectos tendremos la opción de elegir si pertenecerán a ésta o a una
Solución nueva.
Si activas la casilla Create directory for Solution, la Solución (que es un archivo a fin de
cuentas) se alojará en la carpeta que debía ser para el proyecto, y el proyecto junto
con sus archivos generados irán a parar a una sub carpeta con el mismo nombre. Esto
puede ser conveniente cuando se tiene una Solución con varios proyectos. Yo sé que
parece enredado pero con un par de prácticas lo entiendes mejor y te acostumbras.
Normalmente prefiero tener un proyecto por cada Solución, de modo que no tengo que
marcar la casilla indicada y puedo tener todos los archivos del proyecto y de la
Solución dentro de la misma carpeta sin que se confundan. Es así como están
conformadas todas las prácticas de cursomicros.com.
Le damos clic a OK y tendremos una ventana mucho menos traumática. Solo
seleccionamos el AVR con el que trabajaremos. En el futuro podremos cambiar este
microcontrolador por otro. También puedes aprovechar esta ventana para reconocer
las memorias de cada dispositivo y para ver las herramientas que tienen disponibles
desde Atmel Studio 6.

Elección del microcontrolador del proyecto.
En seguida tendremos nuestro proyecto con el Editor de Código mostrándonos el
archivo de código fuente principal listo para que lo editemos. Observa que
el Explorador de la Solución oSolution Explorer muestra los Proyectos de la Solución
así como los archivos de cada Proyecto. Debajo está el marco Properties que informa
las propiedades de cada archivo seleccionado arriba, como su ubicación.
De todas las ventanas aquí mostradas o las que puedan surgir en adelante la que no
deberías perder de vista es el Explorador de la Solución. Desde allí accedes a todos los
archivos del proyecto (los abres con doble clic) y también puedes construir el proyecto
así como establecer su configuración. Si se te llega a perder esta ventana la puedes
visualizar yendo al menú View

Esquema del proyecto creado.

Edición del Código Fuente

Solution Explorer.
El editor de códigos se llama simplemente Code y es accesible desde el menú View.
También se muestra automáticamente cada vez que abrimos un archivo. Como lo
dijimos al inicio, la podemos cerrar, reubicar o dejar flotando como se ve en la
siguiente figura.
El código mostrado corresponde al programa del LED parpadeante ledflasher3, que es
con el que vamos a trabajar. Todavía no lo podemos compilar porque faltan agregar al
proyecto los archivos mencionados en las directivas include. Hablamos de los
archivos avr_compiler.h, usart.hy usart.c. Pero eso lo veremos en la siguiente sección.

Edición del programa Ledflasher3.
Trabajando con Proyectos y Soluciones en C
A diferencia de los compiladores Basic, donde basta con crear un archivo BAS suelto y
luego compilarlo, los programas en C siempre forman parte de un proyecto.
Actualmente desarrollar proyectos en C con Atmel Studio 6 es más sencillo que en
versiones anteriores debido en gran parte a que está adaptado para trabajar
especialmente con los compiladores libres GCC AVR32 y AVR GCC (WinAVR).
Una Solución (Solution) es un conjunto formado por uno o varios proyectos, así que
todo proyecto debe pertenecer a alguna Solución.Atmel Studio 6 puede trabajar con
uno solo o con todos los proyectos de la Solución al mismo tiempo, pero solo puede
administrar una Solución a la vez.
Hay más consideraciones respecto a los Proyectos y las Soluciones, pero creo que será
mejor describirlas mientras creamos el Proyecto.

Creación de un Proyecto en C
Solo hay una forma de crear un proyecto, esto para simplificar las cosas.
Abierto Atmel Studio 6 vamos al menú File
Project deStart Page.

Creando un proyecto desde Start Page.

New Project o hacemos clic en New
De cualquier modo llegaremos al mismo asistente, donde empezamos por seguir lo que
indica la siguiente figura. El nombre y la ubicación del proyecto pueden ser los que
desees.

Elección del nombre y ubicación del proyecto.
Ten en cuenta que Atmel Studio 6 creará una nueva carpeta (con el nombre del
proyecto) dentro de la ubicación indicada. Observa que el nombre de la Solución es el
mismo que el del proyecto.
Todo proyecto debe pertenecer a alguna Solución, y como estamos creando el primer
proyecto,Atmel Studio 6 le asignará una Solución automáticamente. Cuando creemos
los siguientes proyectos tendremos la opción de elegir si pertenecerán a ésta o a una
Solución nueva.
Si activas la casilla Create directory for Solution, la Solución (que es un archivo a fin de
cuentas) se alojará en la carpeta que debía ser para el proyecto, y el proyecto junto
con sus archivos generados irán a parar a una sub carpeta con el mismo nombre. Esto
puede ser conveniente cuando se tiene una Solución con varios proyectos. Yo sé que
parece enredado pero con un par de prácticas lo entiendes mejor y te acostumbras.
Normalmente prefiero tener un proyecto por cada Solución, de modo que no tengo que
marcar la casilla indicada y puedo tener todos los archivos del proyecto y de la
Solución dentro de la misma carpeta sin que se confundan. Es así como están
conformadas todas las prácticas de cursomicros.com.
Le damos clic a OK y tendremos una ventana mucho menos traumática. Solo
seleccionamos el AVR con el que trabajaremos. En el futuro podremos cambiar este
microcontrolador por otro. También puedes aprovechar esta ventana para reconocer
las memorias de cada dispositivo y para ver las herramientas que tienen disponibles
desde Atmel Studio 6.

Elección del microcontrolador del proyecto.
En seguida tendremos nuestro proyecto con el Editor de Código mostrándonos el
archivo de código fuente principal listo para que lo editemos. Observa que
el Explorador de la Solución oSolution Explorer muestra los Proyectos de la Solución
así como los archivos de cada Proyecto. Debajo está el marco Properties que informa
las propiedades de cada archivo seleccionado arriba, como su ubicación.
De todas las ventanas aquí mostradas o las que puedan surgir en adelante la que no
deberías perder de vista es el Explorador de la Solución. Desde allí accedes a todos los
archivos del proyecto (los abres con doble clic) y también puedes construir el proyecto
así como establecer su configuración. Si se te llega a perder esta ventana la puedes
visualizar yendo al menú View

Esquema del proyecto creado.

Solution Explorer.
Edición del Código Fuente
El editor de códigos se llama simplemente Code y es accesible desde el menú View.
También se muestra automáticamente cada vez que abrimos un archivo. Como lo
dijimos al inicio, la podemos cerrar, reubicar o dejar flotando como se ve en la
siguiente figura.
El código mostrado corresponde al programa del LED parpadeante ledflasher3, que es
con el que vamos a trabajar. Todavía no lo podemos compilar porque faltan agregar al
proyecto los archivos mencionados en las directivas include. Hablamos de los
archivos avr_compiler.h, usart.hy usart.c. Pero eso lo veremos en la siguiente sección.
Edición del programa Ledflasher3.

Adición de Archivos o Librerías al Proyecto
Los proyectos en AVR GCC o AVR IAR C raras veces constan de un solo archivo. Casi
siempre hay archivos de configuración o librerías que añadir. De hecho, en
cursomicros.com todas las prácticas incluyen al menos el archivoavr_compiler.h, el
cual permite que los códigos de programa se puedan construir indistintamente para los
compiladores AVR GCC o AVR IAR C. Este archivo forma parte del paquete ASF y lo
puedes hallar en el directorio de instalación de Atmel Studio 6. También puedes
encontrar una copia suya (con ligeras modificaciones) en todos los proyectos de
cursomicros.com.
Las librerías en el lenguaje C se suelen dividir en dos archivos: uno (con extensión .c)
que suele contener los códigos ejecutables de las funciones y otro (con extensión .h)
donde se escriben las definiciones y los prototipos de las funciones, básicamente. Así
por ejemplo, en cursomicros.com se usan las librerías para displays LCD (con
archivos lcd.h y lcd.c), para el puerto serie (con archivos usart.h y usart.c) o para el
bus I2C (con archivos i2c.h e i2c.c).
En los códigos fuente los archivos se invocan mediante la directiva include.
Adicionalmente en los proyectos de WinAVR o AVR IAR C deben incluirse desde sus
entornos de desarrollo. En esta ocasión veremos cómo hacer esto con los
archivos avr_compiler.h, usart.h y usart.c y el procedimiento descrito es el mismo que
debes seguir cuando añadas otros archivos.
Una vez ubicados los archivos avr_compiler.h, usart.h y usart.c, colócalos en la
carpeta de tu proyecto. Esto no es necesario porque que se le podría incluir
dondequiera que esté desde el Explorador de la Solución. Sin embargo habrá proyectos
en los que se quiera editar el archivo incluido y para que dichos cambios no afecten a
los demás proyectos lo más recomendable será tener una copia suya en la carpeta de
cada proyecto, como se ve abajo.
Los archivos avr_compiler.h, usart.h y usart.c deberían estar en la carpeta del
proyecto.
Ahora debemos indexar el archivo al proyecto. Para ello seleccionamos el proyecto en
el Explorador de la Solución y vamos al menú Project
Add Existing Item… También
puedes emplear mi camino preferido, que se ilustra a continuación.
Curso micros
Indexando los archivos avr_compiler.h, usart.h y usart.c al proyecto.
Observa que ahora el archivo añadido también se visualiza en el Explorador de la
Solución.
Archivo avr_compiler.h indexado.

Construcción del Proyecto
Antes de entrar en el proceso de la construcción debemos saber que existen dos
modos básicos de configuración en que se generarán los resultados, que
son Debug y Release. El modo por defecto es Debug.
Uno de los efectos de cambiar del modo Debug al modo Release es que la optimización
del código se adaptará para obtener el archivo HEX de menor tamaño. Esta
optimización es necesaria para que las funciones de delay del programa queden mejor
afinadas.
Elección entre los modos Debug y Release.
También debemos considerar que cada modo generará los archivos de salida en sus
correspondientes carpetas. Hasta ahora solo hemos visto la carpeta Debug pero al
compilar el proyecto en modo release se creará también una carpeta Release. Una
opción para evitar marearnos entre estos modos es ajustar la optimización del código
directamente. Aprendemos eso en una próxima sección. Por el momento cambiemos a
release y sigamos.
Bueno, para construir un proyecto podemos tomar varios caminos, dos de los cuales se
muestran abajo. Un tercer camino es seleccionar el proyecto en el Explorador de la
Solución, hacer clic derecho y en el menú emergente aparecerán las mismas opciones
del menú Build.

Build Solution (construir la Solución). Una Solución puede estar compuesta por varios
proyectos. De ser el caso, con esta opción se construirán todos los proyectos de
laSolución.
Rebuild Solución (reconstruir la Solución). Reconstruye todos los proyectos de la
Solución haciendo previamente una limpieza de los archivos de salida generados antes.
Clean Solution (Limpiar Solución). Para todos los proyectos de la Solución actual,
limpia o borra todos los archivos de salida, como HEX, LSS, MAP, etc., que se crearon
durante una construcción previa.
Build ledflasher (construir ledflasher). En el entorno del lenguaje C construir en
realidad involucra varios procesos, como compilar, enlazar (linkar) y ensamblar.
Normalmente nos referimos a esta acción simplemente como compilar.
Rebuild ledflasher (reconstruir ledflasher). Vuelve a construir el proyecto indicado pero
haciendo una limpieza previa de sus archivos de salida.
Clean ledflasher (limpiar ledflasher). Borra todos los archivos de salida del proyecto
indicado.
Compile (compilar). La opción compilar aparece en el menú Build cuando se selecciona
un archivo C. Al compilar un archivo no obtendremos los archivos HEX o ELF, sino solo
un archivo objeto, que es previo al HEX o ELF.
Puesto que nuestra Solución de ejemplo solo tiene un proyecto, dará lo mismo si
elegimos construir el proyecto o construir la Solución.
Tras construir el proyecto del LED parpadeante nos aparecerá la ventana de
salida Output con los resultados de éxito, como se aprecia abajo. Probablemente tu
ventana Output aparezca en otra disposición pero como aprendimos antes, eso es solo
cuestión de reubicación.
Resultados de la construcción.
Entre los archivos de salida puedes ver que aparecieron los esperados HEX (para
grabar el microcontrolador) y ELF (para la depuración o simulación en programas
como Proteus). Todos estos archivos se depositan en la carpeta Release del proyecto
porque lo compilamos en ese modo.
Lo archivos de salida van a las carpetas Release o Debug.

Renombrar los Archivos del Proyecto
Por defecto los nombres de la Solución, del proyecto y del archivo de código fuente
principal son el mismo. Esto es opcional, pero yo prefiero que mi archivo de código
principal se llame siempremain.c para hacer una mejor distinción de los otros archivos
de código como las librerías.
Renombrar un archivo es sencillo. Solo hay que seleccionarlo en el Explorador de la
Solución, abrir su menú contextual (clic derecho) y escoger la opción Rename. Del
mismo modo también es posible cambiar de nombre el proyecto y la Solución.
Renombrando los archivos del proyecto y la Solución.

Cambiar la Frecuencia del Procesador
Para WinAVR normalmente las únicas rutinas que requieren la definición de la
frecuencia del procesador son las funciones de delay, que se encuentran en los
archivos del mismo nombre disponibles en la carpeta utils del WinAVR. Quienes utilizan
dichos delays suelen editar la constante F_CPU definida en esos archivos.
Pero el archivo avr_compiler.h también define la constante F_CPU antes de utilizar las
funciones de delay de WinAVR. De modo que si vamos a trabajar con este archivo, es
aquí donde debemos editar la constante F_CPU, que casi siempre coincidirá con la
frecuencia del XTAL usado. Gracias a las macros de avr_compiler.hF_CPU también
tendrá efecto en las funciones de delay del compilador IAR AVR C.
Además, las librerías de www.cursomicros.com para los módulos síncronos del AVR
como elUSART o TWI (I2C) también se basan en F_CPU. Queda claro entonces que no
solo la utilizo para calibrar los delays.
En el archivo avr_compiler.h del paquete ASF la constante F_CPU se define
a 11059200Hz. Pero en casi todas las copias de avr_compiler.h disponibles en
www.cursomicros.com F_CPU está redefinida a 8 MHz (como se aprecia en el siguiente
extracto de dicho archivo) porque en la gran mayoría de mis diseños trabajo con esa
frecuencia.
////////////////////////////////////////////////////////////////
#ifndef F_CPU
/* Define la frecuencia de CPU (en Hertz) por defecto, si

no ha

* sido definida. */
#define F_CPU 8000000UL // XTAL de 8 MHz
#endif

Reconfiguración del Proyecto WinAVR
El compilador AVR GCC nació inicialmente para Linux y con el tiempo se desarrolló la
versión para Windows llamada WinAVR. Hasta ahora WinAVR no tiene un entorno
propio y de hecho ya no hace falta gracias a Atmel Studio 6.
Pero anteriormente para usar WinAVR había que invocarlo desde otros entornos
como CodeBlocks,Eclipse o el mismo Studio 4. Esos entornos no eran del todo
compatibles con WinAVR y más temprano que tarde uno tenía que configurar las
opciones del proyecto en el mismo archivo Makefile.
Makefile es un archivo de texto lleno opciones para la construcción del proyecto.
WinAVR sigue trabajando en base a él, solo que para nosotros Atmel Studio 6 lo crea y
edita automáticamente. Cada cambio realizado en la ventana de propiedades del
proyecto es reflejado en el archivo Makefile. Si te interesa echarle un vistazo, puedes
encontrar Makefile entre los archivos de salida en las carpetas Debug y/o Release.
Para acceder a la ventana de propiedades del proyecto, lo seleccionamos y vamos al
menúProject
Properties. Yo, como siempre, prefiero ir por el Explorador de la
Solución, como se ve abajo.
Hay varias categorías desde Build hasta Advance y en muchas de ellas podremos elegir
si la configuración se aplica al modo Debug o Release. Recuerda que cada modo hace
que los archivos de salida (incluyendo el Makefile) vayan a sus respectivas carpetas.
Ventana de propiedades del proyecto.

Optimización del Código
Comúnmente los compiladores C ofrecen varios niveles de optimización del código. Con
WinAVR tenemos los siguientes niveles:
No optimizar (nivel O0). El código no se optimiza nada. Por ejemplo, el programa del
LED parpadeante compilado con este nivel resulta en un código de 3714 bytes y
compilado en nivel Os resulta en 118 bytes. Sí que hay diferencia, ¿verdad? Sin
embargo, muchas veces se usa este modo con fines de depuración porque genera el
mejor archivo ELF. No por nada es el nivel por defecto del modo Debug. Además, esa
diferencia se desvanece cuando se trabaja con programas grandes. Por ejemplo un
programa que utiliza funciones matemáticas con números de punto flotante y la
función Printf en su máxima performance a mí me resultó en 3366 bytes sin
optimización y 3038 con optimización Os.
Optimizar (nivel O1). Optimiza regularmente; bastante, comparado con el nivel O0.
Con este nivel mi programa de prueba resultó en 3066 bytes.
Optimizar más (nivel O2). Con este nivel mi programa de prueba resultó en 3060
bytes.
Optimizar al máximo (nivel O3). Este nivel es particular porque no optimiza para
conseguir el menor código, sino el más veloz, es decir, el código que se ejecuta en el
menor tiempo posible. Para esto el compilador intentará incrustar en línea todas las
funciones que pueda, como las que se llaman solo una vez. Siguiendo con el ejemplo,
mi programa resultó en 3102 bytes.
Optimizar para tamaño (nivel Os). Optimiza para que la construcción genere el menor
código máquina posible. En general el tamaño del código maquina se contrapone a su
velocidad de ejecución. No se puede conseguir un código mínimo y que al mismo
tiempo sea el más veloz. El manual de WinAVR recomienda utilizar este nivel
combinado con la directiva -mcall-prologues, excepto cuando se quiera usar el nivel
O3 para ganar algo de velocidad. El nivel Os es la opción por defecto del modo
Release.
Ahora abramos la ventana de propiedades del proyecto y ubiquémonos
en Toolchain

AVR/GNU C Compiler

Optimization.

Recuerda que puedes elegir si la configuración se aplica al modo Debug o Release y
que cada modo hace que los archivos de salida vayan a sus respectivas carpetas.
Cambio de la optimización del código.
Había señalado anteriormente que para mejorar la optimización de código en tamaño
la ayuda de WinAVR recomendaba el uso de la opción -mcall-prologues. Dice que con
esto se usarán subrutinas especiales para guardar y restaurar los registros en las
entradas y salidas de las funciones que usen muchos registros, a costa de un pequeño
incremento en el tiempo de ejecución. (Es de suponer que se trata de bucles, ¿cierto?)
En fin, sea como fuere, para habilitar la opción -mcall-prologues en Atmel Studio 6 ni
siquiera tenemos que escribirla, simplemente activamos la casilla Use subroutines for
function prologues and epilogues, como de ilustra en la siguiente figura.
Cuando un programa se construye exitosamente no siempre significa que lo hizo de la
mejor forma. A veces puede haber algunos mensajes o advertencias que no se
muestran en la ventana de salida Output. Para ver en detalle estos errores,
advertencias y mensajes debemos inspeccionar la ventana Error List, que está
disponible desde el menú View

Error List.

Lista de errores, advertencias y mensajes de la construcción.
Aquí muestro la ventana flotante pero recuerda que la puedes anclar donde desees. El
caso es que la advertencia mostrada es muy frecuente. Señala que las optimizaciones
del compilador están inhabilitadas y que las funciones del archivo delay.h no
funcionarán adecuadamente. Imagino que con todo lo visto aquí a ti nunca se te
debería presentar. Nosotros no invocamos al archivo delay.h directamente, sino a
través de avr_compiler.h.

Cambiar de Microcontrolador AVR
Observa que ni en WinAVR ni en otros compiladores como IAR AVR C o ImageCraft
AVR se debería seleccionar el AVR del proyecto desde el código fuente, como se suele
hacer en Basic.
Si queremos conocer el microcontrolador AVR para el que está hecho un proyecto o si
queremos cambiarlo, debemos ir a la categoría Device.

Reelección del microcontrolador del proyecto.
Nos aparecerá una ventana muy parecida a la que teníamos cuando creamos el
proyecto. La diferencia es que ya no están los AVR de 32 bits, así que el cambio solo
es factible por otro AVR de 8 bits. Lo mismo ocurre cuando administramos un proyecto
con un AVR de 32 bits. Entendemos que para cambiar de microcontrolador primero
tendríamos que cambiar de compilador, de AVR GCC a AVR32 GCC o viceversa, pero
eso tampoco es posible desde Atmel Studio 6. La única forma sería creando un nuevo
proyecto.
Cambio del microcontrolador del proyecto.

Uso de un Archivo Makefile Externo
Explicamos anteriormente lo que significa el archivo Makefile para el compilador
WinAVR y que en estos días sería muy raro editarlo manualmente. Sin embargo, dada
la gran flexibilidad de WinAVR, a veces incluso administrar la ventana de propiedades
del proyecto en Atmel Studio 6puede resultar confuso.
Has de saber que a diferencia de otros compiladores C, la configuración de compilación
de un proyecto en WinAVR reside íntegramente en su archivo Makefile. Los archivos de
Proyecto y de la Solución que crea Atmel Studio 6 se reducen a simples cascarones en
comparación con Makefile.
Si alguna vez tienes el código fuente de un gran proyecto ajeno pero sin su Makefile,
probablemente no logres compilarlo como el autor lo diseñó, si es que no utilizas la
misma configuración. Tendrías que acertar con la configuración exacta o conseguir el
archivo Makefile original.
Bueno, ya sea que quieras trabar con un archivo Makefile o que simplemente seas uno
de los antiguos nostálgicos que extrañan editarlo a mano (como yo ;), la forma de usar
un archivo Makefile diferente del creado por Atmel Studio 6 es:

Uso de Makefile externo en Atmel Studio 6.

Configurar el Uso de Printf
¿Quieres visualizar números de punto flotante y solo obtienes el signo ? ? Imagino que
esto debe ser frustrante para los usuarios de los compiladores “fáciles” como CCS C.
Espera un momento. Ni el lenguaje C ni el compilador GCC fueron diseñados para los
microcontroladores. Son muy útiles las familias de funciones printf y scanf pero al
mismo tiempo bastante pesadas para la arquitectura de los microcontroladores, sobre
todo si son de 8 bits y no tienen chip matemático.
Por las limitaciones conocidas hasta los mejores compiladores como AVR IAR C o
WinAVR tienen que subdividir a printf y scanf y sus derivados en tres versiones, para
que el usuario escoja la que más se ajuste a su demanda y a las restricciones de su
microcontrolador.
La versión minimizada de printf restringe los especificadores de conversión al uso de
enteros de 16 bits (entre octales, hexadecimales y decimales positivos o negativos), de
caracteres y de cadenas de caracteres.
No están disponibles los números de punto flotante ni los números enteros de 32 bits.
No se permite especificar el ancho de los números y por ende tampoco se disponen de
las opciones de justificación ni de relleno con blancos o ceros.
Según el manual de avr-libc, la versión minimizada de printf se consigue estableciendo
las siguientes opciones de linkador:
-Wl,-u,vfprintf -lprintf_min
La versión predeterminada de printf ofrece todas las características de la versión
completa excepto las relacionadas con los números de punto flotante. En los megaAVR
demanda cerca de 350 bytes más que la versión minimizada.
Es redundante decir que ésta es la versión por defecto y no requiere de ninguna
instrucción al linkador. Pero si previamente se hubo establecido alguna de las otras
versiones, entonces bastaría con quitar las siguientes opciones para regresar a la
versión por defecto de printf.
-Wl,-u,vfprintf
La versión completa de printf brinda todo su potencial, incluyendo las conversiones de
números de punto flotante. En los megaAVR requiere cerca de 380 bytes de memoria
FLASH más que la versión predeterminada.
La versión completa de printf viene con las siguientes opciones de linkador:
-Wl,-u,vfprintf -lprintf_flt -lm
Las preguntas son: ¿y dónde se escriben esas “cosas”?, o ¿hay que escribirlas? ¿Por
qué no se arregla simplemente con un par de clics?, o ¿por qué no es como en CCS C,
o aunque sea como en CodeVisionAVR?
Y las respuestas son: porque WinAVR sigue siendo un huésped de Atmel Studio 6 y
todavía no tienen una mejor interacción. Tal vez en el futuro (de hecho, veremos que
ha habido un pequeño avance en Atmel Studio 6 respecto del Studio 5). Por otro lado,
en realidad CCS C también utiliza varias plantillas de su función printf con diferente
alcance cada una, solo que antes de compilar el programa detecta las características
mínimas necesarias según el código fuente y selecciona automáticamente la plantilla
más adecuada. Genial idea, ¿verdad? Me quito el sombrero. Sospecho
que CodeVisionAVR está cerca de conseguir esa funcionalidad. Por su parte, las
recientes versiones de AVR IAR C ya cuentan con una opción Auto para este propósito.
Retomando el tema, lo que hacen esas “cosas” son dos cosas (como en El gato ;):
primero, le dicen al linkador que no utilice la versión de printf por defecto, así:
-Wl,-u,vfprintf
-Wl indica que la directiva va para el linkador y –u,vfprintf le dice que desconozca el
símbolovfprintf (que representa la función printf y similares) que se encuentra entre
las librerías que usa por defecto y que más bien lo busque en otras librerías. ¿Dónde?
Para la versión minimizada, en la librería indicada por:
-lprintf_min
Y para la versión completa, en las librerías indicadas por:
-lprintf_flt -lm
Según el manual de GNU GCC (que también se instaló con tu WinAVR), la opción
genérica –llibrary se refiere a la librería liblibrary.a, por lo que en realidad las librerías
mencionadas sonlibprintf_min.a , libprintf_flt.a y libm.a.
Si todavía sigues por ahí, selecciona tu proyecto en Atmel Studio 6, muestra su
ventana de propiedades, ve a la categoría Toolchain
AVR/GNU C Linker
General y
marca la casilla de Use vprintf library (-Wl,-u,vfprintf), tal como aparece abajo.
Indicar a WinAVR que no use el printf por defecto en Atmel Studio 6.
Esa casilla de marcado solo está disponible en Atmel Studio 6. Así que las personas
que aún trabajan con el Studio 5 tendrán que escribir la directiva -Wl,u,vfprintf indicada en el cuadro de texto de Other Linker Flags de la
sección Toolchain
AVR/GNU C Linker
Miscellaneous, como se muestra abajo.
Indicar a WinAVR que no use el printf por defecto.
Ya sea que tengamos Atmel Studio 6 ó 5, ahora nos ubicamos en Toolchain
AVR/GNU C Linker
Libraries y añadimos la librería libprintf_min.a, así:
Añadiendo librerías para el linkador en Atmel Studio 6.
Del mismo modo, agrega las librerías libprintf_flt.a y libm.a. Te debería quedar así:
Indicar a WinAVR que primero busque [printf] en libprintf_flt.a (versión completa).
Ahora ya podemos usar printf en sus versiones minimizada o completa. Fíjate bien en
el orden de las librerías añadidas. Si en la lista aparece libprintf_flt.a antes
que libprintf_min.a, entonces se utilizará la versión completa de printf y similares,
como se ve arriba.
Pero si la librería libprintf_min.a tiene mejor nivel que libprintf_flt.a, entonces se usará
la versión minimizada de printf. Esto se ve abajo. Para no confundirte en cualquiera de
los casos puedes quitar la librería que no utilices. Puse este esquema solo para no
estar añadiendo librerías a cada rato y para mostrar sus prioridades de llamada. La
posición relativa de libm.a no importa, pues solo contiene rutinas matemáticas para
printf en su versión completa.
Indicar a WinAVR que primero busque [printf] en libprintf_min.a (versión minimizada).

Configurar el Uso de Scanf
La familia de funciones scanf es mucho menos recurrente que printf. A veces su
empleo es inclusive contraindicado debido a que no ofrece buenos filtros.
Scanf también viene en tres versiones:
La versión minimizada admite el ingreso de caracteres, cadenas de hasta 256
caracteres y números enteros de 16 bits positivos o negativos. No maneja números de
punto flotante, no admite el especificador [, usado para filtrar el ingreso según un
conjunto de caracteres indicado por el usuario. Tampoco permite usar el flag de
conversión %.
Para su uso se requieren las siguientes opciones de linkador. Allí se indican inhabilitar
la versión predeterminada e incluir las librerías libscanf_min.a y libm.a.
-Wl,-u,vfscanf -lscanf_min -lm
La versión predeterminada provee casi todas características de scanf en versión
completa. No ofrece conversiones de números de punto flotante y el ingreso de
caracteres se limita a 256.
No hacen falta opciones de linkador. Si se estableció antes otra versión, habría que
remover al menos las opciones:
-Wl,-u,vfscanf
La versión completa sí permite las conversiones de números de punto flotante.
Para su uso debemos usar las siguientes opciones de linkador. Allí se indican inhabilitar
la versión predeterminada e incluir las librerías libscanf_flt.a y libm.a.
-Wl,-u,vfscanf -lscanf_flt -lm
Ya vimos que agregar librerías y poner las opciones –Wl, u, vfscanf es muy sencillo. El
punto será cómo combinar las opciones de linkador si se usan al mismo tiempo ambas
familias de printf y scanf en versiones no predeterminadas. Cualquiera de las
siguientes dos formas vale (no importa el orden entre vfscanf y vfprintf):
-Wl,-u,vfscanf -Wl,-u,vfprintf
-Wl,-u,vfscanf -u,vfprintf

Indicar a WinAVR que no use el scanf por defecto.
Indicar a WinAVR que no use ni el printf ni el scanf por defecto.
No es necesario que estén presentes las dos librerías libscanf_flt.a y libscanf_min.a al
mismo tiempo, pero al estar presentes las dos, se deberá tener en cuenta el orden en
que aparecen, igual que para printf. Por ejemplo, de acuerdo con la siguiente figura,
scanf funcionará en su versión completa porque su librería está primero, y printf
también pero porque no está la librería de la versión minimizada.
Indicar a WinAVR que primero busque [scanf] en libscanf_flt.a (versión completa).

Simulación del Programa
El simulador AVR Simulator permite monitorizar el curso y evolución del programa
paso a paso, es decir, ejecutar el programa instrucción por instrucción si fuera
necesario y visualizar el contenido de los puertos de E/S, las variables de programa, el
valor de las locaciones internas de la RAM incluyendo los registros de E/S, los Registros
de Trabajo, el estado de la Pila y valor actual del Contador de Programa. También se
puede medir el tiempo transcurrido entre distintos puntos del programa o simular la
respuesta del programa ante ciertos eventos detectados en los puertos de E/S del AVR.

Se ve genial, ¿verdad? Sin embargo, no es mucho comparado con un simulador del
calibre deProteus VSM. Proteus puede hacer todo lo mencionado arriba y que veremos
en seguida, pero mucho mejor. Proteus no solo simula el programa del AVR, sino que
nos permite examinar con mayor acercamiento su interacción con el circuito de
aplicación, y muchas veces en tiempo real.
Lo cierto es que Proteus VSM no es un programa gratuito, aunque debemos reconocer
que bien vale su precio, siendo, de lejos, el mejor simulador de circuitos con
microcontroladores que existe ―al menos que yo conozca―. Así que si estás entre los
afortunados que pueden conseguir Proteus, puedes leer el capítulo que se le dedica en
vez de las siguientes páginas. A propósito, la versión demo disponible en su web
www.labcenter.co.uk no permite simular más que los ejemplos que trae incluidos y
algunos circuitos muy sencillos creados por el usuario; de poco nos serviría.
Para esta sección practicaremos con el pequeño secuenciador de LEDs ledflasher3.
Todas las prácticas de cursomicros.com son proyectos con Solución separada, así que
da lo mismo si abres el archivo del proyecto o de la solución. Puedes abrir el proyecto
o solución desde Atmel Studio 6o con doble clic en cualquiera de sus archivos,
*.cproj o *.atsln, respectivamente.
Entorno de Atmel Studio 6 con el programa de ledflasher3.
Para iniciar el AVR Simulator vamos al menú Debug
Start Debugging and Break o
presionamosAlt + F5 o mediante su icono, como se ve abajo. Se puede incluso usar
cualquiera de los comandos de depuración como Step into, Step over, etc. Cualquier
camino vale.
Con la acción indicada estamos iniciando el depurador, que más que un simulador
también involucra a los verdaderos depuradores como JTAGICE3. Si en este momento
hubiera conectada a la PC alguna de las tarjetas hardware como la STK600 o STK500,
que incorporan interfaces de depuración, entonces Atmel Studio 6 las detectaría y nos
daría a elegir con cuál depurador deseamos trabajar. Pero como no es el caso, la única
opción que nos presenta es el AVR Simulator. Para algunos AVR ni siquiera esta
herramienta estará disponible. Así que la seleccionamos, y hacemos clic en OK. En el
futuro ya no veremos esta ventana y la simulación correrá directamente.
Herramientas de depuración disponibles actualmente para el ATmega88P.
Ahora Atmel Studio 6 nos presentará un entorno con grandes cambios que
describiremos.

Los Comandos de Depuración
La barra de herramientas de depuración contiene atajos de varias de las opciones del
menú Debug y es la que usaremos con mayor recurrencia. Si se llegara a perder se le
puede volver sacar del menú View

Toolbars

Debug.

Barra de herramientas Debug con los principales comandos de depuración.
Estos botones son casi estándar en todos los softwares simuladores, emuladores y
depuradores. De seguro te acordarás para siempre de muchos de ellos. Lo que sí varía
un poco respecto de otros programas son las teclas de atajo que los identifican.
Tabla de Comandos de Depuración en Studio 6

Start Debugging and Break. Inicia el modo de Simulación/Depuración.

Stop Debugging. Detiene el modo de Simulación/Depuración.
Step Into. Ejecuta una instrucción o sentencia del programa, pero si se trata de una
llamada a una subrutina o función, se ingresará en el interior de su código.
Step Over. Con este botón se ejecuta una instrucción o sentencia del programa. Si esta
instrucción/sentencia es una llamada a subrutina o función, se ejecutará toda la
subrutina/función de golpe.
Step Out. Si en el momento actual el flujo del programa se encuentra dentro de una
subrutina o función, se puede usar esta opción para salir de ella.
Run to Cursor. Ejecuta el programa de corrido y se detiene en la línea donde se ubica el
cursor. Para que se habilite, primero debemos colocar el cursor en un punto diferente
del indicado por la flecha amarilla.
Reset. Regresa la ejecución del programa al inicio del código, o mejor dicho, a la
primera sentencia o instrucción disponible.
Continue. Ejecuta el código del programa tan rápido como se pueda; aunque sigue
estando lejos del tiempo real. A diferencia de las opciones anteriores, en este modo las
ventanas no se actualizan sino hasta que paremos con un Break All o en un breakpoint.
Se emplea bastante junto con los breakpoints.
Break All. Detiene la evolución del programa si estamos en modo Continue. En los otros
modos esta opción permanece inactiva.
Show Next Statement. Es la misma flechita que dirige el flujo del programa señalando la
siguiente instrucción o sentencia que se ejecutará.
Depuración en marcha del programa ledflasher3.

Las Ventanas de Depuración
Estas ventanas aparecen en el menú Debug
Windows solo cuando se ingresa en
modo de Depuración. A continuación descubriremos la utilidad de algunas de ellas.
Ventanas del menú Debug

Windows.

La ventana Breakpoints muestra todos los breakpoints del programa, si es que existen.
La forma más cómoda de poner breakpoints es haciendo doble clic en el margen
izquierdo de la línea deseada. Con ello aparecerán las bolitas rojas que los
representan.
Los breakpoints son puntos de parada que no se dejan percibir cuando se recorre el
programa paso a paso con comandos como Step in o Step over. Pero sí frenan la
ejecución de la depuración cuando está en modo Run, que es iniciado por el
comando Continue..
La ventana Processor presenta información de los recursos asociados con el procesador
del AVR, como el Contador de Programa, el Puntero de Pila (Stack Pointer), el Registro
de Estado, losRegistros de Trabajo (R00 a R31) y los Punteros de RAM (X, Y y Z).
Adicionalmente brinda herramientas útiles como el Contador de Ciclos y el cronómetro
Stop Watch.
La ventana IO View muestra los Periféricos del AVR junto con todos sus Registros de
E/S, organizados correspondientemente en dos paneles. Por ejemplo, en la siguiente
figura el panel superior selecciona el módulo del puerto D y el panel inferior lista sus
registros relacionados, en este caso PIND, DDRD y PORTD.
Esta ventana también está disponible y ayuda muchísimo cuando se trabaja en modo
de diseño. La diferencia es que en modo de depuración es posible modificar el valor de
algunos de los Registros de E/S. Se podría cambiar, por ejemplo, el valor de los bits de
PIND para simular el efecto de un switch o pulsador conectado a dicho pin del AVR.
Las ventanas Locals y Autos visualizan las variables de programa. Son muy parecidas.
La ventana Locals muestra todas las variables de ámbito local de función actual. Por
ejemplo en la siguiente figura Locals presenta las
variables i, j, k, b, bi, ba, c y Effect porque actualmente se está ejecutando la
función main.
Por otro lado, la ventana Autos es más selectiva aún. Autos solo muestra las variables
locales involucradas en la operación actual. Por ejemplo, en la figura mostrada Autos
contiene a Effectporque está cerca la ejecución de la sentencia Effect = '1'; según lo
señala la flecha amarilla. El campo type señala el tipo de dato de la variable, con el
signo arroba @ indicando la localidad deRAM donde se ubican. El hecho de que en la
figura se muestre el mensaje de "unimplemented location" significa que las variables
indicadas no se encuentran en la RAM; Al examinar la ventana Regitstry (al final de
esta página) comprobaremos que se hallan implementadas en losRegistros de trabajo.
Las ventanas Watch (hay 4 en total) pueden desplegar cualesquiera variables del
programa, sin importar a qué ámbito pertenezcan (local o global).
Inicialmente Watch aparece vacía y la forma más fácil de colocar las variables en ella
es seleccionar la variable en el código y arrastrarla con el mouse hasta la ventana
Watch, más o menos como se ilustra en la siguiente imagen.
Las ventanas de Memoria no solo despliegan el contenido de las
memorias RAM, FLASH o EEPROM del AVR, sino también de los Registros de
Trabajo (R0 a R31) y de los Registros de E/Sviéndolos mapeados en la RAM.
La ventana Dissassembly muestra el programa en su correspondiente código
ensamblador. Es interesante ver cómo evoluciona la ejecución del programa en
lenguaje C y en ensamblador en paralelo.
La ventana Registry muestra exclusivamente todos los 32 Registros de Trabajo del AVR
(R00 a R31). Vemos en la siguiente figura que después de ejecutarse la
sentencia Effect = '1'; aparece resaltado de rojo el valor del registro R18. Dado que los
campos así resaltados corresponden a los datos que acaban de actualizarse deducimos
fácilmente que la variable local Effect se almacena en el registro R18.
Medición de Tiempos con Stop Watch
El Stop Watch y Cycle Counter se encuentran en la ventana Processor.

Introducción
Aprender a programar microcontroladores significa aprender a usar todos sus recursos
para luego aplicarlos en el diseño deseado. Es un proceso continuo, sistemático y que
demanda por sobre todo bastante paciencia.
En este capítulo empezaremos por conocer el hardware interno de los AVR
enfocándonos en los megaAVR de las series ATmegaXX8 yATmegaXX4, que son
actualmente los mejores microcontroladores de 8 bits de Atmel disponibles en
encapsulados DIP de 28 y 40 pines respectivamente. Las XX indican los kBytes de
memoria FLASH que lleva el megaAVR, por ejemplo, el ATmega168A tiene 16 kBytes
de FLASH.
Entre los miembros de estas familias podemos citar al ATmega48A, ATmega88P,
ATmega168PA, ATmega328P, ATmega164A, ATmega324P, ATmega644PA y
ATmega1284P.
Como se puede notar, al final de cada nombre todavía puede aparecer un sufijo
como A, P, V oPA, que identifica una característica adicional del megaAVR como ser de
una sub-serie que opera a bajo voltaje (V) o que están fabricados con tecnología
picoPower (P) para trabajar consumiendo menos energía. En algunas sub-series se han
corregido ciertos bugs encontrados en versiones anteriores del AVR. Esto no
necesariamente significa que tengan ser mejor, pues todos los microcontroladores de
todas las marcas suelen tener pequeños bugs que en la gran mayoría de aplicaciones
se pasan por alto. Quizá el único momento en que nos percatemos de la sub-serie
específica de nuestro mega AVR sea cuando lo identifique el software programador,
leyendo su código de identificación.
Por lo demás no debería preocuparnos la sub-serie del megaAVR. Los programas son
compatibles tanto en código fuente como en código máquina. Así que para facilitar la
lectura de aquí en adelante nos referiremos a estos megaAVR simplemente
como ATmegaXX4 oATmegaXX8, entendiendo que pueden tener o no el sufijo
explicado.
Existe mucha compatibilidad entre los microcontroladores de Atmel, por lo que la
teoría expuesta es aplicable en gran parte a otras series de AVR como los tinyAVR,
los megaAVR con USB o incluso a los AVR antiguos como los clásicos ATmega8535,
ATmega8515, ATmega16, ATmega32, etc. A decir verdad, los AVR que estudiaremos
son como las versiones mejoradas de los "clásicos" megaAVR citados anteriormente.

Características Comunes de los megaAVR
Citaremos las características más notables de los ATmegaXX8 y ATmegaXX4. Quizá
muchas de ellas no las comprendas de plano. Puedes tomar eso como referencia para
medir tu avance en el dominio del AVR.
Tienen un repertorio de 131 instrucciones. Están optimizadas para generar un mejor
código con los compiladores de alto nivel, en especial el C.
Poseen 32 Registros de Trabajo de 8 bits cada uno. Se denominan desde R0 a R31.
Esto en realidad es aplicable a todas las familias de los AVR de bits, incluyendo
los XMEGA.
Tienen una velocidad de ejecución de hasta 20 MIPS (20 Millones de Instrucciones Por
Segundo), que se alcanzará cuando el reloj del sistema (XTAL) sea de 20 MHz. Aunque
como en cualquier otro microcontrolador, en la práctica no será una velocidad
sostenida, porque en el programa habrá instrucciones que se demoran 2 ó más ciclos
de instrucción. Sin embargo, siguen siendo bastante rápidos si los comparamos por
ejemplo con sus contrapartes de Microchip, los PIC18, los cuales tienen un límite de 10
o 12 MIPS.
Tienen un Timer0 de 8 bits que puede trabajar como Contador o Temporizador o
Generador de hasta dos canales de ondas PWM de 8 bits de resolución.
Tienen un Timer1 de 16 bits que opera en modo Contador, Temporizador o como
Generador de hasta dos canales de ondas PWM con resolución configurable de hasta
16 bits.
Los ATmega1284 tienen adicionalmente un Timer3 idéntico al Timer1.
Tienen un Timer2 de 8 bits, parecido al Timer0 pero equipado adicionalmente para
operar con un XTAL externo de 32 kHz de modo asíncrono.
Tienen un Comparador Analógico.
Tienen un módulo TWI (Two Wire Interface) para comunicaciones con el protocolo
I2C en modos Maestro y Esclavo.
Tienen un módulo SPI programable. Soporta los modos Maestro y Esclavo. Sirve
además como la interface de programación serial del megaAVR.
Tienen un Conversor ADC de 10 bits, con hasta 8 canales de entrada.
Tienen un USART0: Puerto serie Transmisor Receptor Síncrono Asíncrono Universal.
Los ATmegaXX4 tienen adicionalmente un USART1, con características idénticas a las
delUSART0.
Operan con voltajes de alimentación entre 1.8V y 5.5V. Mientras más alta sea la
frecuencia de operación del AVR más alto será el nivel de alimentación requerido, por
ejemplo, para trabajar a la máxima frecuencia de 20 MHz, Vcc debe tener un valor
muy estable entre 4.5V y 5V.
Tienen un Oscilador RC interno configurable como oscilador principal del sistema.
Tienen 6 modos Sleep, para una mejor administración del consumo de la energía.
Tienen un circuito BOD o detector de bajo voltaje de alimentación.
Tienen un temporizador Watchdog, para vigilar que el programa no quede colgado.
Los megaAVR de la serie ATmegaXX8 tienen 3 puertos de E/S (con 23 pines en total) y
los megaAVR de la serie ATmegaXX4 tienen 4 puertos de E/S (con 32 pines en total).
Oscilador del sistema seleccionable, desde el Oscilador RC interno hasta cristales de
cuarzo.
Tienen un modo de programación paralela de alto voltaje HVPP (a 12V) y un modo de
programación serial en bajo voltaje SPI (a 5V). Los ATmegaXX4 pueden
adicionalmente ser programados por su interface de depuración JTAG. Por otro lado, la
interface debugWIREde los ATmegaXX8 no ofrece la función de programación con la
misma capacidad.
Tienen un sistema de depuración OCD con interface JTAG en el caso de
los ATmegaXX4 y con interface debugWIRE en el caso de los ATmegaXX8. En general,
la interface JTAG(compuesta por 4 pines) está disponible en todos los megaAVR de 40
pines o más y la interface debugWIRE (conformada por una sola línea) está presente
en los megaAVR con menos de 40 pines.
Su memoria de programa FLASH tiene una sección de Boot Loader para albergar un
programa cargador del mismo nombre. De los megaAVR estudiados en este curso
(entre los ATmegaXX4 y ATmegaXX8) el único que no ofrece esta funcionalidad es el
ATmega48. El programa de Boot Loader le permite al microcontrolador autoprogramarse sin necesidad de usar un programador externo como el USBasp o AVR
ISP MkII. Es así como trabajan los módulos Arduino, por ejemplo

Empaques de los megaAVR

Diagrama de pines de los ATmegaXX8 en encapsulado PDIP.
Diagrama de pines de los ATmegaXX4 en encapsulado PDIP.

Diagrama de bloques de los megaAVR
El siguiente diagrama muestra los principales elementos de un AVR y que tarde o
temprano los tendrás que memorizar.
Diagrama de bloques simplificado de los megaAVR ATmegaXX4 / ATmegaXX8.
Ahora una somera descripción de lo que representan estos bloques.
El CPU es el circuito encargado de leer, decodificar y ejecutar las instrucciones del
programa. Dispone de 32 registros de trabajo y un ALU (Unidad Aritmético Lógica) con
el que realiza las operaciones de suma, resta, AND lógica, OR lógica, etc.
La Memoria FLASH, de Programa almacena las instrucciones del programa del AVR. Es
una memoria permanente pero que se puede reprogramar para cambiar de tarea.
La Memoria RAM, de Datos aloja las variables que procesa el CPU.
El Contador de Programa es un registro que evoluciona para indicar cuál será la
siguiente instrucción que debe ejecutar el CPU.
La Pila o Stack es un segmento de la memoria RAM para guardar el valor del Contador
de Programa y también variables temporales del programa cuando sea necesario.
Los periféricos del AVR son elementos que se pueden usar para una determinada
tarea; por ejemplo, el Timer0 sirve para temporizaciones. El USART para
comunicaciones serialesRS232, etc. Casi todos ellos serán estudiados en un capítulo
aparte.
Los puertos de E/S, PORTA,..., PORTD, son las líneas hacia/desde el exterior donde se
pueden conectar los dispositivos a controlar, como diodos LED, transistores, LCDs, etc.
Los megaAVR de 40 pines tienen los 4 puertos completos, mientras que a los megaAVR
de 28 pines les falta PORTA y algunos pines en los otros puertos.
Hay más recursos presentes dentro de un AVR que también son imprescindibles pero
cuyo trabajo queda en segundo plano. Algunos de ellos serán abordados en otro
momento.

La Memoria de Programa
Es de tipo FLASH. Aquí es donde se aloja el programa que el CPU ejecutará. Se puede
modificar por completo mediante un dispositivo programador por hasta 10 000 veces.
Pero tampoco deberíamos tanto. No conozco a nadie que haya llegado a ese límite con
un solo microcontrolador. Lo más probable es que, por más cuidado que tengas,
llegues a freír tu AVR antes de tiempo en algún accidente. Eso es algo muy “normal”.
En los AVR las instrucciones de programa son de 16 ó de 32 bits. Pero siendo la gran
mayoría de 16 bits, podemos decir que un AVR de N bytes de memoria FLASH puede
almacenar hasta N/2 instrucciones de código ensamblador.
Antiguamente parecía sencillo reconocer la cantidad de memoria FLASH que tenían
algunos AVR. Por ejemplo, un ATmega32 tiene 32 k-bytes, un ATmega8L tiene 8 kbytes. Los megaAVR de ahora todavía conservan esa correspondencia entre el número
que aparece en su nombre y la cantidad de k-bytes de FLASH, solo que las nuevas
series también llevan un número que puede entrar a confundir un poco.
En la siguiente tabla apreciamos los AVR de las
series ATmegaXX8, ATmegaXX4, ATmegaXX5 yATmegaXX50. Las XX representan la
cantidad de FLASH de megaAVR. Al final de cada nombre aparece el sufijo yy
representando a las letras P, V, A o PA.
Personalmente, creo que al haber varias series, es más fácil separar primero los
números que representan la capacidad de FLASH porque deben ser números redondos
(digitalmente hablando), es decir, deben ser potencias de 2, como 4, 8, 16, 32, 64 ó
128. Por ejemplo, ¿cuánta memoria FLASH tendrá un ATmega3290P?, ¿3290 kbytes,
329 kbytes, 32 kbytes o 3 kbytes? Como el único número redondo es 32, la respuesta
es 32 kbytes y deducimos que este megaAVR es de la serie 90P. Se llega más rápido a
la respuesta si leemos las opciones empezando por la izquierda.
Tabla AVR

AVR

Memoria FLASH Memoria RAM Memoria EEPROM Pines de E/S

ATmega48yy

4K

512

256

23

ATmega88yy

8K

1K

512

23

ATmega168yy

16 K

1K

512

23
Tabla AVR

AVR

Memoria FLASH Memoria RAM Memoria EEPROM Pines de E/S

ATmega328yy

32 K

2K

1K

23

-

-

-

-

-

ATmega164yy

16 K

1K

512

32

ATmega324yy

32 K

2K

1K

32

ATmega644yy

64 K

4K

2K

32

ATmega1284yy

128 K

16 K

4K

32

-

-

-

-

-

ATmega165yy

16 K

1K

512

54/69

ATmega325yy

32 K

2K

1K

54/69

ATmega3250yy

32 K

2K

1K

54/69

ATmega645yy

64 K

4K

2K

54/69

ATmega6450yy

64 K

4K

2K

54/69

Secciones de Aplicación y de Boot Loader
Los AVR ofrecen la posibilidad de escribir en su memoria de programa FLASH incluso
en tiempo de ejecución. Esta función puede ser aprovechada para almacenar datos
procesados por el usuario o para permitir la auto-programación del AVR.
Para facilitar y robustecer el proceso de auto-programación los AVR dividen su
memoria FLASH en dos segmentos lógicos, de Aplicación y de Boot Loader.
La Sección de Aplicación está destinada a almacenar el programa que el AVR ejecuta
habitualmente, como leer sensores, controlar motores, etc.
La Sección de Boot Loader está diseñada para almacenar el código del Boot loader, que
es un pequeño programa para cargar el programa del AVR en la Sección de Aplicación,
así como Windows carga en la RAM de la PC el programa que vamos a utilizar. Un Boot
Loader no es precisamente un mini S.O. porque ya hay S.O. para microcontroladores
conocidos como RTOS(Real Time Operating System).
Además, a diferencia de un S.O. para PC, un Boot Loader debe ser el programa más
pequeño posible y se debe ubicar al final de la memoria FLASH. No todos los megaAVR
tienen Sección de Boot Loader, como el ATmega48.

Secciones de Aplicación y de Boot Loader de la memoria de programa del AVR.
La Sección de Boot Loader siempre se ubica al final de la memoria FLAH pero su
tamaño varía de acuerdo con el AVR y con la configuración establecida por los
fuses BOOTSZ1 y BOOTSZ0. Por ejemplo, el ATmega644PA puede tener una Sección
de Boot Loader entre 512 palabras y 4096 palabras, según la siguiente tabla.
Tabla BOOTSZ1

BOOTSZ1 BOOTSZ0 Tamaño del Boot Loader Dirección del Boot Loader
1

1

512 palabras

0x7E00 - 0x7FFF

1

0

1024 palabras

0x7C00 - 0x7FFF
Tabla BOOTSZ1

BOOTSZ1 BOOTSZ0 Tamaño del Boot Loader Dirección del Boot Loader
0

1

2048 palabras

0x7800 - 0x7FFF

0

0

1096 palabras

0x7000 - 0x7FFF

Los fuses BOOTSZ1 y BOOTSZ0 solo se pueden modificar al grabar el AVR. Su
configuración por defecto siempre establece el tamaño mayor elegible de la Sección de
Boot Loader.
El hecho de que la Sección de Boot Loader esté diseñada para alojar el programa
cargador no significa que esté limitada a esa tarea. Si no la vamos a usar para su
propósito primigenio, podemos emplearla como si fuera parte de la Sección de
Aplicación, o sea, como si no existiera la división entre estas dos Secciones y por ende
no tendrá importancia el valor que pongamos en los fuses BOOTSZ1 y BOOTSZ0.
Configuración de los Fuses de Boot loader en el programa grabador.
A propósito, la configuración de los fuses BOOTSZ1 y BOOTSZ0 en Proteus no tiene
ningún efecto porque Proteus aún no soporta simulaciones con Boot Loader. Así que
están como decorativos.

Configuración de los Fuses de Boot Loader en Proteus.

La Memoria de Datos SRAM
Actualmente suena redundante especificar memoria SRAM (Static RAM) porque casi
todas las RAM de los microcontroladores son estáticas. Decimos simplemente RAM, a la
memoria cuya función “tradicional” es alojar temporalmente los datos que se procesan
en el programa.
La cantidad de RAM disponible internamente depende del modelo de AVR y, a
diferencia de la memoria FLASH, no tiene una directa correspondencia con el nombre
del dispositivo. Aun así, podemos observar en la siguiente tabla que en muchos
modelos existe una relación que se repite (los ATmega128nn rompen la relación, y
pueden no ser los únicos). Los megaAVR con 4K de FLASH tienen 512 bytes de RAM,
los megaAVR con 8 K y 16 K de FLASH tienen 1 K de RAM, y así, tal como se ve en la
tabla.
Tabla AVR

AVR

Memoria FLASH Memoria RAM Algunos Modelos

ATmegaNNN 4 K

512

ATmega48A, ATmega48PA,
ATmega48P/V, ATmega48/V

ATmegaNNN 8 K

1K

ATmega88A, ATmega88PA,
ATmega88P/V, ATmega88/V

1K

ATmega168A, ATmega168PA,
ATmega168P/V, ATmega168/V
ATmega164A, ATmega164PA
ATmega165A, ATmega165PA
ATmega169, ATmega169P

2K

ATmega328, ATmega328P
ATmega324A, ATmega324PA
ATmega325A, ATmega325PA
ATmega3250A, ATmega3250PA
ATmega329P, ATmega3290P

ATmegaNNN 64 K

4K

ATmega644A, ATmega644PA
ATmega645A, ATmega645P
ATmega6450A, ATmega6450P

ATmegaNNN 128 K

4K, 8K o 16K

ATmega128, ATmega1281
ATmega1284, ATmega1284P

ATmegaNNN 16 K

ATmegaNNN 32 K

En los modelos listados y en general en todos los megaAVR de las series más recientes
el espacio de la memoria RAM (entendida en su concepto tradicional) empieza en la
dirección0x0100 y termina en 0x02FF, 0x04FF, 0x08FF, 0x10FF, 0x20FF o 0x40FF,
según el modelo. La verdad, no importa mucho saber dónde termina como la cantidad
misma. La dirección de inicio sí es de consideración pero cuando se programa en
lenguaje ensamblador.
Quizá alguien pudiera preguntar por qué la dirección de inicio no es 0x0000, como en
otros microcontroladores. Porque las primeras direcciones, desde 0x0000 hasta
0x00FF, están reservadas para acceder a los Registros de Trabajo y a los Registros de
E/S del AVR en „modo de memoria‟.
Normalmente los 32 Registros de Trabajo se acceden directamente con instrucciones
como LDI o MOV. Pero también se les puede acceder direccionándolos como si fueran
parte de la memoria RAM, con instrucciones como LD o ST y con las direcciones
presentadas. En ocasiones esto facilitará mover bloques de datos entre la RAM y los
registros de trabajo aprovechando la potencia de los punteros.
Del mismo modo, los Registros de E/S, que tampoco ocupan posiciones reales en la
RAM, pueden ser accedidos como si en verdad fueran RAM con las instrucciones como
LD y ST, para lo cual emplean las direcciones mostradas en la figura.
Los Registros de E/S y los Registros de E/S extendidos son hermanos, por decirlo de
algún modo, y tienen funciones análogas. Están separados solo por tener diferente
modo de acceso, pero eso se explicará mejor en su sección respectiva.
El direccionamiento y distinción de los espacios de la RAM solo son de preocupación al
trabajar en ensamblador. Al programar en lenguajes de alto nivel los compiladores se
encargan de toda la administración de la RAM, salvo que reciban directivas avanzadas.
Espacio de la Memoria RAM del megaAVR.

El Contador de Programa, PC
El PC es un registro que indica la siguiente instrucción que debe ejecutar el CPU. Si
vale 0x0000, ejecutará la primera instrucción de la memoria; si vale 0x0002 ejecutará
la tercera instrucción, y así... Al arrancar microcontrolador, el PC vale 0x0000 y se va
incrementando automáticamente, con lo que el AVR debería ejecutar una a una desde
la primera hasta la última instrucción del programa. En realidad, en el código habrá
instrucciones que modifiquen el valor del PC de modo que el programa nunca termine.

La Pila y el Puntero de Pila
La Pila o STACK es una memoria que almacena temporalmente el valor
del PC (Program Counter) cuando el programa llama a una subrutina o cuando salta a
un Vector de Interrupción. También sirve para guardar datos temporalmente cuando
los 32 Registros de Trabajo no sean suficientes.
Al igual que en una PC, la Pila forma parte de RAM Interna. No es un pedazo de RAM
con características especiales, es una simple área cuya dirección de inicio la puede
establecer el usuario y cuyo tamaño es indefinido porque crece y decrece en tiempo de
ejecución.
Las que sí son especiales son las instrucciones que trabajan con la Pila,
como PUSH y POP. Estas instrucciones no necesitan conocer la locación en la Pila a/de
donde guardarán/recuperarán los datos. Aprovechan, en cambio, su acceso de
tipo LIFO (Last In First Out), que significa “el último dato en entrar será el primero en
Salir”.
Es por eso que siempre se recurre a la analogía con una pila de platos de donde no
podemos tomar un plato que se encuentra en el fondo o en la mitad. Para llegar a él
primero tendríamos que quitar los platos que están encima.
Pero hasta ahí llega la analogía porque a diferencia de las pilas de platos, las pilas en
RAM crecen de arriba abajo, es decir, cada dato que se va depositando en la Pila ocupa
una dirección inferior a la anterior. Esta dirección la va marcando el Puntero de Pila, el
cual se decrementa cada vez que se coloca un dato en la Pila y se incrementa cada vez
que se toma un dato de ella.
La Pila trabaja de cabeza para evitar que sus datos colisionen (se solapen) con las
variables accedidas aleatoriamente, las cuales se van mapeando en la RAM
normalmente de abajo arriba.
Registro SPH

SPH

SP15

SP14

SP13

SP12

SP11

SP10

SP9

SP8

SP7

SP6

SP5

SP4

SP3

SP2

SP1

SP0

Registro SPL

SPL

El Puntero de Pila está representado por los registros de E/S SPH y SPL, que
concatenados actúan como un registro de 16 bits. Como se dijo anteriormente, este
registro tiene un comportamiento de auto-incremento y auto-decremento. La única
intervención por parte del usuario debería ser su inicialización, esto es, cargarle la
última dirección de la RAM. Pero esta operación solo es indispensable al programar en
ensamblador. Los compiladores como el C inicializan la Pila automáticamente. Sin
embargo, incluso en C es importante conocer estos conceptos para entender por
ejemplo por qué un programa para un ATmega328P nunca funcionará en un
ATmega168P, incluso si el tamaño del código es pequeño y le puede caber
sobradamente.

Los Registros de Trabajo y los Punteros X, Y y Z
Todos los AVR de 8 bits, desde los tinyAVR hasta los XMEGA cuentan con 32 Registros
de Trabajonombrados desde R0 hasta R31. Los Registros de Trabajo tienen la función
de alojar los datos más inmediatos que el CPU procesa. ¿Acaso ésa no era tarea de la
RAM?.
Bueno, sucede que en todos los microcontroladores inspirados en la arquitectura de los
procesadores de Intel (como los AVR, ARM y Freescale entre otros) el acceso a la
memoria RAM toma más ciclos que el acceso a los Registros de Trabajo.
En los AVR de 8 bits, por ejemplo, se puede acceder a los Registros de Trabajo en un
solo ciclo, puesto que todos están directamente conectados al CPU, o mejor dicho, son
parte del CPU. En cambio, la mayoría de las instrucciones ensamblador que acceden a
la RAM consumen 2 ciclos de instrucción.
No es posible cargar datos en la RAM directamente ni moverlos entre locaciones
diferentes de la RAM (a menos que tengan DMA, como los AVR32). Para esas
operaciones los Registros de Trabajo actúan como intermediarios.
Pero quizá la participación más notable de Los Registros de Trabajo sea en el ALU
(Unidad Aritmético Lógica) para computar las operaciones aritméticas y lógicas. Por
ejemplo imaginemos que deseamos obtener la raíz cuadrada de un número de punto
flotante ubicado en la RAM y almacenar el resultado de nuevo en la RAM. En lenguaje
C bastaría con escribir una sola sentencia, pero el código máquina generado involucra
una tarea más compleja: al inicio el CPU mueve los 4 bytes del número desde la RAM a
los Registros de Trabajo, luego viene el trabajo pesado, que implica el procesamiento
de varios datos intermedios. En lo posible todos estos datos también estarán en los
registros de trabajo para aprovechar su velocidad y eficacia. Solo al terminar el
cómputo el CPU depositará el resultado en la RAM.
Los compiladores de alto nivel también suelen emplear los Registros de Trabajo para
pasar los argumentos de sus funciones. Creo que con esos ejemplos debe quedar clara
la razón de ser de los Registros de Trabajo.
En la siguiente figura podemos notar que los Registros de Trabajo se parten por la
mitad. La diferencia está en que los primeros 16 registros (R0 a R15) no admiten la
instrucción LDI, que sirve para cargar constantes al registro (otro aspecto primordial
de la programación en ensamblador9. Los registros R26 a R31 tienen la capacidad
adicional de funcionar como punteros de 16 bits cada uno.
Los Registros de Trabajo de los AVR.
El par de registros R27-R26 forma el Puntero X, el par R29-R28 forma el Puntero Y, y
el parR31-R30 forma el Puntero Z.
Los punteros pueden apuntar a (contener la dirección de) cualquier locación del
espacio de RAM. Esto junto con las instrucciones adecuadas conforman el
direccionamiento indirecto más potente, muy útil por ejemplo para mover grandes
bloques de datos.
Terminamos esta sección explicando las direcciones que figuran en el mapa de los
Registros de Trabajo. En principio a los Registros de Trabajo no les debería hacer falta
tener direcciones porque están directamente unidos al CPU. Hay instrucciones
adecuadas como LDI y MOV para acceder a ellos. Sin embargo, los AVR les brindan
direcciones para adicionalmente poder ser accedidos como si fueran parte de la RAM,
es decir, con instrucciones que están diseñadas para la RAM, como LD y ST. De esta
manera se hacen más flexibles las operaciones de transferencias de datos entre los
diferentes espacios de memoria.
Cuando manipulamos los Registros de Trabajo utilizando sus direcciones podemos
decir que los estamos accediendo “en modo RAM”, pero sin perder de vista que los
Registros de Trabajo no pertenecen a la RAM porque no están implementadas
físicamente allí.

Los Registros de E/S
Anteriormente se dijo que para programar el AVR primero había que conocer sus
recursos. Pues bien, todos ellos se pueden controlar mediante los Registros de E/S. por
ejemplo, si queremos manejar el puerto B, debemos conocer los registros PORTB,
DDRB y PINB. Si queremos programar el USART0, debemos conocer los
registros UDR0, UCSR0A, UCSR0B, UCSR0C,UBRR0L y UBRR0H. Si queremos…
En tiempo de ejecución los registros de E/S (Entrada Salida) lo controlan todo, no solo
las operaciones de los módulos periféricos, como se podría inferir a partir de la
denominación E/S, sino que también controlan la performance del mismo CPU. En este
caso los registros a conocer serían MCUCR, MCUSR o SMCR.
Entenderás que no tiene caso seguir mencionando las funciones de otros Registros de
E/S. es por eso que cada módulo se trata por separado estudiando con detenimiento
cada registro y cada uno de los bits que lo componen.
Espero que los mapas de memoria de los Registros de E/S que presento no te hagan
pensar que están divididos en una suerte de bancos, como en los PICmicro. No, señor,
nada de eso. Todos los espacios de memoria en los AVR son lineales. Yo los quise
subdividir para una mejor presentación.
Curso micros
Cada registro es de 8 bits y en total se cuentan 224 registros de E/S, aunque ni
siquiera la mitad están implementados físicamente. Las locaciones que aparecen sin
nombre estánRESERVADAS y no nunca deberían ser accedidas.
De modo similar, hay registros con algunos bits sin nombre ni funcionalidad que
también se consideran reservados. Un ejemplo es el registro SMCR, mostrado abajo.
No está prohibido acceder a dichos bits pero se recomienda dejarlos en 0 por
compatibilidad con futuros AVR. Estas aclaraciones aparecen por doquier en los
datasheets. Yo prefiero decirlas ahora para no tener que estar repitiéndolo a cada rato
después.
Registro SMCR

SMCR

---

---

---

---

SM2

SM1

SM0

SE

Los antiguos microcontroladores AVR solo tenían el espacio de los 64 primeros
Registros de E/S. A ellos les alcanzaba ese espacio aunque a veces a duras penas
porque todos los registros estaban allí apretujados. Los Registros de E/S tenían sus
propias instrucciones de acceso, IN y OUT, que de hecho todavía se usan. Las
instrucciones de ensamblador IN y OUT utilizan el rango de direcciones 0x00 a 0x3F
para acceder a los Registros de E/S. En el esquema mostrado estas direcciones
aparecen fuera de los paréntesis.
Con la aparición de nuevos periféricos en los AVR, aumentaron los registros de E/S y
se sobrepasaba el alcance de las instrucciones IN y OUT. Así fue necesario diseñar los
Registros de E/S para que también pudieran ser direccionados como si fueran parte de
la RAM, o sea, con instrucciones como LD o ST, las cuales tienen mayor cobertura y
permiten repotenciar las transferencias de datos. Para este tipo de acceso, llamado “en
modo RAM”, las instrucciones como LD o ST utilizan el rango de
direcciones 0x20 a 0xFF, que en los mapas de memoria aparecen dentro de los
paréntesis.
A juzgar por sus direcciones, ya podrás deducir que la única diferencia entre
los Registros de E/S(estándar) y los Registros de E/S Extendidos es que estos últimos
se acceden exclusivamente en “modo de memoria” con instrucciones como LD y ST, y
los primeros todavía admiten el uso de las instrucciones clásicas como IN y OUT.
El direccionamiento y distinción de los Registros de E/S estándar o extendidos son de
especial preocupación al trabajar en ensamblador. Al programar en lenguajes de alto
nivel loscompiladores son quienes escogen el modo de acceso y las instrucciones que
consideren más convenientes, salvo que reciban directivas contrarias.

Registros Generales del Microcontrolador
Estos son registros de E/S que no están relacionados con una parte exclusiva del
microcontrolador. Por tanto los volveremos a ver al mencionar en otros capítulos,
aunque sea para referirnos a uno solo de sus bits.

El Registro de Estado SREG
Todo microprocesador tiene un registro para reflejar el estado de las operaciones
lógicas y aritméticas del módulo ALU. SREG también incluye dos bits con funciones
disímiles.
El registro SREG es bastante utilizado en los programas en ensamblador. Cuando se
trabaja con compiladores de alto nivel su presencia solo sería justificable por el uso del
bit I.
Registro SREG

SREG I

T

H

S

V

N

Z

C

Registro de Microcontrolador

I

Global Interrupt Enable
1. Habilitar las interrupciones individuales habilitadas. Las interrupciones individuales serán
habilitadas en otros registros de control.
0. No se habilita ninguna de las interrupciones sin importar sin importar sus
configuraciones individuales.
El bit I se limpia por hardware después de ocurrir una interrupción, y se setea por la
instrucción para habilitar posteriores interrupciones. El bit I también se puede setear o
limpiar por la aplicación con las instrucciones SEI y CLI.

T

Bit Copy Storage
Las instrucciones para copiar bits (Bit LoaD) y BST (Bit STore) usan el bit T como inicio o
destino para el bit de la operación. Con la instrucción BST se puede copiar un bit de un
registro de trabajo al bit T y con la instrucción BLD se puede copiar el bit T a un bit de un
registro de trabajo.
H

Half Carry Flag
El flag H indica un medio acarreo en algunas operaciones aritméticas. El bit H es muy útil
en aritmética BCD.

S

Sign Bit, S = N ? V
El bit S es siempre un or exclusivo entre los flags N y V. Leer abajo.

V

Two’s Complement Overflow Flag
El flag V soporta aritmética de complemento a dos.

N

Negative Flag
El flag N indica un resultado negativo en una operación lógica o aritmética.

Z

Zero Flag
El flag Z indica un resultado cero en una operación lógica o aritmética.

C

Carry Flag
El flag C indica un acarreo en una operación lógica o aritmética.

El Registro MCUCR
MCUCR = MCU Control Register. MCU a su vez significa Micro Controller Unit.
Aunque probablemente de este registro solo vayamos a usar el bit PUD, es bueno
conocerlo ahora aprovechando que hace referencia a muchas de las características del
microcontrolador que se estudiaron en este capítulo. Ampliaremos la funcionalidad del
bit PUD en el capítulo deentrada y salida generales y los bits IVSEL junto
con IVCE serán mejor expuestos en el capítulo de interrupciones.
Registro MCUCR

MCUCR JTD BODS

BODSE

PUD

---

---

IVSEL

IVCE

Registro de Microcontrolador

JTD

JTAG Interface Disable
0. La interface JTAG estará habilitada si está programado el fuse JTAGEN.
1. la interface JTAG está deshabilitada.
Para evitar deshabilitaciones no intencionadas de la interface JTAG, se debe seguir
una secuencia especial para cambiar este bit: el software de aplicación debe escribir
este bit dos veces el valor deseado dentro de cuatro ciclos de reloj. Este bit no se
debe alterar cuando se esté usando el sistema OCD (On Chip Debug)
BODS

BOD Sleep
Sirve para deshabilitar el circuito del BOD durante el modo Sleep, si estaba activo.
Recuerda que se activa por los fuses BODLEVEL2-0.
Para poner 1 en el bit BODS se requiere seguir una secuencia especial que involucra
el bit BODSE. Primero se escribe 1 en los bits BODS y BODSE. Luego se debe escribir
1 en el bit BODS y 0 en el bit BODSE dentro de los siguientes cuatro ciclos de reloj.
El bit BODS estará activo durante tres ciclos de reloj después de haberse seteado.
Para apagar el circuito BOD para el modo Sleep actual se debe ejecutar la
instrucción Sleep mientras el bit BODS esté activo. El bit BODS se limpia
automáticamente después de tres ciclos de reloj.

BODSE

BOD Sleep Enable
El bit BODSE habilita la configuración del bit de control BODS, como se explicó en la
descripción del bit BODS.

PUD

Pull-up Disable
1. Se deshabilitan las pull-up de todos los pines de los puertos, sin importar el valor
de los registros DDRx y PORTx.
0. Las pull-up se habilitaran por los registros DDRx y PORTx.

IVSEL

Interrupt Vector Select
0. Los Vectores de Interrupción se ubican en el inicio de la memoria FLASH.
1. Los Vectores de Interrupción se mueven al inicio de la Sección de Boot Loader de
la memoria FLASH.
Para evitar cambios no intencionados en este bit se debe seguir una secuencia
especial:
a. Escribir 1 en el bit IVCE.
b. Dentro de los cuatro ciclos de reloj siguiente, escribir el valor deseado en el
bit IVSEL mientras se escribe un 0 en el bit IVCE.
Las interrupciones se deshabilitarán automáticamente durante la ejecución de esta
secuencia. Las interrupciones se deshabilitan en el ciclo en que se setea el bit IVCE y
permanecen deshabilitadas hasta después de la instrucción que escribe el bit IVSEL.
Si no se escribe el bit IVSEL, las interrupciones permanecen deshabilitadas por
cuatro ciclos de reloj. El bit I en el registro de estado SREGno se afecta por la
deshabilitación automática.
Nota: si los Vectores de Interrupción están colocados en la Sección de Boot Loader, y
el bit de candado BLB02 está programado, las interrupciones se deshabilitan
durante la ejecución del programa desde la Sección de Boot Loader.
IVCE

Interrupt Vector Change Enable
Se debe escribir 1 en el bit IVCE para habilitar el cambio del bit IVSEL. El bitIVCE se
limpia por hardware cuatro ciclos después de que se haya escrito o cuando se
escribe el bit IVSEL. Setear el bit IVCE deshabilitará las interrupciones, como se
explicó en la descripción del bit IVSEL.

El Registro MCUSR
MCUSR = MCU Status Register. Es el registro de estado del microcontrolador, MCU.
MCUSR está conformado por bits de Flag que sirven para identificar la causa del reset
del microcontrolador. Para averiguar la fuente de reset el programa debe leer el
registro MCUSR tan pronto como sea posible y luego limpiar sus Flags.
Observa que a diferencia de los Flags de las Interrupciones, los Flags de MCUSR se
limpian escribiendo un cero y no un uno.
Registro MCUSR

MCUSR ---

---

---

JTRF

WDRF

BORF

EXTRF

PORF

Registro de Microcontrolador

JTRF

JTAG Reset Flag
Este bit se pone a uno si se produce un reset por un uno lógico en el Registro JTAG
Reset seleccionado por la instrucción de JTAG AVR_RESET. Este bit se pone a cero
por un Reset POR, o escribiendo un cero lógico en el flag.

WDRF

Watchdog Reset Flag
Este bit se pone a uno cuando se produce un Reset por el Watchdog. Este bit se
pone a cero por un reset Power-on o escribiendo un cero lógico en el flag.

BORF

Brown-out Reset Flag
Este bit se pone a uno cuando se produce un Reset Brown-out. Este bit se pone a
cero por un reset Power-on o escribiendo un cero lógico en el flag.
EXTRF

External Reset Flag
Este bit se pone a uno cuando se produce un Reset Externo. Este bit se pone a cero
por un reset Power-on o escribiendo un cero lógico en el flag.

PORF

Power-on Reset Flag
Este bit se pone a uno cuando se produce un Reset Power-on. Este bit se pone a
cero escribiendo un cero lógico en el flag.

Los Fuses de los AVR
Los fuses del microcontrolador establecen una característica importante en su
operación, tanto que solo se puede modificar en el momento de programarlo. Por
ejemplo, no se podría escoger el tipo de oscilador a usar después de haber iniciado el
programa. Sería como cambiarle los neumáticos a un automóvil en marcha. Cada
aplicación puede requerir una configuración particular y si no se establecen los fuses
correctos, el programa puede funcionar mal, suponiendo que funcione. :)
A diferencia de los PICmicro, en los AVR los fusesno pueden formar parte del código
HEX, así que siempre será necesario configurarlos directamente en el entorno del
programa grabador de AVR. Los fuses sí pueden incluirse en los archivos de depuración
como ELF, para simulaciones o depuraciones.
Por otra parte, y esto es para bien, hay muy poca variación en los fuses de los AVR,
inclusive de distintas series. Por eso serán de fácil recordación, en contraste con los
fuses de los PICmicro, donde son casi in-memorizables. La comparación es siempre
con los PIC18, por supuesto, porque los PIC16 en general no están a la altura.
Los fuses están contenidos en los denominados Bytes de Fuses, que son registros (de
8 bits obviamente) implementados en EEPROM (no en la EEPROM de datos de AVR).
Por eso un 1 es el valor por defecto cuando un bit no está programado.
Los megaAVR más recientes tienen 3 Bytes de Fuses llamados Byte de Fuses
Bajo, Alto yExtendido. El Byte de Fuses Bajo es el mismo en todos ellos. Los otros
empiezan a cambiar un poco, en orden más que en funciones, según cada modelo en
particular. Mientras más reciente sea la serie, se encontrarán menos divergencias.
El conjunto de Bytes de Fuses mostrado a continuación pertenece a los AVR de la
serieATmegaXX4. En otras series pueden aparecer, desaparecer o cambiar algunos
fuses, por ejemplo los AVR de la serie ATmegaXX5 tienen adicionalmente el
fuse RSTDISBL. En lo sucesivo se describirán las funciones de los fuses más conocidos,
no solo los presentados aquí.
Tabla Byte de Fuses Bajo

Byte de Fuses Bajo Bit Descripción

Valor por Defecto

CKDIV8

7

Divide clock by 8

0 (programado)

CKOUT

6

Clock output

1 (sin programar)

SUT1

5

Select start-up time

1 (sin programar)

SUT0

4

Select start-up time

0 (programado)

CKSEL3

3

Select Clock source

0 (programado)

CKSEL2

2

Select Clock source

0 (programado)

CKSEL1

1

Select Clock source

1 (sin programar)

CKSEL0

0

Select Clock source

0 (programado)

Tabla Byte de Fuses Alto

Byte de Fuses Alto Bit Descripción

Valor por Defecto

OCDEN

7

Enable OCD

1 (sin programar)

JTAGEN

6

Enable JTAG

0 (programado)

SPIEN

5

Enable Serial Programming

0 (programado)

WDTON

4

Watchdog Timer always on

1 (sin programar)

EESAVE

3

EEPROM memory is preserved 1 (sin programar)

BOOTSZ1

2

Select Boot Size

0 (programado)

BOOTSZ0

1

Select Boot Size

0 (programado)

BOOTRST

0

Select Reset Vector

1 (sin programar)
Tabla Byte de Fuses Extendido

Byte de Fuses Extendido Bit Descripción

Valor por Defecto

–

7 –

1

–

6 –

1

–

5 –

1

–

4 –

1

–

3 –

1

BODLEVEL2

2 Brown-out Detector trigger level 1 (sin programar)

BODLEVEL1

1 Brown-out Detector trigger level 1 (sin programar)

BODLEVEL0

0 Brown-out Detector trigger level 1 (sin programar)

Pongo en relieve estos detalles porque hay muchos programadores, como el
famoso AVRDUDE, donde los fuses se configuran bit a bit al momento de grabar el
AVR. ¿Cómo haríamos si tuviéramos que configurar el uso de un oscilador de XTAL de
16 MHz en un ATmega324P? Primero tendríamos que conocer los fuses
correspondientes, ¿cierto? En este caso son CKSEL0,CKSEL1, CKSEL2 y CKSEL3. Luego
tendríamos que descubrir cuáles son los bits que corresponden a esos fuses y la
combinación correcta para un XTAL de 16MHz. Lamentablemente la posición de los bits
de fuses suele variar entre los distintos ejemplares de AVR, cosa que complica la labor
en entornos donde debemos establecer el código de los fuses en valor hexadecimal. En
la siguiente imagen se muestra la grabación de los fuses desde la línea de comandos
de AVRDUDE.
Grabación de los fuses desde AVRDUDE.
AVRDUDE es muy bueno pero no tiene un entorno gráfico. Se le han desarrollado
algunas máscaras que facilitan su uso como SinaProg o AVR Burn-O-Mat pero no son
del todo convincentes.
También existen programadores con software de mejor interface. Los mejores son
desde luego los mismos productos de Atmel como el programador STK500 que es
comandado desde Atmel Studio 6 como se ilustra abajo. Allí vemos que podemos
configurar los fuses de forma individual simplemente marcando las casillas
correspondientes. Los cambios hechos se reflejarán en los Bytes de Fuses mostrados
en el panel inferior. También es posible modificar los fuses directamente desde allí.

Configuración de los fuses desde Atmel Studio 6.
A propósito de la pregunta previa, la siguiente toma nos indica que hay hasta 8
opciones para usar un XTAL mayor de 8MHz. Podrías elegir cualquiera, a menos que
sepas lo que significan los retardos de reset y de arranque, y estés seguro de que
necesitas uno de ellos. Por supuesto que examinaremos todos estos aspectos más
adelante.

Configuración de los fuses de reloj desde Atmel Studio 6.

CKSEL3-0. Selección del Reloj
Este fuse se representa por los bits CKSEL3, CKSEL2, CKSEL1 y CKSEL0. Sirve para
adaptar el circuito interno del oscilador según el componente externo o interno que se
usará como fuente del reloj del sistema.

Reloj Externo. En este caso la fuente de reloj será una señal de onda cuadrada externa
aplicada al pin XTAL1 del AVR. Su frecuencia podrá estar en todo el rango posible,
desde 0 hasta 20 MHz.
Se establece con los bits CKSEL3-0 = 0000, respectivamente.
Oscilador RC Interno Calibrado. Con esta opción no se necesitarán añadir
componentes externos. El reloj será el oscilador RC Interno, el cual tiene una
frecuencia cercana a 8MHz, y adicionalmente ofrece al diseñador la posibilidad de
ajustar por software dicha calibración hasta en 1%. Es útil para sistemas de bajo costo
aunque de menor nivel de estabilidad. Es la opción por defecto.
Se establece poniendo los bits CKSEL3-0 = 0010, respectivamente.
XTAL Externo de Baja Frecuencia. Para utilizar un XTAL externo de 32.768 kHz con
capacitores de estabilización opcionales. Es una configuración diseñada para
aplicaciones de reloj.
En los ATmegaXX8 y ATmegaXX4 se establece poniendo los bits CKSEL30 = 0100 o 0101. En otros megaAVR puede variar.
Oscilador RC Interno de 128kHz. Este oscilador es parecido al RC de 8MHz, solo que
ofrece menor precisión y no se puede calibrar.
En los ATmegaXX8 y ATmegaXX4 se establece con los bits CKSEL3-0 iguales a 0011.
En otros megaAVR puede no estar disponible.
XTAL Externo de Baja Potencia. Esta configuración soporta cristales de 0.9 MHz hasta
16 MHz. Estos cristales ahorran energía pero su uso es recomendado solo en
ambientes libres de ruido. El XTAL se coloca entre los pines XTAL1 y XTAL2 del AVR y
debe usar capacitores externos de entre 12pf y 22pf, similar al circuito con XTAL
estándar de alta frecuencia.
En los ATmegaXX8 y ATmegaXX4 los valores de los bits CKSEL3-0 dependerán del
rango de frecuencia del XTAL, según la siguiente tabla.
Tabla Rango del XTAL en MHz

Rango del XTAL en MHz CKSEL3-0
0.9 - 3.0

1011 o 1010
Tabla Rango del XTAL en MHz

Rango del XTAL en MHz CKSEL3-0
3.0 - 8.0

1101 o 1100

8.0 - 16.0

1111 o 1110

XTAL Externo Estándar. Con esta configuración el XTAL usado estará entre 0.4 MHz y
20 MHz (el máximo admisible). El XTAL se coloca entre los pines XTAL1 y XTAL2 del
AVR y debe tener capacitores externos de entre 12pF y 22pF.
En los ATmegaXX8 y ATmegaXX4 se establece con los bits CKSEL3-0 = 0111 o 0110,
dependiendo de la frecuencia del cristal usado, según la siguiente tabla.
Tabla Rango del XTAL en MHz

Rango del XTAL en MHz CKSEL3-0
0.4 - 16

0111 o 0110

0.4 - 20

0111

Observa que con los 4 bits CKSEL3-0 es posible formar hasta 16 combinaciones. Entre
las no citadas, algunas están reservadas y no deberían usarse, y otras corresponden a
configuraciones relacionadas con el uso de un Resonador Cerámico en lugar del XTAL
externo estándar o de baja potencia. Excluí las combinaciones del resonador cerámico
para evitar una sobrecarga de números innecesaria.
En la mayoría de las prácticas de cursomicros.com se usa un XTAL de 8 MHz. Puede
ser estándar o de baja potencia. El XTAL de cuarzo brinda el reloj más preciso y
estable, lo que es ideal para aplicaciones que usan los módulos síncronos como
el USART o TWI (I2C).
Debemos recordar que un 0 es un bit programado y un 1 es un bit sin programar. En
las interfaces gráficas de los programadores 0 suele ser una casilla marcada. Por otro
lado, en el entorno de Atmel Studio 6 la elección es directa. La siguiente imagen nos
muestra las opciones de reloj disponibles para el ATmega324P, incluyendo los retardos
de reset y de arranque que trataremos a continuación.
Configuración de los fuses de reloj más los retardos de RESET y de Arranque en Atmel
Studio 6.

SUT1-0. Retardos de Reset y de Arranque
Se representa por los bits SUT1 y SUT0. En realidad se trata de dos temporizadores.
El Retardo de Reset funciona con el circuito RC del Watchdog y se encarga de
mantener el AVR en estado de reset por 4.1 ms ó 65 ms después de un RESET, por
ejemplo, después de conectar la alimentación Vcc del AVR. Esto serviría para que se
estabilice el nivel de Vcc antes de que el CPU del AVR empiece a trabajar. Se
representa por
.
Mecanismo del Retardo de RESET en los AVR.
Pero ahí no termina. Para asegurarse de que el oscilador del sistema también se haya
estabilizado, habrá un Retardo de Arranque hasta que transcurran 1K o 16K ciclos de
reloj CK. Durante ese lapso el AVR también se mantiene en estado de reset y luego
recién el procesador empezará a ejecutar el programa. El Retardo de Arranque no solo
se activa después de un reset sino también después de que el AVR salga de los
estados Power-save o Power-down, que son dos de los seis modos SLEEP que tienen
los megaAVR.
La configuraciones de los retardos de reset y de arranque varían de acuerdo con la
fuente de reloj del sistema. La siguiente tabla muestra solo las opciones admisibles
para un XTAL estándar o de baja potencia.
Tabla CKSEL0

CKSEL0

SUT1
SUT0

0

10

14CK

1K CK

XTAL baja frecuencia, BOD enabled

0

11

4.1ms +
14CK

1K CK

XTAL baja frecuencia, fast rising power

1

00

65ms +
14CK

1K CK

XTAL baja frecuencia, slowly rising
power

1

01

14CK

16K CK

XTAL, BOD enabled

1

10

4.1ms +
14CK

16K CK

XTAL, fast rising power

Retardo de reset

Retardo de
arranque

Fuente de Oscilador, Condiciones de
alimentación
Tabla CKSEL0

CKSEL0

SUT1
SUT0

1

11

Retardo de reset
65ms +
14CK

Retardo de
arranque
16K CK

Fuente de Oscilador, Condiciones de
alimentación
XTAL, slowly rising power

En esta tabla XTAL baja frecuencia no se refiere al XTAL de reloj de 32.768 kHz, sino a
un XTAL estándar o de baja potencia cuya frecuencia no esté cercana a la máxima
admisible por el AVR, que para los ATmegaXX8 o ATmegaXX4 es de 20MHz. Esta
condición concuerda con el valor del bit CKSEL0 dado en las tablas de los bits CKSEL30 vistas anteriormente.
Como se ve algo confuso, vamos a poner un ejemplo de diseño. Supongamos que
nuestra aplicación utilizará un ATmega644P a una frecuencia muy estable de 20 MHz.
En primer lugar debemos utilizar un XTAL estándar de 20MHz, para lo cual debemos
programar los bits CKSEL3-0con 0111 (ver las tablas de arriba). Como el bit CKSEL0
en este caso es 1, descartamos las configuraciones de la tabla donde CKSEL0 es 0. Así
mismo, como 20MHz no es una frecuencia baja, también descartamos la configuración
de XTAL baja frecuencia, slowly rising power. Ahora todavía tenemos las tres opciones
resaltadas en la siguiente tabla.
Tabla CKSEL0

CKSEL0

SUT1
SUT0

0

10

14CK

1K CK

XTAL baja frecuencia, BOD enabled

0

11

4.1ms +
14CK

1K CK

XTAL baja frecuencia, fast rising power

1

00

65ms +
14CK

1K CK

XTAL baja frecuencia, slowly rising
power

1

01

14CK

16K CK

XTAL, BOD enabled

1

10

4.1ms +
14CK

16K CK

XTAL, fast rising power

1

11

65ms +
14CK

16K CK

XTAL, slowly rising power

Retardo de reset

Retardo de
arranque

Fuente de Oscilador, Condiciones de
alimentación
¿Cuál elegir? Vemos que los ciclos de reloj son todos iguales a 16K CK. Entonces
nuestra decisión dependerá del retardo de reset requerido. Si nuestro circuito tiene
una alimentación Vcc que se eleva lentamente (slowly rising power), “debemos” elegir
un retardo de 65ms con SUT1-0 = 11.
Pero si en nuestro circuito el nivel de Vcc se eleva rápidamente (fast rising power),
podemos optar por el retardo de 4.1ms con SUT1-0 = 10. Por supuesto que también
en este caso podemos optar por el retardo de 65ms.
Por último, explicamos lo que significa XTAL, BOD enabled. BOD es un circuito interno
que detecta cuando la tensión de Vcc cae debajo de ciertos niveles. También sirve para
que el AVR no esté trabajando con una alimentación defectuosa. Así que configurar el
retardo de 14 CK (lo mismo que nada) equivale a no utilizar retardo de reset y solo
debe utilizarse cuando nuestra aplicación trabaje con un circuito BOD externo o
interno. El BOD interno del AVR se configura con los bits BODLEVEL2-0.

CKDIV8. Prescaler del Reloj del Sistema
Los megaAVR recientes, como los tratados en este capítulo, tienen un prescaler en el
sistema de reloj, que dividirá la frecuencia del reloj cualquiera que sea su fuente
(XTAL, RC Interno etc.).
El Prescaler puede dividir la frecuencia del reloj entre 256, entre 128, entre 64,… hasta
1, dependiendo del valor que se cargue en el Registro de E/S CLKPR, el cual se puede
modificar en cualquier momento en tiempo de ejecución.
En este momento basta con saber que el bit CKDIV8 configura el registro CLKPR para
que el reloj se divida inicialmente entre 8 o entre 1.
Tabla CKDIV8

CKDIV8 Factor del Prescaler del Reloj
0

8

1

1

El valor por defecto del bit CKDIV8 es 0 = programado = reloj dividido entre 8. Si lo
dejamos así, nuestro AVR operará 8 veces más lento. Es más sencillo desmarcar esa
casilla que escribir el código que reconfigura el Prescaler. A propósito, el Wizard
de CodeVisionAVR genera ese código automáticamente.

BODLEVEL2-0. Voltaje de Disparo del BOD
Es un reset por baja tensión. Esta característica le permite al AVR auto resetearse cada
vez que detecte una caída de tensión en la alimentación Vcc, por debajo de un nivel
establecido por los bits BODLEVEL2, BODLEVEL1 y BODLEVEL0.
En la figura subsiguiente la señal de RESET INTERNO se activa cuando el nivel
de Vcc cae por debajo de VBOT-. Luego para que el procesador del AVR vuelva a
ejecutar el programa no solo bastará que Vcc supere el valor de VBOT+, sino que
transcurra el RETARDO DE RESET. La diferencia entre VBOT+ y VBOT- constituye el
mecanismo de histéresis.
El AVR tiene un filtro pasa-bajas interno para evadir el ruido y evitar un reset ante los
micro valles de tensión en Vcc. La caída de tensión tiene que ser mayor a 2us (valor
típico).
Tabla BODLEVEL2-0

BODLEVEL2-0 Voltaje de disparo de BOD (valor típico)
111

BOD Desactivado

110

1.8

101

2.7

100

4.3

Mecanismo del BOD o Reset interno del AVR producido por la caída de tensión en Vcc.
Configuración de los Fuses de BOD desde Atmel studio 6.

WDTON. Habilitación del Watchdog
El Watchdog o WDT es un temporizador que puede monitorizar el funcionamiento fluido
del microcontrolador. El WDT lo estudiaremos al final porque no es imprescindible. Por
el momento diremos que se trata de un temporizador que una vez habilitado debemos
resetear periódicamente en el programa. Si no lo hacemos, causará un reset en el AVR
y el programa se volverá a ejecutar desde cero.
Cuando el WDT no está habilitado por su fuse WDTON (hardware) todavía es posible
activarlo por software. Pero una vez activado por su fuse no habrá rutina software que
lo apague.
En la mayoría de las prácticas no querremos estar preocupándonos del estado del
WDT, así que la mejor decisión será tenerlo inhabilitarlo, que felizmente es el valor por
defecto del bit WDTON, así que ni siquiera tendríamos que tocarlo.

CKOUT. Salida de Reloj
Cuando este fuse está programado la señal de reloj del sistema saldrá por el
pin CLKO de los megaAVR (ver más abajo). Esta señal incluye el efecto del Prescaler
de reloj.
Puede servir para sincronizar el microcontrolador con otros dispositivos de la
aplicación, pero se usa muy raramente, así que lo habitual es dejar CKOUT con su
valor por defecto 1, o sea sin programar. Si se programa accidentalmente y el diseño
utiliza el pin CLKO para una función de E/S, se puede producir un corto circuito que
dañe el pin o hasta el megaAVR completo.

Pines de salida de reloj en los AVR de las series ATmegaXX8 y ATmegaXX4.
OCDEN y JTAGEN. Habilitar Depuración JTAG
El fuse OCDEN sirve para habilitar el módulo OCDinterno del megaAVR.
El módulo OCD (On Chip Debug) es el sistema de depuración que poseen los
microcontroladores. Está constituido por todo un circuito que puede monitorizar el
estado del CPU, de los periféricos internos, de todos los registros del AVR y de las
memorias RAM, FLASH y EEPROM. Los resultados serán enviados a una computadora a
través de una interface que puede ser JTAG, debugWIRE,PDIo aWire, según el tipo de
microcontrolador.
Del lado de la computadora estará corriendo un programa como Atmel Studio 6 en
mododepuración para recibir todos los datos e ir visualizándolos en la pantalla.
También es posible enviar desde la PC comandos de ejecución del programa como Step
into, Step over, etc. En otras palabras, es como correr el simulador deAtmel Studio
6 o Proteus VSM, pero esto será real y a veces en tiempo real.
En el caso de los ATmegaXX4 el módulo OCD se conecta al exterior mediante la
interface JTAG, la cual está conformada por los pines TMS, TCK, TDI y TDO (ver más
abajo). La interface JTAG debe ser habilitada por separado porque tiene doble función:
Además de la depuración también sirve para programar el AVR. La interface JTAG se
puede habilitar/deshabilitar de dos formas: por hardware, programando el fuse
JTAGEN y por software, mediante el bit JTD del registro MCUCR.
En consecuencia, si vamos a programar nuestro AVR por JTAG solo es necesario
activar el fuseJTAGEN y tener el bit JTD del registro MCUCR en cero. De hecho, el AVR
viene de fábrica conJTAGEN activado y el registro MCUCR inicia con todos sus bits en
cero.
Pero si además vamos a utilizar la interface JTAG para depurar el programa del AVR,
también tenemos que activar el fuse OCDEN. El valor predeterminado de OCDEN es
desactivado y si no vamos a entrar en depuración, debemos dejarlo así para que su
circuito no consuma energía innecesariamente.
Para programación o para depuración, si la interface JTAG está habilitada, los
pines TMS, TCK,TDI y TDO no podrán ser usados como pines de E/S convencionales.
Son 4 pines que solo los megaAVR de varios puertos pueden ostentar.
Pines de interface JTAG en los ATmegaXX4.

DWEN. Habilitar Depuración debugWIRE
No todos los AVR tienen un sistema de depuración sofisticado con interface JTAG
porque demanda muchas líneas de E/S. Para los AVR que no disponen de tantos pines
Atmel diseñó interfaces más sencillas como debugWIRE o aWire, conformadas por una
sola línea, que es el mismísimo pin de RESET del AVR, aunque normalmente no se
grafica así en sus diagramas de pines.
aWire es propio de los AVR de 32 bits y sirve para depuración como para depuración.
En cambio,debugWIRE solo se encuentra en los AVR de 8 bits y no permite la
programación del AVR.
A diferencia de JTAG, para la depuración debugWIRE solo se requiere activar el
fuse DWEN. Con ese hecho se pone en acción el circuito OCD interno del AVR al tiempo
que el pin DW / RESETqueda destinado como pin de E/S para la depuración.
La configuración predeterminada del fuse DWEN es deshabilitada y debemos dejarla
así si no lo vamos a usar. De lo contrario, el circuito de monitoreo del OCD interno
también estará funcionando activamente, consumiendo energía innecesaria; y lo peor
de todo, perderemos la opción de reprogramar nuestro AVR a bajo voltaje. Para
rehabilitarlo tendríamos que utilizar una interface de programación a alto voltaje.
Si retomamos nuestro enfoque sobre los megaAVR de 3 y 4 puertos, diremos que
losATmegaXX4 poseen depuración con interface JTAG y los ATmegaXX8, depuración
con interfacedebugWIRE.
Pin de interface debugWIRE en los ATmegaXX8.

EESAVE. Preservar Contenido de la EEPROM
Cada vez que grabamos nuestro AVR con un nuevo programa se ejecutará
previamente un borrado completo de la memoria FLASH y de la EEPROM interna. Si
queremos que la EEPROM no se borre en este proceso debemos programar este fuse.
Su configuración predeterminada es sin programar, como se muestra abajo.

SPIEN. Habilitación de Programación SPI
Este fuse permite habilitar o deshabilitar la programación SPI. Para entender esto
primero debemos saber que los microcontroladores AVR tienen los siguientes modos
de programación (sin contar la programación por la interface de depuración):
La programación mediante la interface SPI (Serial Port Interface) en bajo voltaje, esto
es, con 0V aplicados al pin RESET. Es la interface más común y está presente en casi
todos los AVR, incluyendo por supuesto en nuestros
estudiados ATmegaXX4 y ATmegaXX8.
La programación mediante la interface Paralela. Aquí el voltaje aplicado al pin RESET
debe estar comprendido 11.5 V y 12.5 V. Este modo permite programar algunos fuses,
comoSPIEN y RSTDISBL, que no son accesibles desde la interface SPI en bajo voltaje.
Sin embargo, la interface paralela tiene el inconveniente de requerir cerca de 15 pines
del AVR.
La programación mediante la interface SPI en alto voltaje, donde se aplica 12V al pin
RESET. Solo se encuentra en los pequeños AVR que no tienen los suficientes pines
para soportar la programación paralela, en algunos tinyAVR para ser más precisos.
Por lo tanto, la programación en alto voltaje, serial o paralela, siempre estará
disponible pero la programación serial SPI se puede deshabilitar mediante el
fuse SPIEN. La modificación del bitSPIEN solo es posible desde la programación en alto
voltaje.
Los AVR vienen con su programación serial activada de fábrica, así que podemos
deshabilitarla en cualquier momento y desde cualquier interface. Sin embargo, dados
los peligros que esto supone algunos softwares de programación nos previenen de su
acceso. Este es el caso del programador AVRFLASH de Mikroe, como se aprecia en la
siguiente imagen.

BOOTSZ1-0. Tamaño de la Sección de Boot Loader
Este fuse configura el tamaño que tendrá la Sección de Boot Loader, estudiada
previamente. Por defecto establece el máximo tamaño disponible. Pero si no vamos a
usar esta característica del megaAVR, no interesan los valores que tengan los
bits BOOSZ1 y BOOTSZ0.

BOOTRST. Ubicación del Vector de Reset
El Vector de Reset es la dirección de la memoria FLASH por donde se empezará a
ejecutar el programa. Normalmente es 0x0000 porque el código del programa empieza
a mapearse desde la primera dirección. La única situación en que esto debe cambiar es
cuando se usa un programa de Boot Loader.
Cuando el fuse BOOTRST está activado el vector de reset será la primera dirección de
la Sección de Boot Loader.
Como es de esperar, la configuración por defecto de este fuse es sin programar y no
debería modificarse a menos que se sepa bien lo que se hace.

Configuración de los fuses BOOTSZ1-0 y BOOTRST desde Atmel Studio 6.

RSTDISBL. Deshabilitar Reset Externo
Suele estar disponible en los AVR que cuentan con pocos puertos. Por defecto este fuse
no está programado y el pin de RESET cumple su función habitual de reiniciar la
ejecución del programa cuando se pone a nivel bajo, además de permitir al AVR entrar
en modo de programación de bajo voltaje.
Pero si programamos el bit RSTDISBL, el pin RESET trabajará como PC6, o sea, como
el séptimo pin de E/S del puerto C. Yo no suelo programar este bit porque me gusta
resetear el AVR a cada rato para asegurarme de que programa siempre inicia bien.
Pero más allá de gustos, debemos insistir en que si el pin RESET pierde su función
habitual, ya no podremos reprogramar nuestro AVR con bajo voltaje. De llegar a esa
fatalidad, deberemos utilizar un programador que trabaje a alto voltaje (12V) para
reponer el estado del fuse RSTDISBL.

Pin de RESET multiplexado en los ATmegaXX8.

SELFPRGEN. Habilitar Auto programación
Este fuse solo está disponible en los megaAVR que no dividen su memoria en secciones
de Aplicación y de Boot Loader, es decir, en los AVR que no soportan el Boot Loader
convencional, por ejemplo, el ATmega48P y la mayoría de los tinyAVR, es decir, en los
AVR con poca memoria FLASH. Con el fuse programado se podrá utilizar la instrucción
de ensamblador SPM en cualquier parte del programa. En caso contrario SPM no
tendrá efecto. Por defecto el fuse SELFPRGEN está sin programar.
Ya sea que se trabaje en C o en ensamblador, el programador normalmente sabe si va
a acceder a la memoria FLASH para escritura. En ese caso se deberá activar este fuse.
Y si no tienes idea de lo que estoy hablando, casi te puedo asegurar que no interesa el
valor que le des a este fuse y hasta te recomendaría que lo actives.
El fuse SELPPRGEN solo está disponible en los AVR de poca memoria FLASH.

Lock Bits o Bits de Candado
No por estudiarlos al final del capítulo significa que los Bits de Lock sean irrelevantes.
Todo lo contrario. Su configuración es más importante que la programación de la
memoria FLASH y de los fuses porque implica precisamente el tipo de protección que
se dará a esos espacios.
Los Bits de Lock establecen un nivel de protección para el contenido de la memoria
FLASH, de los fuses y de los propios Bits de Lock. Por eso el nombre Lock (candado, en
inglés).
Los Bits de lock están contenidos en el llamadoByte de Lock. Este es un registro
independiente cuya programación se lleva a cabo al margen de la programación de los
fuses y de las memorias FLASH y EEPROM del AVR. No hay condiciones especiales para
ello. Si no están bloqueados por ellos mismos, se pueden modificar desde cualquier
interface de programación, sin necesidad de un programador especial. Para mayor
alivio, diremos que solo puede ser necesario marcarlos en un producto terminado. En
el resto de aplicaciones, sobre todo de experimentación, ni siquiera nos acordaremos
de ellos.
Todos los megaAVR disponen de los Bits de Lock generales LB1 y LB2 y, obviamente,
los Bits de Lock de Boot Loader solo están presentes en los megaAVR con soporte
de Boot Loader.
Tabla Byte de Bits de Lock

Byte de
Bit Descripción
Bits de Lock

Valor por defecto

-

7

–

1 (sin programar)

-

6

–

1 (sin programar)

BLB12

5

Boot Lock bit (Bit de candado de Boot Loader) 1 (sin programar)

BLB11

4

Boot Lock bit (Bit de candado de Boot Loader) 1 (sin programar)

BLB02

3

Boot Lock bit (Bit de candado de Boot Loader) 1 (sin programar)

BLB01

2

Boot Lock bit (Bit de candado de Boot Loader) 1 (sin programar)

LB2

1

Lock bit (Bit de candado general)

1 (sin programar)

LB1

0

Lock Bit (Bit de candado general)

1 (sin programar)

Los Bits de Lock generales LB1 y LB2 establecen tres modos de protección de las
memorias del AVR, de sus fuses y de los mismos Bits de Lock, como se describen en la
tabla de abajo. Para entender esto debemos saber que a diferencia de algunos otros
microcontroladores donde la programación de la memoria FLASH suele ir unida con la
programación de sus fuses, en los AVR es posible programar y leer por separado los
diferentes espacios de memoria [no volátiles] que poseen.
El modo por defecto es 1. Allí podremos seguir reprogramando por separado las
memorias FLASH y EEPROM del AVR así como los fuses. Sobre los mismos Bits de
Lock, todavía podremos programarlos para pasar al modo 2 y 3.
En el modo 2 las opciones disponibles desde las interfaces de programación serial y
paralela serán: primero, leer las memorias FLASH, EEPROM y los fuses (para
propósitos de verificación seguramente) y segundo, programar los Bits de Lock,
siempre y cuando vayamos a establecer el modo 3 o el mismo 2, es decir, no
podremos regresar al modo 1 por esta vía. La interface JTAG todavía nos permitirá
escribir en la FLASH y EEPROM, aunque es una puerta que debe haber sido abierta
previamente programando el fuse JTAGEN, lo cual será poco probable.
Si elegimos el modo 3 nuestro AVR quedará con su programa actual y no podremos
volver a programarlo. Con esta opción tampoco se podrá leer el código de programa
del AVR por ninguna interface desde ningún dispositivo externo. Este modo se usa para
proteger el código del programa. Para quienes conozcan los PIC, es como programar el
fuse de protección de código.
Desde el modo 1 siempre podremos pasar al modo 2 o 3, y desde el modo 2 al modo
3. Pero para regresar un nivel de seguridad atrás la única forma es aplicando una
instrucción chip erase, que borrará las memorias EEPROM y FLASH, y los Bits de
Lock regresarán al modo 1. Obviamente, por cuestiones de seguridad los Bits de
Lock son los últimos en borrarse.
Tabla Modo de LB

Modo LB2
Tipo de protección
de LB LB1
1

2

3

11

No se habilita ninguna característica de Candado.

10

Se deshabilitan las posteriores programaciones de las memorias FLASH y
EEPROM tanto en modo de programación Serial y Paralela. También se
bloquean los bits de los fuses.

00

Se deshabilitan las posteriores programaciones y verificaciones de las
memorias FLASH y EEPROM tanto en modo de programación Serial,
Paralela y JTAG (si se tiene). También se bloquean los bits de los Fuses y
los bits de candado de Boot Loader en los modos de programación Serial
y Paralela.

Para comprender el uso de los 4 bits de candado de Boot Loader debemos conocer
primero la función de las Secciones de Aplicación y de Boot Loader, además de las
instrucciones de ensamblador LPM y SPM, que participan activamente en la autoprogramación del AVR.
LPM (Load from Program Memory) sirve para leer un byte de dato de la memoria
FLASH.
SPM (Store to Program Memory) sirve para escribir un byte de dato en la memoria
FLASH.
Ambas instrucciones, LPM y SPM, trabajan con el puntero Z para direccionar la
memoria FLASH.
Tabla Modo de BLB0
Modo BLB02
de BLB0 BLB01

Tipo de protección

1

11

No habrá restricciones para SPM o LPM en su acceso a la Sección de
Aplicación.

2

10

No se permite el uso de SPM para escribir en la Sección de
Aplicación.

00

No se permite el uso de SPM para escribir en la Sección de
Aplicación, ni el uso de LPM para leer la Sección de Aplicación desde
la Sección de Boot Loader.
Si los Vectores de Interrupción están ubicados en la Sección de Boot
Loader, las interrupciones se deshabilitan durante la ejecución
desde la Sección de Aplicación.

01

No se permite el uso de LPM para leer la Sección de Aplicación
desde la Sección de Boot Loader.
Si los Vectores de Interrupción están ubicados en la Sección de Boot
Loader, las interrupciones se deshabilitan durante la ejecución
desde la Sección de Aplicación.

3

4

Tabla Modo de BLB1

Modo BLB12
de BLB1 BLB11

Tipo de protección

1

11

No habrá restricciones para SPM o LPM en su acceso a la Sección de
Boot Loader.

2

10

No se permite el uso de SPM para escribir en la Sección de Boot
Loader.

00

No se permite el uso de SPM para escribir en la Sección de Boot
Loader, ni el uso de LPM para leer la Sección de Boot Loader desde
la Sección de Aplicación.
Si los Vectores de Interrupción están ubicados en la Sección de
Aplicación, las interrupciones se deshabilitan durante la ejecución

3
Tabla Modo de BLB0

Modo BLB02
de BLB0 BLB01

Tipo de protección
desde la Sección de Boot Loader.

4

01

No se permite el uso de LPM para leer la Sección de Boot Loader
desde la Sección de Aplicación.
Si los Vectores de Interrupción están ubicados en la Sección de
Aplicación, las interrupciones se deshabilitan durante la ejecución
desde la Sección de Boot Loader.

El valor predeterminado de todos Bits de Lock es 1 ó sin programar. En ciertos
softwares de programación significa que las casillas respectivas están desmarcadas. En
otros software comoAVRFLASH se indican las configuraciones por sus modos (1, 2, 3 ó
4). Atmel Studio 6 por su parte ofrece una configuración basada en la habilitación o
deshabilitación de las instrucciones SPM y LPM. En cualquier caso, se establecen por
defecto el modo de protección 1 (sin ninguna protección) y deberían quedar así, a
menos que estemos seguros de lo que hacemos.
Configuración y programación de los Bits de Lock desde AVRFLASH.
Configuración y programación de los Bits de Lock desde Atmel Studio 6.

Los Puertos de los AVR
Los puertos se conforman por las líneas del microcontrolador donde se pueden
conectar los dispositivos de Entrada/Salida a controlar, por ejemplo LEDs, displays,
transistores, otros ICs o, mediante relés u optoacopladores, cargas de 110V/220V
como medianos motores.
Los ATmegaXX4 tienen 4 puertos, llamadosPORTA, PORTB, PORTC y PORTD. Todos
ellos están completos, así que disponen de 4 x 8 = 32pines en total.
Los ATmegaXX8 tienen 3 puertos, PORTB, PORTCy PORTD. En total suman 20 pines
con capacidad de ampliarse a 23 pines.
Los pines de los puertos tienen nombres compuestos, como PC4 ADC4/SDA/PCINT12.
Los nombres compuestos implican que tienen funciones multiplexadas. Por ejemplo,
este pin PC4, además de pin digital convencional puede funcionar como el canal 4 del
conversor analógico digital ADC4, como línea serial de datos SDA del módulo TWI
(I2C) o como pin de interrupción por cambio de estado PCINT12.
Cuando los pines no son interface de algún periférico, como el USART, el ADC,
los Timers, etc., es decir, cuando son manual y directamente controlados por sus
registros PORTx, PINx y DDRx se dice que actúan como Entradas y Salidas Generales,
y es a lo que nos dedicaremos en este capítulo.
En principio todos los pines son bidireccionales cuando actúan como líneas de Entrada
y Salida Generales. La dirección es configurable por software. Algunos pines pierden
esa función cuando su control es asumido por algún módulo relacionado, por ejemplo,
una vez configurado el puerto serie, el USART asumirá el control de los pines TXD y
RXD.

Puertos de los ATmegaXX4.
Puertos de los ATmegaXX8.

Capacidades de Voltaje y Corriente
Cuando actúan como salidas, los pines pueden entregar tensiones de hasta Vcc.
Cuando actúan como entradas pueden manejar niveles de hasta 0.5V por encima
de Vcc. El diseño de los pines incluye diodos internos de sujeción que les permiten
soportar tensiones mucho mayores que Vcco inferiores que GND, siempre que la
corriente no sobrepase del orden de los micro Amperios.
Cada pin de E/S puede soportar picos de corriente de hasta 40 mA, pero en estado
estable cada pin de puerto puede suministrar o recibir hasta 20 mA de corriente
cuando Vcc = 5V y hasta 10 mA cuando Vcc = 3V. Sin embargo, esta capacidad no
puede estar presente en todos los pines al mismo tiempo. En seguida tenemos los
límites de corriente total que soportan los puertos en losATmegaXX4:
La suma de las corrientes suministradas por lo pines PB0 - PB7, PD0 - PD7 y XTAL2 no
debería exceder los 100 mA.
La suma de las corrientes suministradas por los pines PA0 - PA3 y PC0 - PC7 no
debería exceder los 100 mA.
La suma de las corrientes recibidas por los pines PB0 - PB7, PD0 - PD7 y XTAL2 no
debería exceder los 100 mA.
La suma de las corrientes recibidas por los pines PA0 - PA3 y PC0 - PC7 no debería
exceder los 100 mA.
Para los ATmegaXX8 la distribución de límites es un poco diferente. (Los
pines ADC7 y ADC6 no están presentes en encapsulados DIP.)
La suma de las corrientes suministradas por lo pines PC0 - PC5, ADC7, ADC6 no
debería exceder los 100 mA.
La suma de las corrientes suministradas por los pines PB0 - PB5, PD5 PD7, XTAL1 y XTAL2no debería exceder los 100 mA.
La suma de las corrientes suministradas por los pines PD0 - PD4 y RESET no debería
exceder los 100 mA.
La suma de las corrientes recibidas por los pines PC0 - PC5, PD0 PD4, ADC7 y RESET no debería exceder los 150 mA.
La suma de las corrientes recibidas por los pines PB0 - PB5, PD5 PD7, ADC6, XTAL1 yXTAL2 no debería exceder los 150 mA.

Las Resistencias de Pull-up
Una de las cualidades que distinguen a los microcontroladores de los
microprocesadores es que encierran en un solo chip todos los elementos posibles de un
sistema de control. Con este fin los AVR incorporan en todos sus puertos transistores a
manera de fuente de corriente que en la práctica funcionan como resistencias de pullup.
Estas pull-ups nos pueden ahorrar el uso resistencias de sujeción externas en los pines
de los puertos configurados como entradas. Laspull-ups se podrían equiparar con
resistencias de entre 20 K y 50 K. a partir de dichos valores podemos calcular la
corriente que puede fluir por ellas si están activadas.
Las pull-ups se pueden habilitar pin por pin independientemente escribiendo un 1 en su
registro de salida PORT. Las-pull ups solo serán efectivas en los pines que actúan como
entradas; en los pines configurados como salidas las pull-ups quedan automáticamente
deshabilitadas.
Existe un bit llamado PUD en el registro MCUCR cuya función es deshabilitar todas las
pull-ups de todos los puertos si su valor es 1. El bit PUD (Pull-Ups Disable) inicializa a
0 y un posible interés por setearlo puede ser eliminar la pequeña corriente que puede
fluir por las pull-ps cuando los pines en cuestión se conectan a 0 lógico.
La siguiente figura muestra la conexión de un pulsador al AVR aprovechando la pull-up
de un pin de E/S. Fíjate en que las pull-ups no se pueden usar como resistencias para
excitar dispositivos como LEDs, relés, etc.
Ejemplo de uso de las resistencias de pull-up.
La figura de ejemplo muestra la pull-up de un solo pin pero están presentes en todos
los pines de E/S del AVR.

Configuración y Manejo de los Puertos
Cuando los pines trabajan como entradas y salidas generales su control descansa
principalmente en los Registros de E/S MCUCR, DDRx, PORTx y PINx, donde x puede
ser A, B, C o D.
Del registro MCUCR solo interviene el pin PUD, cuya función es deshabilitar las pull-ups
de todos los pines cuando PUD = 1. Pero si PUD = 0, la habilitación de las pull-ups
todavía requiere de cierta configuración por parte de los registros DDRx, PORTx y
PINx.
Registro MCUCR

MCUCR

JTD

BODS

BODSE

PUD

---

---

IVSEL

IVCE

Cada puerto tiene sus correspondientes registros DDRx, PORTx y PINx, así por ejemplo
el puerto A tiene sus registros DDRA, PORTA y PINA. Lo mismo es aplicable a los otros
puertos.
Registro PORTA

PORTA
Registro PINA

PORTA7 PORTA6 PORTA5 PORTA4 PORTA3 PORTA2 PORTA1 PORTA0
PINA

PINA7

PINA6

PINA5

PINA4

PINA3

PINA2

PINA1

PINA0

DDA7

DDA6

DDA5

DDA4

DDA3

DDA2

DDA1

DDA0

Registro DDRA

DDRA

El registro PORTx es para escribir datos en los pines del puerto x que están
configurados como salida. Ése es su uso habitual. Sin embargo, escribir en los bits de
PORTx cuyos pines estén configurados como entradas significa activar o desactivar las
pull-ups de dichos pines (ver la tabla de abajo). Ésta es la segunda función de PORTx.
Leer el registro PORTx solo significa obtener el último valor que se escribió en este
registro.
El registro PINx es para leer el estado de los pines del puerto x, sin importar si los
pines están configurados como entradas o como salidas. La función alternativa de este
registro es para conmutar el estado de los pines configurados como salidas cuando se
escribe un 1 en su bit correspondiente de PINx.
El registro DDRx es para configurar la dirección del puerto x, es decir, para establecer
cuáles pines serán entradas y cuáles serán salidas. (Data Direction Register = Registro
de Dirección de Datos). Después de un reset todos los puertos inician con sus pines
configurados como entradas, pero se pueden reconfigurar en cualquier punto del
programa.
Si se escribe un 0 en un bit de DDRx, entonces el pin correspondiente en el puerto x
será de entrada y si se escribe un 1, el pin será de salida. Detesto mencionar a los
PICmicro, pero creo que te puede servir saber que la implicancia del 1 y el 0 en los
PICmicros es al revés.
0 → entrada
1 → salida
Por ejemplo, si escribimos el valor 11110000 en DDRB, entonces los cuatro pines de
menor peso del puerto B serán entradas digitales y los cuatro pines superiores serán
de salida.
Si escribimos 11111111 en DDRA, todos los pines del puerto A serán de salida, y si
escribimos00000000 en DDRB todo el puerto B será de entrada. La codificación de lo
expuesto sería así:
DDRA =0xFF;// 0xFF = 0b11111111

DDRB =0x00;// 0x00 = 0b00000000

Luego podremos leer y escribir en los puertos mediante PORTA y PINB, así.
unsignedchar regval;
PORTA =0x73;// Escribir 0b01110011 en el puerto A
regval = PINB;// Leer puerto B
Hasta aquí estuvo todo muy fácil porque los puertos completos estaban configurados
para entrada o salida. Ahora veremos casos de configuración mixta y lo que sucede,
por ejemplo, si escribimos en PINx o si leemos de PORTx. Si además trabajamos con el
pin PUD para habilitar las resistencias de pull-up, tendremos que valernos de una tabla
para no enredarnos.
Tabla Caso

Caso

Bit en
DDRx

Bit en
PORTx

Bit PUD (en
MCUCR)

Dirección de PullEstado de Pin
Pin
up

1

0

0

X

Entrada

No

Tri-Estado (Alta
impedancia)

2

0

1

0

Entrada

SÍ

Alto, debido a la pullup.

3

0

1

1

Entrada

No

Tri-Estado (Alta
impedancia)

4

1

0

X

Salida

No

Bajo (0 lógico)

5

1

1

X

Salida

No

Alto (1 lógico)

Casos 4 y 5. Son los más simples. Si un pin está configurado como salida, de ningún
modo tendrá pull-up y su estado de salida será 1 ó 0 lógico, dependiendo del valor
escrito en su respectivo bit de PORTx.
Caso 1. Si un pin está configurado como entrada y su bit respectivo en PORTx vale 0,
el pin tampoco tendrá pull-up y su estado será de alta impedancia.
Caso 2. Si un pin está configurado como entrada y su bit respectivo en PORTx vale 1,
el pin tendrá pull-up y su estado se leerá como 1 lógico (por la pull-up), siempre que el
bit PUDdel registro MCUCR valga 0.
Caso 3. Raramente se suele poner PUD a 1, pero si se hace, se deshabilitarán las pullups de todos los pines. Por tanto los pines de entrada serán siempre de alta
impedancia.
Más adelante está una práctica de interface con teclado matricial donde se utiliza.
La notación de números binarios empleando el prefijo 0b no es parte del C Estándar y
aunqueAVR GCC la admita, AVR IAR C no la reconoce. Por tanto solo es recomendable
el uso de la notación hexadecimal.

Control de Dispositivos Básicos
Secuenciador de 3 Efectos
Este programa está inspirado en el led flasher 2 de Seiichi Inoue y lo puedes ubicar en
el sitio web http://www.piclist.com/images/www/hobby_elec/e_pic6_b.htm. Estos son
los tres efectos que se pueden escoger.
Tabla Tabla secuenciador de tres efectos

Cada efecto se podrá seleccionar ingresando su correspondiente número mediante el
teclado.
El reto es que el primer LED alumbra a su plenitud, el segundo LED, un poquito menos
y el tercero, como la colita de un cometa, brilla menos aún.
¿Cómo conseguiremos variar la intensidad de brillo de un LED? Regulando la cantidad
de corriente que fluye por él. Y ¿cómo variamos su corriente sin emplear un
potenciómetro o algo por el estilo? Bueno, las dos formas más habituales se conocen
como modulaciones PWM y PRM, a la segunda de las cuales se apega la onda generada
por el patrón de efecto de nuestro secuencial. PRM es la sigla de Modulación por
Frecuencia de Pulsos, en inglés.

Ondas de corriente en los LEDs y su valor promedio.
Dado que los LEDs que parpadean dan miles de centelleos por segundo, nuestros ojos
no lo podrán percibir así, solo veremos una disminución en su luminosidad.
Es el valor promedio de la corriente lo que cuenta para el brillo del LED. No obstante,
tampoco hay una proporción directa entre estos dos parámetros. Así un LED aparezca
prendido la sexta parte del tiempo que otro, no significa que vaya a brillar 6 veces
menos. Por otro lado, este hecho es aprovechable por otras aplicaciones como el
control de varios displays de 7 segmentos o los letreros matriciales de LEDs, que
veremos más adelante.
Circuito para los LEDs y el microcontrolador AVR.
Para generar los efectos de desplazamiento de los LEDs y las ondas PRM de cada LED
se emplea una matriz llamada Pattern. Es una gran matriz pero el software emplea
índices relativos para dividirla en varios bloques. La separación de los bloques en tres
grupos, uno para cada efecto, también es una división lógica.
Cada uno de los bloques de la matriz Pattern es una posición en el desplazamiento de
los LEDs. Por ejemplo, en la posición 2 del primer efecto se ejecutará el bloque 2 por
100 veces cíclicamente. El bloque 2 es el tercero porque en el mundo digital se
empieza desde 0 y su patrón es el conjunto de datos 0xe7,0x24,0x24,0x66,0x24,0x24.
No dice mucho porque está en hexadecimal, pero si lo pasamos a binario, que es como
originalmente lo elaboré, y lo ordenamos de arriba abajo, tenemos esto
11100111
00100100
00100100
01100110
00100100
00100100
Como se ve, este bloque indica que en este lapso los bits 2 y 5 siempre estarán
activos, los bits 1 y 6 se prenden en dos de seis partes, y los bits 0 y 7 solo se prenden
la sexta parte del tiempo. Si no tenemos un osciloscopio de 8 canales, una gráfica de
Proteus nos puede mostrar las ondas correspondientes. En la sección Gráficos de
Simulación del capítulo de Proteus se describe paso a paso cómo obtener este
resultado. La única diferencia es que en esta ocasión la gráfica es de tipo DIGITAL en
vez de ANALÓGICO.

/************************************************************************
******
* FileName:
* Purpose:
vía USART
* Processor:

main.c
LED Flasher 3. Secuenciador de 3 efectos seleccionables

ATmega164P

* Compiler:

AVR IAR C y AVR GCC (WinAVR)

* Author:

Shawn Johnson. http://www.cursomicros.com.

*

Basado en el led flasher 2 de Seiichi Inoue localizado en
*

http://hobby_elec.piclist.com/index.htm.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License:

Se permiten el uso y la redistribución de este código con

*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y las notas de autor y copyright de arriba.

*************************************************************************
****/

#include "avr_compiler.h"
#include "usart.h"

/************************************************************************
******
Patrón que contiene el patrón de efecto del secuencial
led1

111111111111111111111111111111...

Prendido 6 de 6 partes

led2

100100100100100100100100100100...

Prendido 2 de 6 partes

led3

100000100000100000100000100000...

Prendido 1 de 6 partes

- Cada bloque tiene 6 items
- Cada bloque se repite 100 veces
- Hay una pausa de 150 µs entre los ítems
- Hay 12/11 bloques en total
-> Cada barrido dura

6 * 100 * 150 * 12 = 1.08 segundos aprox.

*************************************************************************
*****/

PROGMEM const char Pattern[]=
{
// Efecto 1. 12 bloques de 6 items
0x81,0x81,0x81,0x81,0x81,0x81,0xc3,0x42,0x42,0xc3,0x42,0x42,
0xe7,0x24,0x24,0x66,0x24,0x24,0x7e,0x18,0x18,0x3c,0x18,0x18,
0x3c,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,
0x3c,0x24,0x24,0x3c,0x24,0x24,0x7e,0x42,0x42,0x66,0x42,0x42,
0xe7,0x81,0x81,0xc3,0x81,0x81,0xc3,0x00,0x00,0x81,0x00,0x00,
0x81,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
// Efecto 2. 11 bloques de 6 items
0x80,0x80,0x80,0x80,0x80,0x80,0xc0,0x40,0x40,0xc0,0x40,0x40,
0xe0,0x20,0x20,0x60,0x20,0x20,0x70,0x10,0x10,0x30,0x10,0x10,
0x38,0x08,0x08,0x18,0x08,0x08,0x1c,0x04,0x04,0x0c,0x04,0x04,
0x0e,0x02,0x02,0x06,0x02,0x02,0x07,0x01,0x01,0x03,0x01,0x01,
0x03,0x00,0x00,0x01,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,
// Efecto 3. 11 bloques de 6 items
0x01,0x01,0x01,0x01,0x01,0x01,0x03,0x02,0x02,0x03,0x02,0x02,
0x07,0x04,0x04,0x06,0x04,0x04,0x0e,0x08,0x08,0x0c,0x08,0x08,
0x1c,0x10,0x10,0x18,0x10,0x10,0x38,0x20,0x20,0x30,0x20,0x20,
0x70,0x40,0x40,0x30,0x40,0x40,0xe0,0x80,0x80,0xc0,0x80,0x80,
0xc0,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00
};

/************************************************************************
*****
* Main function

*************************************************************************
***/
int main(void)
{
unsignedint i, j, k, b, bi, ba;
char c, Effect;

DDRB =0xFF;// Setup PORTB for output
usart_init();// Initialize USART @ 9600 N 1

puts("nr Escoja un efecto

");

puts("nr (1) Barrido simétrico");
puts("nr (2) Barrido a la derecha");
puts("nr (3) Barrido a la izquierda");

Effect = '1';// Por defecto iniciar con efecto 1

while(1)
{
start:
/* Establecer parámetros de barrido del efecto actual */
switch(Effect)
{
case '1': ba =0; b=12;break;
case '2': ba =6*12; b=11;break;
case '3': ba =6*(12+11); b=11;break;
}

/* Iniciar barrido del efecto actual */
for(i=0; i<b; i++)// Para barrer b bloques
{
for(j=0; j<100; j++)// Cada bloque se repite 100 veces
{
bi = ba +6*i;
for(k=0; k<6; k++)// Cada bloque tiene 6 items
{
// PORTB = Pattern[bi+k];
PORTB =pgm_read_byte(&(Pattern[bi+k]));

/* Este bloque sondea el puerto serie esperando recibir una
* opción válida para cambiar de efecto
*/
if(kbhit())// Si hay datos en el USART,..
{
c = getchar();// Leer dato
if((c<='3')&&(c>='1'))// Si es una opción válida,...
{
Effect = c;// Tomar opción
goto start;//

y reiniciar

}
}
delay_us(150);
}
}
}
}
}

Delays Antir rebote
Cuando apretamos un pulsador o movemos un switch, la señal de tensión relacionada
no cambiará su valor establemente, sino que se darán pequeños rebotes en el contacto
que generarán ondas irregulares. A veces se puede implementar un sencillo filtro pasabajas para evadir estos rebotes. Como este circuito puede resultar algo incómodo de
armar, casi siempre se prefiere añadir una rutina anti rebote en el programa.
Sencillo filtro anti rebote para un pulsador.
De los tantos mecanismos realizables poner un delay es el más simple. Una vez
detectado el cambio de tensión se espera un lapso de tiempo hasta que la señal se
estabilice y luego se vuelve testear la entrada. Una variante es, luego de responder al
primer pulso esperar un tiempo para que se estabilice la señal.
En cuanto al enunciado del programa: cada vez que presionemos un botón (pulsador)
se prenderá un LED más del puerto B y con otro botón se hará lo mismo para apagar
un LED más. De modo que aprovecharemos el circuito de la práctica anterior. Para
comprobar la utilidad de la aparente irrelevante rutina anti rebote , puedes probar lo
que sucede si le quitas los Delays al programa.
Circuito para el microcontrolador AVR.
/************************************************************************
******
* FileName:

main.c

* Purpose:

Delays antirrebote

* Processor:

ATmega164P

* Compiler:

AVR IAR C y AVR GCC (WinAVR)

* Author:

Shawn Johnson. http://www.cursomicros.com.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License:

Se permiten el uso y la redistribución de este código con

*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y las notas de autor y copyright de arriba.

*************************************************************************
****/

#include "avr_compiler.h"

int main(void)
{
char port =0;

DDRD =0x00;// PORTD como entrada
PORTD =0x0C;// Activar pull-ups de pines PD2 y PD3

DDRB =0xFF;// PORTB como salida
PORTB =0x00;// Limpiar PORTB
while(1)// Bucle infinito
{
if((PIND &0x04)==0)// Si PD2 vale 0 (pulsador presionado)
{
port <<=1;// Desplazar port a la izquierda
port |=0x01;// Setear bit 0 de port
PORTB = port;// Colocar en PORTB
delay_us(30000);// Delay antirrebote, 30ms
while((PIND &0x04)==0)// Mientras PD2 valga 0, esperar
continue;
}

if((PIND &0x08)==0)// Si PD3 vale 0 (pulsador presionado)
{
port >>=1;// Desplazar port a la derecha
port &=0x7F;// Limpiar bit 7 de port
PORTB = port;// Colocar en PORTB
delay_us(30000);// Delay antirrebote, 30ms
while((PIND &0x08)==0)// Mientras PD3 valga 0, esperar
continue;
}
}
}

Control de Displays 7 segmentos
Un display 7 segmentos no es más que una matriz de 7 diodos LED dispuestos de
forma que encendiéndolos apropiadamente se pueden formar los números del 0 al 9 y
algunas letras del alfabeto. Se dividen en dos grupos: de ánodo común y de cátodo
común. El que vamos a emplear en esta práctica pertenece al segundo grupo y se
muestra abajo.
Display 7 segmentos de cátodo común.
Como ves arriba, cada LED del display está identificado por una letra entre a y g.
Algunos displays también cuentan con un led en la parte inferior llamado dp (decimal
point), pensado para representar un punto decimal.
Yendo a la práctica. Este programa controla 4 displays de 7 segmentos multiplexados.
Esta técnica es muy conocida y consiste en prender los displays uno a uno en tiempos
sucesivos pero con la suficiente frecuencia como para que parezcan que están
prendidos los cuatro al mismo tiempo.
Los datos que visualizarán los displays serán ingresados por el puerto serie de la PC.
Circuito para los displays 7 segmentos y el microcontrolador AVR.
En el programa se emplea el array bcdcodes para almacenar los códigos 7 segmentos
de los números del 0 al 9.
// Matriz de códigos hexadeximales. Formato: xgfedcba , x = don't care
constchar bcdcodes[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};
Aquí tampoco se distingue mucho por estar en hexadecimal, pero convertido en binario
y ordenado de arriba abajo lo notaremos mejor.
Hexa

binario

---------------0x3f=00111111// 7-segment code of 0
0x06=00000110// 7-segment code of 1
0x5b=01011011// 7-segment code of 2
0x4f=01001111// 7-segment code of 3
0x66=01100110// 7-segment code of 4
0x6d=01101101// 7-segment code of 5
0x7d=01111101// 7-segment code of 6
0x07=00000111// 7-segment code of 7
0x07=01111111// 7-segment code of 8
0x7f=01101111// 7-segment code of 9
De acuerdo con el circuito, el display está conectado a los 7 primeros pines de PORTB
del AVR (a con 0, b con 1... y g con 6). El pin 7 queda libre, por eso el bit 7 de cada
dato no interesa, don‟t care.
De acuerdo con el circuito, el display está conectado a los 7 primeros pines de PORTB
del AVR (a con 0, b con 1... y g con 6). El pin 7 queda libre, por eso el bit 7 de cada
dato no interesa, don‟t care.
/************************************************************************
******
* FileName:

main.c

* Overview:

Control de 4 displays 7 segmentos con interface USART

* Processor:

ATmega164P

* Compiler:

AVR IAR C y AVR GCC (WinAVR)

* Author:

Shawn Johnson. http://www.cursomicros.com.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License:

Se permiten el uso y la redistribución de este código con

*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y las notas de autor y copyright de arriba.

*************************************************************************
****/

#include "avr_compiler.h"
#include "usart.h"

char GetNumStr(char* buffer,unsignedchar len);// Prototipo de función

int main(void)
{
// Matriz de códigos hexadeximales. Formato: xgfedcba , x = don't care
constchar bcdcodes[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};

unsignedint d, digit;
signedchar i;
char buff[10]="1234";// Iniciar con 1234
char buff2[10];

DDRB =0xFF;// Todo PORTB para salida
DDRD =0xF0;// Nible alto de PORTD para salida

usart_init();
puts("rn Escribe un número para el display 7 segmentos rn");

while(1)
{
for(d=0; d<4; d++)// Para pasar por los 4 displays
{
if(GetNumStr(buff2,4))// Si se leyó una cadena de 4 númeroa
strcpy(buff, buff2);// Actualizar cadena para visualizar

i = strlen(buff)-d-1;// Calcular posición de dígito
if(i >=0)
digit = buff[i];// Obtener dígito
else
digit = '0';// Rellenar con ceros a la izquierda
PORTD =0xf0;// Apagar displays temporalmente
PORTB = bcdcodes[digit-'0'];// Poner nuevo dato en display
PORTD = PORTD &(~(0x10<<d));// Activar nuevo display
delay_us(100);
}
}
}

//***********************************************************************
*****
// Lee una cadena de texto de 'len' números
// El tamaño de 'buffer' debe ser mayor que 'len'.
//***********************************************************************
*****
char GetNumStr(char* buffer,unsignedchar len)
{
char c;
staticunsignedchar i=0;
if(kbhit())
{
c = getchar();
if((c<='9'&&c>='0')&&(i<len))// Si c está entre 0 y 9
{
buffer[i++]= c;// Guardar en buffer
putchar(c);// Eco
}
elseif((c=='b')&&(i))// Si c es RETROCESO y si i>0
{
i--;//
putchar(c);// Eco
}
elseif((c=='r')&&(i))// Si c es ENTER y si i>0
{
buffer[i]= '0';// Poner un 0x00 (fin de cadena)
putchar(c);// Eco
i =0;// Resetear contador
return1;// Retornar con 1
}
}
return0;// Retornar con 0
}

Retraso y Frecuencia de Repetición
Antes, en los tiempos analógicos, gran parte de la interface de usuario de los aparatos
electrónicos implicaba la presencia de algún tipo de potenciómetros y switches.
Actualmente, una característica de los sistemas digitales es su manipulación basada en
botones. Un botón para esto, otro para aquello... Inclusive a un mínimo de ellos se le
puede dotar de múltiples funciones para el completo control de algún proceso.
El programa de esta práctica implementa el concepto que tantas veces leemos
como retraso de repetición - frecuencia de repetición. De hecho, podemos encontrar
este mecanismo en casi cualquier artefacto digital.
Aprovechando que ya tenemos armado nuestro circuito de 4 displays 7 segmentos,
haremos un contador que se incremente cada vez que se pulse un botón y se
decremente cuando se pulse otro. Si un botón permanece apretado por más de cierto
tiempo (retraso de repetición), se iniciará un incremento/decremento continuo, según
la razón establecida (frecuencia de repetición).
Pero aquí no termina la cosa. Con criterio de buen diseñador deberíamos prever lo que
pasaría si alguien presionara un botón sin antes haber soltado el anterior. Eso
dependerá de la aplicación. En este programa yo decidí dejar sin efecto al segundo
botón mientras el primero esté pulsado. Está de más decir que los rebotes deben ser
filtrados.

Circuito para los displays 7 segmentos y el microcontrolador AVR.
La tarea de la función CheckButtons la puedes leer en su respectivo encabezado. Allí
dice que si se llama periódicamente cada 100us el retraso de repetición será
de 600ms y la frecuencia de repetición de 1/100ms = 10 incrementos/decrementos
por segundo. Estos parámetros se pueden modificar editando los límites de conteo de
las variables a y b.
Tras ser detectada la señal negativa (botón pulsado), el programa seguirá testeando el
pin continuamente durante 25 ms. Solo se reconocerá como válido el pulso que
permaneció estable en este lapso. Eso servirá para filtrar los pequeños pulsos de
rebote.
Una característica notable de este mecanismo, comparable solo con las interrupciones,
es que permite atender a los botones sin necesidad de que el programa se trabe
esperando a que se pulsen o liberen.
Sin embargo, debo aclarar que el tiempo de los 100us no se cumple estrictamente y
puede variar bastante de acuerdo con la aplicación. En esta práctica ni se nota, pero si
realmente llegara a interesar se puede ajustar o superar con las interrupciones de
algún Timer.
/************************************************************************
******
* FileName:

main.c

* Purpose:

Retraso de repetición y velocidad de repetición

* Processor:

ATmega164P

* Compiler:

AVR IAR C y AVR GCC (WinAVR)

* Author:

Shawn Johnson. http://www.cursomicros.com.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License:

Se permiten el uso y la redistribución de este código con

*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y las notas de autor y copyright de arriba.

*************************************************************************
****/

#include "avr_compiler.h"

/* Prototipos de función */
void CheckButtons(void);
void IncCNT(void);
void DecCNT(void);
/* Definiciones */
#define MaxCNT
(arbitrario)

5000

// Máximo Valor de CNT

#define ButtonI

( PIND & (1<<2) )

// PD2 Botón de incremento

#define ButtonD

( PIND & (1<<3) )

// PD3 Botón de decremento

/* Variables globales */
volatileunsignedint CNT;// Contador

/************************************************************************
*****
* Main function

*************************************************************************
***/
int main(void)
{
// Matriz de códigos hexadeximales. Formato: xgfedcba , x = don't care
constchar bcdcodes[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};

unsignedint i, j, digit;
signedchar idx;
char buff[10];

DDRB =0x7F;// Los 7 bits altos de PORTB para salida
PORTB =0x80;// 0 a pines de salida, sin Pull-up en pin de entrada
DDRD =0xF0;// Nible alto de PORTD para salida
PORTD =0x0C;// Activar Pull-ups de pines PD2 y PD3

CNT =123;// Inicializar contador
while(1)
{
for(j=0; j<100; j++)
{
for(i=0; i<4; i++)// Para pasar por los 4 displays
{
CheckButtons();
itoa(CNT, buff,10);// Convertir número en cadena
idx = strlen(buff)-i-1;// Calcular posición de carácter
if(idx >=0)
digit = buff[idx];// Obtener carácter
else
digit = '0';// Poner 0 en displays sin número
PORTD |=0xf0;// Apagar displays temporalmente
PORTB = bcdcodes[digit-'0'];// Poner nuevo dato en
display
PORTD &=(~(0x10<<i));// Activar nuevo display
}
}
}
}

/************************************************************************
*****
*

Mide el tiempo que los botones I y D están pulsados. Si dicho tiempo

*

llega a 25ms llamará a IncCNT (o DecCNT) y luego de 600ms la llamará

*

continuamente tras cada 100ms. Un botón no responderá mientras el

*

anterior no sea liberado.
*

Para cumplir los tiempos citados debería ser llamada cada 100us.

*************************************************************************
***/
void CheckButtons(void)
{
staticunsignedint a;// Cuenta el tiempo que ButtonI está pulsado
staticunsignedint b;// Cuenta el tiempo que ButtonD está pulsado

if((ButtonI==0)&&(b==0))// Si ButtonI pulsado y ButtonD libre
{
a++;// Incrementar contador
if(a==250)// Si llegó a 25 ms
{
IncCNT();// Procesar por primera vez
}
elseif(a>6250)// Si pasaron más de 600 ms
{
IncCNT();// Procesar por segunda y demás veces
a -=1000;// Siguientes incrementos serán cada 100 ms
}
}
else
a=0;// Resetear contador

if((ButtonD==0)&&(a==0))// Si ButtonD pulsado y ButtonI libre
{
b++;// Incrementar contador
if(b==250)// Si llegó a 25 ms
{
DecCNT();// Procesar por primera vez
}
elseif(b>6250)// Si pasaron más de 600 ms
{
DecCNT();// Procesar por segunda y demás veces
b -=1000;// Siguientes incrementos serán cada 100 ms
}
}
else
b=0;// Resetear contador
}

/************************************************************************
*****
* Incrementan o decrementan CNT.
* El valor de CNT está limitado al rango [0 : MaxCNT]
*************************************************************************
***/
void IncCNT(void)
{
if(CNT<MaxCNT)// Si CNT < MaxCNT
CNT++;// Incrementar CNT
}

void DecCNT(void)
{
if(CNT>0)// Si Duty > 0
CNT--;// Decrementar CNT
}

Control de Motor Paso a Paso
A diferencia de un motor DC, que solo tiene una bobina y que empieza a girar apenas
se le conecta la alimentación, con una velocidad que varía de acuerdo con el voltaje
aplicado; los motores de pasos tienen cuatro bobinas y avanzan o retroceden solo un
pequeño ángulo de giro, llamado ángulo de paso, por cada combinación de voltaje
aplicada en sus boninas. Para mantener la marcha del motor es necesario cambiar
periódicamente la combinación de voltajes en sus terminales.
Con 4 bobinas un motor PAP (Paso A Paso) puede presentar hasta 8 terminales, 2 por
cada bobina. Los terminales se pueden unir interna o externamente dando lugar a dos
tipos de motores PAP: los unipolares y los bipolares. Puedes ver la siguiente figura
para identificar el origen de tu motor PAP. Observa que si tienes un motor unipolar de
8 ó 6 terminales, lo puedes convertir en un motor bipolar; pero no es posible arreglar
un motor bipolar para que trabaje como unipolar, a menos que separes sus bobinas
internamente.
Motores de pasos Unipolares y Bipolares.
Aunque tengan más terminales, los motores PAP Unipolares son más fáciles de
manejar por el hardware requerido. En estos motores las bobinas se polarizan en una
sola dirección, por lo que basta con un switch o transistor por cada bobina para
energizarla.
En cambio en los motores PAP bipolares cada bobina debe polarizarse en ambas
direcciones. Esto es equivalente a hacer girar un motor DC en ambas direcciones; y tú
sabes que para eso se requiere de un circuito llamado Puente-H.
Para controlar un motor PAP Unipolar las bobinas deben ser polarizadas
secuencialmente de acuerdo con la orientación que se le quiera dar al rotor. Para
comprender esto debes recordar la ley de atracción y repulsión entre polos
magnéticos, así como la ley de Lens, que explica la orientación del campo magnético
generado por la corriente que fluye por una bobina.
En el motor PAP unipolar no deben polarizarse las 4 bobinas al mismo tiempo porque
generarían un campo magnético simétrico y el rotor no sabría a dónde girar. A partir
de esto se deduce que existen hasta tres modos de hacer girar un motor PAP unipolar,
pero los dos principales son:
Modo Full Step o de paso completo. Es cuando las bobinas se polarizan de dos en dos.
En la siguiente animación los terminales medios están unidos interna o externamente
al círculo que representa la alimentación positiva. Entonces para polarizar las bobinas
se ponen a nivel bajo (azul) sus terminales largos. He resaltado en amarillo cada
bobina polarizada. Eso ayuda a distinguir la secuencia de polarización y a descubrir que
no existen más que 4 combinaciones de polarización aplicables, las cuales deben
repetirse cíclicamente.
Los números hexadecimales al lado de la tabla se obtienen asumiendo que los puntos
azules son ceros y los rojos unos. De hecho esa suposición no importa mucho, como
tampoco importa que los terminales medios estén conectados a la alimentación
positiva o a GND, ni que la secuencia de pasos inicie en 0x09. Puedes invertir las
polarizaciones y al final te sorprenderá que el motor siga girando en la misma
dirección. Lo único que hará que cambie de dirección es que reciba la secuencia de
pasos en orden invertido.
Operación de un motor de pasos unipolar en modo Full step.
Modo Half Step o de paso medio. Es cuando las bobinas se polarizan de a una y de a
dos intercaladamente. Si te fijas bien en la tabla de pasos, verás que también incluye
los 4 pasos del modo full step. Obviamente esos son los momentos en que hay dos
bobinas polarizadas, en los otros 4 pasos solo se polariza una bobina. La ventaja de
este mecanismo respecto del modo Full step es que se pueden realizar movimientos de
giro más finos. La desventaja es que puede disminuir el torque, o fuerza de giro del
rotor.
Operación de un motor de pasos unipolar en modo Half step.
El programa de esta práctica soporta ambos modos de control, los cuales se establecen
desde la consola mediante la tecla „*‟. Por cada vez que se pulse la tecla „+‟ el motor
avanzará 1 ó medio paso, dependiendo del modo actual seleccionado y por cada vez
que se pulse la tecla „-‟ el motor retrocederá 1 ó medio paso.
Circuito para el motor paso a paso y el microcontrolador AVR.
Puesto que la tabla de pasos del modo

Half Step también incluye los pasos del

modo Full Step, no fue necesario crear dos tablas separadas. Los 8 pasos del motor
se encuentran en el array steps.
/* Matriz de pasos */
constchar steps[]={0x09,0x0D,0x0C,0x0E,0x06,0x07,0x03,0x0B};

/************************************************************************
******
* FileName:
* Overview:
USART

main.c
Control de motor PAP en modos Full step y Half step, vía

* Processor:

ATmega164P

* Compiler:

AVR IAR C y AVR GCC (WinAVR)

* Author:

Shawn Johnson. http://www.cursomicros.com.
*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License:

Se permiten el uso y la redistribución de este código con

*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y las notas de autor y copyright de arriba.

*************************************************************************
****/

#include "avr_compiler.h"
#include "usart.h"

int main(void)
{
/* Matriz de pasos */
constchar steps[]={0x09,0x0D,0x0C,0x0E,0x06,0x07,0x03,0x0B};

signedchar step;// Marca el paso actual
char modo, c;

DDRB =0x0F;// Configurar Nibble bajo para salida
PORTB =0x00;// Limpiar nibble bajo. Sin Pull-ups en nibble alto

step =1;// Iniciar en cualquier paso

modo =0xff;// Esto será Full step (0x00 para half step)

usart_init();// Inicializar USART0 @ 9600 N 1
puts("rn Control de Motor PAP en Full step y Half steprn");
puts("rn (*): Conmutar entre Full step y Half step");
puts("rn (+): Avanzar 1 o 1/2 paso");
puts("rn (-): Retroceder 1 o 1/2 paso");

while(1)// bucle infinito
{
if(kbhit())// Esperar a que llegue un dato del puerto serie
{
c = getchar();// Leer dato recibido
switch(c)
{
case '*':

modo =~modo;// Conmutar modo

if(modo)
puts("rn Full step");
else
puts("rn Half step");
break;

case '+':

step++;// Medio paso adelante

if( modo &&(step&0x01))// En modo Full step y si es necesario...
step++;//

... medio paso más

if(step>7)
step =0;// Dar la vuelta
PORTB = steps[step];// Ejecutar el paso
break;

case '-':

step--;// Medio paso atrás
if( modo &&(step&0x01))// En modo Full step y si es necesario...
step--;//

... medio paso más

if(step<0)
{
step =7;// Dar la vuelta
if(modo)// En full step empezar desde 6
step--;
}
PORTB = steps[step];// Ejecutar el paso
break;
}
}
}
}

Control de Teclado Matricial
Un teclado matricial es un conjunto de botones (switches) dispuestos en forma de
malla, de modo que no se requieran de muchas líneas para su interface. De hecho, la
mayoría de los teclados (incluyendo quizá el de tu computadora) funciona con una
estructura similar. En esta práctica trabajaremos con un teclado de 4×4.
Como se aprecia en la siguiente imagen, cada botón del teclado está conectado a
alguna de las filas Row, por un lado; y por el otro, a alguna de las columnas Col.
Teclado Matricial
Aspecto físico y estructura interna de un teclado.
La siguiente figura esboza la conexión entre un microcontrolador y un teclado de 4×4.
Obviamente, no se puede leer el estado de una tecla como un pulsador cualquiera.
Pero es fácil darse cuenta de que una tecla pulsada establece la conexión entre una de
las filas Row y una de las columnas Col.

Conexión de un teclado a un microcontrolador.
Por ejemplo, al presionar la tecla „6‟ se unen las líneas Row 1 y Col 2. O sea, si
sacamos un 1 (ó 0) por el pin de Row 1, también deberíamos leer un 1 (ó 0) en el pin
de Col 2, o viceversa. Generalizando, solo hay un par Row-Col que identifica cada
tecla.
En consecuencia, para saber cuál fue la tecla pulsada debemos sondear una a una
todas las combinaciones Row-Col. Una vez detectada la condición de circuito cerrado,
se usa el parRow-Col para deducir la posición de la tecla pulsada.
Luego de expuesta la relativa sencillez de este teclado podemos sentirnos ansiosos
empezar a codificar el programa de control. Solo hay que poner especial cuidado en la
dirección de los pines y su conexión. Un mínimo descuido causaría un cortocircuito que
dañaría el AVR.

La funcionalidad del programa no puede ser más sencilla. El valor de cada tecla
pulsada será enviado a la consola de puerto serie de la PC.

Circuito para el teclado matricial y el microcontrolador AVR.
La función de bajo nivel para el teclado eskeypad_scan. En el interior de esta función
el nibble bajo de PORTB se configura como salida y el nibble alto, como entrada, con
sus correspondientes pull-ups. Al salir, todo PORTB queda como entrada para facilitar
su posible posterior uso para otras rutinas.
Según mi código, el valor leído en las columnas cuando no hay teclas pulsadas debería
ser „1‟ lógico, y „0‟ cuando si las hay. Para eso es necesario que dichas líneas estén
sujetas a Vcc por medio de resistencias, de pull-ups. Si se deshabilitan las pull-ups, el
programa no funcionará a menos que se las sustituyan por resistencias externas con la
misma función.
/************************************************************************
******
* FileName:

main.c

* Purpose:

Control de teclado matricial de 4x4

* Processor:

ATmega164P

* Compiler:

AVR IAR C y AVR GCC (WinAVR)

* Author:

Shawn Johnson. http://www.cursomicros.com.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License:

Se permiten el uso y la redistribución de este código con

*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y las notas de autor y copyright de arriba.

*************************************************************************
****/

#include "avr_compiler.h"
#include "usart.h"

#define

kpd_PORT

PORTB

// Port write

#define

kpd_PIN

PINB

// Port read

#define

kpd_DDR

DDRB

// Dirección de puerto

char keypad_read(void);
void keypad_released(void);
char keypad_scan(void);

int main(void)
{
char tecla;

usart_init();// Inicializar USART0 @ 9600 N 1

puts("rn Control de Teclado Matricial de 4x4 rn");

while(1)
{
nop();// Alguna otra tarea
tecla = keypad_read();// Leer teclado
if(tecla)// Sí hubo Tecla pulsada (si es diferente de 0)
{
putchar(tecla);
keypad_released();// Esperar teclado libre
}
}
}

//***********************************************************************
*****
// Escanea el teclado y retorna el valor ASCII de la tecla presionada por
// al menos 25ms. En otro caso retorna 0x00.
//***********************************************************************
*****
char keypad_read(void)
{
char c1, c2;
c1 = keypad_scan();// Escanear teclado
if(c1)// Si hubo alguna tecla pulsada
{
delay_us(25000);// Delay antirrebote
c2 = keypad_scan();// Escanear otra vez
if( c1==c2 )// Si Ambas teclas leídas son iguales
return c2;//

entonces aceptarla

}
return0x00;
}

//***********************************************************************
*****
// Espera hasta que el teclado quede libre.
//***********************************************************************
*****
void keypad_released(void)
{
delay_us(10);//
while(keypad_scan())// Mientras se detecte alguna tecla pulsada
continue;// seguir escaneando.
}

//***********************************************************************
*****
// Escanea el teclado y retorna el valor ASCII de la primera tecla que
// encuentre pulsada. De otro modo retorna 0x00.
//***********************************************************************
*****
char keypad_scan(void)
{
unsignedchar Col, Row;
char RowMask, ColMask;
// Col0 Col1 Col2 Col3
conststaticchar keys[]={'7', '8', '9', 'A',// Row 0
'4', '5', '6', 'B',// Row 1
'1', '2', '3', 'C',// Row 2
'.', '0', '#', 'D'};// Row 3

kpd_DDR =0x0F;// Nibble alto entrada, nibble bajo salida
kpd_PORT =0xF0;// Habilitar pull-ups del nibble alto

RowMask =0xFE;// Inicializar RowMask a 11111110

for(Row=0; Row<4; Row++)
{
kpd_PORT = RowMask;//
delay_us(10);// Para que se estabilice la señal
ColMask =0x10;// Inicializar ColMask a 00010000

for(Col=0; Col<4; Col++)
{
if((kpd_PIN&ColMask)==0)// Si hubo tecla pulsada
{
kpd_DDR =0x00;// Todo puerto entrada otra vez
return keys[4*Row+Col];// Retornar tecla pulsada
}

ColMask <<=1;// Desplazar ColMask para escanear
}//

siguiente columna
RowMask <<=1;// Desplazar RowMask para escanear
RowMask |=0x01;//

siguiente fila

}

// Se llega aquí si no se halló ninguna tecla pulsada
kpd_DDR =0x00;// Todo puerto entrada otra vez
return0x00;// Retornar Código de no tecla pulsada
}

Letrero Matricial de LEDs

Un letrero matricial es uno de los proyectos más atractivos de la electrónica. Su
elaboración puede ser sencilla por su diseño, aunque algo engorrosa por la
implementación del hardware.
En esta oportunidad aprenderemos a diseñar un panel de 8 filas y de 64 columnas, es
decir, de 512 LEDs, pero verás que ampliar o reducir el tamaño será tan simple como
añadir o quitar registros en el hardware o cambiar un solo número en el software.
El letrero desplegará un mensaje estático incluido en el firmware. En el capítulo de las
memorias FLASH y EEPROM se presenta unletrero de LEDs programable cuyos
mensajes se ingresan vía el puerto serie.

El hardware
Sabemos que para encender un LED necesitamos de una señal de control, aparte de la
alimentación (Vcc o GND), ¿cierto? Con esto en mente deberíamos suponer que para
un letrero de 515 LEDs se necesitarían de 512 señales saliendo del microcontrolador.
Pero no es así. Podemos resolver parte del problema multiplicando las señales del
microcontrolador con ayuda de dispositivos como multiplexores, decodificadores o
registros serie-paralelo como el 74164,74595 o el CD4094. Los dos últimos son muy
parecidos y son a la vez mejores porque cuentan con doble buffer. Uno para almacenar
internamente los datos seriales que van ingresando al registro y otro que se conecta al
exterior..
Diagrama funcional del registro de desplazamiento 74HC595.
Todos estos registros son de 8 bits pero tienen la característica de poder ser montados
en cascada para multiplicar sus salidas. Por ejemplo, abajo vemos cómo conectar 8
registros 74HC595 en cascada, lo que equivale a un "mega-registro" serie-paralelo de
64 bits con DATAcomo principal entrada serial de datos, CLOCK como reloj común
y STROBE como la señal que deja salir por los pines Qx (al mismo tiempo) todos los
datos ingresados serialmente. Del mismo modo se pueden ir añadiendo tantos
registros como salidas paralelas se deseen.
Conexión en cascada de 8 registros 74HC595 para el letrero matricial de LEDs.
Para nuestro letrero DATA, CLOCK y STROBE serán las señales que saldrán del
microcontrolador. Las 64 salidas col0 a col63 controlarán los LEDs. Si nuestro letrero
va trabajar con duty cycle de 1/4 debemos duplicar el circuito mostrado. Eso del duty
cycle lo entenderemos en el camino.
DS es la entrada serial de datos. SH_CP es el reloj de los bits de datos, es decir, con
cada pulso aplicado al pin SH_CP ingresará en el registro 74HC595 el bit presente en
su pin DS. Los bits así cargados van siendo almacenados en el registro interno 8STAGE SHIFT REGISTER (ver el diagrama funcional del 75HC595). Recién cuando
apliquemos un pulso en el pin ST_CP todos los datos cargados pasarán al segundo
registro interno 8-BIT STORAGE REGISTER y de allí salen a los pines Qx.
Si el pin OE vale 1 lógico, todas las salidas estarán en alta impedancia. En nuestro
circuito está conectado a GND de modo que el estado de los pines Qx es siempre 1 ó
0. El pin MR es reset, y pone a cero todos los pines Qx si vale 0. Para nuestro diseño
no necesitamos resetear los registros de esta forma, así que lo mantenemos conectado
a VCC. Los pines Q7' y Q7 tienen el mismo valor. Q7' permite la conexión en cascada
sin mermar la corriente del pin Q7.
Los registros 74HC595 deben ser de la serie HC, o sea, de tecnología CMOS porque
son los que pueden manejar la mayor corriente, que según el datasheet es de 35mA
en los pines Qx actuando como fuentes o sumideros, aunque esta corriente no puede
estar presente en todos los pines Qx al mismo tiempo Debemos saber que esos 35mA
son un valor extremo que no debería permanecer constante, pero como en el letrero
matricial los LEDs nunca están prendidos todos al mismo tiempo, será perfecto para
bombear todos los picos de corriente posibles.
Por otro lado, si nos basamos solo en este mecanismo para ampliar nuestras señales,
para controlar los 512 LEDs tendríamos que usar 512/8 = 64 registros 74HC595, lo
cual nos llevaría a un circuito muy difícil de implementar. La técnica para salvar este
segundo inconveniente es un artificio que consiste en encender grupos de LEDs en
tiempos diferentes pero con la suficiente frecuencia como para dar la impresión de
estar encendidos todos al mismo tiempo.
Obviamente, en un letrero matricial los LEDs quedan mejor agrupados en filas y/o
columnas. En la siguiente figura los ánodos de los LEDs se unen formando las
columnas (cols) y los cátodos se unen formando las filas (rows). También se puede
armar una configuración alternativa invirtiendo la polaridad de todos los LEDs. En ese
caso los transistores serán de tipo PNP.

Tablero de LEDs para el letrero matricial
Los valores de las resistencias R1 a R64 dependen de la corriente que tiene que fluir
por los LEDs, la cual a su vez depende de los mismos LEDs. Hay que tener en cuenta
que los LEDs no estarán prendidos al 100 % sino la octava parte (por las ocho filas) y
también que la corriente promedio no siempre es proporcional al brillo del LED
prendido, es decir, que un LED esté prendido la octava parte no significa que vaya a
brillar ocho veces menos.
Sobre los transistores: deben tener la capacidad de controlar la corriente proveniente
de todos los LEDs de cada fila. Podrían ser MOSFETs como el IRF520 y si el tablero es
pequeño, hasta unULN2803 podría ser suficiente. Los transistores trabajarán en modo
corte-saturación, por lo que sus resistencias de base (que no están presentes en este
circuito) pueden tranquilamente ser de 10k ó 4.7k.

Los barridos
Una vez estructurado el hardware de la matriz de LEDs nos damos cuenta de que
podemos encender los LEDs que queramos de cualquier fila o de cualquier columna
simplemente activando las coordenadas de dichos LEDs. Por ejemplo, si queremos
prender los LEDs de las columnas 0, 3, 4, 7 y 63 de la fila 5 se vería así:

Encendiendo los LEDs del letrero matricial
Sin embargo, no es posible encender varios LEDs que pertenezcan a diferentes filas y
diferentes columnas al mismo tiempo. Es aquí donde entra a jugar el software.
Por ejemplo en la siguiente animación se muestra cómo para visualizar la letra G se
encienden los LEDs correspondientes pero en tiempos diferentes. La primera figura
muestra la secuencia del barrido en cámara lenta pero en la práctica los barridos serán
tan rápidos que los LEDs se verán como en la segunda figura.
Animación en slow motion de la visualización de figuras en el tablero de LEDs.
Visualización en tiempo real de figuras en el tablero de LEDs.
Cuando encendemos de esta forma los LEDs, es decir, activando solo 1 de las 8 filas
cada vez, se dice que el letrero trabaja con un duty cycle de 1/8 porque en promedio
cada LED permanece prendido la octava parte del tiempo. Los letreros de LEDs
disponibles en el mercado como enwww.ledauthority.com suelen utilizar un duty cycle
de 1/4, es decir, encienden 2 filas de LEDs al mismo tiempo en cada barrido. De ese
modo, se consigue una mayor iluminación de los LEDs.
Armar un letrero con duty cycle de 1/4 es tan simple como juntar varios letreros de 4
filas cada uno. Por ejemplo, si queremos construir un letrero de 8x64 LEDs, armamos
dos letreros de 4x64 y los ponemos uno encima del otro. El hardware es casi el mismo
y aunque deberemos utilizar el doble de registros 74HC595, bien vale la pena por la
buena iluminación que se consigue.
Matriz de LEDs para un letrero con duty cycle de 1/4

Los caracteres
De lo visto anteriormente queda claro que encendiendo los LEDs convenientemente
podemos formar en el letrero la figura que deseemos. Será el microcontrolador quien
de acuerdo con su programa se encargue de generar los barridos activando las filas y
columnas adecuadamente según un patrón establecido. Este patrón corresponde a
letras, figuras o números que queramos y se puede estructurar de diversas formas.
Vamos a representar el patrón con un array de datos, donde cada dato represente una
columna del panel de LEDs. De esta forma, si asignamos un 0 a un LED apagado y un
1 a un LED prendido, podemos establecer a partir de cada columna un byte de dato.
Luego podremos agrupar ordenadamente todos estos datos y escribirlos en un formato
conocido.

Por ejemplo, para el pequeño texto de arriba el array escrito en lenguaje C quedaría
algo así:
const char matrix[] = {0xFC, 0x12, 0x11, 0x12, 0xFC, 0x00, 0x3F, 0x40, 0x80, 0x40,
0x3F, 0x00, 0xFF, 0x11, 0x11, 0x31, 0xCE, 0x00};
Este array puede tener cualquier nombre pero de aquí en adelante me referiré a el
como matrix.

Generación automática de matrix
Ya vimos que para generar el array matrix que se visualizará en el panel de LEDs hace
falta conocer el sistema binario y/o hexadecimal. Pero para quienes no tengan la
paciencia de componerla manualmente sobre todo si quieren experimentar con textos
grandes, les presento una de varias herramientas que encontré en Internet. Se
llama LCD font maker y, aunque fue diseñado para componer patrones de caracteres o
gráficos para displays LCD o GLCD, también nos servirá para nuestro panel de LEDs.
Su uso es tan fácil como seguir los tres pasos que se nos indican en la siguiente figura.
En el Paso 1 escogemos la fuente que deseemos. Yo escogí Verdana-Negrita-11
porque vi que da letras que se ajustan bien a la altura del letrero.
Antes de ir al siguiente paso es preferible poner ahora en Char input el texto que
mostrará el letrero. Yo dejé unos espacios al principio para que el texto empiece a
aparecer desde el costado derecho. Con Offset puedes centrar y ajustar vertical y
horizontalmente el patrón del texto. Hay que establecer Height (altura) a 8
y Width (ancho) lo ajustamos hasta que cubra todo el texto, en mi caso fue de 230.
En el Paso 2 sale una ventana donde debemos establecer los parámetros que se
indican abajo.
En el Paso 3 se nos genera el código del array que representa nuestro texto. El
resultado aparecerá como se muestra en la siguiente figura.
Podemos seleccionar el código generado mediante el botón Copy all para luego pegarlo
en el código fuente de nuestro proyecto. El código del array está declarado
como unsigned char code Bmp001 pero como nuestro programa estará escrito para los
compiladores AVR IAR C y AVR GCC, debemos luego cambiarlo por PROGMEM const
char matrix, como se ve abajo (matrix es un nombre a escoger). Para el
compilador CodeVisionAVR los calificadores PROGMEM const los debemos reemplazar
por __flash.

PROGMEMconst charmatrix[]=
{
/*---------------------------------------------------------------------------Source file / text :

www.cursomicros.com

Width x Height (pixels) :230X8
----------------------------------------------------------------------------*/
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x1F,0xFC,0xE0,
0x7C,0x0F,0x0F,0x7C,0xE0,0xFC,0x1F,0x03,0x00,0x03,0x1F,0xFC,0xE0,0x7C,0x0F,0x0F,
0x7C,0xE0,0xFC,0x1F,0x03,0x00,0x03,0x1F,0xFC,0xE0,0x7C,0x0F,0x0F,0x7C,0xE0,0xFC,
0x1F,0x03,0x00,0x00,0xE0,0xE0,0x00,0x00,0x3C,0x7E,0xC3,0x81,0x81,0x81,0x42,0x00,
0x7F,0xFF,0x80,0x80,0x80,0x40,0xFF,0xFF,0x00,0xFF,0xFF,0x02,0x03,0x03,0x03,0x00,
0x4E,0x9F,0x99,0x99,0x99,0xF9,0x72,0x00,0x3C,0x7E,0xC3,0x81,0x81,0xC3,0x7E,0x3C,
0x00,0xFF,0xFF,0x02,0x01,0x01,0xFF,0xFE,0x02,0x01,0x01,0xFF,0xFE,0x00,0x00,0xFF,
0xFF,0x00,0x00,0x3C,0x7E,0xC3,0x81,0x81,0x81,0x42,0x00,0xFF,0xFF,0x02,0x03,0x03,
0x03,0x00,0x3C,0x7E,0xC3,0x81,0x81,0xC3,0x7E,0x3C,0x00,0x4E,0x9F,0x99,0x99,0x99,
0xF9,0x72,0x00,0x00,0xE0,0xE0,0x00,0x00,0x3C,0x7E,0xC3,0x81,0x81,0x81,0x42,0x00,
0x3C,0x7E,0xC3,0x81,0x81,0xC3,0x7E,0x3C,0x00,0xFF,0xFF,0x02,0x01,0x01,0xFF,0xFE,
0x02,0x01,0x01,0xFF,0xFE,0x00
};

El código fuente
Escribir el programa dependerá de varios factores, como el tamaño del letrero, la
forma cómo se presenta el texto (efecto), la longitud de los mensajes, de si los
mensajes son estáticos o programables en tiempo de ejecución, etc. En esta ocasión el
letrero solo muestra un mensaje en desplazamiento. Por tanto el código fuente será
muy simple. En la siguiente práctica tendremos un letrero programable.

/******************************************************************************
* FileName: main.c
* Overview: LED sign. Letrero Matricial de LEDs.
* Processor: ATmega164P
* Compiler: AVR IAR C y AVR GCC (WinAVR)
* Author:

Shawn Johnson. http://www.cursomicros.com.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License: Se permiten el uso y la redistribución de este código con
*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y la nota de autor de arriba.

*****************************************************************************/

#include "avr_compiler.h"

#define SDATA_HIGH() PORTD |= (1<<5)
#define SDATA_LOW() PORTD &= ~(1<<5)

#define SCLOCK_HIGH() PORTD |= (1<<6)
#define SCLOCK_LOW() PORTD &= ~(1<<6)

#define SOEN_HIGH() PORTD |= (1<<7)
#define SOEN_LOW() PORTD &= ~(1<<7)

#define WIDTH 64

PROGMEMconst charmatrix[]=
{
/*---------------------------------------------------------------------------Source file / text :

www.cursomicros.com
Width x Height (pixels) :230X8
----------------------------------------------------------------------------*/
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x1F,0xFC,0xE0,
0x7C,0x0F,0x0F,0x7C,0xE0,0xFC,0x1F,0x03,0x00,0x03,0x1F,0xFC,0xE0,0x7C,0x0F,0x0F,
0x7C,0xE0,0xFC,0x1F,0x03,0x00,0x03,0x1F,0xFC,0xE0,0x7C,0x0F,0x0F,0x7C,0xE0,0xFC,
0x1F,0x03,0x00,0x00,0xE0,0xE0,0x00,0x00,0x3C,0x7E,0xC3,0x81,0x81,0x81,0x42,0x00,
0x7F,0xFF,0x80,0x80,0x80,0x40,0xFF,0xFF,0x00,0xFF,0xFF,0x02,0x03,0x03,0x03,0x00,
0x4E,0x9F,0x99,0x99,0x99,0xF9,0x72,0x00,0x3C,0x7E,0xC3,0x81,0x81,0xC3,0x7E,0x3C,
0x00,0xFF,0xFF,0x02,0x01,0x01,0xFF,0xFE,0x02,0x01,0x01,0xFF,0xFE,0x00,0x00,0xFF,
0xFF,0x00,0x00,0x3C,0x7E,0xC3,0x81,0x81,0x81,0x42,0x00,0xFF,0xFF,0x02,0x03,0x03,
0x03,0x00,0x3C,0x7E,0xC3,0x81,0x81,0xC3,0x7E,0x3C,0x00,0x4E,0x9F,0x99,0x99,0x99,
0xF9,0x72,0x00,0x00,0xE0,0xE0,0x00,0x00,0x3C,0x7E,0xC3,0x81,0x81,0x81,0x42,0x00,
0x3C,0x7E,0xC3,0x81,0x81,0xC3,0x7E,0x3C,0x00,0xFF,0xFF,0x02,0x01,0x01,0xFF,0xFE,
0x02,0x01,0x01,0xFF,0xFE,0x00
};

constunsignedintLEN=sizeof(matrix);

/*****************************************************************************
* Main function
****************************************************************************/
intmain(void)
{
unsignedintbad;// Base index
unsignedintidx;// Index
unsignedchardato;// dato
unsignedcharrow;// Fila
unsignedcharcol;// Columna
unsignedchari;

DDRD=0xE0;
DDRB=0xFF;
PORTD=0x00;
PORTB=0x00;

while(1)
{
for(bad=0;bad<LEN;bad++)// Bucle para LEN "frames"
{
for(i=0;i<6;i++)// Bucle de 6 barridos por "frame"
{
for(row=1;row;row<<=1)// Cada barrido pasa por las 8 filas
{
for(col=WIDTH;col;col--)// Cada fila iene WIDTH columnas
{
idx=bad+col-1;// Calcular índice de elemento

if(idx<LEN)// Si está dentro del rango
dato=pgm_read_byte(&matrix[idx]);// Extraer dato
else
dato=0x00;// si no, asumir 0x00

if(dato&row)// Si el bit de row es 1
SDATA_HIGH();// colocar 1 en DS
else
SDATA_LOW();// o, colocar 0
nop();nop();

SCLOCK_HIGH();// Pulso de reloj para
nop();nop();// validar el dato colocado
SCLOCK_LOW();
}
PORTB=0x00// Desactiva todas las filas

SOEN_HIGH();// Pulso para sacar todos
nop();nop();// los datos cargados
SOEN_LOW();

PORTB=row;// Activar la fila actual
}
}
}
}
}
descargar

La simulación
Dudo que todos los lectores consigan implementar en la práctica real un letrero
matricial completo debido a la relativa complejidad del hardware, pero creo que al
menos podrán ver su diseño en una buena simulación gracias a Proteus.
Debemos notar que para la simulación en Proteus no es necesario armar el circuito
completamente. Para este diseño por ejemplo he ignorado las resistencias y los
transistores de las columnas y filas del panel. Se puede (o debe) editar el parámetro
Minimum Trigger Time de las matrices de LEDs para mejorar la visualización de los
LEDs sobre todo si se cambia la frecuencia del XTAL.

Simulación del letrero matricial de LEDs en Proteus
El realismo de simulación también dependerá de la potencia de la computadora. En
computadoras lentas el contenido del panel se desplaza más lentamente, aunque se
puede mejorar la animación modificando algunos parámetros, como la cantidad de
barridos por frame en el código fuente, solo para fines de la simulación.

Introducción
Este capítulo está dedicado a los LCDs alfanuméricos con controlador Hitachi
HD44780 o compatible, es decir, la mayoría. Hay diversas firmas,
como Optrex, Sharp, Crystalfontz America,Tianma, etc., que producen muchísimos
LCDs de este tipo. Los hay desde 1 a 4 líneas, desde 8 a 40 letras por línea, algunos
con iluminación de fondo, con diferente tecnología de fabricación, etc. Dada la
compatibilidad en el control de todos ellos, la elección de un modelo en particular
queda a tu cargo. El LCD utilizado en este curso es de 2 líneas, de 16 letras cada una.

Un display LCD de 2 líneas, de 16 caracteres cada una.
Si bien es necesario conocer un dispositivo para sacarle el máximo provecho, en
primera instancia a la mayoría de los aficionados solo le interesa ponerlo en práctica
aunque sea de forma limitada. Si eres uno de ellos, y por el momento quieres
ahorrarte algo de tiempo, puedes saltar a la sección Interface de un Display LCD.

Pines del LCD
Pines del display LCD.

Tabla Número de Pin

Número de Pin Símbolo
1

Vss

2

Vcc o Vdd

3

Vee o Vo

4

RS
5

R/W

6

E

7...14

DB0...DB7

15 y 16

AyK

Pines del LCD.
Los pines 15 y 16 corresponden a la iluminación de fondo del LCD, pero aquí el orden
varía mucho. Sea como fuere, los 14 primeros pines siempre deberían coincidir.
Tabla Nombre de señal

Nombre de
Función
señal
DB0-DB7
o
D0-D7

8 líneas de bus de datos. Para transferencia bidireccional de datos entre el MCU y
el LCD. DB7 también se puede usar como bit busy flag. En operación de 4 bits solo
se usa el nibble alto.

E

Enable – Señal de inicio de operación de lectura/escritura.

R/W

Señal para seleccionar operación de lectura o escritura.
0 : Escribir en LCD
1 : Leer de LCD

RS

Register Select
0 : Registro de comandos (escritura).
: Busy flag + puntero de RAM (lectura).
1 : Registro de datos (escritura, lectura). Acceso a DDRAM o CGRAM.

Vee o Vo

Ajuste de contraste del LCD. Vee = GND es máximo contraste.

Vdd o Vcc

Alimentación = +5 V típicamente.

Vss

Alimentación = 0 V (GND).

AyK

Son los pines de Ánodo y Cátodo de la iluminación de fondo que tienen algunos
LCD.
Un modo de operación del LCD (con ventajas y desventajas) le permite trabajar sin
conectar el pin RW al microcontrolador. En ese modo pin RW siempre debe plantarse a
GND.

LCDs con iluminación de fondo
Algunos LCDs tienen iluminación de fondo. Esta característica se basa en diferentes
tecnologías, siendo la más habitual el empleo de una matriz de diodos LED colocados
detrás de la pantalla.
La iluminación basada en LEDs suele activarse con los pines 15 y 16, identificados
como A (de ánodo) y K (de cátodo) pero no necesariamente en ese orden. Estos pines
son independientes del controlador interno del LCD así que de poco sirve que nuestro
LCD diga ser compatible con HD44780. La polaridad varía tanto que en los diagramas
he puesto 15/16 para no especificar. En todo caso, la independencia de los
pines A y K permitirá que todas las prácticas de este curso funcionen con iluminación o
sin ella.

Si los pines de iluminación en tu LCD no están marcados como A y K puedes consultar
su datasheet para ver cuáles son o averiguarlo manualmente del mismo modo que
compruebas la polaridad de un LED: aplica 5 V entre los pines 15 y 16 y si prende,
eureka! ya lo tienes. Como en todo LED, no debes olvidar ponerle una resistencia en
serie, como se ve arriba. ¿Resistencia de cuánto?
Tú sabes que hay todo tipo de diodos LED: algunos prenden a penas, mientras que
otros, con la misma energía, alumbran como una linterna (bueno, casi :). Creo que eso
da cuenta de su divergencia, pero en términos generales, los LEDs de la iluminación
requieren cerca de 4.3V y consumen algo de 300 mA. De aquí calculamos que el valor
de la resistencia debe andar por los 5 a 20 ohms. Queda a tu criterio hacer los ajustes
para que alumbren tanto como quieras.

Memorias del LCD
CGROM - Character Generator ROM
Es la zona de memoria donde se encuentran grabados los patrones de todos los
caracteres que puede visualizar el LCD de fábrica. Tiene grabados cerca de 200 (varía
mucho) tipos de caracteres de 5×7 puntos (lo más común) o 32 caracteres de 5×10
puntos. Este último modo es raramente usado porque no todos los modelos lo
soportan.

Tabla estándar de caracteres de la CGROM.

DDRAM - Display Data RAM
La DDRAM almacena los códigos de las letras que se visualizan en la pantalla
del LCD. Tiene capacidad de 80 bytes, un byte por carácter si la fuente es de 5×7
puntos. Observa que no siempre se podrán visualizar los 80 caracteres.
Por ejemplo, si quisiéramos mostrar el mensaje Hello en la pantalla, deberíamos enviar
a laDDRAM los códigos ASCII de cada letra de esa palabra. El controlador interno del
LCD tomará esos códigos para buscar en la CGROM sus correspondientes patrones de
visualización y luego los mostrará en la pantalla.
La siguiente figura muestra la correspondencia entre las locaciones de la DDRAM y las
posiciones de las letras que vemos en la pantalla de un LCD de 2 líneas,
particularmente de uno de 2×16. Fíjate en que los 80 bytes de la DDRAM se dividen en
dos sectores de 40 bytes, un sector por línea, así:
Línea 1, con sector de DDRAM desde 0x00 hasta 0x27.
Línea 2, con sector de DDRAM desde 0x40 hasta 0x67.
Por lo tanto, podemos entender que siempre tenemos un LCD virtual de 2×40; aunque
solo podamos ver 8, 16 ó 20 letras por cada línea. Los otros datos escritos en la
DDRAM permanecen allí aunque no se visualicen.

Posiciones en DDRAM de las letras de la pantalla (números en hexadecimal).

CGRAM - Character Generator RAM
La CGRAM es una RAM de 64 bytes donde el usuario puede programar los patrones de
nuevos caracteres gráficos, ya sean de 5×7 puntos (hasta 8 caracteres) o de 5×10
puntos (hasta 4 caracteres). Este tema lo detallaré en la práctica final.

El Puntero de RAM
Llamado también Address Counter, es un registro que sirve para acceder a las
memorias RAM del LCD. Por ejemplo, si el Puntero de RAM vale 0x00, accedemos a la
locación de DDRAM (o CGRAM) de esa dirección.
Ahora bien, solo hay un puntero de RAM que trabaja con las dos RAMs del LCD, y para
saber a cuál de ellas accede actualmente debemos ver la instrucción enviada más
recientemente.
Las instrucciones Clear Display, Return Home y Set DDRAM Address designan el
Puntero de RAM a la DDRAM, mientras que Set CGRAM Address lo designa a la CGRAM.
Afortunadamente, en la gran mayoría de los casos, el Puntero de RAM estará
apuntando a la DDRAM. Además, en este caso viene a representar la posición del
cursor (visible o no) del LCD en la pantalla.

Set de Instrucciones del Display LCD
Es el controlador interno HD44780 (u otro) del LCD quien ejecutará las operaciones de
mostrar las letras en la pantalla, mover el cursor, desplazar el contenido de la pantalla,
etc. Lo que nos toca a nosotros es enviarle los códigos de esas operaciones. A
continuación, un resumen.
Instrucciones del Display LCD

Código
Instrucciones
RS R/W DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0
Clear Display

0

0

0

0

0

0

1

0 0

0

0

0

0

0

0

1

×

Entry Mode Set

0 0

0

0

0

0

0

1

I/D S

Display ON/OFF
Control

0 0

0

0

0

0

1

D

C

B

Cursor or Display
Shift

0 0

0

0

0

1

S/C R/L ×

×

Function Set

0 0

0

0

1

DL

N

×

Set CGRAM Address

0 0

0

1

Puntero de RAM (CGRAM)

Set DDRAM Address

0 0

1

Puntero de RAM (DDRAM)

Read Busy Flag
& RAM Pointer
Instrucciones de datos

0

Return Home

Instrucciones de
comando

0 0

0 1

BF

Puntero de RAM (DDRAM o
CGRAM)

Write to CGRAM
or DDRAM

1 0

Escribir dato

F

×
Instrucciones del Display LCD

Código
Instrucciones
RS R/W DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0
Read from CGRAM
or DDRAM

1 1

Leer dato

Conviene saber que las instrucciones Clear Display y Return Home tienen un tiempo de
ejecución de cerca de 1.52 ms. Las demás toman algo de 40 µs.
El LCD cuenta con dos registros internos principales, que dividen, grosso modo, las
instrucciones en de datos y de comando.
Poniendo el pin RS = 1 accedemos al registro de datos y mediante él a cualquier
locación de la DDRAM o CGRAM, para operaciones de lectura y escritura de datos.
Con RS = 0 accedemos al registro de comandospara escribir instrucciones de control
del LCD (Clear Display, Function Set, etc.). En el caso de una lectura, obtenemos un
dato particular que contiene el valor del puntero de RAM junto con el bit Busy flag.

Clear display: 0 0 0 0 0 0 0 1
Limpia toda la pantalla del LCD. También retorna el cursor a su posición inicial (cima
izquierda), esto es, designa el puntero de RAM a la dirección 0x00 de la DDRAM.

Return home: 0 0 0 0 0 0 1 x
Regresa el cursor a su posición inicial pero sin alterar el texto del display, es decir, solo
designa el puntero de RAM a la dirección 0x00 de la DDRAM.

Entry mode set: 0 0 0 0 0 1 I/D S
Establece el modo de incremento o decremento y modo de desplazamiento del LCD.
I/D = 1: El puntero de RAM se incrementa en 1 después de leer o escribir un dato. Así
accedemos automáticamente a la siguiente locación de DDRAM o CGRAM. Si es
DDRAM, este puntero representa la posición del cursor en la pantalla y el incremento
significa su avance a la derecha.
I/D = 0: El puntero de RAM se decrementa en 1 después de leer o escribir un dato.
S = 1: Si se escribe un nuevo dato de carácter en el LCD, entonces el display entero se
desplaza a la derecha cuando I/D = 0 o a la izquierda cuando I/D = 1.
S = 0: El display no se desplaza luego de escribir en la DDRAM. Esto es lo usual.
Display on/off control: 0 0 0 0 1 D C B
Prende o apaga el Display, el Cursor y la función Blink del cursor.
D = 1: El display se prende.
D = 0: Apaga el display. (No significa que los datos de las RAMs se vayan a borrar.)
C = 1: Despliega el cursor.
C = 0: No despliega el cursor
B = 1: La letra indicada por el cursor parpadea.
B = 0: La letra no parpadea.

Cursor or display shift: 0 0 0 1 S/C R/L x x
Desplaza el cursor o el display a la derecha o la izquierda sin escribir o leer datos.
Tabla S/C

S/C R/L Operación
0 0

Mueve el cursor a la izquierda (puntero de RAM se decrementa en 1)

0 1

Mueve el cursor a la derecha (puntero de RAM se incrementa en 1)

1 0

El Cursor y el display entero se desplazan a la izquierda

1 1

El Cursor y el display entero se desplazan a la derecha

Function set: 0 0 1 DL N F x x
Configura la longitud del bus de datos, el número de líneas y el tipo de fuente.
DL = 1 : La interface con el LCD es mediante un bus de datos de 8 bits.
DL = 0 : La interface con el LCD es mediante un bus de datos de 4 bits.
N = 1: Configura un display de 2 líneas.
N = 0: Configura un display de 1 línea.
F = 0: Fuente de carácter de 5×7 puntos.
F = 1: Fuente de carácter de 5×10 puntos.

Set DDRAM address: 1AAAAAAA
Designa el puntero de RAM a la nueva dirección AAAAAAA de la DDRAM. Digamos que
sirve para controlar la posición del cursor del LCD.
Ejemplo, para escribir un texto en la segunda línea del display (que tiene dirección
inicial 0x40), primero habría que enviar el comando Set DDRAM Address con el
número 0x40 en el parámetro AAAAAAA.

Set CGRAM address: 01AAAAAA
Designa el puntero de RAM a la nueva dirección AAAAAAA de la CGRAM.

Read Busy Flag & RAM Pointer: BF AAAAAAA
Leer bit Busy Flag (BF) y el valor del puntero de RAM. BF = 1 indica que una operación
interna está en progreso. El LCD no aceptará una nueva instrucción hasta que BF sea
0.
El valor de AAAAAAA leído representa el valor del puntero de RAM.
Es posible prescindir del bit BF. Para ello debemos esperar el tiempo adecuado antes
de enviar la siguiente instrucción.

Write data to CGRAM / DDRAM: DDDDDDDD
Escribe el dato de 8 bits DDDDDDDD en la DDRAM o CGRAM, dependiendo de cuál de
las dos esté siendo direccionada actualmente. Después de la escritura el puntero de
RAM se incrementa o decrementa, según se haya configurado el display. Ver
instrucción Entry Mode Set.

Read data from CGRAM / DDRAM: DDDDDDDD
Lee un dato de 8 bits de la DDRAM o CGRAM, dependiendo de cuál de ellas esté siendo
direccionada actualmente. Después de la lectura el puntero de RAM se incrementa o
decrementa en uno, según la configuración del display. Ver instrucción Entry Mode Set.

Inicialización del LCD
os LCDs tienen un circuito interno de reset que los inicializan automáticamente tras la
alimentación. Lo cierto es que la auto-inicialización no siempre es fiable. Por eso existe
la inicialización por software, que permite una completa configuración de los
parámetros del LCD. Se constituye de una serie de pasos aparentemente bastante
exóticos, sobre todo los primeros, pero que se justifican si tratamos de entender que
este procedimiento debe ser capaz de configurar el LCD para que funcione con bus de
datos de 4 u 8 bits, sin importar cómo estuvo operando antes, es decir, podemos
cambiar "al vuelo" entre un modo y otro.
Además de ello cada nueva instrucción debe ser enviada al LCD asegurándonos de que
no se encuentre ocupado. El LCD indica su disponibilidad mediante el llamado
bit BF (Busy Flag). BF = 1 indica LCD ocupado y BF = 0 es LCD listo. BF es el MSbit del
byte que se lee del LCD cuando el pin RS = 0. Obviamente la lectura implica que
debemos poder controlar el pin RW. De no usar este pin en nuestra conexión, debemos
hacer pausas entre una instrucción y otra. Pero incluso si usamos el bit BF, al inicio
debemos poner pausas porque se supone que el LCD aún no sabe si va trabajar con
bus de datos de 4 u 8 bits y no sabe cómo responder a las instrucciones de lectura (no
sabe si entregar bytes enteros o en nibbles). Que enredo!, ¿verdad? Por eso los
siguientes flowcharts se ven tan complicados pese a tratarse de los LCD más simples
del mundo. La inicialización de los LCD gráficos por ejemplo es más pequeña.
Inicialización del LCD con bus de datos de 4 bits.
Inicialización del LCD con bus de datos de 8 bits.
Interface entre un Microcontrolador y un display LCD
Esta presentación es poco usual. Los libros o los manuales de los compiladores suelen
resaltar solo la interface de la librería que proveen. Esta exposición va pensando en los
noveles usuarios del Arduino, que encuentran algo confusa la inicialización de su
librería de LCD por contemplar todos los modos de operación viables.
Aunque los LCDs parezcan simples de usar, para bien o para mal sus características
abren puertas a diversos modos de interface. Considerando que el bus de datos puede
ser de 8 o 4 bits y que se puede usar o prescindir de la línea de control RW, podemos
obtener los siguientes 4 tipos de conexión. (Nota, la conexión de los pines de
alimentación, de contraste y de la iluminación de fondo se estudia en la sección pines
del LCD)
Interface de display LCD

8 líneas de Datos

4 líneas de Datos

3 líneas de Control (conRW) Interface de 11 líneas Interface de 7 líneas
2 líneas de Control (sinRW)

Interface de 10 líneas Interface de 6 líneas

La interface de 11 líneas se trabaja con los 8 bits del bus de datos y las 3 líneas de
Control. El uso del pin RW controla las operaciones de escritura (RW = 0) y lectura
(RW = 1) del LCD. Las lecturas nos permiten por un lado conocer si el LCD está
ocupado o no para saber si podemos enviar la siguiente instrucción de escritura, así
como leer la posición actual del cursor. La otra finalidad de las lecturas es obtener los
datos de las memorias DDRAM y CGRAM del LCD. Los datasheets dicen que el acceso
bidireccional a las rams del LCD permiten utilizarlas como memoria extendida del
sistema. Ahora parece hasta ridículo pero tiene cierto sentido considerando que estos
LCDs nacieron en la prehistoria de los microcontroladores, donde los
microcontroladores tenían muy poca RAM o en su lugar se usaban microprocesadores
(que simplemente no tienen RAM).
Interface de 11 líneas entre un microcontrolador y un LCD
En la interface de 10 líneas el pin RW del LCD va siempre plantado a GND (RW = 0).
Ello significa que el LCD solo aceptará operaciones de escritura del microcontrolador.
Renunciar a la lectura de las memorias RAM es un hecho que pasa casi desapercibido.
El punto clave de no controlar el pin RW es no enviar al LCD una nueva instrucción sin
que haya terminado de procesar la anterior. Ya que no podemos leer del LCD para
saber su estado, debemos calcular su disponibilidad a partir de los tiempos que
demoran en ejecutarse las instrucciones. Por ejemplo, una vez inviada la
instrucción Clear Display debemos esperar al menos 1.6 ms (que es su tiempo de
ejecución) antes de enviar la siguiente instrucción.
Interface de 10 líneas entre un microcontrolador y un LCD
En la interface de 7 líneas el bus de datos del LCD se conecta con el microcontrolador
por sus 4 pines más altos: D4, D5, D6 y D7. Como todas las instrucciones (de datos y
de comando) son de un byte, los bytes deben ser transferidos en dos mitades. Primero
se envía o recibe el nibble alto y luego el nibble bajo, siendo cada nibble validado por
un pulso del pin Enable. Esas rutinas extras harán crecer un poco el firmware
(programa del microcontrolador).
En la contraparte, con el microcontrolador aún disponiendo de las tres líneas de
control, podemos realizar cualquier operación de lectura y escritura, lo mismo que en
la interface completa de 11 líneas pero ahorrándonos 4 pines. Este beneficio suele
prevalecer sobre el hándicap derivado del firmware.
Los LCDs están fabricados con tecnología CMOS, por lo que algunos modelos sugieren
conectar los pines de entrada no usados a alguna señal estable para evitar que por
ellos se filtre algún ruido que pueda perturbar la operación del LCD.
Interface de 7 líneas entre un microcontrolador y un LCD
Por último tenemos la interface de 6 líneas. Aquí se nos juntan todas las desventajas
software de tener que trabajar a base de nibbles y de renunciar a las lecturas del LCD
para obtener datos de sus memorias RAM o para saber si el LCD está ocupado o no
antes de poder enviarle una nueva instrucción. A pesar de todo eso, pueden darse
ocasiones, como disponer de un microcontrolador de muy pocos pines, donde
tengamos que recurrir a esta conexión.
Interface de 6 líneas entre un microcontrolador y un LCD

Librería Para Display LCD
Tenemos a continuación una librería para controlar un LCD con una interface de 4 bits
y usando el bit BF (Busy Flag). Si tuviste la paciencia de leer las páginas anteriores,
verás que es un claro reflejo de todo lo expuesto. Y si no, de todos modos en seguida
citaré ligeramente cómo utilizarla.
La librería trabaja para los compiladores IAR C yAVR GCC y consta de dos
archivos lcd.h y lcd.c. Ambos deberán ser indexados al proyecto en el entorno de AVR
IAR C o Atmel Studio 6 paraWinAVR (ante alguna duda puedes ir a la secciónAdición de
Archivos o Librerías al Proyecto). En el código fuente, sin embargo, solo se debe
indicar el archivo de cabecera i2c.h mediante la directiva #include.

#include "lcd.h"
La librería utiliza por defecto el puerto B del AVR tanto para el bus de datos del LCD
como para las líneas de control E, RS y R/W. Esta interface se puede modificar en los
#defines del archivo i2c.h. Nota que por cada puerto se deben cambiar los tres
registros, PORT, DDR y PIN. Esa podría ser una configuración de cierta recurrencia, en
cambio, no deberíamos tocar lcd.c, salvo que por alguna razón deseemos editar su
código.
Funciones Básicas Disponibles
lcd_init(). Obviamente es la primera función a llamar. Tras ejecutarse el LCD debe
quedar inicializado, con la pantalla limpia y con el cursor en el primer casillero.
lcd_gotorc(r,c). El LCD tiene un cursor que, si bien puede mostrarse en pantalla, suele
configurarse para que permanezca oculto. Bien, visible o no, el cursor avanza
automáticamente tras cada letra que se escribe. Con lcd_gotorc(r,c) podemos mover el
cursor manualmente a la fila r y columna c. El parámetro r puede valer entre 1 y 2, y
el valor de c va de 1 en adelante.
lcd_puts(s). Visualiza la cadena s en el LCD empezando en la posición actual del
cursor. La cadena s es almacenada en RAM. No se suelen mostrar grandes datos en un
LCD, así que no implemente una función análoga que opere sobre la memoria FLASH.
lcd_clear(). Limpia la pantalla del LCD y coloca el cursor al inicio, en la fila 1,
columna 1.
lcd_data(c). Escribe una sola letra en el LCD, en la posición actual del cursor.
lcd_data() también permite crear caracteres gráficos, como se muestra en una práctica
más adelante.
lcd_cmd(com). Ocasionalmente también usaremos esta función para enviar comandos
al LCD, por ejemplo:

lcd_cmd(LCD_LINE2); // Mover cursor al inicio de línea 2
lcd_cmd(LCD_CLEAR); // Limpiar pantalla
lcd_cmd(LCD_CURBLK); // Mostrar Cursor + Blink
lcd_cmd(LCD_CURSOR); // Mostrar solo Cursor
lcd_cmd(LCD_CGRAM+7); // Mover Puntero de RAM a dirección 7 de la CGRAM

Las constantes LCD_CLEAR y algunas más se hallan definidas en el archivo lcd.h. Por
supuesto que también se pueden formar cualesquiera códigos de instrucciones de los
estudiados antes.

/******************************************************************************
* FileName: lcd.h
* Purpose: Librería de funciones para controlar un display LCD con chip
*

Hitachi HD44780 o compatible. La interface es de 4 bits.
* Processor: ATmel AVR
* Compiler: AVR IAR C y AVR GCC (WinAVR)
* Author:

Shawn Johnson. http://www.cursomicros.com.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License: Se permiten el uso y la redistribución de este código con
*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y las notas de autor y copyright de arriba.

*****************************************************************************/

#include "avr_compiler.h"

//****************************************************************************
// CONFIGURACIÓN DE LOS PINES DE INTERFACE
//****************************************************************************

/* Define el puerto a donde se conectará el bus de datos del LCD
* Se utilizará el nible alto del puerto escogido (ejem. PB4-DB4,...,PB7-DB7)
*/
#define lcd_DATAout PORTB
#define lcd_DATAin PINB
#define lcd_DATAddr DDRB

// Registro PORT del puerto
// Registro PIN del puerto
// Registro DDR del puerto

/* Define el puerto a donde se conectarán las líneas de control del LCD
* E, RW y RS. Puede ser el mismo puerto del bus de datos.
*/
#define lcd_CTRLout PORTB
#define lcd_CTRLin PINB

// Registro PORT del puerto
// Registro PIN del puerto

#define lcd_CTRLddr DDRB

// Registro DDR del puerto

/* Define los números de los pines del puerto anterior que corresponderán a
* las líneas E, RW y RS del LCD.
*/
#define lcd_E
#define lcd_RW
#define lcd_RS

3

// Pin Enable
2

1

// Pin Read/Write
// Pin Register Select

//****************************************************************************
// CÓDIGOS DE COMANDO USUALES
//****************************************************************************
#define LCD_CLEAR 0x01 // Limpiar Display
#define LCD_RETHOM 0x02 // Cursor a inicio de línea 1
#define LCD_LINE1 0x80 // Línea 1 posición 0
#define LCD_LINE2 0xC0 // Línea 2 posición 0
#define LCD_DDRAM 0x80 // Dirección 0x00 de DDRAM
#define LCD_CGRAM 0x40 // Dirección 0x00 de CGRAM
#define LCD_CURSOR 0x0E // Mostrar solo Cursor
#define LCD_BLINK 0x0D // Mostrar solo Blink
#define LCD_CURBLK 0x0F // Mostrar Cursor + Blink
#define LCD_NOCURBLK 0x0C // No mostrar ni Cursor ni Blink
//****************************************************************************
// PROTOTIPOS DE FUNCIONES
//****************************************************************************
voidlcd_init(void);// Inicializa el LCD
voidlcd_puts(char*s);// Envía una cadena ram al LCD
voidlcd_gotorc(charr,charc);// Cursor a fila r, columna c
voidlcd_clear(void);// Limpia el LCD y regresa el cursor al inicio
voidlcd_data(chardat);// Envía una instrucción de dato al LCD
voidlcd_cmd(charcom);// Envía una instrucción de comando al LCD
charlcd_read(charRS);// Lee un dato del LCD
voidlcd_write(charinst,charRS);// Escribe una instrucción en el LCD
voidlcd_nibble(charnibble);
voidldelay_ms(unsignedchar);

/******************************************************************************
* FileName: lcd.c
* Purpose: Librería de funciones para controlar un display LCD con chip
*

Hitachi HD44780 o compatible. La interface es de 4 bits.

* Processor: ATmel AVR
* Compiler: AVR IAR C y AVR GCC (WinAVR)
* Author:

Shawn Johnson. http://www.cursomicros.com.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License: Se permiten el uso y la redistribución de este código con
*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y las notas de autor y copyright de arriba.

*****************************************************************************/

#include "lcd.h"

//****************************************************************************
// Ejecuta la inicialización software completa del LCD. La configuración es
// de: interface de 4 bits, despliegue de 2 líneas y caracteres de 5x7 puntos.
//****************************************************************************
voidlcd_init(void)
{
/* Configurar las direcciones de los pines de interface del LCD */
lcd_DATAddr|=0xF0;
lcd_CTRLddr|=(1<<lcd_E)|(1<<lcd_RW)|(1<<lcd_RS);

/* Secuencia de inicialización del LCD en modo de 4 bits*/
lcd_CTRLout&=~((1<<lcd_E)|(1<<lcd_RW)|(1<<lcd_RS));
ldelay_ms(45);// > 40 ms
lcd_nibble(0x30);// Function Set: 8-bit
ldelay_ms(5);// > 4.1 ms
lcd_nibble(0x30);// Function Set: 8-bit
ldelay_ms(1);// > 100 µs
lcd_nibble(0x30);// Function Set: 8-bit
ldelay_ms(1);// > 40 µs
lcd_nibble(0x20);// Function Set: 4-bit
ldelay_ms(1);// > 40 µs
lcd_nibble(0x20);// Function Set: 4-bit, 2lines, 4×7font
lcd_nibble(0x80);//
lcd_write(0x0C,0);// Display ON/OFF Control: Display on, Cursor off, Blink off
lcd_write(0x01,0);// Clear Display
lcd_write(0x06,0);// Entry Mode Set
}

//****************************************************************************
// Escribe una instrucción en el LCD:
// Si RS = 0 la instrucción es de comando (Function Set, Entry Mode set, etc).
// Si RS = 1 la instrucción es de dato y va a la DDRAM o CGRAM.
//****************************************************************************
voidlcd_write(charinst,charRS)
{
while(lcd_read(0)&0x80);// Esperar mientras LCD esté ocupado
if(RS)
lcd_CTRLout|=(1<<lcd_RS);// Para escribir en DDRAM o CGRAM
else
lcd_CTRLout&=~(1<<lcd_RS);// Para escribir en Registro de Comandos
delay_us(5);// Permite actualizar el Puntero de RAM
lcd_nibble(inst);// Enviar nibble alto
lcd_nibble(inst<<4);// Enviar nibble bajo
}
//****************************************************************************
// Envía el nibble alto de 'nibble' al LCD.
//****************************************************************************
voidlcd_nibble(charnibble)
{
lcd_CTRLout&=~(1<<lcd_RW);// Establecer Modo de escritura
lcd_DATAddr|=0xF0;// Hacer nibble alto de bus de datos salida
lcd_DATAout=(nibble&0xF0)|(lcd_DATAout&0x0F);// Colocar dato
delay_us(2);// tAS, set-up time > 140 ns
lcd_CTRLout|=(1<<lcd_E);// Pulso de Enable
delay_us(2);// Enable pulse width > 450 ns
lcd_CTRLout&=~(1<<lcd_E);//
lcd_DATAddr&=0x0F;// Hacer nibble alto entrada
}

//****************************************************************************
// Lee un byte de dato del LCD.
// Si RS = 1 se lee la locación de DDRAM o CGRAM direccionada actualmente.
// Si RS = 0 se lee el 'bit de Busy Flag' + el 'Puntero de RAM'.
//****************************************************************************
charlcd_read(charRS)
{
charhigh,low;
if(RS)
lcd_CTRLout|=(1<<lcd_RS);// Para leer de DDRAM o CGRAM
else
lcd_CTRLout&=~(1<<lcd_RS);// Para leer Busy Flag + Puntero de RAM
lcd_CTRLout|=(1<<lcd_RW);// Establecer Modo Lectura
lcd_DATAddr&=0x0F;// Hacer nibble alto entrada
delay_us(2);// tAS, set-up time > 140 ns
lcd_CTRLout|=(1<<lcd_E);// Habilitar LCD
delay_us(2);// Data Delay Time > 1320 ns
high=lcd_DATAin;// Leer nibble alto
lcd_CTRLout&=~(1<<lcd_E);// Para que el LCD prepare el nibble bajo
delay_us(2);// Enable cycle time > 1200 ns
lcd_CTRLout|=(1<<lcd_E);// Habilitar LCD
delay_us(2);// Data Delay Time > 1320 ns
low=lcd_DATAin;// Leer nibble bajo
lcd_CTRLout&=~(1<<lcd_E);//
return(high&0xF0)|(low>>4);// Juntar nibbles leídos
}

//****************************************************************************
// Envían cadenas RAM terminadas en nulo al LCD.
//****************************************************************************
voidlcd_puts(char*s)
{
unsignedcharc,i=0;
while(c=s[i++])
lcd_write(c,1);// Instrucción 'Write Data to DDRAM/CGRAM'
}
//****************************************************************************
// Ubica el cursor del LCD en la columna c de la línea r.
//****************************************************************************
voidlcd_gotorc(charr,charc)
{
if(r==1)r=LCD_LINE1;
elser=LCD_LINE2;
lcd_write(r+c-1,0);// Instrucción 'Set DDRAM Address'
}

//****************************************************************************
// Limpia la pantalla del LCD y regresa el cursor a la primera posición
// de la línea 1.
//****************************************************************************
voidlcd_clear(void)
{
lcd_write(LCD_CLEAR,0);// Instrucción 'Clear Display'
}

//****************************************************************************
// Envían instrucciones de comando y de datos al LCD.
//****************************************************************************
voidlcd_cmd(charcom)
{
lcd_write(com,0);// Cualquier instrucción de comando
}
voidlcd_data(chardat)
{
lcd_write(dat,1);// Instrucción 'Write Data to DDRAM/CGRAM'
}

//****************************************************************************
// Genera un delay de n milisegundos
//****************************************************************************
voidldelay_ms(unsignedcharn)
{
while(n--)
delay_us(1000);
}

Prácticas con LCD
“Hello World”
Mostrar un mensaje de “Hello World” en el LCD es un programa casi tan trillado como
hacer parpadear un LED.
Según la configuración por defecto de la librería para el LCD, debemos usar la conexión
mostrada en el esquema de abajo. La configuración de puertos y de pines a usar se
puede cambiar en archivo lcd.h.
El pin VEE (o Vo) del LCD establece el contraste de la pantalla. Muchas veces se
prefiere quitar el potenciómetro y conectar VEE a tierra para fijar el máximo contraste.
En los siguientes circuitos haremos algo parecido.
Circuito para el LCD y el microcontrolador AVR.

/************************************************************************
******
* FileName:

main.c

* Purpose:

LCD - Visualización de texto

* Processor:

ATmega164P

* Compiler:

AVR IAR C y AVR GCC (WinAVR)

* Author:

Shawn Johnson. http://www.cursomicros.com.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License:

Se permiten el uso y la redistribución de este código con
*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y las notas de autor y copyright de arriba.

*************************************************************************
****/

#include "avr_compiler.h"
#include "lcd.h"

void delay_ms(unsignedint t)
{
while(t--)
delay_us(1000);
}

int main(void)
{
lcd_init();// Inicializar LCD

while(1)
{
lcd_gotorc(1,7);// Cursor a fila 1 posición 7
lcd_puts("Hello");// Escribir Hello
lcd_gotorc(2,7);// Cursor a fila 2 posición 7
lcd_puts("World");// ...

delay_ms(600);// Pausa de 600 ms
lcd_clear();// Limpiar pantalla
delay_ms(400);// ...
}
}

Visualización de Números
Los LCDs solo entienden de caracteres alfanuméricos y algunos otros, pero no saben
reconocer números. En esta práctica veremos cómo hacerlo implementando un sencillo
reloj. No será el más preciso, pero servirá de buen ejemplo parar formatear números.
Para el circuito, de ahora en adelante, en vez del potenciómetro, colocaremos un diodo
1N4148 en el pin VEE para fijar la tensión (VDD-VEE) a cerca de 4.3 V. En la mayoría
de los LCDs este valor brinda un muy aceptable nivel de contraste de la pantalla.

Circuito para el LCD y el microcontrolador AVR.
La función lcd_puts recibe como parámetro un array de tipo char, que en su forma más
usada sería una cadena texto.
Para visualizar números en el LCD primero debemos convertirlos en cadenas de texto.
La conocida función sprintf (acrónimo de string print formatted) puede formatear
cadenas y números en diferentes bases y colocarlas en el array que recibe como
primer parámetro. Sprintf está basada en printf, así que tiene las mismas
características y limitaciones. En este programa solo se convierten números enteros.
Pero si deseas utilizar números de punto flotante tendrás que habilitar el uso de la
librería que contiene printf en versión completa. Para más información puedes revisar
la secciónConfiguración de printf del capítulo Atmel Studio 6 y WinAVR.
Sprintf soporta varios formatos de números e incluso en su modo básico requiere de
cierta memoria que a veces podría ser de consideración. Para ese caso también se
pueden usar otras funciones de la librería C estándar stdlib.h, como itoa, por ejemplo.
Normalmente no las uso porque tienen variaciones entre los compiladores y al menos
para las prácticas como ésta prefiero no tocar esas divergencias.
/************************************************************************
******
* FileName:

main.c

* Purpose:

LCD - Visualización de números

* Processor:

ATmega164P

* Compiler:

AVR IAR C y AVR GCC (WinAVR)

* Author:

Shawn Johnson. http://www.cursomicros.com.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License:

Se permiten el uso y la redistribución de este código con

*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y las notas de autor y copyright de arriba.

*************************************************************************
****/

#include "avr_compiler.h"
#include "lcd.h"

void delay_ms(unsignedint t)
{
while(t--)
delay_us(1000);
}

int main(void)
{
char buff[17];// Array de 17 elementos tipo char
unsigned seg, min, hor;
seg = min =0;
hor =12;

lcd_init();// Inicializar LCD
lcd_gotorc(1,4);// Cursor a fila 1 posición 4
lcd_puts("easy clock");

for(;;)
{
sprintf(buff,"%2d:%02d:%02d ", hor, min, seg);// Formatear
lcd_gotorc(2,5);// Cursor a fila 2 posición 5
lcd_puts(buff);// Enviar buffer a LCD

if(++seg >59)
{
seg =0;
if(++min >59)
{
min =0;
if(++hor >12)
hor =1;
}
}
delay_ms(998);
}
}

Mostrar Texto en Desplazamiento en LCD
Como parte de su funcionalidad, el controlador interno del LCD puede
ejecutar instrucciones para desplazar lo mostrado en la pantalla una posición hacia la
izquierda o la derecha. Los códigos para desplazar la pantalla (ver la sección referida)
son 0x1C y 0x18. Con eso en el código solo tendríamos que escribir

lcd_cmd(0x1C);
Para mover todo el display incluyendo el cursor a la derecha, y
lcd_cmd(0x18);
Para mover el display a la izquierda.
Puede parecer interesante, pero como lo comprobarás en la práctica Comunicación PCAVR-LCD, funciona en un rango restringido y no es útil cuando solo queremos
desplazar el texto de una sola línea. Estas limitaciones llevan a muchos a realizar esos
efectos mediante rutinas software, como lo que haremos en esta práctica: Mostrar por
el LCD un mensaje que pasa como una marquesina.
Circuito para el LCD y el microcontrolador AVR.

El Código fuente

/******************************************************************************
* FileName: main.c
* Purpose: LCD - Textos en desplazamiento
* Processor: AVR ATmegaXX4
* Compiler: AVR IAR C & AVR GCC (WinAVR)
* Author:

Shawn Johnson. http://www.cursomicros.com.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License: Se permiten el uso y la redistribución de este código con
*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y las notas de autor y copyright de arriba.

*****************************************************************************/

#include "avr_compiler.h"
#include "lcd.h"

#define LCD_LEN 16

// Para LCD de 2×16

PROGMEMconstcharTaine[]="

"EL HAMBRE PRODUCE POEMAS INMORTALES. 

LA ABUNDANCIA, SOLAMENTE INDIGESTIONES Y TORPEZAS"

voiddelay_ms(unsignedintt)
{
while(t--)
delay_us(1000);
}

intmain(void)
{
unsignedcharj;// Índice relativo
unsignedchari;// Índice base
charc;

";
lcd_init();

lcd_puts(" Scrolling text ");// Escribir "Scrolling text" en LCD

while(1)
{
start:
i=0;
for(;;)
{
lcd_cmd(LCD_LINE2);// Cursor a inicio de línea 2

for(j=0;j<LCD_LEN-1;j++)
{
c=pgm_read_byte(&Taine[i+j]);// Obtener dato de matriz
if(c)// Si es dato válido,
lcd_data(c);// enviarlo a LCD
else// Si no (c = 0x00 = fin),
gotostart;// salir de los dos bucles for
}
delay_ms(400);
i++;
}
}
}
Descripción del programa
El texto de la pantalla se desplaza una posición cada 400 ms. Si te parece que avanza
muy lento, puedes disminuir esta pausa. No obstante, podrías empezar a ver como si
hubiera dos letras por casillero de la pantalla. Ello se debe a que el carácter enviado al
LCD no se muestra ni se borra de inmediato. Es lo que sus datasheets indican
como tiempo de respuesta de visualización.
En general, a diferencia del Basic, en C es muy mal visto el uso de un goto, salvo un
caso extremo. goto funciona como en el ensamblador: salta a otro punto del
programa, identificado con una etiqueta. Mi goto salta a la etiqueta start para salir de
dos bucles al mismo tiempo. Dicen que ése es uno de los pocos casos considerados
extremos: salir intempestivamente de varios bucles anidados. A decir verdad, siempre
hay algoritmos alternativos para evitar el goto.

Caracteres gráficos en LCD
La creación de caracteres gráficos puede ser un tema superfluo. Aun así, suponiendo
que no faltarán algunas personas obsesivas como yo, que siempre quieren probarlo
todo, he preparado esta práctica para ir cerrando el capítulo.
Hagamos un poco de memoria. Cuando enviamos el código de un carácter
alfanumérico a la DDRAMdel LCD, su chip interno buscará en la CGROM el patrón
correspondiente y luego lo visualizará en la pantalla. Así se escriben todos textos (y así
hemos trabajado hasta ahora).
Ahora bien, si el código enviado vale entre 0x00 y0x07 (o 0x08 y 0x0F), el chip interno
buscará su patrón de visualización en la CGRAM. Siendo ésta una RAM de
lectura/escritura, podemos programar en ella los diseños que se nos ocurran.
Mapa de memoria para la creación de nuevos caracteres.
La CGRAM (Character Generator RAM) consta de 64 bytes en los que se pueden
escribir los patrones de 8 nuevos caracteres de 5×7 puntos ó 4 caracteres de 5×10
puntos. Aquí veremos el primer caso.
Cuando los caracteres son de 5×7 puntos los 64 bytes se dividen en 8 bloques de 8
bytes cada uno, y cada bloque almacena el patrón de un nuevo carácter. El esquema
mostrado arriba indica que:
El primer bloque de CGRAM, con direcciones desde 0b00000000 hasta 0b00000111,
corresponde al código 0x00 (ó 0x80) de la DDRAM.
El segundo bloque CGRAM, con direcciones desde 0b00001000 hasta 0b00001111,
corresponde al código 0x01 (ó 0x88) de la DDRAM; y así sucesivamente.
Por ejemplo, la figura de arriba indica que se han rellenado los dos primeros bloques
con los patrones de dos pacman. Hasta ahí solo se han creado dos nuevos caracteres.
Para mostrarlos en el LCD habría que escribir un código así:

lcd_data(0x00); // Visualizar primer pacman
lcd_data(0x01); // Visualizar segundo pacman
La práctica
Se muestra en el LCD un pacman que se desplaza de uno a otro lado.

Circuito para el LCD y el microcontrolador AVR.

El código fuente

/******************************************************************************
* FileName: main.c
* Purpose: LCD - Creación de caracteres gráficos personalizados
* Processor: AVR ATmegaXX4
* Compiler: AVR IAR C & AVR GCC (WinAVR)
* Author:

Shawn Johnson. http://www.cursomicros.com.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License: Se permiten el uso y la redistribución de este código con
*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y las notas de autor y copyright de arriba.

*****************************************************************************/

#include "avr_compiler.h"
#include "lcd.h"

#define LCD_LEN 16

// Para LCD de 2×16

/* Identificadores de los nuevos caracteres */
#define PacR1 0x00
#define PacR2 0x01
#define PacL1 0x02
#define PacL2 0x03

/* Patrones de los nuevos caracteres a crearse */
PROGMEMconstcharPattern1[]={0x0F,0x1C,0x18,0x10,0x18,0x1C,0x0F,0x00};
PROGMEMconstcharPattern2[]={0x00,0x0E,0x1F,0x10,0x1F,0x0E,0x00,0x00};
PROGMEMconstcharPattern3[]={0x1E,0x07,0x03,0x01,0x03,0x07,0x1E,0x00};
PROGMEMconstcharPattern4[]={0x00,0x0E,0x1F,0x01,0x1F,0x0E,0x00,0x00};

voiddelay_ms(unsignedintt)
{
while(t--)
delay_us(1000);
}

voidcreate_char(unsignedchara,PGM_Pp)
{
unsignedchari;
lcd_cmd(LCD_CGRAM+a);// Instrucción Set CGRAM Address
for(i=0;i<8;i++)
lcd_data(pgm_read_byte(p+i));
}

intmain(void)
{
signedchari;// Debe ser variable con signo

lcd_init();// Inicializar LCD

/* Crear los nuevos caracteres (los pacman's)
* Esto es volcar los patrones de los pacman en la CGRAM */
create_char(PacR1*8,Pattern1);
create_char(PacR2*8,Pattern2);
create_char(PacL1*8,Pattern3);
create_char(PacL2*8,Pattern4);

lcd_clear();// Limpiar pantalla y regresar a DDRAM
lcd_puts(" Hungry Pacman ");// Escribir "Hungry Pacman" en LCD

while(1)
{
/* Pacman desplazándose a la derecha */
for(i=0;i<LCD_LEN;i++)
{
lcd_cmd(LCD_LINE2+i);// Cursor a posición i de línea 2

if(i&0x01)// Si bit 0 de i es 1,
lcd_data(PacR1);// enviar pacman abierto
else// Si no,
lcd_data(PacR2);// enviar pacman cerrado

lcd_cmd(LCD_LINE2+i-1);// Cursor a posición anterior de línea 2
lcd_data(' ');// para borrar pacman previo

delay_ms(200);
}

/* Pacman desplazándose a la izquierda */
for(i=LCD_LEN;i>=0;i--)
{
lcd_cmd(LCD_LINE2+i);// Cursor a posición i de línea 2

if(i&0x01)// Si bit 0 de i es 1,
lcd_data(PacL1);// enviar pacman abierto
else// Si no,
lcd_data(PacL2);// enviar pacman cerrado

//lcd_cmd(LCD_LINE2+i+1); // Cursor a posición anterior de línea 2
lcd_data(' ');// para borrar pacman previo

delay_ms(200);
}
}
}

Descripción del programa
Después de iniciado el LCD, los datos que se le envíen irán a la DDRAM (para mostrar
caracteres en la pantalla). Como los patrones de los pacman deben ir en
la CGRAM necesitamos establecerla como destino. Para eso enviamos el comando Set
CGRAM Address con la dirección de CGRAM que queremos acceder. La
función create_char rellena con 8 bytes del array p que se le pasa como parámetro el
segmento de CGRAM que empieza en la dirección a.

voidcreate_char(unsignedchara,PGM_Pp)
{
unsignedchari;
lcd_cmd(LCD_CGRAM+a);// Instrucción Set CGRAM Address
for(i=0;i<8;i++)
lcd_data(pgm_read_byte(p+i));
}
Notemos que al término de la función create_char el puntero de RAM sigue dirigido a
la CGRAM. Por tanto para visualizar los caracteres en la pantalla, incluyendo los nuevos
caracteres creados, tenemos que volver a seleccionar la DDRAM. Para esto tenemos la
instrucción Set DDRAM Address, Return Home y Clear Display. Todas estas
instrucciones están relacionadas con el cursor, el cual a su vez no es otra cosa que el
puntero RAM trabajando sobre la DDRAM. Yo usé la tercera opción con
sentencia lcd_clear() que aparentemente no tenía sentido porque la pantalla ya está
limpia tras la inicialización.
Como hemos creado los 4 pacman en los 4 primeros bloques (de 8 bytes) de la
CGRAM, los códigos para accederlos serán 0 (PacR1), 1 (PacR2), 2 (PacL1)
y 3 (PacL2), respectivamente.
Por si no quedó claro cómo se forman los patrones de los pacman, aquí tenemos los
dos primeros. (Los bits × no importan, pueden ser 1s o 0s.)

PROGMEM constcharPattern1[]={0x0F,0x1C,0x18,0x10,0x18,0x1C,0x0F,0x00};
PROGMEM constcharPattern2[]={0x00,0x0E,0x1F,0x10,0x1F,0x0E,0x00,0x00};
Solo debemos tener un poco de paciencia para elaborar los arrays de los patrones.
Ahora que si no la tienes o prefieres un atajo, puedes utilizar una de las tantas
herramientas que abundan (me gusta la flexibilidad de LCD Font Creator). Inclusive los
mismos compiladores comoCodeVisionAVR o MikroC proveen los suyos. Abajo se ve
cómo el generador de caracteres de MikroC nos cambia el descifrado de código binario
por simples clics. El código generado incluye una función que crea y visualiza el nuevo
carácter, pero a mí solo me interesa el array.
Comunicación PC – AVR – LCD
Aquí tenemos un programa cliché en los ejemplos de interface entre un
microcontrolador y una computadora mediante el puerto serie. El programa terminal
envía por el puerto serie las letras que presionemos en el teclado. El AVR los recibirá,
los reflejará al PC y también los visualizará en el display LCD. Haremos que algunas
teclas generen instrucciones especiales:
La tecla Escape, de código 27, sirve pare limpiar la pantalla del LCD.
La tecla Retroceso o Backspace, de código0x08 = „b‟, lleva el cursor del LCD una
posición atrás.
La tecla + desplaza toda la pantalla a la derecha.
La tecla - desplaza toda la pantalla a la izquierda.
En esta ocasión será de utilidad tener el cursor a la vista.

Circuito para el LCD y el microcontrolador AVR.

El código fuente

/******************************************************************************
* FileName: main.c
* Purpose: LCD - Control de LCD desde PC mediante AVR
* Processor: AVR ATmegaXX4
* Compiler: AVR IAR C & AVR GCC (WinAVR)
* Author:

Shawn Johnson. http://www.cursomicros.com.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License: Se permiten el uso y la redistribución de este código con
*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y las notas de autor y copyright de arriba.

*****************************************************************************/

#include "avr_compiler.h"
#include "lcd.h"
#include "usart.h"

intmain(void)
{
lcd_init();// Ver interface en "lcd.h"
lcd_cmd(LCD_CURBLK);// Mostrar Cursor + Blink

usart_init();// 9600 - 8N1
puts("nr Acceso a LCD desde PC nr");
puts("nr Escribe en el LCD... nr");

while(1)
{
if(kbhit())// Si hay algún dato en el buffer de recepción
{
charc=getchar();// Obtener un dato

switch(c)
{
case'+':lcd_cmd(0x1C);// Desplazar display a la derecha
break;
case'-':lcd_cmd(0x18);// Desplazar display a la izquierda
break;
case'b':lcd_cmd(0x10);// Mover cursor atrás
lcd_data(' ');// Escribir espacio blanco
lcd_cmd(0x10);// Cursor atrás
putchar(c);// Devolver dato
break;
case0x1B:lcd_clear();// Limpiar LCD
puts("nr");
break;
default:lcd_data(c);// Escribir c en LCD
putchar(c);// Devolver dato
}
}
}
}
Descripción del programa
El archivo lcd.h contiene los códigos de los comandos más usuales del LCD
como 0x01(LCD_CLEAR), 0x80 (LCD_DDRAM) o 0x0F (LCD_CURBLK) que utilizamos
en este programa. De hecho si intentamos etiquetar cada una de las instrucciones
derivadas del LCD, formaríamos una lista casi interminable de códigos con nombres
que lejos de ayudar nos enredaría. Algunos lo hacen pero yo prefiero regresar a
la descripción de las instrucciones y tomar los códigos que me interesan. Para mí eso
es más sencillo.
En este programa los códigos de interés fueron:
0x1C para desplazar la pantalla a la derecha.
0x18 para desplazar la pantalla a la izquierda
0x10 para mover el cursor una posición atrás y
0x14 para mover el cursor una posición adelante (no se usó en el código).

Animación Gráfica en Display LCD
Hace algún tiempo vi en forosdeelectronica.comun post donde se presentaba un
programa de animación en un display LCD de 2x16. Ya anteriormente vimos cómo
crear animaciones con texto o caracteres gráficos pero lo que tenía de especial este
proyecto era que la figura animada era un caballo que ocupaba varias casillas del LCD.
No soy muy seguidor de los foros, ni siquiera del foro de cursomicros, y hasta donde
sé nadie había presentado su versión del caballo animado.
El proyecto, hecho por los chicos de microgenios, me llamó la atención no por la
complejidad que podía presentar sino, por el contrario, por lo sencillo que podía
resultar si se ideaba un algoritmo adecuado con ayuda del software adecuado. Por lo
demás el circuito es el mismo de siempre.
Circuito para el LCD y el microcontrolador AVR.
El algoritmo, como cualquiera lo podría imaginar, es mostrar las diversas posiciones de
un caballo corriendo en diferentes lugares del LCD. Podemos armar la figura de un
caballo juntando varios caracteres gráficos como si de un rompecabezas se tratara.
Hasta ahí el reto se pone interesante.
Pero crear un pacman de 5x8 pixeles es una cosa y otra muy diferente es crear un
caballo de varias piezas de ese tamaño. Hay quienes podrían recurrir a un programa
como Excel para confeccionar los patrones de las piezas de sus caballos. No es mala
idea. Yo por mi parte reafirmo lo que dije sobre LCD Font Maker en las prácticas de
los letreros matriciales de LEDs, que es el mejor programa de su clase que he podido
encontrar.
Lo primero que se me vino a la mente fue crear las imágenes individuales de los
caballos que luego compaginadas le darían vida. Son solo tres marcos o frames en
total. Los puedes ver abajo.
Curso micros
Curso micros
LCD Font Maker mostrando los tres fotogramas que constituirán la animación.
En cada caso el alto y el ancho son de 16 y 24 pixeles respectivamente. Si dividimos
cada caballo en 8 piezas, cada una será de 8x6, lo que en la práctica derivará en
caracterss de 5x7. Esto es porque no todos los LCD soportan los caracteres de 5x8
pixeles.
Cada fotograma se dividirá en 8 piezas de 6 bytes cada una.
La figura de arriba nos devela que un fotograma queda mejor dividido en 8 piezas si
cada una de ellas se compone de 6 bytes, es decir, cada carácter personalizado debe
ser formado por bytes tomados del esquema de forma vertical. En principio esto es
contrario a la estructura horizontal de los patrones que deben formar los nuevos
caracteres en la CGRAM, pero eso se puede arreglar por vía software. Esta división es
la mejor forma de ahorrar memoria y sobre todo es un esquema que se ajusta
perfectamente a una de las opciones de generación de matrices que ofrece el
programa LCD Font Maker. En el paso 2 de este programa debemos escoger la cuarta
opción, como se muestra abajo.
Elección del tipo de barrido para la generación de matrices.
En el paso 3 obtendremos las matrices de cada caballo que dibujamos. Hacemos las
modificaciones necesarias en ellas considerando que por su tamaño irán almacenadas
en la memoria FLASH y las colocamos en nuestro código fuente. Puedes revisar la
sección variables en FLASH si tienes dudas sobre este tema.

/* Patrones de los nuevos caracteres a crearse */
PROGMEMconstunsignedcharHorse0[]={
0x00,0x60,0x70,0x78,0x18,0x00,0x70,0x78,0x78,0x78,0x78,0x00,0x70,0x70,0x70,0x78,
0x78,0x00,0x7E,0x1E,0x0E,0x3E,0x3F,0x00,0x06,0x03,0x01,0x00,0x38,0x00,0x4F,0x07,
0x0F,0x0F,0x0B,0x00,0x13,0x03,0x03,0x3F,0x47,0x00,0x03,0x13,0x0F,0x00,0x00,0x00
};
PROGMEMconstunsignedcharHorse1[]={
0x40,0x60,0x70,0x70,0x30,0x00,0x70,0x78,0x78,0x78,0x70,0x00,0x70,0x70,0x78,0x78,
0x78,0x00,0x7E,0x1E,0x0E,0x3F,0x3E,0x00,0x01,0x00,0x00,0x00,0x38,0x00,0x4F,0x07,
0x03,0x0F,0x77,0x00,0x03,0x03,0x03,0x03,0x3F,0x00,0x53,0x0B,0x06,0x00,0x00,0x00
};
PROGMEMconstunsignedcharHorse2[]={
0x40,0x70,0x78,0x38,0x18,0x00,0x70,0x78,0x78,0x78,0x78,0x00,0x70,0x70,0x70,0x78,
0x78,0x00,0x7E,0x3E,0x0E,0x3E,0x3F,0x00,0x07,0x00,0x20,0x1F,0x07,0x00,0x03,0x0B,
0x1F,0x1F,0x27,0x00,0x49,0x13,0x17,0x1F,0x1F,0x00,0x07,0x0E,0x18,0x20,0x40,0x00
};
El código fuente
/******************************************************************************
* FileName: main.c
* Purpose: LCD - Creación de caracteres gráficos personalizados
* Processor: AVR ATmegaXX4
* Compiler: AVR IAR C & AVR GCC (WinAVR)
* Author:
*

Shawn Johnson. http://www.cursomicros.com.

Inspirado en un proyecto de www.microgenios.br.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License: Se permiten el uso y la redistribución de este código con
*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y las notas de autor y copyright de arriba.

*****************************************************************************/

#include "avr_compiler.h"
#include "lcd.h"

voiddraw_horse(unsignedcharp);
voidcreate_horse(PGM_Phorse);
voiddelay_ms(unsignedintt);

/* Patrones de los nuevos caracteres a crearse */
PROGMEMconstunsignedcharHorse0[]={
0x00,0x60,0x70,0x78,0x18,0x00,0x70,0x78,0x78,0x78,0x78,0x00,0x70,0x70,0x70,0x78,
0x78,0x00,0x7E,0x1E,0x0E,0x3E,0x3F,0x00,0x06,0x03,0x01,0x00,0x38,0x00,0x4F,0x07,
0x0F,0x0F,0x0B,0x00,0x13,0x03,0x03,0x3F,0x47,0x00,0x03,0x13,0x0F,0x00,0x00,0x00
};
PROGMEMconstunsignedcharHorse1[]={
0x40,0x60,0x70,0x70,0x30,0x00,0x70,0x78,0x78,0x78,0x70,0x00,0x70,0x70,0x78,0x78,
0x78,0x00,0x7E,0x1E,0x0E,0x3F,0x3E,0x00,0x01,0x00,0x00,0x00,0x38,0x00,0x4F,0x07,
0x03,0x0F,0x77,0x00,0x03,0x03,0x03,0x03,0x3F,0x00,0x53,0x0B,0x06,0x00,0x00,0x00
};
PROGMEMconstunsignedcharHorse2[]={
0x40,0x70,0x78,0x38,0x18,0x00,0x70,0x78,0x78,0x78,0x78,0x00,0x70,0x70,0x70,0x78,
0x78,0x00,0x7E,0x3E,0x0E,0x3E,0x3F,0x00,0x07,0x00,0x20,0x1F,0x07,0x00,0x03,0x0B,
0x1F,0x1F,0x27,0x00,0x49,0x13,0x17,0x1F,0x1F,0x00,0x07,0x0E,0x18,0x20,0x40,0x00
};

intmain(void)
{
unsignedchari,h;//
lcd_init();// Inicializar LCD

while(1)
{
/* Horse desplazándose a la derecha */
for(i=0;i<16;i++)// Para LCD de 2×16
{
h=i%3;// Caballo que corresponde
switch(h)
{
case0:create_horse((PGM_P)Horse0);break;// Crear caballo 0
case1:create_horse((PGM_P)Horse1);break;// Crear caballo 1
case2:create_horse((PGM_P)Horse2);break;// Crear caballo 2
}
draw_horse(i);// Dibujar en posición i el caballo recién creado
delay_ms(400);
}
}
}

/*****************************************************************************
* Dibuja el caballo presente en CGRAM a partir de la columna p.
* Cada caballo se forma por 8 caracteres gráficos identificados de 0 a 7.
****************************************************************************/
voiddraw_horse(unsignedcharp)
{
unsignedchari;
lcd_clear();// Limpiar pantalla y regresar a DDRAM
for(i=0;i<8;i++)// 8 piezas por cada caballo
{
if(i<4)
lcd_cmd(LCD_LINE1+p+i);
else
lcd_cmd(LCD_LINE2+p+i-4);
lcd_data(i);
}
}

/******************************************************************************
* Crear los caracteres que componen los caballos. Volcar sus patrones en CGRAM.
* Previamente se limpia el LCD para evitar parapadeos.
*****************************************************************************/
voidcreate_horse(PGM_Phorse)
{
unsignedchari,j,b,dato;

lcd_clear();// Limpiar pantalla
lcd_cmd(LCD_CGRAM);// Instrucción Set CGRAM Address

for(i=0;i<8;i++)// 8 piezas por caballo
{
/****************************************************
* Bucle para crear un carácter gráfico.
* 6 bytes por pieza, pero se toman los 5 primeros.
***************************************************/
for(b=0;b<8;b++)// 8 bits
{
dato=0x00;
for(j=0;j<5;j++)
{
if(pgm_read_byte(horse+6*i+j)&(1<<b))
dato|=(1<<(4-j));
}
lcd_data(dato);
}
}
}

voiddelay_ms(unsignedintt){
while(t--)
delay_us(1000);
}
Descripción del programa
Probablemente muchos habrían pensado en crear cada uno de los 8 caracteres gráficos
de cada caballo por separado creando 8 matrices por caballo y luego repetir todo el
proceso para cada uno de los caballos que componen la animación. A mí ese tipo de
algoritmos me parecen tediosos e inflexibles. Como es más fácil armar un
rompecabezas de piezas grandes, yo preferí crear matrices por cada caballo. Será cosa
del programa saber descomponer cada matriz en los 8 caracteres gráficos. De hecho,
pude haber creado una sola matriz conteniendo todos los fotogramas de la animación
pero quizá hubiera sido un poco exagerado.
La función que se encarga de esto es create_horse. Es fácil ver cómo se hace aquí la
división en 8 partes. Lo que parece algo intrincado es la forma como se transpone cada
una de esas piezas. Estoy hablando del bucle que convierte la estructura vertical de
cada una de esas piezas en una estructura horizontal antes de ser enviadas a la
memoria CGRAM del LCD. Este bucle solo considera los 5 primeros bytes porque el
sexto representa el espacio que hay entre los casilleros de todo display LCD.
/******************************************************************************
* Crear los caracteres que componen los caballos. Volcar sus patrones en CGRAM.
* Previamente se limpia el LCD para evitar parapadeos.
*****************************************************************************/
voidcreate_horse(PGM_Phorse)
{
unsignedchari,j,b,dato;

lcd_clear();// Limpiar pantalla
lcd_cmd(LCD_CGRAM);// Instrucción Set CGRAM Address

for(i=0;i<8;i++)// 8 piezas por caballo
{
/****************************************************
* Bucle para crear un carácter gráfico.
* 6 bytes por pieza, pero se toman los 5 primeros.
***************************************************/
for(b=0;b<8;b++)// 8 bits
{
dato=0x00;
for(j=0;j<5;j++)
{
if(pgm_read_byte(horse+6*i+j)&(1<<b))
dato|=(1<<(4-j));
}
lcd_data(dato);
}
}
}
create_horse vuelca los 8 patrones de cada caballo en los 64 primeros bytes de la
CGRAM. Esta es toda la memoria disponible así que el proceso se repite cada vez que
se va a mostrar un caballo diferente.
Imprimir los nuevos caracteres en la CGRAM no necesariamente significa visualizarlos
en el LCD. El LCD muestra lo que le indica su DDRAM y al principio ésta le dice mostrar
blancos, i.e. LCD limpio. Pero una vez iniciada la animación la DDRAM ya contiene los
códigos del 0 al 7 que representan el caballo, y como la visualización es un proceso de
barrido con una constante lectura de la DDRAM, los siguientes nuevos caracteres sí
serían visualizados de inmediato. Es para evitar los parpadeos que esto pudiera
producir que al inicio de la función create_horse se pone un lcd_clear().
La visualización de cada fotograma es un comando explícito de la función draw_horse.
En esta función se envían los 8 códigos, del 0 al 7, a la DDRAM del LCD
distribuyéndolos los 4 primeros en la fila superior y los cuatro siguientes en la fila
inferior, todos empezando por la casilla cuyo número se le envía como parámetro p.

/*****************************************************************************
* Dibuja el caballo presente en CGRAM a partir de la columna p.
* Cada caballo se forma por 8 caracteres gráficos identificados de 0 a 7.
****************************************************************************/
voiddraw_horse(unsignedcharp)
{
unsignedchari;
lcd_clear();// Limpiar pantalla y regresar a DDRAM
for(i=0;i<8;i++)// 8 piezas por cada caballo
{
if(i<4)
lcd_cmd(LCD_LINE1+p+i);
else
lcd_cmd(LCD_LINE2+p+i-4);
lcd_data(i);
}
}
Para terminar, cabe destacar la utilidad que ha tenido el operador módulo %. Como
sabemos, este operador devuelve el residuo de la división entre dos números, en este
caso entre i y 3, donde i es el recorrido del caballo por las 16 casillas del LCD y 3 son
los fotogramas que constituyen la animación. De este modo, mientras i avanza de 0 a
15, h se incrementa cíclicamente en el rango 0...2, con lo que se consigue la
animación repitiendo sus tres fotogramas básicos en las diferentes posiciones del LCD.

while(1)
{
/* Horse desplazándose a la derecha */
for(i=0;i<16;i++)// Para LCD de 2×16
{
h=i%3;// Caballo que corresponde
switch(h)
{
case0:create_horse((PGM_P)Horse0);break;// Crear caballo 0
case1:create_horse((PGM_P)Horse1);break;// Crear caballo 1
case2:create_horse((PGM_P)Horse2);break;// Crear caballo 2
}
draw_horse(i);// Dibujar en posición i el caballo recién creado
delay_ms(400);
}
}

Introducción
Hay una analogía que siempre recuerdo desde que la leí en un buen libro de Turbo
Pascal cuando aprendía a programar en dicho lenguaje. Cuando vamos a recibir una
visita en nuestra casa podemos ir a la puerta a cada momento para ver si ya llegó y
atenderla apropiadamente, o podemos quedarnos haciendo nuestras labores cotidianas
esperando a que sea la visita quien llame a la puerta para ir a recibirla.
Ir a la puerta constantemente se compara por ejemplo con testear los puertos del AVR
para ver si se presionó algún pulsador o algún teclado y actuar en consecuencia. Eso
se conoce como técnica Polling o de sondeo e implica el desperdicio de recursos y
ciclos de CPU.
En este capítulo aprenderemos a atender nuestras visitas justo cuando llamen a la
puerta para que el AVR no se canse en vano y que se ponga a “dormir”, si fuera
posible. Ésta es solo una pequeña muestra de lo que se puede conseguir con las
interrupciones.

¿Qué son las Interrupciones?
Una interrupción es una llamada “inesperada”, urgente e inmediata a una función
especial denominada Interrupt Service Routine (ISR).
El mecanismo funciona así: sin importar lo que esté haciendo en main o cualquier
función relacionada con main, cuando ocurra la interrupción el CPU hará una pausa y
pasará a ejecutar el código de ISR. Tras terminarlo, el CPU regresará a la tarea que
estaba realizando antes de la interrupción, justo donde la había suspendido.
Aunque es posible provocar interrupciones desde el programa ejecutándolas como si
fueran funciones ordinarias, las interrupciones son disparadas (llamadas)
por eventos del hardware del microcontrolador. El evento puede ser algún cambio en
cierto pin de E/S, el desbordamiento de un Timer, la llegada de un dato serial, etc. Se
puede deducir por tanto que las fuentes de interrupción están relacionadas con la
cantidad de recursos del microcontrolador.

Los Vectores de Interrupción
Cada interrupción está identificada por un Vector de Interrupción, que no es otra cosa
que una dirección particular en la memoria FLASH. Todos los Vectores están ubicados
en posiciones consecutivas de la memoria FLASH y forman la denominada Tabla de
Vectores de Interrupción. El RESET no es una interrupción pero su dirección 0x0000
también se conoce como Vector de Reset. Por defecto, la Tabla de Vectores de
Interrupciónestá ubicada en las primeras posiciones de la memoria, tal como se ve
abajo. Solo cuando se habilita el uso de la Sección de Boot Loader toda la tabla se
desplazará al inicio de dicha sección.
Enseguida se presenta una tabla con las 35 interrupciones posibles en los ATmegaXX4.
Debemos recordar que solo los ATmega1284 tienen el Timer3 y por tanto las 4
interrupciones relacionadas con el Timer3 no estarán disponibles en los otros megaAVR
de esta serie. Aprenderemos de a poco y para empezar en este capítulo nos
ocuparemos de las 7 interrupciones externas, desde INT0 hasta PCINT3. Las restantes
serán estudiadas en sus módulos respectivos.
Tabla Num

Num
Vector

Dirección de
Programa

Nombre de
Fuente de interrupción
Vector de Interrupción

1

0x0000

RESET

External Pin, Power-on Reset,
Brown-out Reset,
Watchdog Reset, and JTAG AVR
Reset

2

0x0002

INT0

External Interrupt Request 0

3

0x0004

INT1

External Interrupt Request 1

4

0x0006

INT2

External Interrupt Request 2

5

0x0008

PCINT0

Pin Change Interrupt Request 0

6

0x000A

PCINT1

Pin Change Interrupt Request 1

7

0x000C

PCINT2

Pin Change Interrupt Request 2

8

0x000E

PCINT3

Pin Change Interrupt Request 3

9

0x0010

WDT

Watchdog Time-out Interrupt

10

0x0012

TIMER2_COMPA Timer/Counter2 Compare Match A

11

0x0014

TIMER2_COMPB

Timer/Counter2 Compare Match B

12

0x0016

TIMER2_OVF

Timer/Counter2 Overflow

13

0x0018

TIMER1_CAPT

Timer/Counter1 Capture Event

14

0x001A

TIMER1_COMPA Timer/Counter1 Compare Match A

15

0x001C

TIMER1_COMPB

Timer/Counter1 Compare Match B
Tabla Num

Num
Vector

Dirección de
Programa

Nombre de
Fuente de interrupción
Vector de Interrupción

16

0x001E

TIMER1_OVF

17

0x0020

TIMER0_COMPA Timer/Counter0 Compare Match A

18

0x0022

TIMER0_COMPB

Timer/Counter0 Compare match B

19

0x0024

TIMER0_OVF

Timer/Counter0 Overflow

20

0x0026

SPI_STC

SPI Serial Transfer Complete

21

0x0028

USART0_RX

USART0 Rx Complete

22

0x002A

USART0_UDRE

USART0 Data Register Empty

23

0x002C

USART0_TX

USART0 Tx Complete

24

0x002E

ANALOG_COMP

Analog Comparator

25

0x0030

ADC

ADC Conversion Complete

26

0x0032

EE_READY

EEPROM Ready

27

0x0034

TWI

2-wire Serial Interface

28

0x0036

SPM_READY

Store Program Memory Ready

29

0x0038

USART1_RX

USART1 Rx Complete

30

0x003A

USART1_UDRE

USART1 Data Register Empty

31

0x003C

USART1_TX

USART1 Tx Complete

32

0x003E

TIMER3_CAPT

Timer/Counter3 Capture Event

33

0x0040

TIMER3_COMPA Timer/Counter3 Compare Match A

Timer/Counter1 Overflow
Tabla Num

Num
Vector

Dirección de
Programa

Nombre de
Fuente de interrupción
Vector de Interrupción

34

0x0042

TIMER3_COMPB

Timer/Counter3 Compare Match B

35

0x0044

TIMER3_OVF

Timer/Counter3 Overflow

Para entender cómo funciona el mecanismo de las interrupciones en bajo nivel,
recordemos que el Contador de Programa es un registro que dirige cada una de las
instrucciones que ejecuta el CPU. Pues bien, al dispararse la interrupción el hardware
guardará en la Pila el valor actual delContador de Programa y lo actualizará con el
valor del Vector de Interrupción respectivo, de modo que el CPU pasará a ejecutar el
código que se encuentre a partir de esa dirección. Al final del código de la interrupción
debe haber una instrucción de retorno que restaure el Contador de Programa con el
valor que se había guardado en la Pila. La instrucción es reti y la pone el compilador.
Si se llegara a producir el evento excepcional en que se disparen dos o más
interrupciones al mismo tiempo, se ejecutarán las interrupciones en orden de
prioridad. Tiene mayor prioridad la interrupción cuyo Vector se ubique más abajo, es
decir, entre todas, la interrupción INT0 tiene siempre las de ganar.
La estructura y características de la Tabla de Vectores de Interrupción pueden variar
entre las diferentes familias de megaAVR y a veces entre diferentes partes de una
misma serie. Por ejemplo, los megaAVR de la serie ATmegaXX8 no tienen
la interrupción externa INT2 y tampoco las interrupciones PCINT3 (porque les falta el
puerto A). Además, el ATmega48 no dispone de la funcionalidad de Boot Loader, así
que este AVR no puede desplazar su Tabla de Vectores de Interrupción. La ausencia de
algunas interrupciones hace que los otros Vectores cambien de valor.
En cualquier caso, para nosotros, los programadores en C o Basic, es suficiente tener
en cuenta los nombres de los Vectores de Interrupción, que en la tabla de arriba se
resaltan con enlaces en en azul.
Los nombres de los Vectores de Interrupción presentados corresponden al datasheet y
no necesariamente son idénticos a los que utilizan los compiladores AVR GCC o AVR
IAR C. Estos nombres se encuentran definidos en los archivos de dispositivo de cada
AVR, ubicados en la carpeta include de cada compilador. La instalación por defecto
de AVR GCC con Atmel Studio 6 en Windows 7 marca la ruta C:Program Files
(x86)AtmelAtmel Studio
6.0extensionsAtmelAVRGCC3.4.0.65AVRToolchainavrincludeavr. Allí los puedes
ubicar, y de hecho es recomendable examinarlos de vez en cuando.
Pero si por el momento deseas ahorrarte el trabajo te diré que la única diferencia es el
apéndice_vect. Es decir, en todos los archivos de dispositivo de AVR IAR C y de AVR
GCC (en sus versiones actuales) los nombres de los Vectores de Interrupción son los
mismos que aparecen en el datasheet pero con el añadido _vect, como se muestra en
la siguiente tabla de ejemplo. Está de más decir que en nuestros programas debemos
usar la forma con _vect.
Tabla Nombre de Vector de Interrupción en datasheet

Nombre de
Nombre de
Vector de Interrupción Vector de Interrupción
en datasheet
en archivo de dispositivo
INT0

INT0_vect

INT1

INT1_vect

INT2

INT2_vect

PCINT0

PCINT0_vect

PCINT1

PCINT1_vect

PCINT2

PCINT2_vect

PCINT3

PCINT3_vect

TIMER0_COMPA

TIMER0_COMPA_vect

TIMER0_COMPB

TIMER0_COMPB_vect

TIMER0_OVF

TIMER0_OVF_vect

USART0_RX

USART0_RX_vect

USART0_UDRE

USART0_UDRE_vect

USART0_TX

USART0_TX_vect

Lamentablemente para quienes programan en CodeVisionAVR, Pavel Haiduc decidió –
no sé por qué– usar otros nombres para los Vectores de Interrupción. No solo son
diferentes de los indicados en los datasheets sino que la Tabla de Vectores empieza en
2 y no en 1 (sin incluir el Vector de reset, claro está). Así que ellos no tendrán más
remedio que recurrir a los archivos de dispositivo de sus AVR, los cuales se hallan en la
fácilmente ubicable carpeta inc creada porCodeVisionAVR en su directorio de
instalación.
Las Funciones de Interrupción
La Función de Interrupción o ISR va siempre identificada por su Vector de
Interrupción, y su esquema varía ligeramente entre un compilador y otro, puesto que
no existe en el lenguaje C un formato estándar. Lo único seguro es que es una función
que no puede recibir ni devolver ningún parámetro.
En el compilador AVR GCC (WinAVR) la función de interrupción se escribe con la
palabra reservadaISR acompañada del Vector_de_Interrupcion. En las versiones
anteriores del compilador se solía usar SIGNAL en vez de ISR pero actualmente ese
método está considerado «deprecated» (censurado).
Recordemos que el Vector_de_Interrupcion debe tener la terminación _vect, como se
indicó anteriormente, y si tienes dudas puedes buscar en la carpeta include del
directorio de instalación de AVR GCC (WinAVR).

ISR(Vector_de_Interrupcion)
{
// Código de la función de interrupción.
// No requiere limpiar el flag respectivo. El flag se limpia por hardware
}
Por otro lado, en AVR IAR C y CodeVisionAVR la construcción es un poquito más
elaborada. Requiere el empleo de la directiva #pragma vector y la palabra
reservada __interrupt. ElNombre_de_Interrupcion queda a la libertad del programador.

#pragma vector = Vector_de_interrupcion
__interruptvoidNombre_de_Interrupcion(void)
{
// Código de la función de interrupción.
// No requiere limpiar el flag respectivo. El flag se limpia por hardware
}
Por fortuna, estas divergencias entre AVR IAR C y AVR GCC solo se presentan en el
encabezado de la función. La implementación del cuerpo de la función es idéntica en
ambos compiladores. Además existe la posibilidad de utilizar macros que adapten el
esquema de la función de interrupción de AVR IAR C al formato de AVR GCC. Estas
macros están escritas en el archivoavr_compiler.h del Atmel Software Framework o
ASF y que siempre se usa en los programas de cursomicros.com.
En realidad, CodeVisionAVR ofrece otras formas adicionales de implementar una
función de interrupción pero, por más que se parezcan en forma, lamentablemente
ninguna de ellas es compatible con el código de AVR IAR C o AVR GCC, debido a las
diferencias en los nombres de losVectores de Interrupción.

Control de las Interrupciones
Hay dos tipos de bits para controlar las interrupciones: los Bits Enable, que habilitan
las interrupciones, y los Bits de Flag, que indican cuál interrupción se ha producido.
Bueno, eso para decirlo a grandes rasgos.
Hay un bit enable individual para cada interrupción y además hay un bit enable
general I(ubicado en el registro SREG) que afecta a todas las interrupciones.
Para habilitar una interrupción hay que setear su bit enable individual como el bit
enable generalI. También se pueden habilitar varias interrupciones del mismo modo.
Ninguna habilitación individual tendrá efecto, es decir, no disparará una interrupción si
el bit I está en cero.
Por otro lado, cada interrupción tiene un Bit de Flag único, que se setea
automáticamente por hardware cuando ocurre el evento de dicha interrupción. Eso
pasará independientemente de si la interrupción está habilitada o no. Si la interrupción
fue previamente habilitada, por supuesto que se disparará.
Cada interrupción habilitada y disparada, saltará a su correspondiente Función de
Interrupción oISR, de modo que a diferencia de algunos otros microcontroladores no
será necesario sondear los flags de interrupción para conocer la fuente de interrupción.
Los AVR van más lejos y tienen un hardware que limpia automáticamente el bit
de Flag apenas se empiece a ejecutar la función de interrupción. Pero puesto que los
flags se habilitan independientemente de si las interrupciones están habilitadas o no,
en ocasiones será necesario limpiarlos por software y en ese caso debemos tener la
especial consideración de hacerlo escribiendo un uno y no un cero en su bit respectivo.
Sí, señor, dije, uno.
Al ejecutarse la función de interrupción también se limpia por hardware el bit enable
general Ipara evitar que se disparen otras interrupciones cuando se esté ejecutando la
interrupción actual. Sin embargo, la arquitectura de los AVR le permite soportar ese
tipo de interrupciones, llamadas recurrentes o anidadas, y si así lo deseamos podemos
setear en el bit I dentro de la ISR actual.
A propósito, el ya famoso bit enable general I se puede escribir como cualquier otro bit
de un registro de E/S. Pero dada su especial importancia, existen dos exclusivas
instrucciones de ensamblador llamadas sei (para setear I) y cli (para limpiar I). El
archivo avr_compiler.h ofrece las macros sei() y cli() para llegar a esas instrucciones.
Práctica: Uso de la interrupción INTx
En estas prácticas de ejemplo evitaremos programas sofisticados con códigos grandes
que desvíen la atención hacia una breve aplicación de la teoría expuesta. Por eso no
nos vendrá mal volver a los socorridos LEDs parpadeantes.
El programa tendrá dos tareas: la rutina principal se encargará de parpadear un LED y
la función de interrupción hará bascular otro LED cada vez que presionemos un
pulsador. Esto será como fusionar dos programas que alguna vez hicimos. “Correr dos
programas a la vez…” Dicen que algo así le paso por la cabeza a Bill Gates cuando
pensó en MS Windows.
De las señales que se generan al presionar el botón escogeremos el flanco de bajada
para disparar la interrupción INT0.

Circuito para probar las interrupciones externas del microcontrolador AVR.

El código fuente
Cada aplicación puede tener sus propias especificaciones, pero, en general, un buen
hábito de programación es poner la sentencia sie(); que setea el bit I del
registro SREG cuando ya todo esté listo para atender a la interrupción.
Al analizar la estructura del programa, notamos que la función ISR es totalmente
independiente de main, es decir, no es referenciada desde ningún punto de main.
Una vez habilitada, la interrupción se disparará cuando alguien presione el botón (en el
flanco de bajada). En ese preciso instante (quizá cuando se esté ejecutando PINC =
0x02 o quizá en algún punto dentro de delay_ms(600)) el CPU pasará a ejecutar la
función ISR. Al salir de ISR, el CPU regresará a continuar la tarea que estaba
ejecutando antes de la interrupción.

/******************************************************************************
* FileName: main.c
* Purpose: Uso de la interrupción INTx
* Processor: megaAVR
* Compiler: AVR IAR C y AVR GCC (WinAVR)
* Author:

Shawn Johnson. http://www.cursomicros.com.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License: Se permiten el uso y la redistribución de este código con
*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y las notas de autor y copyright de arriba.

*****************************************************************************/

#include "avr_compiler.h"

voiddelay_ms(unsignedintt)
{
while(t--)
delay_us(1000);
}

//****************************************************************************
// Interrupt Service Routine, ISR
// Esta función se ejecuta cuando se detecta un flanco de bajada en el pin INT0
//****************************************************************************
ISR(INT0_vect)
{
PINC=0x01;// Conmutar pin PC0
delay_ms(40);// Para pasar los rebotes
}

//****************************************************************************
// Función principal
//****************************************************************************
intmain(void)
{
DDRC=0x03;// Pines PC0 y PC1 para salida (LEDs)
PORTD=0x04;// Habilitar pull-up de pin PD2/INT0 (pulsador)

/* Habilitar y configurar la interrupción INT0 para que se dispare con
* cada flanco de bajada detectado en el pin INT0 (PD2)
*/
EIMSK=(1<<INT0);// Habilitar INT0
EICRA=(2<<INT0*2);// Elegir flanco de bajada (modo 2)

sei();// Habilitación general de interrupciones

while(1)// Bucle infinito
{
PINC=0x02;// Conmutar pin PC1
delay_ms(600);// Pausa de 600ms
}
}

El Modo Sleep
El modo Sleep es un estado en que se detiene el oscilador del sistema y, por tanto,
dejan de funcionar todas las partes del microcontrolador que dependen de él,
incluyendo en algunos casos el mismo procesador, es decir, se “congela” la ejecución
del programa. Sin embargo los valores de todos los registros y puertos del
microcontrolador permanecerán inalterables. En este estado se dice que el
microcontrolador está durmiendo, por el término sleep = sueño, en inglés.
La pregunta es ¿para qué sirve un microcontrolador con su hardware congelado? Pues
hay aplicaciones donde el microcontrolador debe atender ciertas tareas solo cuando
ocurre un evento externo como por ejemplo la pulsada de un botón. El resto del
tiempo no hace nada útil.
Al hacer que el microcontrolador se ponga a dormir y que despierte solo cuando cierto
evento se lo demande, se consigue ahorrar muchísima energía que se perdería con el
CPU y demás periféricos estando activos en vano. Esto es clave sobre todo en circuitos
alimentados por baterías.
Los microcontroladores AVR tienen un sistema oscilador sofisticado que divide el reloj
en varias ramificaciones que van a los diferentes módulos del AVR. De esa forma, su
modo Sleep tiene hasta 6 diferentes niveles dependiendo de las ramificaciones del reloj
que se pongan a congelar. Detallar cada una de ellas en este momento extendería
tanto el tema que movería el enfoque de las interrupciones. Solo diré que el mayor
nivel, es decir, donde el hardware del AVR se congela por completo se
denomina Power-down.
El modo Power-down se configura escribiendo el valor 0x02 en el registro SMCR y
luego solo bastará con ejecutar la instrucción de ensamblador sleep para que el AVR
“cierre sus ojos”.
El modo Sleep es muy propio de los microcontroladores y no existe en el lenguaje C
una sentencia para la instrucción sleep. Cada compilador la implementa a su modo. En
el archivoavr_compiler.h original se utiliza la macro sleep_enter(), pero yo le añadí
otra definida comosleep(), a secas.
Tocamos el modo Sleep ahora porque el evento por excelencia que puede despertar al
microcontrolador es el disparo de una interrupción proveniente de una parte del
microcontrolador que no esté congelado. Puesto que las
interrupciones INTx y PCINTx son externas, ellas pueden sacar al AVR incluso del
sueño más profundo, o sea del modo Power-down.
Cuando se dispare una interrupción lo primero que hará el CPU al despertar es ejecutar
la primera instrucción de ensamblador que sigue a sleep, e inmediatamente después
pasará a ejecutar la función de interrupción ISR.
Si aún recuerdas el retardo de arranque, te diré que es aquí donde entra en acción:
esto es, después de descongelarse el oscilador del sistema, habrá un tiempo
llamado retardo de arranque en que el AVR espera en estado de RESET hasta que el
oscilador se haya estabilizado por completo. Luego, recién, el CPU reiniciará su trabajo.
De los 6 modos Sleep del AVR, el retardo de arranque solo actúa en los niveles Powerdown y Power-save porque en los demás niveles, el oscilador del AVR no está del todo
detenido.

Práctica: Interrupciones Múltiples + Modo Sleep
Si al programa anterior le quitásemos la tarea de la rutina principal, el AVR ya no
tendría nada que hacer allí. Éste puede ser un buen momento para tomar una siesta.
Por otro lado, en esta ocasión experimentaremos con las tres
interrupciones, INT0, INT1 e INT2, al mismo tiempo como ejemplo de uso de
interrupciones múltiples.

Circuito para probar las interrupciones externas del microcontrolador AVR.

El código fuente
/******************************************************************************
* FileName: main.c
* Purpose: Uso de las interrupciones INTx + Modo Sleep
* Processor: ATmega164P
* Compiler: AVR IAR C y AVR GCC (WinAVR)
* Author:

Shawn Johnson. http://www.cursomicros.com.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License: Se permiten el uso y la redistribución de este código con
*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y las notas de autor y copyright de arriba.

*****************************************************************************/

#include "avr_compiler.h"

voiddelay_ms(unsignedintt)
{
while(t--)
delay_us(1000);
}

//****************************************************************************
// Gestor de Interrupción INT0
// Esta función se ejecuta cuando se detecta un flanco de bajada o de subida
// en el pin INT0
//****************************************************************************
ISR(INT0_vect)
{
PINC=0x01;// Conmutar pin PC0
delay_ms(40);// Para pasar los rebotes
}

//****************************************************************************
// Gestor de Interrupción INT1
// Esta función se ejecuta cuando se detectan flancos de subida en el pin INT1
//****************************************************************************
ISR(INT1_vect)
{
PINC=0x02;// Conmutar pin PC1
delay_ms(40);// Para pasar los rebotes
}

//****************************************************************************
// Gestor de Interrupción INT2
// Esta función se ejecuta cuando el pin INT2 se encuentra en nivel bajo
//****************************************************************************
ISR(INT2_vect)
{
PINC=0x04;// Conmutar pin PC2
delay_ms(40);// Para pasar los rebotes
}
//****************************************************************************
// Función principal
//****************************************************************************
intmain(void)
{
DDRC=0x07;// Pines PC0, PC1 y PC2 para salida (LEDs)

/* Habilitar pull-ups de pines INT0/PD2, INT1/PD3 e INT2/PB2 para los
* pulsadores. Se asume que estos pines están configurados como entradas
*/
PORTD=0x0C;//
PORTB=0x04;//

/* Habilitar las interrupciones INT0, INT1 e INT2 y configurarlas para que
* se disparen:
* INT0 con cada flanco (de bajada o subida) en el pin INT0/PD2 (modo 1)
* INT1 con cada flanco de subida en el pin INT1/PD3

(modo 3)

* INT2 mientras haya un nivel bajo en el pin INT2/PB2

(modo 0)

*/
EIMSK=(1<<INT0)|(1<<INT1)|(1<<INT2);// Habilitar INT0, INT1 e INT2
EICRA=(1<<INT0*2)|(3<<INT1*2)|(0<<INT2*2);// Elegir flancos

sei();// Habilitación general de interrupciones

while(1)// Bucle infinito
{
/* Entrar en modo sleep (Power-Down mode) */
SMCR=(1<<SM1)|(1<<SE);
sleep();

nop();
}
}
El AVR despertará con el disparo de la interrupción, ejecutará nop() (que también
equivale a una instrucción de ensamblador) y luego llamará a la
función ISR respectiva. Lo demás es historia conocida.
Aunque en algunos casos el nop() es recomendable, en esta ocasión lo puse solo para
esta explicación.

Interrupciones de Cambio de Pin, PCINTx
Esta interrupción se dispara cada vez que se detecta un cambio de nivel lógico 1 a 0 o
viceversa en cualquiera de los pines de los puertos del AVR, sin importar si están
configurados como entrada o como salida. Aunque no es propiamente reconocido,
también se podría decir que se dispara con los flancos de subida y de bajada en los
pines de puertos. En ese sentido, se parece bastante a las interrupciones INTx. La
interrupción de Cambio de Pin también puede sacar al AVR del modo Sleep.
Esta interrupción no está presente en los AVR antiguos como los ATmega32,
ATmega16, ATmega8535, etc.
Aparte del bit enable general I, del registro SREG, las interrupciones de cambio de pin
se habilitan pasando por las dos siguientes etapas, no necesariamente en el orden
citado.
Primero, se debe setear el bit que identifica el puerto donde se encuentran los pines
que generarán las interrupciones. Estos son bits de enable ubicados en el
registro PCICR (Pin Change Interrupt Control Register). Para los megaAVR de 4
puertos la correspondencia es la siguiente.
Registro PCICR

PCICR

---

---

---

---

PCIE3

PCIE2

PCIE1

PCIE0
Y para los megaAVR de 3 puertos como los de la serie 8xx, la correspondencia entre
los bits PCIEx (Pin Change Interrupt Enable) y los puertos del AVR es
Registro PCICR

PCICR

---

---

---

---

---

PCIE2

PCIE1

PCIE0

Luego se deben setear los bits enable que identifican individualmente los pines de los
puertos. Estos bits se encuentran en los registros de máscara PCMSK (Pin Change
Mask Register). Hay un registro de máscara por cada puerto del AVR aunque la
relación varía según el número de puertos del AVR, como se indica en la siguiente
tabla.
Tabla Registro de máscara

megaAVR de 4 puertos

megaAVR de 3 puertos

Registro de máscara Puerto Registro de máscara Puerto
PCMSK0

PORTA PCMSK0

PORTB

PCMSK1

PORTB PCMSK1

PORTC

PCMSK2

PORTC PCMSK2

PORTD

PCMSK3

PORTD ---

---

Cada bit del registro de máscara PCMSK corresponde a su respectivo pin de PORT. Por
ejemplo, si en un AVR de 4 puertos seteamos los bits 4 y 7 de PCMSK2, estaremos
habilitando las interrupciones de los pines 4 y 7 de PORTC. Esta correspondencia se
cumple incluso en los AVR cuyos puertos no tengan los 8 pines completos.
Otra forma de seleccionar los pines de interrupción es ubicándolos directamente por
sus nombres PCINTx. Para esto también debes estar revisando el diagrama de pines
del AVR.
Registro PCMSK0

PCMSK0 PCINT7 PCINT6 PCINT5 PCINT4 PCINT3 PCINT2 PCINT1 PCINT0
Registro PCMSK1

PCMSK1 PCINT15 PCINT14 PCINT13 PCINT12 PCINT11 PCINT10 PCINT9 PCINT8
Registro PCMSK2

PCMSK2 PCINT23 PCINT22 PCINT21 PCINT20 PCINT19 PCINT18 PCINT17 PCINT16
Registro PCMSK3

PCMSK3 PCINT31 PCINT30 PCINT29 PCINT28 PCINT27 PCINT26 PCINT25 PCINT24
Observa que cada bit PCINTx corresponde a un pin del AVR con el mismo nombre.

Una vez producido el cambio de nivel en uno o varios de los pines habilitados para
interrupción, se activará el flag respectivo PCIF (Pin Change Interrupt Flag) del
registro PCIFR y luego se llamará a la función de interrupción ISR. Así como hay un bit
enable para cada puerto, en este nivel también hay un bit de flag correspondiente. Es
de prever que el siguiente esquema pertenece a los AVR de 4 puertos. Allí, por
ejemplo, si un pin de PORTB cambia de nivel, entonces se activará el flag PCIF1.
Registro PCIFR
PCIFR

---

---

---

---

PCIF3

PCIF2

PCIF1

PCIF0

Por supuesto, en los AVR de 3 puertos hay variación en el mapa del registro PCIFR (Pin
Change Interrupt Flag Register).
Registro PCIFR

PCIFR

---

---

---

---

---

PCIF2

PCIF1

PCIF0

Como de costumbre, el flag PCIF será limpiado por el hardware al ejecutarse el gestor
de interrupción ISR. Sin embargo, como este flag puede activarse sin necesidad de que
esté seteado el bit enable general I (del registro SREG), a veces se tendrá que limpiar
por software. En ese caso se limpia escribiendo un 1. Para evitar llegar a esta situación
es recomendable habilitar la Interrupción de Cambio de Pin después de realizar en los
puertos todas las operaciones necesarias que pudieran ocasionar cambios de nivel en
sus pines, por ejemplo, activar las pull-ups.
Finalmente, debemos tener en cuenta que hay un Vector de Interrupción [de Cambio
de Pin] por cada puerto de AVR,
llamados PCINT0_vect, PCINT1_vect, PCINT2_vect y PCINT3_vect. La activación del
flag PCIFx conducirá a la ejecución de la función ISR identificada por el
vectorPCINTx_vect.

Práctica: Interrupciones de Cambio de Pin
En el programa el AVR permanece en estado Sleep (Power-down) y despierta cada vez
que se presionen los pulsadores conectados a los pines PD5, PD6 y PD7. (Puede haber
más o menos pulsadores y se pueden elegir cualesquiera otros) Cada pulsador hará
conmutar un LED conectado al puerto B. Los diodos LED deben conmutar solo al
presionar los pulsadores y no al soltarlos.
Circuito para probar las interrupciones del microcontrolador AVR.

El código fuente
/******************************************************************************
* FileName: main.c
* Purpose: Uso de las Interrupciones de Cambio de Pin, PCINTx
* Processor: megaAVR
* Compiler: AVR IAR C y AVR GCC (WinAVR)
* Author:

Shawn Johnson. http://www.cursomicros.com.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License: Se permiten el uso y la redistribución de este código con
*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y las notas de autor y copyright de arriba.

*****************************************************************************/
#include "avr_compiler.h"

//****************************************************************************
// Interrupt Service Routine
// Esta función se ejecuta cuando se detectan cambios de nivel (flancos de
// subida o de bajada) en los pines PD5, PD6 o PD7.
//****************************************************************************
ISR(PCINT3_vect)
{
chardato=~(PIND|0x1F);// Enmascarar bits PD5, PD6 y PD7

switch(dato)
{
case(1<<5):PINC=0x01;break;// Cambio de pin PD5
case(1<<6):PINC=0x02;break;// Cambio de pin PD6
case(1<<7):PINC=0x04;break;// Cambio de pin PD7
default:nop();// Cambio de más de un pin
}
delay_us(40000);// 40ms para pasar los rebotes
}

//****************************************************************************
// Función principal
//****************************************************************************
intmain(void)
{
DDRC=0x07;// Pines PC0, PC1 y PC2 para salida (LEDs)

/* Habilitar pull-up de pines PD5, PD6 y PD7 (pulsadores) */
PORTD=(1<<5)|(1<<6)|(1<<7);

/* Habilitar interrupciones PCINT de PORTD en los pines PD5, PD6 y PD7 */
PCICR=(1<<PCIE3);
PCMSK3=(1<<5)|(1<<6)|(1<<7);

sei();// Habilitación general de interrupciones

while(1)// Bucle infinito
{
/* Entrar en modo sleep (Power-Down mode) */
SMCR=(1<<SM1)|(1<<SE);
sleep();
}
}

Práctica: Control de Teclado por Interrupciones
Alguna vez leí en uno de los documentos de Microchip que la interrupción
de Cambio de PORTB de sus PICmicros fue pensada para controlar los pequeños
teclados matriciales. En ese entonces los megaAVR aún no tenían una característica
similar.
Pero como sabemos ahora, los AVR como los que estamos estudiando llegaron más
lejos y nos permiten manejar no solo uno sino varios teclados matriciales con el
mínimo hardware, en estado Sleep y desde cualquier puerto del microcontrolador.
En este programa el AVR debe permanecer durmiendo (modo Power-down) y despertar
solo cuando se pulsa una tecla para leerla y mostrarla en el terminal serial.

Circuito para el teclado matricial y el microcontrolador AVR.

El código fuente

/******************************************************************************
* FileName: main.c
* Purpose: Control de Teclado mediante Interrupciones PCINTx
* Processor: ATmega164P
* Compiler: AVR IAR C y AVR GCC (WinAVR)
* Author:

Shawn Johnson. http://www.cursomicros.com.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License: Se permiten el uso y la redistribución de este código con
*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y las notas de autor y copyright de arriba.

*****************************************************************************/

#include "avr_compiler.h"
#include "usart.h"
#include "keypad.h"

voidSetupInt(void);

//****************************************************************************
// Interrupt Service Routine
// Esta función se ejecuta cuando se detectan cambios de nivel (flancos de
// subida o de bajada) en los pines PB4...PB7.
//****************************************************************************
ISR(PCINT1_vect)
{
chardato=keypad_read();// Leer teclado
if(dato)// Si fue tecla válida
{
/* Esperar a que haya espacio en el buffer de transmisión */
while((UCSR0A&(1<<UDRE0))==0);
/* Colocar dato en el buffer de transmisión */
UDR0=dato;
/* Esperar a que el teclado quede libre */
keypad_released();
}
SetupInt();
}

//****************************************************************************
// Función principal
//****************************************************************************
intmain(void)
{
usart_init();// Setup USART0 @ 9600-8N1
puts("rn Control de Teclado mediante Interrupciones PCINTx rrn");

SetupInt();

/* Habilitar las interrupciones PCINT de PORTB en los pines PB4...PH7 */
PCICR=(1<<PCIE1);
PCMSK1=0xF0;

sei();// Habilitación general de interrupciones

while(1)// Bucle infinito
{
/* Entrar en modo sleep (Power-Down mode) */
SMCR=(1<<SM1)|(1<<SE);
sleep();
}
}

//****************************************************************************
// Prepara el puerto B para que detecte un cambio de tensión producido al
// presionar una tecla.
//****************************************************************************
voidSetupInt(void)
{
/* Configurar PORTB: Nibble de Rows salida, Nibble de Rows bajo
* nible alto entrada y habilitar pull-ups de nibble alto */
PORTB=0xF0;
DDRB=0x0F;
}
Según el circuito y la librería del teclado, cuando no hay teclas pulsadas las
líneas Col (nibble alto de PORTB) se leen como „1‟ lógico (gracias a las pull-ups), y así
deberían permanecer mientras el AVR está “soñando”. Por tanto, para que haya un
cambio de nivel al pulsar una tecla, las líneas de Row (nibble bajo de PORTB) deberían
sacar „0‟ lógico. De esto se encarga la funciónSetupInt.

//****************************************************************************
// Prepara el puerto B para que detecte un cambio de tensión producido al
// presionar una tecla.
//****************************************************************************
voidSetupInt(void)
{
/* Configurar PORTB: Nibble de Rows salida, Nibble de Rows bajo
* nible alto entrada y habilitar pull-ups de nibble alto */
PORTB=0xF0;
DDRB=0x0F;
}

Introducción
La interface con una computadora se puede realizar por cualquiera de sus puertos
externos más conocidos: serie, paralelo o el USB. El paralelo casi ni se encuentra en
las computadoras de hoy y por el momento el puerto USB nos queda fuera de alcance
por la complejidad del desarrollo del firmware (programa del microcontrolador). Así
nos quedamos con el puerto serie.
Aprenderemos a volcar datos desde nuestro microcontrolador a la pantalla de la
computadora (data logging), así como a enviar datos mediante el teclado del PC hacia
el microcontrolador.

El Estándar RS-232
Toda comunicación elaborada entre dos dispositivos requiere conocer el protocolo que
la gobierna a nivel hardware y software. Para el puerto serie se trata del Estándar RS232, o más bien EIA/TIA-232 por las siglas de Electronics Industry
Association y Telecommunications Industry Association, sus desarrolladores.
El RS-232 fue originariamente pensado para regir las comunicaciones entre
computadoras y equipos de módem de la época (hace más de 40 años). Con el tiempo
han surgido otras versiones como RS-232-C, RS-232-D, RS-232-E, etc., una más
reciente que la otra, pero con variaciones inapreciables por ser uno de los estándares
menos estrictos. Después de todo, es solo un Estándar Recomendado o
“Recommended Standard”; de ahí la RS.
En la literatura técnica se acostumbra mucho utilizar los términos DTE y DCE para
referir a los dispositivos que se comunican según el Estándar RS-232. DTE (Data
Terminal Equipment) suele representar a la computadora y DCE (Data Circuitterminating Equipment) designa a cualquier dispositivo conectado a la computadora
(un módem se sobrentendía antes).
Sin embargo, estos conceptos no quedan del todo claros en redes del tipo
computadora-computadora o microcontrolador-microcontrolador usando el puerto
serie. Así que por comodidad en adelante hablaremos de computadora y módem,
viendo como módem ¾hasta donde quepa¾ a cualquier dispositivo conectable al
puerto serie (el circuito de nuestro microcontrolador).
Ahora pasemos a describir los principales aspectos que nos “recomienda” el estándar.

Voltajes de los Niveles Lógicos RS-232
En las comunicaciones seriales RS-232 los valores para representar los 1‟s y 0‟s lógicos
son muy diferentes de los que estamos acostumbrados a usar en el mundo TTL. Allí no
existen los 5V (para el 1) y 0V (para el 0).
Para entenderlo más fácilmente veamos la siguiente figura, donde se compara la forma
de onda de una señal RS-232 con la forma de onda de una señal digital convencional.

Niveles de tensión para los 1s y 0s lógicos.
Puedes notar la enorme diferencia: los 1 lógicos se representan con voltajes negativos
y los 0 lógicos, por voltajes positivos; además del amplio rango de los voltajes.
Un 1 lógico se expresa por una tensión de –5V a –15V. Este estado se llama spacing.
Un 0 lógico se da cuando la tensión en cualquiera de las líneas es de +5V hasta +15V.
Este estado se conoce como marking.

Formato de Transferencia de Datos
Como en toda comunicación serial, los datos viajan en grupos de bits. En este caso
cada grupo o carácter consta de un bit Start, los bits de Datos (8 por lo general),
un bit de Paridad (opcional) y finaliza con uno o dos bits de Stop.
Formato de un byte de dato en el Estándar RS-232.
Bit Start. Es la transición de 1 a 0 e indica el inicio de una transferencia. En la lógica
RS-232 podría significar una transición de -15V a +15V y en lógica TTL es una
transición de 5V a 0V.
Bits de Datos. Forman los datos en sí que se desean transmitir. Cada dato puede ser
de 5, 6, 7 u 8 bits. Por supuesto, siempre preferimos trabajar con 8 bits (1 byte). El
primer bit a transmitir es el menos significativo o LSbit (Least Significant Bit).
Bit de Paridad. Este bit es opcional y se puede enviar después de los bits de datos.
Sirve para ayudar a detectar posibles errores en las transferencias de datos. Es muy
raramente usado, primero, porque es poco efectivo (solo podría detectar errores, no
corregirlos) y, segundo, porque hay mejores formas de tratamiento de errores.
Bits Stop. Los bits de Stop son estados de 1 lógico. El Estándar dice que puede haber
1, 1.5 ó 2 bits de Stop al final de los datos (o del bit de paridad si lo hubiera).

Velocidad de Transmisión (Baud Rate)
El Baud Rate es el número de bits que se transmiten por segundo.
Debido a que estamos hablando de un tipo de transmisión asíncrona, no existe una
señal de reloj que sincronice los bits de datos. Para que los dispositivos transmisor y
receptor se entiendan correctamente también es necesario que operen con el mismo
baud rate. Los valores más comunes que fija el Estándar RS-232 son: 1200, 2400,
4800, 9600, 19200, 38400, 56000, 57600, 115200, 128000, 256000. Aunque las
versiones más recientes del Estándar ponen un límite de 20 kbits, es común emplear
los valores altos como 115200 (siempre que sea posible).
Sabemos que no es lo mismo la interface entre una computadora y un
microcontrolador usando un cable de 2 m de longitud que conectarlo a un PLC a 8 m
de distancia: la longitud del cable y la interferencia presente en el ambiente son
factores a considerar a la hora de escoger el baud rate.

Señales del Puerto Serie
Internamente el puerto serial de una computadora es controlado por un circuito
integrado (por ejemplo el 16750, de 40 pines). De esas líneas solo 9 salen al exterior y
desembocan en un conector DB9 macho (el que nosotros vemos y donde
conectábamos nuestro programador serial). Raras veces se ve que salen más líneas
para llegar a un conector DB25.
El uso de las 9 señales tiene más sentido cuando se trabaja con un módem. Por eso
vamos a seguir hablando de módem, pese a que bien puede ser reemplazado por otro
dispositivo.

Pines del conector DB9 (macho) del puerto serie.
En la figura mostrada las direcciones de las flechas señalan si los pines son de entrada
o de salida. Del mismo modo, los colores ayudan a asociar los pines con funciones
análogas o complementarias, así:
TD y RD se encargan de transmitir y recibir los datos, respectivamente.
RTS y CTS sirven para controlar el Control del Flujo de Datos (Handshaking) hardware.
DTR, DSR y DCD intervienen en el establecimiento de la comunicación.
Además de ellos, están la infaltable tierra (SG) y RI, usada exclusivamente en
conexiones con un módem.
Ahora bien, cuando vamos a conectar la computadora a un microcontrolador nuestro
interés se puede reducir a tres líneas: TD, RDy SG. Las demás: o pueden ignorarse, o
pueden conectarse al mismo puerto serie artificiosamente para evitar problemas de
comunicación, o pueden usarse para implementar un Control del Flujo de Datos
(Handshaking) hardware, con el microcontrolador emulando algunas funciones de
módem. En cualquiera de los tres casos, eso dependerá del software de computadora
usado para controlar el puerto serie.
En el conector hembra la posición de los pines se puede ver diferente a fin de
establecer una conexión cruzada, es decir, para que el TD de un dispositivo se una con
elRD del otro y viceversa. Lo mismo debe pasar con los pares RTS-CTS y DTR-DSR.

Control del Flujo de Datos (Handshaking)
Generalmente la computadora superará a su interlocutor (módem u otro) tanto en
velocidad de transferencia como en los buffers de recepción de datos. Para que el
módem no empiece a perder los datos llegados el Estándar contempla mecanismos de
control de flujo de datos ya sea vía hardware o software.
El control de flujo de datos por software se identifica por el uso de los
caracteres Xon(ASCII 0x11) y Xoff (ASCII 0x13). El diálogo es así: cuando, por alguna
razón, el módem ya no desea recibir más datos de la computadora entonces le envía el
carácter Xoffdiciéndole que suspenda la transmisión al menos temporalmente. Cuando
el módem esté nuevamente dispuesto a aceptar más datos le enviará el carácter Xon y
la computadora reanudará la transferencia.
Lo bueno del método es que el hardware requerido es el mínimo (ver la siguiente
figura) y lo malo es que ambos dispositivos deben soportar las transferencias full
dúplex que exige el estándar RS232 para este caso.

Conexión básica para el Handshaking software.
El control de flujo por hardware hace participar activamente a las líneas RTS y CTS.
En un tipo de comunicación simplex el protocolo es éste: cuando la computadora
quiere enviar datos al módem pone un 1 en RTS. Si el módem está dispuesto a recibir
esos datos, le responderá con un 1 por la línea CTS y la computadora empezará a
transmitir los datos; de otro modo, le responderá con un 0 y la computadora tendrá
que posponer el envío de datos.
Cuando la comunicación es half dúplex o full dúplex el protocolo varía, pero eso ya no
lo tocaremos aquí para no seguir enredándolo. Solo vemos lo suficiente del estándar,
en este caso para entender las conexiones alternativas entre una computadora y un
microcontrolador que se usan en algunas ocasiones y que veremos en la siguiente
sección.
Cableado para el handshaking hardware entre dos dispositivos.
En el diagrama se han sumado las líneas DTR, DSR y DCD (de color marrón), por las
que los dispositivos se informan el uno al otro si están listos o no para iniciar la
comunicación.

nterface Serial Microcontrolador-computadora
Como es de esperar, el enfoque se divide en dos partes:

Requerimientos Hardware
Nos vamos a enfocar en dos aspectos.
Primero veamos el tema del transceiver. Dado que los niveles de tensión en el
Estándar RS-232 (de –12V, 0V y +12V en la computadora) no son compatibles con los
niveles habituales de los microcontroladores (de 0 y 5V), se requiere de un transceiver
que convierta estas tensiones de unos niveles a otros y viceversa.
Sin duda, el MAX232 es el más famoso de todos. Como se ve en su esquema,
mostrado abajo, el MAX232 puede trabajar con una fuente de alimentación de 5V y
provee dos canales de transmisión y dos de recepción, aunque solo se suele usar un
par. A su gran tamaño se suma como desventaja el uso de condensadores externos,
para “bombear” la carga necesaria en los circuitos doblador e inversor de voltaje.
Interface entre un microcontrolador y un computador mediante el transceiver MAX232.
El mismo fabricante del MAX232, Dallas Semiconductors, ofrece sus versiones
mejoradas como el MAX203, que no requiere de capacitores externos, o el MAX202,
que brinda protección contra cargas electrostáticas.
Mejor aun para pequeños circuitos sería el DS275 (de 8 pines), el cual tampoco
requiere de capacitores externos y cuenta con el par justo de drivers de transmisión y
recepción de datos. Su principal inconveniente es que está diseñado para operar solo
en transferenciashalf dúplex .
Para conocer más del funcionamiento interno de los transceivers es recomendable que
consultes sus respectivos datasheets.
Interface entre un microcontrolador y un computador mediante el transceiver DS275.
El segundo aspecto hardware que interesa es el relacionado con el Control del Flujo de
Datos (Handshaking): en los dos esquemas presentados anteriormente las retroconexiones en el conector DB9 (de color violeta) son opcionales. Solo harán falta
cuando el programa terminal de la computadora esté configurado para utilizar los pines
indicados, así:
RTS (7) se conecta a CTS (8) para que siempre que la computadora desee enviar datos
al microcontrolador, se responda a sí mismo con un “permiso concedido”.
Análogamente, DTR (4) se une a DSR (6) para que cuando la computadora informe un
“estoy listo para la comunicación”, su eco (haciéndose pasar por el microcontrolador)
le responda con un “yo también lo estoy”. A veces DTR también se dirige a DCD (1).

Requerimientos Software
Por un lado necesitamos unas rutinas para el microcontrolador que gestionen las
funciones del Estándar RS-232. Éstas pueden implementarse tranquilamente a nivel
software debido a su simplicidad o mediante el módulo USART, el cual por supuesto
ofrecerá mucha más eficiencia y flexibilidad.
Por otro lado, necesitaremos un programa de computadora que se encargue de
controlar su puerto serie. A su vez, este programa puede ser uno desarrollado por
nosotros mismos, que nos permitiría tener el control total del puerto serie y podríamos
transmitir y recibir todo tipo de datos (binarios o de texto). También podríamos
implementar técnicas alternativas de control de flujo de datos (aparte de los descritos
arriba), o sofisticados mecanismos para el control de errores en la transferencias de
datos. Como ves, se ve muy atractivo, pero también requiere de conocimientos a
mediano nivel sobre programación en lenguajes como Visual C++, Delphi o Visual
Basic.
Como alternativa práctica, podemos usar softwares como el Hyperterminal de
Windows,Serial Port Monitor, Putty o Tera Term. Estos son programas de tipo consola
que nos permiten visualizar los datos que se transfieren hacia/desde el puerto serie.
Por no ofrecer tanta flexibilidad nos limitaremos a trabajar con datos de texto.
Conforme vamos escribiendo los caracteres en la consola, se irán enviando hacia
nuestro microcontrolador. Así mismo, los caracteres enviados desde el
microcontrolador se irán mostrando en la consola, todo en tiempo real.
Interface del programa Tera Term

Uso del Programa Tera Term
Ya tenemos todo listo para el microcontrolador. Ahora nos falta ejecutar algún
programa terminal en nuestra computadora para empezar el intercambio de datos. De
los muchos que hay vamos a elegir el Tera Term, que es lo mejor que he podido
encontrar y no lo digo precisamente porque sea de licencia GPL. Lo puedes bajar
libremente desde su webhttp://ttssh2.sourceforge.jp, o haciendo clic aquí.
“Tera Term es un emulador de terminal (programa de comunicaciones) que soporta:
Conexiones con el puerto serie.
Conexiones TCP/IP (telnet, SSH1, SSH2).
Comunicaciones IPv6
Emulación de VT100 y de VT200/300.
Emulación de TEK4010.
Protocolos de Transferencia de Archivos (Kermit, XMODEM, YMODEM, ZMODEM, BPLUS y Quick-VAN).
Scripts usando el „Lenguaje Tera Term‟.
Sets de caracteres Japonés, Inglés, Ruso y Coreano.
Codificación de caracteres UTF-8.”
Bien, una vez descargado, los instalas aceptando lo que se te pida hasta llegar a

Si no los vas a utilizar o si no tienes idea de lo que significan, te recomiendo
desmarcar las casillas de TTSSH, CygTerm+, LogMeTT, TTEdit y TTProxy. De lo
contario, la instalación te pedirá aceptar los términos y la configuración de cada
aplicación por separado.
Suponiendo que seguiste mi recomendación, la instalación terminará enseguida y solo
se creará un icono de acceso en el escritorio.
Cada vez que iniciemos Tera Term se nos presentará la siguiente ventana, donde
debemos escoger la opción Serial y en Port debemos seleccionar el puerto serie COM a
usar. Normalmente las computadoras actuales solo tienen un puerto COM disponible, el
COM1. Después de hacer clic en OK se abrirá el puerto serial. Tera Term es muy
potente y será capaz de quitarle el control del puerto a alguna otra aplicación que lo
esté utilizando.
Y así de rápido estaremos al frente de la siguiente ventana, que ya podemos utilizar
para nuestras comunicaciones seriales, ya que su configuración por defecto suele ser la
más habitual. La barra de título indica que el baud rate usado es de 9600 y que se
tiene seleccionado el puerto COM1.

De vez en cuando será necesario cambiar la configuración de comunicación que usa
Tera Term por defecto. Para ello debemos ir al menú Setup Serial Port… El
parámetroTransmit delay es el retardo que habrá entre cada uno de los datos que se
envíen y/o entre cada línea de caracteres. Quizá interese incrementar este retardo
cuando el microcontrolador no tenga la capacidad de recibir los datos con la prestancia
necesaria. En cuando a los otros parámetros, ya los discutimos de sobra en el Estándar
RS232.
Otra ventana donde encontrar algunas opciones útiles está en menú Setup
Terminal…New-line establece si el cursor pasará a la siguiente línea con los
caracteres CR (Carriage Return = 0x0D = „r‟) y/o LF (LineFeed = Newline= 0x0A =
„n‟). Por lo general estos caracteres se manejan a nivel firmware (desde el programa
del microcontrolador), del mismo modo el eco lo hace e microcontrolador y raras veces
será recomendable marcar la casilla de Local echo.

Ahora vamos a modificar el aspecto del programa. Puede que sea un tema superfluo
pero al menos para mí esta apariencia vale mucho. Primero cambiaremos el tipo de
fuente yendo al menú Setup
Font… Y bueno, qué te puedo decir, escoge la fuente de
tu preferencia.
Para quienes deseen que la consola luzca como el terminal simulado de Proteus
pueden ir al menú Setup
Window. Allí escogemos la forma del cursor (Cursor
Shape) como Horizontal line. Luego en el marco Color empezamos por cambiar el color
de fondo (Background) a negro y después el color de fuente (Text) a verde como se
indica en la figura.
Ya te habrás dado cuenta de que la ventana del terminal se limpia cada vez que
cambias su tamaño. Para evitar que lo haga debemos ir al menú Setup
Aditional
settings… y desmarcar la opción Clear display when window resized, como se ve abajo.
Para limpiar la ventana podemos usar las opciones Clear screen y Clear buffer del
menú Edit. La primera opción simplemente recorre el contenido de la ventana, por eso
es preferible usar Clear buffer, que resetea todo el contenido de la ventana. Las otras
opciones de este menú hablan por sí solas.

Para terminar configuraremos el uso del teclado numérico. Esto no es un aspecto
decorativo, es muy importante y lo deje para el final solo por ser algo engorroso. Por
defecto, Tera Term emula el teclado VT100, por lo que algunas teclas como „+‟ o „-‟ no
funcionarán como en un teclado convencional.
Bueno, para ir directo al grano, seguimos el directorio de instalación de Tera Term, que
en Windows 7 suele ser C:Program Files (x86)teraterm/ y utilizamos un editor de
texto como el bloc de notas o Notepad++ para abrir el archivo KEYBOARD.CNF. Este
archivo tiene varias secciones; nosotros nos ubicamos en [VT numeric keypad], que
empieza en la línea 29 como se ve abajo.
Las líneas precedidas de punto y coma (;) son comentarios y no cuentan. Son los
números mostrados en naranja los que debemos editar: debemos cambiarlos todos por
la palabraoff, desde Num0 hasta PF4, 18 en total, y debe quedar como en la siguiente
figura.

Ahora guarda el archivo con los cambios realizados y reinicia el programa Tera Term. O
también puedes ir al menú Setup
Load key map… y recargar manualmente el
archivoKEYBOARD.CNF.
Finalmente, puesto que usaremos este programa con tanta frecuencia y no querremos
estar configurándolo cada vez que lo ejecutemos, debemos guardar la configuración
realizada yendo al menú Setup
Save setup… en la ventana presentada simplemente
hacemos clic en Guardar y con eso bastará para que Tera Term se abra siempre con la
configuración actual.

El USART, USART0 y USART1 de los AVR
USART es la sigla de Universal Synchronous Asynchronous Receiver Transmitter. Es el
periférico que incorporan muchos microcontroladores para comunicarse con
dispositivos que soportan el estándar RS-232. De su nombre se deprende que puede
trabajar en modo Síncrono o Asíncrono. En esta presentación nos enfocaremos
exclusivamente al modo asíncrono.
Algunos AVR (como los viejos ATmega32, ATmega8535, etc.) poseen un solo módulo
USART llamado simplemente USART. Los AVR mejorados como los que utilizamos en
cursomicros tienen uno o dos módulos, llamados USART0 y USART1, con
características de control idénticas. Por ejemplo, los megaAVR de la
serieATmegaXX8 tienen USART0 y los de la familia ATmegaXX4 tienen adicionalmente
el USART1.
El USART0 y el USART1 son gemelos. Entre ellos y los viejos USART de los AVR hay
una mínima diferencia que muchas veces se pasará por alto sin notarlo. De hecho,
aparte de los nombres de los registros de relacionados, el control del USART es
compatible en todos los AVR que lo tienen; incluso hay un gran parecido con los de
otros microcontroladores. Así que creo que valdrá la pena demorarnos un poco en esta
teoría.
Las características comunes del USART en los AVR son:
Asíncronamente pueden trabajar en modo full-dúplex , esto es transmitir y recibir
datos, al mismo tiempo inclusive.
Pueden generar interrupciones al recibir datos o después de enviarlos.
Puede operar en modo SPI maestro. Esta opción no está disponible en los viejos AVR.
Operan en background (detrás del escenario) para que las transferencias de datos se
lleven a cabo mientras el CPU realiza otras tareas.
El baud rate es configurable por el usuario.
Los datos pueden ser de 5, 6, 7, 8 ó 9 bits. Puede trabajar con uno o dos bits de Stop.
Y además de aceptar el bit paridad puede computar a nivel hardware su valor para el
dato actual.

Los Registros del USART
Además de estos registros todavía faltan por citar los que controlan las interrupciones
del USART. Aunque ya los conocemos bastante bien los veremos por separado.
Los nombres de los registros y de sus bits varían de acuerdo con el número de USART.
En toda mi exposición tomo como referencia el USART0 y por ello verás el número 0
acompañando a cada registro. Si utiliza el USART1 se debe cambiar el 0 por el 1 en
cada registro y en cada bit; y si se utiliza el USART viejo simplemente se quita el 0.
Por ejemplo, en el USART1 el registro UCSR0A se debe suprimir por UCSR1A. Lo
mismo debe aplicarse a los nombres de cada bit.
UDR0. El USART tiene dos buffers para las transferencias de datos: un buffer de
transmisión (donde se cargan los datos a trasmitir) y un buffer de recepción (donde se
almacenan los datos recibidos). Mediante el registro UDR0 se accede a ambos buffers.
Esto es, se accede al buffer de transmisión (en las operaciones de escritura) y al buffer
de recepción (en las operaciones de lectura). UDR0 significa USART Data Register 0.
Su nombre en los USART1 es UDR1 y en los USART viejos se llama simplemente UDR.
UCSR0A, UCSR0B y UCSR0C. (USART Control and Status Register A, B y C). Son los
Registros de Control y Estado del USART0. Creo que los nombres lo dicen todo. Para
los que aún usan los viejos AVR, deben saber que los registros UCSRC y UBRRH
comparten la misma locación en espacio de los registros de E/S. Ellos deben setear el
bit 7 para escribir en el registro UCSRC y limpiarlo para escribir en UBRRH. Las
operaciones de lectura son inusuales.
UBRR0L y UBRR0H. Son los registros generadores de Baud Rate del USART0. Juntos
forman un registro de 16 bits cuyo valor establece la velocidad de transferencia de
datos.
Todos estos registros son de 8 bits. UDR0 y el último par no tienen formatos
preestablecidos y podrían aceptar cualesquiera valores. Los registros de control y
estado tienen los siguientes mapas de bits:
Registro UCSR0A

UCSR0A

RXC0

TXC0

UDRE0

FE0

DOR0

UPE0

U2X0

MPCM0

RXCIE0

TXCIE0

UDRIE0

RXEN0

TXEN0

UCSZ02

RXB80

TXB80

UPM00

USBS0

UCSZ01

UCSZ00

UCPOL0

Registro UCSR0B

UCSR0B

Registro UCSR0C

UCSR0C

UMSEL01 UMSEL00 UPM01

En lo sucesivo iremos describiendo las funciones de estos dos registros y de cada uno
de sus bits. Algunos bits están relacionados con la operación del USART en modo
síncrono, tema que por el momento no nos compete, y serán ignorados.

Inicialización del USART
Lo primero que debemos hacer con el USART es configurar su operación: esto es,
establecer el modo de operación, fijar el formato de los datos, poner la velocidad de
las transferencias baud rate y habilitar los módulos Receptor y/o Transmisor. Los bits
de los registros de Control y Estado relacionados con la configuración de la librería
[usart.h y usart.c] usada en cursomicros.com son los siguientes. Los bits no
mencionados los trataremos de cerca al estudiar los modos de operación Síncrono y
SPI Master.
MPCM0. Recordemos que inicialmente el estándar RS232 permite comunicaciones solo
entre dos dispositivos (DTE y DCE). Actualmente los USART también soportan
comunicaciones con otros protocolos como del RS485, donde se pueden engarzar al
bus varios dispositivos en relaciones maestro-esclavo y con direcciones que los
identifican (parecido al bus I2C). Para que el USART del AVR pueda integrarse a esas
redes debemos setear el bit MPCM0 (Multiprocessor Communication Mode). Para
comunicaciones RS232 este bit se debe mantener en cero.
TXEN0 y RXEN0. Son los bits para habilitar los módulos Transmisor y Receptor del
USART. No es necesario habilitar los dos módulos al mismo tiempo. Al habilitar un
módulo (seteando el bit TXEN0 o RXEN0), éste asumirá el control del pin respectivo
(TXD0 para el transmisor y RXD0 para el receptor) y por tanto dichos pines dejarán de
funcionar como entrada y salida generales, sin importar el valor de los bits
correspondientes en los registros DDR, PORT y PIN.
UMSEL01 y UMSEL00. Estos bits establecen uno de los tres modos en que puede
trabajar el USART0: Modo Asíncrono, modo Síncrono y modo SPI Maestro. La
configuración por defecto es Asíncrono, con ambos bits iguales a cero, así que por el
momento no tendremos que tocarlos.
USBS0. Si este bit vale 0, el módulo transmisor enviará un bit Stop al final de cada
dato. Si vale 1, enviará 2 bits Stop. El módulo receptor solo evalúa el primer bit Stop
recibido, así que no le interesa esta configuración. Tampoco tocaremos este bit puesto
que trabajaremos con un solo bit Stop porque es el valor preestablecido en todos
sistemas RS232, y así nos ahorramos settings adicionales ;).
UCSZ02, UCSZ01 y UCSZ00. Estos bits establecen el tamaño de los datos que se
utilizarán en las transferencias, los cuales pueden ser desde 5 hasta 9 bits. El formato
seleccionado será empleado por ambos módulos, transmisor y receptor. Si los datos
son de 8 bits se trabajará con el valor completo del registro UDR0; si los datos son de
menos bits se obviarán los bits de mayor peso del registro UDR0; y si los datos son de
9 bits, los 8 bits de UDR0 se complementarán con el bit TXB80 (para las
transmisiones) y RXB80 (para las recepciones).
Por defecto todos estos bits inician a cero, así que observando la siguiente concluimos
en que deberemos cambiarlos a 011.

Tabla UCSZ02
UCSZ02 UCSZ01 UCSZ00 Tamaño del Carácter
0

0

0

5 bits

0

0

1

6 bits

0

1

0

7 bits

0

1

1

8 bits

1

0

0

Reservado

1

0

1

Reservado

1

1

0

Reservado

1

1

1

9 bits

U2X0. Como habíamos estudiado, el baud rate es la velocidad de transferencias de
datos, se mide en bits/segundo y hay valores estándar que debemos respetar (9600,
19200, 115200, etc.).
Seteando el bit U2X0 se divide el prescaler generador de baud rate de modo que la
velocidad de transferencia de datos se multiplique por dos. Este bit solo tiene efecto en
el modo Asíncrono y dependiendo de su valor se disponen de dos fórmulas para
calcular el valor del baud rate final. F_CPU es la frecuencia del procesador y recuerda
que es una constante definida en el archivo avr_compiler.h.
Tabla Baud rate del USART del megaAVR

U2X0 = 1 (Velocidad Doble)

U2X0 = 0 (Velocidad Normal)

UBRR0 está conformado por la unión de dos registros de E/S: UBRR0H y UBRR0L. Con
su capacidad de 16 bits y la fórmula de U2X0 = 1 se pueden obtener los suficientes
valores de baud rates como para hacernos olvidar de la fórmula con U2X0 = 0. De
hecho, analizando las fórmulas se deduce que cualquier valor de baud con U2X = 0
también se puede conseguir con la fórmula de U2X0 = 1, aunque a veces esto
devendrá en una menor performance en el muestreo de la señal de recepción.
En la práctica no interesa tanto el hecho de que U2X0 = 1 “incremente” la velocidad de
las transferencias, sino que se pueden obtener baud rates más precisos.
En consecuencia, la mejor decisión en la mayoría de los casos será escoger la primera
fórmula, con U2X = 1. De allí debemos despejar UBRR0 para calcular su valor. Esto
nos da siguiente expresión, que escrita en forma lineal sería UBRR0 =
F_CPU/(8*BAUD) – 1.
El valor generado para UBRR0 raras veces será exacto y en tales casos se deberá
escoger el más cercano y recalcular el valor del baud rate. Debemos tener cuidado con
los resultados obtenidos puesto que hay valores impuestos por el estándar RS232 y
que debemos respetar. En todo caso, puedes revisar las tablas de baud rate presentes
en los datasheets.
Con todo lo expuesto ya podemos implementar la siguiente función de inicialización del
USART0. Es irónico que tanta teoría se condense en tan poco código, pero es más
gracioso saber que la configuración establecida se puede resumir con la notación BAUD
8N1, que leído en orden significa Baud rate = BAUD, formato de datos = 8 bits, No
(sin) bit de paridad y 1 bit de Stop. Deberemos recordar esos parámetros a la hora de
configurar elsoftware terminal del lado de la computadora.
//***********************************************************************
*****
// Inicializa el USART0.
//***********************************************************************
*****
void usart_init(void)
{
/* Configurar baud rate

*/

UCSR0A |=(1<<U2X0);
UBRR0 = F_CPU/(8*USART_BAUD)-1;

/* Configurar modo de operación Asíncrono y formato de frame a
* 8 bits de datos, 1 bit de stop y sin bit de paridad.
UCSR0C =(1<<UCSZ01)|(1<<UCSZ00);

/* Habilitar módulos Receptor y Transmisor
UCSR0B =(1<<RXEN0)|(1<<TXEN0);

#if defined( __GNUC__ )

*/

*/
/* Asociar las funciones 'putchar' y 'getchar' con las funciones de
entrada
* y salida (como printf, scanf, etc.) de la librería 'stdio' de AVRGCC */
fdevopen((int(*)(char, FILE*))putchar,(int(*)(FILE*))getchar);
#endif
}
Una aclaración para quienes usen los viejos AVR: en dichos AVR los
registros UBRRH yUBRRL se encuentran bastante lejos uno del otro en el espacio de
los registros de E/S. Como consecuencia la sentencia de asignación UBRR =
F_CPU/(8*USART_BAUD)-1; no será válida. Tendrán que escribirlos por separado

Transmisión de Datos
El dato que el USART0 transmitirá debe ser previamente cargado en el registro UDR0.
Con ello el dato pasará al Registro de Desplazamiento de Transmisión si es que está
vacío. Luego el dato saldrá serialmente bit a bit por el pin TXD0. Pero si el Registro de
deslazamiento se encuentra transmitiendo un dato previo, el nuevo dato permanecerá
enUDR0 hasta que el registro de desplazamiento quede disponible. En ese lapso de
tiempo el registro UDR0 no podrá aceptar un nuevo dato.

Módulo de Transmisión del USART0.
Mientras el registro UDR0 contenga algún dato, el bit UDRE0 (del registro UCSR0A)
valdrá 0. Cuando UDR0 esté nuevamente libre (cuando haya descargado su contenido
en el registro de desplazamiento), el flagUDRE0 se seteará automáticamente por
hardware. Por tanto, será necesario comprobar que el bit UDRE0 valga 1 antes de
intentar escribir un nuevo dato en UDR0. La activación del flag UDRE0 puede generar
una interrupción si es que está habilitada. Detallaremos las interrupciones del USART
en otra sección más adelante.
Una conclusión de lo descrito es que es posible escribir en UDR0 hasta dos datos sin
pausa intermedia, el primero irá al registro de desplazamiento y el segundo se quedará
enUDR0. Así podemos decir que el USART0 tiene un buffer FIFO de transmisión de dos
niveles.
Con lo expuesto ya se puede codificar la siguiente función putchar. El encabezado de
esta función está de acuerdo con su definición en la librería stdio.h del compilador C.
Allí se indica que putchar debe tener un parámetro de entrada y uno de salida, ambos
de tipo int, aunque no en la práctica no parezca que sea necesario.
//***********************************************************************
*****
// Transmite el byte bajo de 'dato' por el USART
//***********************************************************************
*****
int putchar(int dato)
{
/* Esperar a que haya espacio en el buffer de transmisión */
while((UCSR0A &(1<<UDRE0))==0);

/* Colocar dato en el buffer de transmisión */
UDR0 = dato;
return dato;
}
Existe un flag adicional llamado TXDC (en el registro UCSR0A) que se setea cuando se
haya terminado de transmitir el dato del registro de desplazamiento y al mismo tiempo
no exista otro dato presente en el registro UDR0. Este flag también tiene la capacidad
de disparar una interrupción ante dicho evento. En ese caso el flag TXDC se limpiará
automáticamente al ejecutarse la función ISR; pero siendo de lectura/escritura
también se puede limpiar por software escribiéndole uno sobre él.

Recepción de Datos
Los datos seriales que llegan ingresan bit a bit por el pin RXD0 y se van depositando
en el Registro de Desplazamiento de Recepción. Cuando el dato esté completo (cuando
llegue su bit Stop) pasará paralelamente al registro UDR0. Luego podremos leer el
dato del registro UDR0. Pero si el registro UDR0 está ocupado con datos previos que
no han sido leídos por el procesador, el nuevo dato permanecerá en el registro de
desplazamiento hasta que haya espacio en UDR0.
Módulo de Recepción del USART0.
Apenas haya un dato en UDR0 se seteará el flag RXC0 (del registro UCSR0A). Así que
para saber si hay un dato allí pendiente de ser leído debemos comprobar el bit RXC0.
Esté flag es de solo lectura y se limpiará automáticamente cuando hayamos terminado
de leer todos los datos del buffer UDR0. La activación del flag RXC0 también puede
generar una interrupción si está previamente habilitada.
No debemos confundir el UDR0 de recepción con el UDR0 de transmisión. Aunque
tengan el mismo nombre, corresponden a dos registros diferentes. Es más, el UDR0 de
recepción en realidad es un buffer de dos niveles (puede almacenar hasta dos datos).
Ahora podemos decir que el USART0 tiene un buffer FIFO capaz de almacenar hasta
tres datos al mismo tiempo: dos en el buffer UDR0y uno en el registro de
desplazamiento. Si en este momento se detectara la llegada de un nuevo dato por el
pin RXD0, dicho dato se perdería y como señal se activaría el flag de
desbordamiento DOR0 (Data OverRun).
Como no querremos que ocurra dicho evento trágico será recomendable que leamos de
inmediato cada nuevo que llegue al USART. Siguiendo esta recomendación la función
de recepción tendrá el siguiente aspecto. Esta implementación también toma como
referencia la definición de getchar presente en el archivo stdio.h del compilador C.
//***********************************************************************
*****
// Recibe un byte de dato del USART
//***********************************************************************
*****
int getchar(void)
{
/* Esperar a que haya al menos un dato en el buffer de recepción */
while((UCSR0A &(1<<RXC0))==0);

/* Leer y retornar el dato menos reciente del buffer de recepción */
return UDR0;
}

Registros del USART0
El Registro UCSR0A
Registro UCSR0A

UCSR0A RXC0 TXC0

UDRE0

FE0

DOR0

UPE0

U2X0

MPCM0

Registro de Microcontrolador

RXC0

USART Receive Complete
Este bit de flag vale uno cuando hay datos no leídos en el buffer de recepción y
vale cero cuando el buffer de recepción está vacío (esto es, no contiene datos por
leer). Si el módulo receptor está deshabilitado, el buffer de recepción será liberado
y consecuentemente el bit RXC0 se pondrá a cero. E flag RXC0 se puede usar para
generar una Interrupción de Recepción Completada (ver la descripción del bit
RXCIE0).

TXC0

USART Transmit Complete
Este bit de flag se pone a uno cuando un dato completo ha terminado de salir del
Registro de Desplazamiento de Transmisión y no hay ningún dato presente en el
registro UDR0. El bit TXC0 se limpia automáticamente cuando se ejecuta la función
de interrupción, o se puede limpiar por software escribiendo uno sobre él. El flag
TXC0 puede generar la Interrupción de Transmisión Completada (ver la descripción
del bit TXCIE0).

UDRE0

USART Data Register Empty
El flag UDRE0 indica si el buffer de transmisión (UDR0) está listo para recibir un
nuevo dato. Si el bit UDRE0 vale uno, el buffer está vacío, y por tanto está listo
para ser escrito. El flag UDRE0 puede generar una Interrupción de Registro de Dato
Vacío (ver descripción del bit UDRIE0). El bit UDRE0 se pone a uno después de un
reset para indicar que el Transmisor está listo.
FE0

Frame Error
Este bit se pone a uno si el siguiente carácter en el buffer de recepción tuvo un
error de frame en la recepción del dato. Esto es, cuando el primer bit Stop del
siguiente carácter en el buffer de recepción es cero. Este bit será válido hasta que
se lea el buffer de recepción UDR0. El bit FE0 vale cero cuando el bit Stop del dato
recibido es uno. Siempre que se escriba en el registro UCSR0A este bit se debe
mantener en cero.

DOR0

Data OverRun
Este bit se pone a uno cuando se detecta una condición de desbordamiento de
dato. Ocurre un desbordamiento de dato (Data OverRun) cuando el buffer de
recepción está lleno (con dos caracteres), contiene un nuevo carácter esperando
en su Registro de Desplazamiento, y se detecta un nuevo bit Start. Este bit es
válido hasta que se lea el buffer UDR0. Siempre que se escriba en el registro
UCSR0A este bit se debe mantener en cero.

UPE0

USART Parity Error
Este se pone a uno al detectarse un Error de Paridad en el buffer de recepción
cuando están habilitados la recepción del bit de Paridad y su comprobación
(UPM01 = 1). Este bit será válido hasta que se lea el buffer UDR0. Siempre que se
escriba en el registro UCSR0A este bit se debe mantener en cero.

U2X0

Double the USART Transmission Speed
Este bit solo tiene efecto en el modo de operación asíncrono. Se debe escribir cero
en este bit cuando se usa el modo de operación síncrono.
En las comunicaciones asíncronas, si se escribe uno en este bit se reducirá el
divisor del generador de baud rate de 16 a 8, dando como resultado la
multiplicación por dos de la velocidad de transferencia de datos.

MPCM0

Multi-processor Communication Mode
Este bit habilita el modo de Comunicación Multiprocesador. Cuando se escribe uno
en el bit MPCM0, serán ignorados todos los frames recibidos por el USART que no
contengan información de dirección. El módulo Transmisor no queda afectado por
la configuración de los bits MPCM0.

El Registro UCSR0B
Registro UCSR0B
UCSR0B RXCIE0 TXCIE0

UDRIE0

RXEN0

TXEN0

UCSZ02

RXB80

TXB80

Registro de Microcontrolador

RXCIE0

RX Complete Interrupt Enable
Al escribir uno en este bit se habilita la Interrupción de Recepción Completada
cada vez que se active el flag RXC0. Para que se genere la interrupción será
necesario que también el bit enable general I de SREG valga uno.

TXCIE0

TX Complete Interrupt Enable
Al escribir uno en este bit se habilita la Interrupción de Transmisión Completada
cada vez que se active el flag TXC0. Para que se genere la interrupción será
necesario que también el bit enable general I de SREG valga uno.

UDRIE0

USART Data Register Empty Interrupt Enable
Al escribir uno en este bit se habilita la interrupción al activarse el flag UDRE0. Se
generará una Interrupción de Dato de Registro Vacío solo si valen uno el bit
UDRIE0, el flag de Global de Interrupciones en el registro SREG y el bit UDRE0 en
el registro SREG.

RXEN0

Receiver Enable
Al escribir uno en este bit se habilita el módulo Receptor del USART. El receptor
tomará el control del pin RXD0. Al deshabilitar el receptor se liberará el buffer de
recepción invalidando los flags FE0, DOR0 y UPE0.

TXEN0

Transmitter Enable
Al escribir uno en este bit se habilita el módulo Transmisor del USART. El
transmisor tomará el control del pin TXD0. La des-habilitación del transmisor
(escribiendo 0 en TXEN0) no se hará efectiva hasta que se completen las
transmisiones en marcha y las pendientes, esto es, cuando el registro UDR0 y el
registro de desplazamiento estén vacíos. Cuando se deshabilite el Transmisor el
pin TXD0 quedará en libertad.

UCSZ02

Character Size
El bit UCSZ02 combinado con los bits UCSZ01 y UCSZ00 del registro UCSRC
establece el número de los bits de datos (tamaño del carácter) en los frames que
usarán el Transmisor y el Receptor.

RXB80

Receive Data Bit 8
RXB80 es el noveno bit del dato recibido cuando se trabaja con datos seriales de 9
bits. Se debe leer antes de leer los bits bajos del registro UDR0.
TXB80

Transmit Data Bit 8
TXB80 es el novena bit del dato a transmitir cuando se trabaja con datos de 9 bits.
Se debe escribir antes de escribir los bits bajos del registro UDR0.

El Registro UCSR0C
Registro UCSR0C

UCSR0C UMSEL01 UMSEL00

UPM01

UPM00

USBS0

UCSZ01

UCSZ00

UCPOL0

Registro de Microcontrolador

UMSEL0
1
UMSEL0
0

USART Mode Select
Estos bits seleccionan el modo de operación del USART como se muestra en la
siguiente tabla.
Tabla UMSEL01

UMSEL01 UMSEL00 Mode
0

USART Asíncrono

0

1

USART Síncrono

1

0

Reservado

1
UPM01:
UPM00

0

1

Master SPI (MSPIM)

Parity Mode
Estos bits habilitan y configuran la generación y comprobación del bit de
paridad. Si está habilitado, el Transmisor generará y enviará automáticamente
el bit de paridad de cada dato. El Receptor calculará el bit de paridad de cada
dato recibido y lo comparará con la configuración del bit UPM00. Si se detecta
una discordancia, se seteará el flag UPE0 del registro UCSR0A.
Tabla UPM01

UPM01 UPM00 Modo de Paridad
0

Deshabilitado

0

1

Reservado

1

0

Habilitado, Paridad Par

1
USBS0

0

1

Habilitado, Paridad Impar

Stop Bit Select
Este bit selecciona el número de bits Stop que utilizará el Transmisor. El
Receptor “ignora” esta configuración.
Tabla USBS0

USBS0 Bits Stop
0
1
UCSZ01:
UCSZ00

1 bit
2 bits

Character Size
Estos bits se combinan con el bit UCSZ02 del registro UCSR0B para establecer
el número de bits de los datos (tamaño de carácter) que utilizarán el
Transmisor y el Receptor.
Tabla UCSZ02

UCSZ02 UCSZ01 UCSZ00 Tamaño del Carácter
0

0

0

5 bits

0

0

1

6 bits

0

1

0

7 bits

0

1

1

8 bits

1

0

0

Reservado

1

0

1

Reservado
1

0

Reservado

1
UCPOL0

1
1

1

9 bits

Clock Polarity
Este bit solo se usa en el modo de operación Síncrono. En el modo Asíncrono
debe permanecer en cero. El bit UCPOL0 establece la relación entre el cambio
de los datos de salida y la señal de reloj XCK0, y la relación entre el muestreo
de los datos de entrada y la señal de reloj XCK0.
Tabla UCPOL0

UCPOL0

El Cambio de Datos ocurre
(A la salida del pin TxD0)

El Muestreo de Datos ocurre
(A la entrada del pin RxD0)

0

En el flanco de Subida del pin
XCK0

En el flanco de Bajada del pin
XCK0

1

En el flanco de Bajada del pin
XCK0

En el flanco de Subida del pin
XCK0

Los Registros UBRR0L y UBRR0H
Registro UBRR0H

UBRR0H

---

---

---

---

Bit 11

Bit 10

Bit 9

Bit 8

Registro UBRR0L

UBRR0L Bit 7 Bit 6

Bit 5

Bit 4

Bit 3

Bit 2

Bit 1

Bit 0

Registro de Microcontrolador

Bit
15:12

Reserved

Bit 11:0

UBRR11:0: USART Baud Rate Register

Estos bits están reservados para usos futuros. Cuando se escriba en UBRR0H este bit
debe permanecer en cero por compatibilidad con futuros dispositivos.

Este es un registro de 12 bits que contiene el baud rate del USART. El registro
UCRR0H contiene los 4 bits más significativos, y UBRR0L contiene los 8 bits menos
significativos del baud rate del USART. Si se cambia el baud rate cuando haya
transmisiones o recepciones de datos en curso, estas operaciones serán
estropeadas. La escritura en UBRR0L actualizará de inmediato el prescaler del baud
rate.

Tablas de Baud Rate
Algunos compiladores como CodeVisionAVRpueden calcular automáticamente el valor
de los registros UBRR para generar el baud rate que le indiquemos, pueden incluso
avisar el error producido. En otros casos será recomendable ver por nosotros mismos
si el error es aceptable para nuestras aplicaciones.
Si es una cuestión crucial, también se puede optar por cambiar de XTAL por otro cuya
frecuencia sea múltiplo del baud rate deseado; por ejemplo, para los baud rates
múltiplos de 9600 (que son la gran mayoría) los XTALes de 3.6884 MHz, 11.0592 MHz
ó 18.4320 MHz derivan en errores de 0.00%.
Curso micros
Librerías para el USART
Una vez más las librerías presentadas son las compaginaciones de los códigos
elaborados previamente, con los añadidos que demanda separarlos en dos archivos,
uno de código y otro de configuraciones: usart.c y usart.h. En realidad no hay mucho
que comentar. Es una librería muy corta que consta apenas de las funciones básicas de
entrada y salida de un byte de dato, aparte de la función de inicialización, usart_init.
usart_init(). Inicializa el USART0 para el uso de datos de 8 bits, sin bit de paridad y 1
bit Stop. El baud rate o velocidad de transferencias de datos se establece en el archivo
usart.h, puesto que normalmente será el único parámetro a editar.
/* Define la velocidad de transmisión del USART (en bits/segundo),
* si aún no ha sido definida.
*/
#ifndef USART_BAUD
#define USART_BAUD
#endif

9600UL
getchar(). Lee un byte de dato del buffer de recepción del USART. Si no hay un dato
allí presente, la función esperará hasta que llegue uno. Así que para no perder tiempo
en una espera innecesaria se recomienda antes usar la macro kbhit() descrita más
adelante.
Es posible hacer que esta función “emita” eco del dato recibido, para que se visualice
en el terminal. En general esto no es requerido porque el programa podría devolver los
datos individualmente si se desease, pero hay funciones como scanf o gets de la
librería stdio.h para las que será muy conveniente configurar el eco de getchar,
simplemente des-comentando la directiva que define la
constante__GETCHAR_ECHO__ del archivo usart.h.
/* Descomentar el siguiente #define para que la funcion 'getchar' haga
* eco de los caracteres recibidos.
*/
//#define __GETCHAR_ECHO__

putchar(). Coloca un byte de dato en el buffer de transmisión del USART. Si el buffer
está disponible la escritura será inmediata, de lo contrario, esperará a que esté
nuevamente disponible, espera que será muy corta (a lo sumo de 1 ms para un baud
rate de 9600).
Las funciones getchar y putchar están escritas para encajar con sus definiciones en el
archivo sdtio.h del compilador C, de modo que podremos utilizar todas las funciones
disponibles en las librerías sdtio.h de AVR IAR C y AVR GCC, como puts(), printf(),
scanf(), etc.
kbhit(). En el archivo usart.h se ha colocado adicionalmente la macro kbhit(), que en el
lenguaje C es una función que se usa para saber si se ha presionado una tecla, o sea
para saber si hay un dato en el buffer del teclado. De ahí su nombre kb = keyboard =
teclado + hit = acción de presionar un botón.
Obviamente los microcontroladores no tienen teclados de computadora, pero ya que de
algún modo se conectan a uno, es común usar esta función para comprobar si la
computadora ha enviado un dato (que probablemente se originó en su teclado).
/* Usar kbhit para ver si hay algún dato en el buffer de recepción
* antes de llamar directamente a la función getchar para evitar
* esperas innecesarias.
*/
#define

kbhit()

(UCSR0A & (1<<RXC0))
/************************************************************************
******
* FileName:
* Overview:
Asíncrono
* Processor:

usart.h
Macros y prototipos de funciones para el USART0 en modo

ATmel AVR con USART0

* Compiler:

AVR IAR C y AVR GCC (WinAVR)

* Author:

Shawn Johnson. http://www.cursomicros.com.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License:

Se permiten el uso y la redistribución de este código con

*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y las notas de autor y copyright de arriba.

*************************************************************************
****/

#include "avr_compiler.h"

/* Define la velocidad de transmisión del USART (en bits/segundo), si aún
no
* ha sido definida.
*/
#ifndef USART_BAUD
#define USART_BAUD
#endif

9600UL
/* Usar kbhit para ver si hay algún dato en el buffer de recepción antes
de
* llamar directamente a la función getchar para evitar esperas
innecesarias.
*/
#define

kbhit()

(UCSR0A & (1<<RXC0))

/* Descomentar el siguiente #define para que la funcion 'getchar' haga
eco de
* los caracteres recibidos.
*/
//#define __GETCHAR_ECHO__

/* Macros para AVR GCC */
#if defined( __GNUC__ )
#ifdef putchar
#undef putchar
#endif
#ifdef getchar
#undef getchar
#endif
#endif

/* Definiciones de funciones */
void usart_init(void);
int putchar(int);
int getchar(void);
/************************************************************************
******
* FileName:

usart.c

* Overview:

Implementa funciones para el USART0 en modo Asíncrono

* Processor:

ATmel AVR con USART0

* Compiler:

AVR IAR C y AVR GCC (WinAVR)

* Author:

Shawn Johnson. http://www.cursomicros.com.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License:

Se permiten el uso y la redistribución de este código con

*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y las notas de autor y copyright de arriba.

*************************************************************************
****/

#include "usart.h"

//***********************************************************************
*****
// Inicializa el USART0.
//***********************************************************************
*****
void usart_init(void)
{
/* Configurar baud rate

*/

UCSR0A |=(1<<U2X0);
UBRR0 = F_CPU/(8*USART_BAUD)-1;
/* Configurar modo de operación Asíncrono y formato de frame a
* 8 bits de datos, 1 bit de stop y sin bit de paridad.

*/

UCSR0C =(1<<UCSZ01)|(1<<UCSZ00);

/* Habilitar módulos Receptor y Transmisor

*/

UCSR0B =(1<<RXEN0)|(1<<TXEN0);

#if defined( __GNUC__ )
/* Asociar las funciones 'putchar' y 'getchar' con las funciones de
entrada
* y salida (como printf, scanf, etc.) de la librería 'stdio' de AVRGCC */
fdevopen((int(*)(char, FILE*))putchar,(int(*)(FILE*))getchar);
#endif
}

//***********************************************************************
*****
// Transmite el byte bajo de 'dato' por el USART
//***********************************************************************
*****
int putchar(int dato)
{
/* Esperar a que haya espacio en el buffer de transmisión */
while((UCSR0A &(1<<UDRE0))==0);

/* Colocar dato en el buffer de transmisión */
UDR0 = dato;
return dato;
}
//***********************************************************************
*****
// Recibe un byte de dato del USART
//***********************************************************************
*****
int getchar(void)
{
/* Esperar a que haya al menos un dato en el buffer de recepción */
while((UCSR0A &(1<<RXC0))==0);

/* Leer y retornar el dato menos reciente del buffer de recepción */
#if defined ( __GETCHAR_ECHO__ )
return(putchar(UDR0));
#else
return UDR0;
#endif
}

Práctica: Hello
El programa maneja el ingreso y salida de cadenas de texto.
Circuito para probar el USART del microcontrolador AVR.

El código fuente
/******************************************************************************
* FileName: main.c
* Purpose: Comunicación básica por USART0
* Processor: ATmel AVR con módulo TWI
* Compiler: AVR IAR C y AVR GCC (WinAVR)
* Author:

Shawn Johnson. http://www.cursomicros.com.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License: Se permiten el uso y la redistribución de este código con
*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y las notas de autor y copyright de arriba.

*****************************************************************************/
#include "avr_compiler.h"
#include "usart.h"

intmain(void)
{
charname[20];

usart_init();// Inicializar USART0 @ 9600-8N1

puts("nr www.cursomicros.com ");
puts("nr =================== nr");

puts("nr - ¿Cuál es tu nombre?... nr - ");
scanf("%s",name);
printf(" - Gusto en conocerte, %s",name);

while(1);
}
Para que el programa responda como se muestra en la siguiente imagen será
necesario que configurar el uso del eco ya sea en el entorno de Tera Term, como se
indicó en la sección Uso de Tera Term, o en el archivo usart.h, como se indicó en la
sección Librerías para el USART. De lo contrario la función scanf no mostrará el texto
que vayas ingresando por el teclado.
Las Interrupciones del USART
Seguiremos considerando que todo lo expuesto es igualmente válido para el USART1 e
incluso para el USART de los viejos AVR. Con eso aclarado… El USART0 puede generar
tres interrupciones, a citar:
Interrupción de Recepción Completada. Cuando se acaba de recibir un nuevo dato,
esto es, cuando un dato recién llegado acaba de pasar del Registro de desplazamiento
al buffer del registro UDR0 se activará el flag RXC0 el cual podrá disparar la
interrupción, si es que está habilitada.
Esta interrupción se habilita seteando el bit RXCIE0, aparte del bit enable general I,
claro está.
El flag RXC0 es algo especial porque es de solo lectura y no se limpia automáticamente
al ejecutarse la función de interrupción ISR, sino únicamente cuando el buffer de
recepción esté vacío. Esto significa que si la interrupción está habilitada se disparará
sucesivamente mientras haya uno o más datos por leer.
Registro UCSR0A
UCSR0A

RXC0

TXC0

UDRE0

FE0

DOR0

UPE0

RXCIE0

TXCIE0

UDRIE0

RXEN0

TXEN0

UCSZ02

U2X0

MPCM0

Registro UCSR0B

UCSR0B

RXB80

TXB80

Interrupción de Registro de Datos Vacío. Se refiere al registro de datos UDR0 del
módulo transmisor. Esta interrupción se habilita seteando el bit UDRIE0 (aparte deI), y
se disparará cuando se active a uno el flag UDRE0. Este evento ocurre cuando el
registro UDR0 está vacío, sea después de un reset o después de depositar su contenido
en el Registro de desplazamiento de modo que está dispuesto a aceptar un nuevo
dato.
El flag UDRE0 se limpia automáticamente al ejecutarse la función de interrupción ISR.
También se puede limpiar escribiéndole un uno.
Interrupción de Transmisión Completada. Esta interrupción se habilita seteando el
bit TXEN0 (además de I), y se disparará cuando se active el flag TXC0, es decir,
cuando un dato se haya terminado de transmitir, o dicho de otro modo, cuando el dato
haya salido por completo del Registro de desplazamiento. Creo que con eso queda
clara su diferencia respecto de la Interrupción de Registro de Dato Vacío. En la práctica
significa que los datos se envían más rápido usando la Interrupción de Registro de
Datos Vacío porque los espacios entre datos se reducen al mínimo, de modo que la
Interrupción de Transmisión completada será raramente usada.
El flag TXC0 se limpia automáticamente al ejecutarse la función de interrupción ISRo
puede limpiarse por software escribiéndole un uno.

Práctica: Interrupciones del USART
Cada dato que llegue al USART es vuelto a enviar al terminal serial. Por otro lado, se
envía todo un buffer (una cadena de texto) pero usando solo interrupciones. Usaremos
el mismo circuito de la práctica anterior.
Circuito para probar las interrupciones del USART del microcontrolador AVR.

El código fuente
/******************************************************************************
* FileName: main.c
* Purpose: Uso de las interrupciones del USART
* Processor: ATmel AVR con módulo TWI
* Compiler: AVR IAR C y AVR GCC (WinAVR)
* Author:

Shawn Johnson. http://www.cursomicros.com.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License: Se permiten el uso y la redistribución de este código con
*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y las notas de autor y copyright de arriba.

*****************************************************************************/
#include "avr_compiler.h"
#include "usart.h"

volatilecharbuf[]="nr Uso de interrupciones del USART nrr";

//****************************************************************************
// Gestor de interrupciones (Interrupción de Recepción Completada).
// La interrupción de recepción se dispara cuando haya llegado algún dato.
//****************************************************************************
ISR(USART0_RX_vect)
{
charc=UDR0;// Leer dato
UDR0=c;// Devolver dato
}

//****************************************************************************
// Gestor de interrupciones (Interrupción de Registro de Datos Vacío).
// La interrupción se dispara cuando se pueda enviar un nuevo dato.
// La ISR deposita en el registro de transmisión el siguiente dato a enviar.
//****************************************************************************
ISR(USART0_UDRE_vect)
{
staticunsignedchari=0;
charc=buf[i];
if(c!=0)// ¿ Fin de buffer ?
{
UDR0=c;
i++;
}
}

intmain(void)
{
usart_init();// Inicializar USART0 @ 9600-8N1

/* Habilitar las interrupciones de 'Recepción Completada' y
* de 'Registro de Datos Vacío'.
*/
UCSR0B|=(1<<RXCIE0)|(1<<UDRIE0);
sei();// Setear bit I de SREG

while(1)
{
/* Entrar en Modo sleep (Idle mode).
* Es el único modo sleep en que el USART todavía puede
* generar interrupciones */
SMCR=(1<<SE);
sleep();
}
}
Observa que en la función principal main no se transfiere ningún dato. Todos viajan
por interrupciones. La interrupción de recepción se usa con mucha frecuencia y es la
más fácil de captar.

//****************************************************************************
// Gestor de interrupciones (Interrupción de Recepción Completada).
// La interrupción de recepción se dispara cuando haya llegado algún dato.
//****************************************************************************
ISR(USART0_RX_vect)
{
charc=UDR0;// Leer dato
UDR0=c;// Devolver dato
}
Eso es todo: cuando haya algún dato llegado lo recibimos y lo devolvemos :)

La interrupción de transmisión no es tan usual como la anterior. Es una técnica algo
sofisticada y muy eficiente pero que raras veces resulta realmente necesaria. En el
programa funciona así: una vez habilitada, la interrupción se disparará de inmediato ya
que el registro de transmisión UDR0 estará vacío. Así se empieza a enviar el primer
dato de buf. La interrupción se volverá a disparar dada vez que el USART termine de
enviar el dato anterior y parará en el último dato, cuando su flag se limpie por el solo
hecho de empezar a ejecutarse la ISR, aunque en esa ocasión ya no se toque el
registro UDR0. Fin de la historia.
//***********************************************************************
*****
// Gestor de interrupciones (Interrupción de Registro de Datos Vacío).
// La interrupción se dispara cuando se pueda enviar un nuevo dato.
// La ISR deposita en el registro de transmisión el siguiente dato a
enviar.
//***********************************************************************
*****
ISR(USART0_UDRE_vect)
{
staticunsignedchar i =0;
char c = buf[i];
if(c !=0)// ¿ Fin de buffer ?
{
UDR0 = c;
i++;
}
}

El buffer circular o buffer de anillo
Recordemos que no podemos depositar un dato en el registro de transmisión del
USART hasta que se termine de enviar el dato anterior. Mientras se espera a que eso
pase el CPU podría perder tiempo valioso. La solución es guardar los datos en un buffer
y que se vayan transmitiendo a su momento utilizando interrupciones, parecido a lo
que se vio en la práctica pasada.
Por otro lado, el registro de recepción, al ser un buffer de dos niveles, sí puede recibir
un segundo dato antes de que se haya leído el dato previo. Pero si siguen llegando
más datos sin que sean recogidos, no solo se perderían, sino que bloquearían el
USART. Incluso si los datos se leyeran a tiempo utilizando interrupciones, ¿qué se
haría con ellos si el CPU aún está ocupado procesando los datos previos? ¡Tienes
razón! Podrían ir guardándose en un buffer.
Pues bien, en ambos casos las cosas saldrán mejor si se usa un buffer de tipo circular.
Un buffer circular es un recurso de programación tan viejo como útil en el intercambio
de todo tipo de datos, no solo en comunicaciones del USART. No es más que un buffer
o array ordinario que adopta su nombre por la forma en que se ponen y sacan sus
elementos.
Un buffer circular trabaja básicamente con dos índices, que aquí
llamaremos Inpointer yOutpointer. No son como los punteros que define el lenguaje C,
son simples variables que operan como índices para acceder a los elementos del
buffer. Ambos índices tienen avance incremental y cíclico, es decir, se incrementan de
uno en uno y luego de apuntar al último elemento del buffer vuelven a apuntar al
primero. Eso explica su nombre.
Estructura de un buffer circular de N elementos.
Al inicio los dos índices apuntan al primer elemento del buffer. La pregunta es ¿cuándo
y cómo se incrementan?
Cada nuevo dato a guardar en el buffer será depositado en la casilla actualmente
apuntada por Inpointer. A continuación Inpointer se incrementa en 1. (Inpointer para
datos In = entrada.)
Por otro lado, cada dato que salga del buffer será el de la casilla actualmente apuntada
por Outpointer. A continuación Outpointer se incrementa en 1. (Outpointer para datos
Out = salida.)
Con todo lo expuesto ya puedes ensayar cómo funciona el buffer circular. Descubrirás
que tiene un comportamiento FIFO: los primeros datos en entrar serán los primeros en
salir; que en tanto haya espacio en el buffer siempre se podrán meter más datos sin
importar en qué posiciones vayan, evitando el riesgo de sobrescribir posiciones ya
ocupadas. ¿Podrías hacer eso con un buffer lineal? Muy difícil, ¿verdad? Ahora pasemos
de los ensayos a la práctica real.
Para saber si en el buffer hay espacio para meter más datos o si hay al menos un dato
que sacar, se debe usar la diferencia entre las posiciones de los punteros. Por lo
confuso que se ve eso, es preferible emplear una variable adicional que se incremente
con cada dato ingresado y se decremente con cada dato extraído.

Práctica: Buffer circular con Interrupciones
El uso de un buffer circular en las recepciones de datos es una técnica robusta que se
convierte en una necesidad de facto por razones ya explicadas. En las transmisiones,
en cambio, brinda una eficiencia superflua y hasta innecesaria, salvo que la aplicación
realmente la requiera.
No quiero poner programas de esos aquí porque son demasiado grandes como
ejemplos. Superficialmente esta práctica se ve igual que la primera de este capítulo.
Solo que ahora todos los datos son transferidos por interrupciones pasando por buffers
circulares.
Circuito para probar el USART del microcontrolador AVR.

El código fuente
/******************************************************************************
* FileName: main.c
* Purpose: Uso de buffers circulares con las interrupciones del USART
* Processor: ATmel AVR con módulo TWI
* Compiler: AVR IAR C y AVR GCC (WinAVR)
* Author:

Shawn Johnson. http://www.cursomicros.com.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License: Se permiten el uso y la redistribución de este código con
*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y las notas de autor y copyright de arriba.

*****************************************************************************/

#include "avr_compiler.h"
#include "usart.h"
#include "lcd.h"

charGetFromTXBuffer(void);
charGetFromRXBuffer(void);
voidPutToTXBuffer(chardata);
voidPutToRXBuffer(chardata);

#define TXBufferSize 50

// Tamaño de buffer circular de Tx

#define RXBufferSize 50

// Tamaño de buffer circular de Rx

volatilecharTXBuffer[TXBufferSize];// Buffer circular de Tx
volatilecharRXBuffer[TXBufferSize];// Buffer circular de Rx
volatileunsignedcharTXInpointer=0;
volatileunsignedcharRXInpointer=0;
volatileunsignedcharTXOutpointer=0;
volatileunsignedcharRXOutpointer=0;
volatileunsignedcharTXBufferData=0;
volatileunsignedcharRXBufferData=0;
volatileunsignedcharTXBufferSpace=TXBufferSize;
volatileunsignedcharRXBufferSpace=RXBufferSize;
//****************************************************************************
// Gestor de interrupciones (Interrupción de Recepción Completada).
// La interrupción de recepción se dispara cuando haya llegado algún dato.
//****************************************************************************
ISR(USART0_RX_vect)
{
charc=UDR0;// Leer dato

if(RXBufferSpace)// Si hay espacio en RXBuffer
PutToRXBuffer(c);
else// RXBuffer está lleno
nop();// Código para TXBuffer lleno
}

//****************************************************************************
// Gestor de interrupciones (Interrupción de Registro de Datos Vacío).
// La interrupción se dispara cuando se pueda enviar un nuevo dato.
//****************************************************************************
ISR(USART0_UDRE_vect)
{
charc;

if(TXBufferData)// Si hay datos en TXBuffer
{
c=GetFromTXBuffer();// Estraer dato
UDR0=c;// Enviarlo
}
}

intmain(void)
{
charc;
unsignedchari=0;
constchar*text="nr Escribe en el LCD... nr";

usart_init();// Inicializar USART0 @ 9600-8N1

lcd_init();// Inicializat LCD. Ver interface en "lcd.h"
lcd_cmd(LCD_CURBLK);// Mostrar Cursor + Blink

while(c=text[i++])// Cargar TXBuffer
{
if(TXBufferSpace)// Si hay espacio en TXBuffer
PutToTXBuffer(c);// Meter c
else
nop();// Código para TXBuffer lleno
}

/* Habilitar las interrupciones de 'Recepción Completada' y
* de 'Registro de Datos Vacío'.
*/
UCSR0B|=(1<<RXCIE0)|(1<<UDRIE0);
sei();// Setear bit I de SREG

while(1)
{
if(RXBufferData)// Si hay datos en RXbuffer
{
c=GetFromRXBuffer();// Obtener un dato
switch(c)
{
case0x1B:lcd_clear();// Limpiar LCD
break;
case0x08:lcd_cmd(0x10);// Cursor atrás
lcd_data(' ');// Escribir espacio blanco
lcd_cmd(0x10);// Cursor atrás
break;
default:lcd_data(c);// Escribir c
}
}
// Algunas otras tareas...
nop();
}
}

//****************************************************************************
// Extrae un dato de TXBuffer.
// Antes de llamar se debe comprobar si hay algún dato con if(TXBufferData)
//****************************************************************************
charGetFromTXBuffer(void)
{
charc=TXBuffer[TXOutpointer];// Extraer dato
if(++TXOutpointer>=TXBufferSize)// Al pasar el límite
TXOutpointer=0;// Dar la vuelta
TXBufferData--;// Un dato menos
TXBufferSpace++;// Un espacio más
returnc;//
}

//****************************************************************************
// Extrae un dato de RXBuffer.
// Antes de llamar se debe comprobar si hay algún dato con if(RXBufferData)
//****************************************************************************
charGetFromRXBuffer(void)
{
charc=RXBuffer[RXOutpointer];// Extraer dato
if(++RXOutpointer>=RXBufferSize)// Al pasar el límite
RXOutpointer=0;// Dar la vuelta
RXBufferData--;// Un dato menos
RXBufferSpace++;// Un espacio más
returnc;//
}
//****************************************************************************
// Ingresa un dato en TXBuffer
// Antes de llamar se debe comprobar si hay espacio con if(TXBufferSpace)
//****************************************************************************
voidPutToTXBuffer(chardata)
{
TXBuffer[TXInpointer]=data;// Ingresar dato
if(++TXInpointer>=TXBufferSize)// Al pasar el límite
TXInpointer=0;// Dar la vuelta
TXBufferData++;// Un dato más
TXBufferSpace--;// Un espacio menos
}

//****************************************************************************
// Ingresa un dato en RXBuffer
// Antes de llamar se debe comprobar si hay espacio con if(RXBufferSpace)
//****************************************************************************
voidPutToRXBuffer(chardata)
{
RXBuffer[RXInpointer]=data;// Ingresar dato
if(++RXInpointer>=RXBufferSize)// Al pasar pasar el límite
RXInpointer=0;// Dar la vuelta
RXBufferData++;// Un dato más
RXBufferSpace--;// Un espacio menos
}
Descripción del programa
Como ves, hay dos buffers circulares, uno para transmisiones y otro para recepciones.
Una vez implementados su uso es bastante simple. Solo compara esto: para enviar y
recibir datos en un programa rústico se utilizan funciones como putchar para depositar
un dato en el registro de transmisión, y getchar para leer del mini buffer de recepción
de 2 datos.
En cambio, con los buffers circulares podemos usar las funciones
como PutToTXBuffer oGetFromRXBuffer para depositar/leer en/de sus “megabuffers”
de transmisión y recepción. Se usan las variables
como RXBufferData o TXBufferSpace para comprobar si hay datos o espacios para ellos
en los buffers.

Práctica: LCD serial RS232
Los LCDs seriales son muy atractivos por su fácil conexión a un microcontrolador. Los
hay con interface RS232, I2C o SPI. Con solo buscar en Google verás la gran cantidad
de modelos disponibles, aunque con precios que pueden desalentar a muchos. Lo
sorprendente en la mayoría de los casos es que se tratan de los mismos displays
LCD de interface paralela y controlador interno HD44780 que todos conocemos, solo
que llevan montada una pequeña tarjeta de control basada en un microcontrolador que
hace el bypass en la interface serie - paralela. Los LCDs seriales que ofrece la empresa
de los PICAXE son un ejemplo de ello. Abajo se muestra un modelo que utiliza un
PIC16F628 como controlador.
LCD serial de PICAXE

Vista del anverso y reverso del LCD serial AXE033 de Revolution Education Ltd .
Si se puede conectar un AVR a una PC, dime si no se podrá conectar a otro AVR. Es
tan trivial que en vez de trabajar con nuestros acostumbrados megaAVR,
aprovecharemos esta oportunidad para conocer algo de la programación de
los tinyAVR. Sí, en esta práctica utilizaremos los tinyAVR.
Conectar dos microcontroladores puede ser útil cuando uno solo no basta ya sea quizá
por la falta de algunos pines o porque se le quiere dar al segundo microcontrolador una
tarea dedicada o exclusiva para que la desarrolle con la máxima eficiencia posible, o
por una combinación de ambas razones, como en esta práctica.
El circuito
Una idea es tomar un LCD paralelo cualquiera y convertirlo en serial. Aunque el
tamaño de su circuito resultante incomode un poco, su coste puede ser muy inferior
sobre todo si el diseño será final, donde se podría escoger un AVR con los recursos
mínimamente necesarios.
Como el tinyAVR del circuito remoto tendrá siempre la única tarea de controlar
directamente el LCD, no se escatiman los pines de interface. Así que operaremos el
LCD en modo de 8 bits y comprobando el bit de Busy Flag para lograr su mejor
performance.

Circuito para el LCD serial (comunicación entre dos microcontroladores AVR)

Código fuente de ejemplo de uso del LCD serial
Como siempre, los archivos principales de cada AVR se llaman main.c. Siendo este el
código de ejemplo de uso de nuestro LCD serial, puede ser compilado para
cualquiermegaAVR o tinyAVR. En este caso el programa está compilado para otro
tinyAVR con USART de modo que ambos programas utilizan la misma librería para el
USART, mostrada al final. Este programa es claramente más pequeño que un
programa para un LCD paralelo. Ahora pasemos a explicar cómo funciona.
Como sabemos, el LCD tiene dos tipos de instrucciones: de comando y de datos de
caracteres.
Los datos de caracteres se envían directamente con la función putchar. Estos
caracteres serán recibidos y por el otro AVR y los visualizará en la pantalla del LCD.
Solo si el LCD tuviera activa la memoria CGRAM dichos datos irían allá para
crear caracteres personalizados.

putchar(c);// Escribir carácter
Por otro lado, los datos que se envíen precedidos por el valor 0xFE serán entendidos y
ejecutados por el otro AVR como instrucciones de comando, como Clear display, Set
DDRAM Address, etc. Los códigos de dichos comandos son los mismos que usa el
controlador interno del LCD, salvo LCD_INIT = 0x00. Usé ese valor como código
personalizado al verlo libre.

//****************************************************************************
// Limpia el LCD y regresa el cursor a la primera posición de la línea 1.
//****************************************************************************
voidlcd_clear(void)
{
putchar(0xFE);// Prefijo de comando
putchar(LCD_CLEAR);// Enviar instrucción 'Clear Display'
}
El valor 0xFE lo tomé arbitrariamente. Como consecuencia, se podrán visualizar en el
LCD todos los caracteres de su tabla CGROM excepto el que tenga el código 0xFE.
Dado que ese carácter corresponde a la segunda mitad de la tabla, es un “garabato”
que varía de un modelo de LCD a otro, y creo que se puede vivir sin él.

/******************************************************************************
* FileName: main.c
* Purpose: Control de LCD serial RS232
* Processor: ATmel tinyAVR o megaAVR con USART
* Compiler: AVR IAR C y AVR GCC (WinAVR)
* Author:

Shawn Johnson. http://www.cursomicros.com.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License: Se permiten el uso y la redistribución de este código con
*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y las notas de autor y copyright de arriba.

*****************************************************************************/

#include "avr_compiler.h"
#include "usart.h"

// Prototipos de funciones
voidlcd_init(void);
voidlcd_puts(char*);
voidlcd_clear(void);
voidlcd_gotorc(char,char);

// Definiciones
#define LCD_INIT 0x00 // Inicializar LCD (user command)
#define LCD_CLEAR 0x01 // Limpiar Display
#define LCD_LINE1 0x80 // Línea 1 posición 0
#define LCD_LINE2 0xC0 // Línea 2 posición 0
#define LCD_CURSOR 0x0E // Mostrar solo Cursor
#define LCD_BLINK 0x0D // Mostrar solo Blink
#define LCD_CURBLK 0x0F // Mostrar Cursor + Blink

intmain(void)
{
PORTB=0x07;// Habilitar pull ups de pines PB0, PB1 y PB2

usart_init();
lcd_init();
lcd_puts("Emulacion de LCD n serial RS232 ");

while(1)
{
if((PINB&(1<<0))==0)// Si botón de pin PB0 está pulsado...
{
lcd_init();// Reinicializar el LCD
while((PINB&(1<<0))==0);// Esperar botón libre
}
elseif((PINB&(1<<1))==0)// Si botón de pin PB1 está pulsado...
{
lcd_clear();
lcd_puts("Programacion de n AVR y Arduino");
while((PINB&(1<<1))==0);
}
elseif((PINB&(1<<2))==0)// Si botón de pin PB2 está pulsado...
{
lcd_clear();
lcd_puts("Web site: ncursomicros.com");
while((PINB&(1<<2))==0);
}
}
}

//****************************************************************************
// Inicializa el LCD
//****************************************************************************
voidlcd_init(void)
{
delay_us(100);// Para que el otro AVR complete su inicialización
putchar(0xFE);// Enviar prefijo de comando
putchar(LCD_INIT);// Enviar comando (personalizado)
delay_us(40000);// Tiempo de inicialización del LCD
}

//****************************************************************************
// Envía cadenas ROM terminadas en null al LCD.
//****************************************************************************
voidlcd_puts(char*s)
{
unsignedcharc,i=0;
while(c=s[i++]){
if(c=='n')
lcd_gotorc(2,1);// Ir a línea 2
else
putchar(c);// Escribir carácter
}
}

//****************************************************************************
// Limpia el LCD y regresa el cursor a la primera posición de la línea 1.
//****************************************************************************
voidlcd_clear(void)
{
putchar(0xFE);// Prefijo de comando
putchar(LCD_CLEAR);// Enviar instrucción 'Clear Display'
}

//****************************************************************************
// Ubica el cursor del LCD en la columna c de la línea r.
//****************************************************************************
voidlcd_gotorc(charr,charc)
{
if(r==1)r=LCD_LINE1;
elser=LCD_LINE2;
putchar(0xFE);// Prefijo de comando
putchar(r+c-1);// Enviar instrucción 'Set DDRAM Address'
}

Código fuente del AVR controlador del LCD
La mitad del código tiene funciones para manejar el LCD en bajo nivel y no vamos a
detallar en este momento cómo funciona. Todo lo relacionado a ese tema ya lo
estudiamos en el capítulo el display LCD.
El resto del programa se encarga de gestionar la recepción de datos del USART. En
este programa el uso de interrupciones y del buffer circular es vital e imprescindible.
Este AVR debe recibir todos los datos posibles del otro AVR porque no tiene forma de
decirle “espérame, que estoy ocupado ejecutando la instrucción anterior”. El tamaño
del buffer circular debería ser el máximo posible.
Puesto que este programa está escrito para ser compilado para un tinyAVR con USART
y no para nuestros conocidos ATmegaXX4 o ATmegaXX8, las rutinas relacionadas con
el USART varían ligeramente, pues los nombres de los registros del USART varían
ligeramente.
Programar el USART de los tinyAVR es casi idéntico a hacerlo con los USART de los
megaAVR, solo cambian los nombres de sus registros y del Vector de Interrupción.
Hablaremos de sus registros en la siguiente sección y en cuanto al Vector de
Interrupción de Recepción Completada, debo aclarar que, aunque el datasheet sugiere
que se debería llamar USART0_RX_vect, solo el compilador AVR IAR C lo define de esa
manera. En el archivo de dispositivo del compilador AVR GCC en cambio este Vector se
define comoUSART_RX_vect. La macro #define usada en el código reconocerá de qué
forma está definido el Vector de interrupción.

//****************************************************************************
// Gestor de interrupciones (Interrupción de Recepción Completada).
// La interrupción de recepción se dispara cuando haya llegado algún dato.
//****************************************************************************
#ifdef USART0_RX_vect
ISR(USART0_RX_vect)
#else
ISR(USART_RX_vect)
#endif
{
charc=UDR;// Leer dato

if(BufferSpace)// Si hay espacio en Buffer
PutToBuffer(c);
else// Buffer está lleno
{
Inpointer=0;// Código para Buffer lleno
Outpointer=0;// ...
BufferData=0;
BufferSpace=BufferSize;
PutToBuffer(c);
UDR='F';
}
}

/******************************************************************************
* FileName: main.c
* Purpose: LCD serial RS232 + Buffer circular
* Processor: ATmel tinyAVR con USART
* Compiler: AVR IAR C y AVR GCC (WinAVR)
* Author:

Shawn Johnson. http://www.cursomicros.com.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License: Se permiten el uso y la redistribución de este código con
*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y las notas de autor y copyright de arriba.

*****************************************************************************/
#include "avr_compiler.h"
#include "usart.h"

//****************************************************************************
// Prototipos de función
//****************************************************************************
charGetFromBuffer(void);
voidPutToBuffer(char);

voidlcd_init(void);
voidlcd_cmd(char);
voidlcd_data(char);
voidlcd_write(char,char);
charlcd_read(char);

//****************************************************************************
// CONFIGURACIÓN DE LOS PINES DE INTERFACE
//****************************************************************************

/* Define el puerto a donde se conectará el bus de datos del LCD
* Se utilizará el nible alto del puerto escogido (ejem. PB4-DB4,...,PB7-DB7)
*/
#define lcd_DATAout PORTB
#define lcd_DATAin PINB
#define lcd_DATAddr DDRB

// Registro PORT del puerto
// Registro PIN del puerto
// Registro DDR del puerto
/* Define el puerto a donde se conectarán las líneas de control del LCD
* E, RW y RS. Puede ser el mismo puerto del bus de datos.
*/
#define lcd_CTRLout PORTD
#define lcd_CTRLin PIND

// Registro PORT del puerto
// Registro PIN del puerto

#define lcd_CTRLddr DDRD

// Registro DDR del puerto

/* Define los números de los pines del puerto anterior que corresponderán a
* las líneas E, RW y RS del LCD.
*/
#define lcd_E

6

#define lcd_RW
#define lcd_RS

// Pin Enable
5

4

// Pin Read/Write
// Pin Register Select

// Definiciones y Variables globales
#define LCD_INIT

0x00

#define BufferSize 70

// Comando personalizado para inicializar LCD
// Tamaño del buffer circular

// Variables globales
volatilecharBuffer[BufferSize];// Buffer circular
volatileunsignedcharInpointer=0;
volatileunsignedcharOutpointer=0;
volatileunsignedcharBufferData=0;
volatileunsignedcharBufferSpace=BufferSize;

voiddelay_ms(unsignedintu){
while(u--)
delay_us(1000);
}

//****************************************************************************
// Gestor de interrupciones (Interrupción de Recepción Completada).
// La interrupción de recepción se dispara cuando haya llegado algún dato.
//****************************************************************************
#ifdef USART0_RX_vect
ISR(USART0_RX_vect)
#else
ISR(USART_RX_vect)
#endif
{
charc=UDR;// Leer dato

if(BufferSpace)// Si hay espacio en Buffer
PutToBuffer(c);
else// Buffer está lleno
{
Inpointer=0;// Código para Buffer lleno
Outpointer=0;// ...
BufferData=0;
BufferSpace=BufferSize;
PutToBuffer(c);
UDR='F';
}
}

//****************************************************************************
intmain(void)
{
charc;
charpf=0;// Flag de prefijo de comando

usart_init();// Inicializar USART0 @ 9600-8N1

/* Habilitar la 'Interrupción de Recepción Completada' del USART */
UCSRB|=(1<<RXCIE);
sei();// Setear bit I de SREG

lcd_init();// Inicializat LCD

while(1)
{
if(BufferData)// Si hay datos en Buffer
{
c=GetFromBuffer();// Leer
if((pf==0)&&(c==0xFE))//
{
pf=1;
}
elseif(pf==1)
{
if(c==LCD_INIT)// Comando personalizado
lcd_init();
else// Comando estándar
lcd_cmd(c);
pf=0;
}
else
{
lcd_data(c);
}
}
}
}

//****************************************************************************
// Extrae un dato de Buffer.
// Antes de llamar se debe comprobar si hay algún dato con if(BufferData)
//****************************************************************************
charGetFromBuffer(void)
{
charc=Buffer[Outpointer];// Extraer dato
if(++Outpointer>=BufferSize)// Al pasar el límite
Outpointer=0;// Dar la vuelta
BufferData--;// Un dato menos
BufferSpace++;// Un espacio más
returnc;//
}

//****************************************************************************
// Ingresa un dato en Buffer
// Antes de llamar se debe comprobar si hay espacio con if(BufferSpace)
//****************************************************************************
voidPutToBuffer(chardata)
{
Buffer[Inpointer]=data;// Ingresar dato
if(++Inpointer>=BufferSize)// Al pasar pasar el límite
Inpointer=0;// Dar la vuelta
BufferData++;// Un dato más
BufferSpace--;// Un espacio menos
}

//****************************************************************************
// Ejecuta la inicialización software completa del LCD. La configuración es:
// Interface de 8 bits, despliegue de 2 líneas y caracteres de 5x7 puntos.
//****************************************************************************
voidlcd_init(void)
{

/* Configurar las direcciones de los pines de interface del LCD */
lcd_DATAddr=0xFF;
lcd_CTRLddr|=(1<<lcd_E)|(1<<lcd_RW)|(1<<lcd_RS);

/* Secuencia de inicialización del LCD en modo de 8 bits*/
lcd_CTRLout&=~((1<<lcd_E)|(1<<lcd_RW)|(1<<lcd_RS));
delay_ms(45);// > 40 ms
lcd_write(0x30,0);// Function Set: 8-bit
delay_ms(5);// > 4.1 ms
lcd_write(0x30,0);// Function Set: 8-bit
delay_ms(1);// > 100 µs
lcd_write(0x30,0);// Function Set: 8-bit
delay_ms(1);// > 40 µs
lcd_write(0x38,0);// Function Set: 8-bit, 2lines, 4×7font
lcd_cmd(0x0C);// Display ON/OFF Control:
lcd_cmd(0x01);// Clear Display
lcd_cmd(0x06);// Entry Mode Set
}

//****************************************************************************
// Envían instrucciones de comando y de datos al LCD.
//****************************************************************************
voidlcd_cmd(charcmd)
{
while(lcd_read(0)&0x80)// Mientras LCD ocupado
continue;// esperar
lcd_write(cmd,0);
}
voidlcd_data(chardata)
{
while(lcd_read(0)&0x80)// Mientras LCD ocupado
continue;// esperar
lcd_write(data,1);
}

//****************************************************************************
// Escribe una instrucción en el LCD:
// Si RS = 0 la instrucción es de comando (Function Set, Entry Mode set, etc).
// Si RS = 1 la instrucción es de dato y va a la DDRAM/CGRAM.
// El LCD debe estar libre antes de llamar esta función.
//****************************************************************************
voidlcd_write(chardata,charRS)
{
if(RS)
lcd_CTRLout|=(1<<lcd_RS);// Para escribir en DDRAM o CGRAM
else
lcd_CTRLout&=~(1<<lcd_RS);// Para escribir en Registro de Comandos
delay_us(5);// Permite actualizar el Puntero de RAM

lcd_CTRLout&=~(1<<lcd_RW);// Establecer Modo de escritura
lcd_DATAddr=0xFF;// Puerto para salida
lcd_DATAout=data;// Colocar dato
delay_us(1);// tAS, set-up time > 140 ns
lcd_CTRLout|=(1<<lcd_E);// Pulso de Enable
delay_us(2);// Enable pulse width > 450 ns
lcd_CTRLout&=~(1<<lcd_E);//
}

//****************************************************************************
// Lee un byte de dato del LCD.
// Si RS = 1 se lee la locación de DDRAM/CGRAM.
// Si RS = 0 se lee el 'bit de Busy Flag' + el 'Puntero de RAM'.
//****************************************************************************
charlcd_read(charRS)
{
chardata;
if(RS)
lcd_CTRLout|=(1<<lcd_RS);// Para leer de DDRAM o CGRAM
else
lcd_CTRLout&=~(1<<lcd_RS);// Para leer Busy Flag + Puntero de RAM

lcd_CTRLout|=(1<<lcd_RW);// Establecer Modo Lectura
lcd_DATAddr=0x00;// Puerto para entrada
delay_us(2);// tAS, set-up time > 140 ns
lcd_CTRLout|=(1<<lcd_E);// Habilitar LCD
delay_us(2);// Data Delay Time > 1320 ns
data=lcd_DATAin;// Leer dato
lcd_CTRLout&=~(1<<lcd_E);//
returndata;//
}
Códigos fuente de la librería para el USART
Habíamos dicho al iniciar el capítulo que los USART de todos AVR son muy parecidos,
incluso si son de otra familia.
En el caso concreto del ATtiny2313A lo único que tenemos que cambiar en nuestra
librería del USART para los megaAVR son los nombres de sus registros. Puesto que los
conocidos ATmegaXX8 tienen dos USART (USART0 y USART1), es lógico entender que
aparezcan los distintivos 0 y 1 en todos sus registros. Incluso en los ATmegaXX8, que
solo tienen un USART (USART0), sus registros también se identifican por llevar el 0,
por cuestiones de compatibilidad.
En cambio el único USART del ATtiny2313A se llama simplemente USART, a secas, y
sus registros del mismo modo se escriben sin 0 ni nada. Observa a continuación las
semejanzas.
Registros de los ATmegaXX4 y ATmegaXX8.
Registro UCSR0A

UCSR0A

RXC0

TXC0

UDRE0

FE0

DOR0

UPE0

U2X0

MPCM0

RXCIE0

TXCIE0

UDRIE0

RXEN0

TXEN0

UCSZ02

RXB80

TXB80

UPM00

USBS0

UCSZ01

UCSZ00

UCPOL0

UPE

U2X

MPCM

Registro UCSR0B

UCSR0B

Registro UCSR0C

UCSR0C

UMSEL01 UMSEL00 UPM01

Registro UDR0

UDR0
Registro UBRR0H

UBRR0H
Registro UBRR0L

UBRR0L
Registros del ATtiny2313A.
Registro UCSR0A

UCSRA

RXC

TXC

UDRE

FE

DOR
Registro UCSR0B

UCSRB

RXCIE

TXCIE

UDRIE

RXEN

TXEN

UCSZ2

RXB8

TXB8

UMSEL0

UPM1

UPM0

USBS

UCSZ1

UCSZ0

UCPOL

Registro UCSR0C

UCSRC

UMSEL1

Registro UDR0

UDR
Registro UBRR0H

UBRRH
Registro UBRR0L

UBRRL
Oh! hay un pequeño detalle que olvidé mencionar: A diferencia de
los ATmegaXX8 yATmegaXX4, los registros de Baud rate, UBRRH y UBRRL, en
el ATtiny2313A no ocupan posiciones contiguas en el espacio de los registros de E/S y
por tanto el compilador no los puede juntar para tratarlos como un solo registro de 16
bits. Por eso en la rutina deconfiguración de baud rate ambos registros se cargan por
separado.

/******************************************************************************
/* Configurar baud rate */
UCSRA|=(1<<U2X);
UBRRH=(F_CPU/(8*USART_BAUD)-1)>>8;
UBRRL=(F_CPU/(8*USART_BAUD)-1);
...

/******************************************************************************
* FileName: usart.h
* Overview: Macros y prototipos de funciones para el USART en modo Asíncrono
* Processor: ATmel tinyAVR con USART
* Compiler: AVR IAR C y AVR GCC (WinAVR)
* Author:

Shawn Johnson. http://www.cursomicros.com.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License: Se permiten el uso y la redistribución de este código con
*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y la nota de autor de arriba.

*****************************************************************************/

#include "avr_compiler.h"

/* Define la velocidad de transmisión del USART (en bits/segundo), si aún no
* ha sido definida.
*/
#ifndef USART_BAUD
#define USART_BAUD 9600UL
#endif

/* Usar kbhit para ver si hay algún dato en el buffer de recepción antes de
* llamar directamente a la función getchar para evitar esperas innecesarias.
*/
#define kbhit()

(UCSRA & (1<<RXC))

/* Descomentar el siguiente #define para que la funcion 'getchar' haga eco de
* los caracteres recibidos.
*/
//#define __GETCHAR_ECHO__

/* Macros para AVR GCC */
#if defined( __GNUC__ )
#ifdef putchar
#undef putchar
#endif
#ifdef getchar
#undef getchar
#endif
#endif

/* Definiciones de funciones */
voidusart_init(void);
intputchar(int);
intgetchar(void);

/******************************************************************************
* FileName: usart.c
* Overview: Implementa funciones para el USART en modo Asíncrono
* Processor: ATmel tinyAVR con USART
* Compiler: AVR IAR C y AVR GCC (WinAVR)
* Author:

Shawn Johnson. http://www.cursomicros.com.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License: Se permiten el uso y la redistribución de este código con
*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y la nota de autor de arriba.

*****************************************************************************/

#include "usart.h"

#if defined( __GNUC__ )
FILE_stream=FDEV_SETUP_STREAM((int(*)(char,FILE*))putchar,/* function to write one char to
device */
(int(*)(FILE*))getchar,/* function to read one char from device */
_FDEV_SETUP_RW);/* flags, see below */
#endif

//****************************************************************************
// Inicializa el USART0.
//****************************************************************************
voidusart_init(void)
{
/* Configurar baud rate */
UCSRA|=(1<<U2X);
UBRRH=(F_CPU/(8*USART_BAUD)-1)>>8;
UBRRL=(F_CPU/(8*USART_BAUD)-1);

/* Configurar modo de operación Asíncrono y formato de frame a
* 8 bits de datos, 1 bit de stop y sin bit de paridad. */
UCSRC=(1<<UCSZ1)|(1<<UCSZ0);

/* Habilitar módulos Receptor y Transmisor */
UCSRB=(1<<RXEN)|(1<<TXEN);

/* Para el compilador AVR GCC, asociar las funciones 'putchar' y 'getchar'
* con las funciones de entrada y salida (como printf, scanf, etc.) de la
* librería 'stdio' */
#if defined( __GNUC__ )
// fdevopen((int (*)(char, FILE*))putchar, (int (*)(FILE*))getchar);
stdin=stdout=&_stream;
#endif
}

//****************************************************************************
// Transmite el byte bajo de 'dato' por el USART
//****************************************************************************
intputchar(intdato)
{
/* Esperar a que haya espacio en el buffer de transmisión */
while((UCSRA&(1<<UDRE))==0);

/* Colocar dato en el buffer de transmisión */
UDR=dato;
returndato;
}

//****************************************************************************
// Recibe un byte de dato del USART
//****************************************************************************
intgetchar(void)
{
/* Esperar a que haya al menos un dato en el buffer de recepción */
while((UCSRA&(1<<RXC))==0);

/* Leer y retornar el dato menos reciente del buffer de recepción */
#if defined ( __GETCHAR_ECHO__ )
return(putchar(UDR));
#else
returnUDR;
#endif
}

Práctica: Teclado serial RS232
Conectar dos microcontroladores puede ser útil cuando uno solo no basta ya sea quizá
por la falta de algunos pines o porque se le quiere dar al segundo microcontrolador una
tarea dedicada o exclusiva para que la desarrolle con la máxima eficiencia posible, o
por una combinación de ambas razones, como en esta práctica.

El circuito
Circuito para el teclado serial (comunicación entre dos microcontroladores AVR).

Los códigos fuente
Los siguientes dos programas utilizan la misma librería del usart usada en la práctica
anterior.
Código fuente del AVR del teclado.

/******************************************************************************
* FileName: main.c
* Purpose: Control de Teclado mediante Interrupciones PCINTx
* Processor: AVR ATtiny2313A
* Compiler: AVR IAR C & AVR GCC (WinAVR)
* Author:

Shawn Johnson. http://www.cursomicros.com.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License: Se permiten el uso y la redistribución de este código con
*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y las notas de autor y copyright de arriba.

*****************************************************************************/

#include "avr_compiler.h"
#include "usart.h"

//****************************************************************************
// Configuración del puerto de interface
//****************************************************************************
#define kpd_PORT PORTB // Port write
#define kpd_PIN PINB // Port read
#define kpd_DDR DDRB // Dirección de puerto

//****************************************************************************
// Prototipos de funciones
//****************************************************************************
charkeypad_read(void);// Retorna el valor ASCII de la tecla presionada
// o retorna 0x00 si no se presionó nada
voidkeypad_released(void);// Espera hasta que el teclado esté libre
charkeypad_scan(void);// Escanea el teclado

voidSetupInt(void);

//****************************************************************************
// Interrupt Service Routine
// Esta función se ejecuta cuando se detectan cambios de nivel (flancos de
// subida o de bajada) en los pines PB4...PB7.
// Nota:
// Según el datasheet del ATtiny2313A, el Vector de Interrupción para las
// 'Interrupciones de Cambio de Pin' debería ser PCIN0_vect.
// Sin embargo, para el compilador AVR IAR C PCIN1_vect
// y para el compilador AVR GCC es PCINT_B_vect
//****************************************************************************
#ifdef PCINT_B_vect
ISR(PCINT_B_vect)
#else
ISR(PCIN1_vect)
#endif
{
chardato=keypad_read();// Leer teclado
if(dato)// Si fue tecla válida
{
putchar(dato);

/* Esperar a que el teclado quede libre */
keypad_released();
}
SetupInt();
}

//****************************************************************************
// Función principal
//****************************************************************************
intmain(void)
{
usart_init();// Setup USART0 @ 9600-8N1
// puts("rn Control de Teclado mediante Interrupciones PCINTx rrn");

SetupInt();

/* Habilitar las interrupciones PCINT de PORTB en los pines PB4...PH7 */
GIMSK=(1<<PCIE);//////////
PCMSK=0xF0;//////////

sei();// Habilitación general de interrupciones

while(1)// Bucle infinito
{
/* Entrar en modo sleep (Power-Down mode) */
MCUCR=(1<<SM0)|(1<<SE);/////
sleep();
}
}

//****************************************************************************
// Prepara el puerto B para que detecte un cambio de tensión producido al
// presionar una tecla.
//****************************************************************************
voidSetupInt(void)
{
/* Configurar PORTB: Nibble de Rows salida, Nibble de Rows bajo
* nible alto entrada y habilitar pull-ups de nibble alto */
PORTB=0xF0;
DDRB=0x0F;
}

//****************************************************************************
// Escanea el teclado y retorna el valor ASCII de la tecla presionada por
// al menos 25ms. En otro caso retorna 0x00.
//****************************************************************************
charkeypad_read(void)
{
charc1,c2;
c1=keypad_scan();// Escanear teclado
if(c1)// Si hubo alguna tecla pulsada
{
delay_us(25000);// Delay antirrebote
c2=keypad_scan();// Escanear otra vez
if(c1==c2)// Si Ambas teclas leídas son iguales
returnc2;// entonces aceptarla
}
return0x00;
}

//****************************************************************************
// Espera hasta que el teclado quede libre.
//****************************************************************************
voidkeypad_released(void)
{
delay_us(10);//
while(keypad_scan())// Mientras se detecte alguna tecla pulsada
continue;// seguir escaneando.
}

//****************************************************************************
// Escanea el teclado y retorna el valor ASCII de la primera tecla que
// encuentre pulsada. De otro modo retorna 0x00.
//****************************************************************************
charkeypad_scan(void)
{
unsignedcharCol,Row;
charRowMask,ColMask;
// Col0 Col1 Col2 Col3
conststaticcharkeys[]={'7','8','9','A',// Row 0
'4','5','6','B',// Row 1
'1','2','3','C',// Row 2
'.','0','#','D'};// Row 3

kpd_DDR=0x0F;// Nibble alto entrada, nibble bajo salida
kpd_PORT=0xF0;// Habilitar pull-ups del nibble alto

RowMask=0xFE;// Inicializar RowMask a 11111110

for(Row=0;Row<4;Row++)
{
kpd_PORT=RowMask;//
delay_us(10);// Para que se estabilice la señal
ColMask=0x10;// Inicializar ColMask a 00010000

for(Col=0;Col<4;Col++)
{
if((kpd_PIN&ColMask)==0)// Si hubo tecla pulsada
{
kpd_DDR=0x00;// Todo puerto entrada otra vez
returnkeys[4*Row+Col];// Retornar tecla pulsada
}

ColMask<<=1;// Desplazar ColMask para escanear
}// siguiente columna
RowMask<<=1;// Desplazar RowMask para escanear
RowMask|=0x01;// siguiente fila
}

// Se llega aquí si no se halló ninguna tecla pulsada
kpd_DDR=0x00;// Todo puerto entrada otra vez
return0x00;// Retornar Código de no tecla pulsada
}
Esto sería un ejemplo de uso del teclado serial.

/******************************************************************************
* FileName: main.c
* Purpose: Control de teclado matricial serie de 4x4
* Processor: ATmel tinyAVR o megaAVR con USART
* Compiler: AVR IAR C & AVR GCC (WinAVR)
* Author:

Shawn Johnson. http://www.cursomicros.com.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License: Se permiten el uso y la redistribución de este código con
*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y las notas de autor y copyright de arriba.

*****************************************************************************/

#include "avr_compiler.h"
#include "usart.h"
intmain(void)
{
usart_init();// Inicializar USART @ 9600 N 1

puts("rn Control de Teclado serie de 4x4 rn");

while(1)
{
if(kbhit())// Sí llegó un dato del teclado
putchar(getchar());// Leerlo y Enviarlo a la PC
}
}

Introducción
Por más que actualmente nuestro alrededor esté repleto por la tecnología digital,
sabemos que el mundo no nació así y que hay cosas que tampoco van a cambiar. No
podemos cambiar la naturaleza analógica de los fenómenos naturales como la presión,
la temperatura, la luminosidad, la electricidad, el magnetismo, etc.
El transductor elemental que se utiliza para digitalizar las señales de estos fenómenos
es el Conversor Analógico a Digital, ADC, que convierte una tensión eléctrica en un
valor numérico. De algún modo, cualquier otra señal puede llegar a manifestarse
eléctricamente, de allí a tensión eléctrica, y la tenemos. Así es como funcionan por
ejemplo los sensores de luz, de temperatura (de calor), etc.

Conceptos Básicos
Antes de entrar de lleno en la programación del conversor ADC de los AVR vamos a
conocer algunos conceptos que nos ayudarán para no perdernos en la teoría. Puedes
saltearte este apartado si deseas pero presiento que en algún momento regresarás

Resolución y Voltajes de Referencia del ADC
Un conversor ADC es un circuito que toma valores analógicos de tensión y los
convierte en códigos binarios. Los valores que definen los límites de las tensiones a
medir se denominan voltajes de referencia y se representan por Vref- (el mínimo)
y Vref+ (el máximo).
La resolución del conversor queda determinada por la cantidad de bits que representan
el resultado de la conversión. Así, se pueden encontrar conversores de 8 bits, de 12
bits, etc.
Un ADC de n bits puede representar hasta
valores digitales, de modo que a la
entrada analógica igual a Vref- le asignará el 0 digital y la entrada igual a Vref+ le
asignará el
digital. A los otros valores analógicos se les asignará los otros
valores digitales distribuidos equidistantemente.
Entre Vref- y Vref+ se pueden concebir infinitos valores analógicos, pero con n bits
solo se pueden formar
valores discretos diferentes. Por lo tanto habrá valores
analógicos que no podrán ser representados con exactitud.
La diferencia entre dos valores analógicos correspondientes a dos valores digitales
consecutivos se define como resolución de voltaje de ADC.

Por ejemplo, en un ADC 10 bits con Vref- = 0 V y Vref+ = 5V, la resolución alcanzada
será de (5-0)/1023 = 4.88 mV. Significa que el máximo error posible será de 4.88/2 =
2.44 mV.
Es poco usual encontrar aplicaciones donde Vref- sea diferente de GND = 0V y
dondeVref+ sea diferente de VCC = 5V. En estas condiciones se puede aplicar una
regla de tres para deducir que una entrada analógica Vin cualquiera (entre 0 y Vref+)
será convertida en un valor numérico que se puede calcular con la siguiente fórmula:

Por más que actualmente nuestro alrededor esté repleto por la tecnología digital,
sabemos que el mundo no nació así y que hay cosas que tampoco van a cambiar. No
podemos cambiar la naturaleza analógica de los fenómenos naturales como la presión,
la temperatura, la luminosidad, la electricidad, el magnetismo, etc.
El transductor elemental que se utiliza para digitalizar las señales de estos fenómenos
es el Conversor Analógico a Digital, ADC, que convierte una tensión eléctrica en un
valor numérico. De algún modo, cualquier otra señal puede llegar a manifestarse
eléctricamente, de allí a tensión eléctrica, y la tenemos. Así es como funcionan por
ejemplo los sensores de luz, de temperatura (de calor), etc.
Conceptos Básicos
Antes de entrar de lleno en la programación del conversor ADC de los AVR vamos a
conocer algunos conceptos que nos ayudarán para no perdernos en la teoría. Puedes
saltearte este apartado si deseas pero presiento que en algún momento regresarás

Resolución y Voltajes de Referencia del ADC
Un conversor ADC es un circuito que toma valores analógicos de tensión y los
convierte en códigos binarios. Los valores que definen los límites de las tensiones a
medir se denominan voltajes de referencia y se representan por Vref- (el mínimo)
y Vref+ (el máximo).
La resolución del conversor queda determinada por la cantidad de bits que representan
el resultado de la conversión. Así, se pueden encontrar conversores de 8 bits, de 12
bits, etc.
Un ADC de n bits puede representar hasta
valores digitales, de modo que a la
entrada analógica igual a Vref- le asignará el 0 digital y la entrada igual a Vref+ le
asignará el
digital. A los otros valores analógicos se les asignará los otros
valores digitales distribuidos equidistantemente.
Entre Vref- y Vref+ se pueden concebir infinitos valores analógicos, pero con n bits
solo se pueden formar
valores discretos diferentes. Por lo tanto habrá valores
analógicos que no podrán ser representados con exactitud.
La diferencia entre dos valores analógicos correspondientes a dos valores digitales
consecutivos se define como resolución de voltaje de ADC.

Por ejemplo, en un ADC 10 bits con Vref- = 0 V y Vref+ = 5V, la resolución alcanzada
será de (5-0)/1023 = 4.88 mV. Significa que el máximo error posible será de 4.88/2 =
2.44 mV.
Es poco usual encontrar aplicaciones donde Vref- sea diferente de GND = 0V y
dondeVref+ sea diferente de VCC = 5V. En estas condiciones se puede aplicar una
regla de tres para deducir que una entrada analógica Vin cualquiera (entre 0 y Vref+)
será convertida en un valor numérico que se puede calcular con la siguiente fórmula:

El ADC de aproximaciones sucesivas
Casi todos los módulos ADC de los microcontroladores son de aproximaciones
sucesivas. Funcionan con cuatro elementos básicos: un comparador analógico,
una lógica de control, un conversor digital analógicoDAC y el reloj que guía los pasos
de la conversión. Los DAC son mucho más simples que los ADC y entregan resultados
casi de inmediato.
Cada número binario generado va siendo convertido en una tensión analógica Vout que
luego se compara con la tensión de entrada que queremos medir Vin. Si son iguales (o
los más cercanos posible), ¡eureka! Es el número binario que corresponde a Vin. Así de
simple.

Diagrama de básico de un conversor ADC de aproximaciones sucesivas.
Ahora bien, siendo el conversor mostrado de 10 bits y pudiéndose generar hasta 1024
números binarios distintos, ¿se tendrán que hacer 1024 comparaciones?
No, solo 10. El algoritmo a seguir es similar al que responde a la clásica pregunta
capciosa: Si entre 100 bolitas hay solo una que pesa un poco más que las otras,
¿cuántas veces habrá que llevarlas a una balanza para encontrarla? (rpta: 6)
Para entender mejor cómo funciona este ADC vamos a imitar su operación.
Supongamos que el ADC trabaja con tensiones de referencia de 0 V y 5 V y que
queremos medir una señal Vin de 4.00000 Volts. Como el ADC es de 10 bits, dará los
10 pasos mostrados en la siguiente tabla:
Tabla Paso #

Paso #

Binario Generado D9 ... Vout (DAC)
D0
(Voltios)

Vin (ADC)
(Voltios)

¿Vout > Vin ?

1

10 0000 0000

2.50244

4.00000

Sí, queda D9

2

11 0000 0000

3.75366

4.00000

Sí, queda D8
Tabla Paso #

Paso #

Binario Generado D9 ... Vout (DAC)
D0
(Voltios)

Vin (ADC)
(Voltios)

¿Vout > Vin ?

3

11 1000 0000

4.37927

4.00000

No, limpiar
D7

4

11 0100 0000

4.06647

4.00000

No, limpiar
D6

5

11 0010 0000

3.91000

4.00000

Sí, queda D5

6

11 0011 0000

3.98826

4.00000

Sí, queda D4

7

11 0011 1000

4.02737

4.00000

No, limpiar
D3

8

11 0011 0100

4.00782

4.00000

No, limpiar
D2

9

11 0011 0010

3.99804

4.00000

Sí, queda D1

10

11 0011 0011

4.00293

4.00000

No, limpiar
D0

Valor
final

11 0011 0010

3.99804

---

---

El primer número binario generado tiene el bit D9 seteado. Este número se convierte
en el valor analógico Vout, que después se compara con Vin. Como la comparación
(Vout < Vin) es positiva nos quedamos con este bit. Después se prueba seteando el
siguiente bit, D8, y como ahora la evaluación (Vout < Vin) sigue siendo afirmativa
también nos quedamos con este bit. En seguida se setea el bit D7; ahora la evaluación
(Vout < Vin) es negativa y debemos limpiar D7. Y se sigue con el resto de manera
similar hasta alcanzar el bit D0.
Como vemos, al final nos quedamos con el valor 11 0011 0010, que significa una
tensión de 3.99804 V y que comparado con nuestros 4.00000 V nos da un error de
0.00196 V = 1.96 mV ó de 0.049%. Nada mal. Es fácil ver que a mayor resolución en
bits habrá más aproximación.

El Módulo ADC de los AVR
Los AVR de la serie megaAVR tienen un ADC de aproximaciones sucesivas de 10 bits.
Es uno solo pero está multiplexado para dar cabida hasta a 8 entradas analógicas. Se
convierte solo una entrada o la diferencia de dos entradas analógicas a la vez.
Concretamente, los AVR de la serie ATmegaXX4 cuentan con 8 canales de conversión,
todos ubicados en su puerto A. Por otro lado los AVR de la serie ATmegaXX8 destinan
todo su puerto C como canales de entrada del ADC.
El detalle con los ATmegaXX8 es que su puerto C solo tiene 6 pines si vienen en
empaque DIP pero tiene 8 pines en los empaques TQFP y QFN/MLF. De modo que
estos megaAVR pueden tener 6 u 8 canales de conversión dependiendo de su
empaque.

Operación del Módulo ADC
En las aplicaciones ordinarias solo los registros ADCSRA y ADMUX son los que se
manipulan activamente. Los registros ADCH y ADCL son de solo lectura y como no
tienen formato, basta con recordar sus nombres.
ADCSRA. Es el principal registro de control y estado del ADC. Manipulando los bits de
este registro iniciamos la conversión, establecemos la velocidad de conversión o
elegimos el formato del resultado de la conversión. Veremos los detalles en adelante.
ADCSRB. Es el segundo registro de control y estado del ADC. Sus pocos bits
funcionales configuran el modo de conversiones automáticas y como ese modo es
raramente usado, el registro ADCSRB tampoco está muy avistado en los programas.
Los viejos AVR no lo tienen.
ADCH y ADCL. Son los registros que almacenan el resultado de la conversión. Uno
guarda 8 bits y el otro los dos bits restantes. La forma como se distribuyen se debe
especificar con el bit ADLAR del registro ADMUX.
Para cuestiones de programación estos registros se fusionan para formar un único
registro de 16 bits normalmente llamado ADC o ADCW.
ADMUX. Es el registro que selecciona el canal de conversión y establece losvoltajes de
referencia.
DIDR0. Es también un nuevo registro no disponible en los viejos AVR. Su función es
desconectar los pines seleccionados como canales latentes del conversor para así
evitar que se desgaste corriente en parte del circuito del ADC.
Registro ADCSRA

ADCSRA

ADEN

ADSC

ADATE

ADIF

ADIE

---

ACME

---

---

---

ADPS2

ADPS1

ADPS0

ADTS1

ADTS0

Registro ADCSRB

ADCSRB

ADTS2
Registro ADMUX

ADMUX

REFS1

REFS0

ADLAR

MUX4

MUX3

MUX2

MUX1

MUX0

ADC7D

ADC6D

ADC5D

ADC4D

ADC3D

ADC2D

ADC1D

ADC0D

Registro DIDR0

DIDR0
Registro ADCH

ADCH
Registro ADCL

ADCL
El ADC de los megaAVR es bastante fácil de controlar si se le emplea en su modo
habitual de conversiones normales de entrada única. Tras configurar este modo, se
escoge un canal a la vez y se setea el bit ADSC para iniciar la conversión. Después
esperamos a que este mismo bit se limpie automáticamente por hardware como señal
de que la conversión terminó, y cuando lo haga podremos leer el resultado. Eso sería
todo.
El proceso descrito se puede desglosar en los siguientes pasos. Los pasos 2 al 5 (que
están relacionados con el registro ADMUX) no necesariamente tienen que ir en ese
orden de hecho se pueden juntar todos en un solo paso. Esto es porque el
registro ADMUXtrabaja con un buffer de respaldo del cual es actualizado
continuamente para asegurar un buen trabajo del ADC. Esto es, cuando escribimos
en ADMUX en realidad escribimos en su buffer, el cual se copia continuamente al
registro ADMUX hasta que se inicia la conversión. En ese momento el copiado continuo
se detiene y se reanudará cuando termine la conversión. De ese modo se evitan los
resultados 'frankenstenianos' de lo que sería un mal uso del registro ADMUX.
Seleccionar el reloj del conversor ADC, con los bits ADPS2:ADPS0.
Seleccionar los voltajes de referencia del conversor, usando los bits REFS1 yREFS0.
Establecer la justificación del resultado con el bit ADLAR.
Seleccionar el canal o los canales de entrada del ADC, con los bits MUX4:MUX0.
Encender el módulo ADC, seteando el bit ADEN.
Iniciar la conversión, seteando el bit ADSC.
Esperar a que termine la conversión. Cuando esto pase el flag ADIF se pondrá a uno y
si la conversión es normal y el bit ADSC se limpiará automáticamente.
Leer el resultado de la conversión del par de registros ADCH: ADCL.
Para bien o para mal, la flexibilidad del ADC complica aún más su control, pues
también existen el modo de conversiones diferenciales y el modo de conversiones
auto-disparadas, el cual se puede aplicar para cada caso, es decir, el ADC puede
desdoblar su operación hasta en cuatro modos.

Señales para iniciar conversiones normales y auto-disparadas.
Conversiones normales de entrada única, donde el ADC convierte el valor de un canal,
cada vez que se setee el bit ADSC.
Conversiones normales de entradas diferenciales, donde el ADC convierte la diferencia
de dos canales. La conversión se inicia seteando el bit ADSC.
Conversiones auto-disparadas de entrada única, donde el ADC convierte un canal
cuando se produzca alguno de los eventos seleccionados por los
bits ADTS2,ADTS1 y ADTS0, que puede ser, por ejemplo, el desbordamiento de un
Timer o simplemente la activación del flag ADIF. Este último caso es peculiar y se
conoce como modo de conversiones de corrida libre, porque el ADC realiza
conversiones una tras otra sin cesar. Aquí la primera conversión se inicia seteando el
bit ADSC.
Conversiones auto-disparadas de entradas diferenciales. Es una combinación de los
dos modos anteriores.
No hay una forma explícita de establecer cada uno de estos cuatro modos de operación
del ADC. Si la conversión será de entrada única o diferencial será resultado de
configurar el multiplexor del ADC con los bits MUX4...MUX0. Por defecto las
conversiones seránnormales. Si queremos que se disparen automáticamente habrá que
configurarlas con los bits ADTS2, ADTS1 y ADTS0 del registro ADCSRB, siempre que el
bit ADATE de ADCSRA valga uno.
Tabla ADTS2
ADTS2 ADTS1 ADTS0 Fuente de disparo
0

0

0

Modo de Corrida Libre

0

0

1

Comparador Analógico

0

1

0

Interrupción Externa INT0

0

1

1

Coincidencia del Timer/Counter0

1

0

0

Desbordamiento del Timer/Counter0

1

0

1

Coincidencia B del Timer/Counter1

1

1

0

Desbordamiento del Timer/Counter1

1

1

1

Evento de Captura del Timer/Counter1

Selección del Canal de Conversión
Las 8 entradas analógicas del ADC son los pines del puerto A de los AVR que tienen 4
puertos o más. En los AVR de 3 puertos son los pines del puerto C; estos AVR tienen 6
entradas analógicas si vienen en empaque PDIP u 8 si vienen en empaque TQFP, QFP o
MLF. Las dos entradas adicionales son pines independientes, que no forman parte de
ningún puerto.
El ADC solo puede tomar una o dos entradas analógicas por conversión, así que cada
vez que se desee obtener un valor analógico externo se debe seleccionar previamente
dicha entrada analógica mediante los bits MUX4, MUX3, MUX2, MUX1 y MUX0 del
registro ADMUX.
Registro ADMUX

ADMUX REFS1

REFS0

ADLAR MUX4

MUX3

MUX2

MUX1

MUX0

La siguiente tabla muestra todas las opciones posibles que se pueden obtener. Parece
complicado de descifrar pero enseguida lo explicaremos.
Cananles del ADC del microcontrolador AVR

MUX4:MUX0

Entrada
Única

Entrada Diferencial
Positiva

Entrada Diferencial
Negativa

Ganancia
Cananles del ADC del microcontrolador AVR

MUX4:MUX0

Entrada
Única

00000

ADC0

00001

ADC1

00010

ADC2

00011

Entrada Diferencial
Positiva

Entrada Diferencial
Negativa

Ganancia

ADC3
N/A

00100

ADC4

00101

ADC5

00110

ADC6

00111

ADC7

01000

ADC0

ADC0

10x

01001

ADC1

ADC0

10x

01010

ADC0

ADC0

200x

01011

ADC1

ADC0

200x

01100

ADC2

ADC2

10x

01101

ADC3

ADC2

10x

01110

ADC2

ADC2

200x

01111

ADC3

ADC2

200x

10000

ADC0

ADC1

1x

10001

ADC1

ADC1

1x

N/A
Cananles del ADC del microcontrolador AVR

Entrada Diferencial
Positiva

Entrada Diferencial
Negativa

Ganancia

10010

ADC2

ADC1

1x

10011

ADC3

ADC1

1x

10100

ADC4

ADC1

1x

10101

ADC5

ADC1

1x

10110

ADC6

ADC1

1x

10111

ADC7

ADC1

1x

11000

ADC0

ADC2

1x

11001

ADC1

ADC2

1x

11010

ADC2

ADC2

1x

11011

ADC3

ADC2

1x

11100

ADC4

ADC2

1x

11101

ADC5

ADC2

1x

MUX4:MUX0

Entrada
Única

11110

1.1V (

)

11111

0 V (GND)

Observemos en primer lugar que la tabla se puede separar en tres secciones. La
primera parte corresponde al denominado modo de conversión simple o de entrada
única, la segunda parte es del modo de conversión diferencial y en la tercera parte no
se elige ningún canal analógico sino que se convierte el valor de la tensión
o GND.
El modo de conversión de entrada única es el que se emplea en la gran mayoría de las
aplicaciones. Aquí los bits del multiplexor MUX4 y MUX3 valen 0 y el ADC solo puede
convertir una de las 8 entradas analógicas a la vez. El diagrama funcional del ADC en
este caso se representa así.
Diagrama básico del ADC para conversiones de entrada única.
Debemos tener en cuenta que este modo es compatible en todos los módulos ADC de
los megaAVR que lo tienen. Sobra decir que no tendrán ningún efecto las
configuraciones deMUX4:MUX0 que eligen los canales ADC6 y ADC7 en los AVR que no
tienen estos pines. De hecho los megaAVR de 3 puertos no tienen el bit MUX4 y es
recomendable dejarlo siempre en 0.
En el modo de conversión diferencial las entradas analógicas se agrupan de a dos.
Cada par se constituye por una Entrada Diferencial Positiva y una Entrada Diferencial
Negativa. Todas las combinaciones posibles están establecidas por los
bits MUX4:MUX0, de acuerdo con la tabla de arriba. Se puede observar allí y en la
figura mostrada abajo que en este modo interviene además un amplificador de
ganancia que puede multiplicar la diferencia entre las entradas por un factor de 1x,
10x o 200x antes de ser convertido a su valor analógico. El inconveniente del
amplificador es que reduce la resolución del ADC a 8 bits si se usa la ganancia
de 1x o 10x y hasta a 6 bits con la ganancia de 200x. Este modo de operación del ADC
solo está disponible en los megaAVR de 4 puertos o más.
Diagrama básico del ADC para conversiones de entradas diferenciales.
Finalmente, hay dos combinaciones de los bits MUX4:MUX0 que hacen que el valor
analógico a convertir sea la señal de tierra GND o una tensión de referencia
denominada
que varía según el microcontrolador. En los megaAVR de las series
4xx y 8xx que son los enfocados con prioridad en cursomicros.com esta tensión
vale 1.1V. Si vamos a trabajar con esta característica utilizando otros modelos de AVR,
deberemos revisar su datasheet.

Conversión de las tensiones de referencia GND y

.

Los Voltajes de Referencia
Son los valores analógicos límites entre los que deberá estar comprendida la tensión
analógica a convertir. El nivel superior se representa por Vref+ y el inferior por Vref-.
Como seguramente lo habrás notado en los diagramas anteriores, el valor de Vref- es
igual al negativo de Vref+ cuando el ADC realiza conversiones diferenciales y es igual a
0V en los demás casos. En cualquier modo de operación Vref+ se puede configurar por
los bits REFS1 y REFS0, del registro ADMUX y podemos escoger una de estas tres
opciones:
El valor del pin AVCC
El valor del pin AREF
Un Voltaje de Referencia Interno (de 1.1V o 2.56 V) que provee el megaAVR.
Registro DMUX

ADMUX REFS1

REFS0

ADLAR MUX4

MUX3

MUX2

MUX1

MUX0

Si los bits REFS1:REFS0 eligen como voltaje de referencia Vref+ al pin AVCC, ese pin
lo debemos conectar a Vcc mediante un filtro pasa-bajas. Puesto que el
pin AREF queda internamente conectado a AVCC, es más que recomendable colocarle
un capacitor para filtrar el ruido. Abajo se muestra un boceto del circuito descrito.
Recordemos que el pin AVCC es en principio la alimentación del puerto que lleva los
canales del ADC, así como la alimentación del mismo módulo ADC. Sin embargo,
incluso de no usar el ADC, el pin AVCC se debe conectar a Vcc, con filtro o sin él.

Si los bits REFS1:REFS0 eligen como voltaje de referencia Vref+ al pin AREF, podemos
conectar a ese pin cualquier fuente de voltaje, siempre que no supere el valor de la
alimentación Vcc y en caso de que trabajemos con canales diferenciales no debe ser
menor de 2 V. De esto último se deduce que no se podría elegir el Voltaje de
Referencia Interno de 1.1V, descrito luego.
Es muy poco frecuente programar el ADC para que opere de este modo, pero si lo
vamos a hacer, debemos fijarnos bien en la configuración de los bits REF1 y REF0. Si
aplicamos una fuente de tensión en el pin AREF y los bits REF1 y REF0 seleccionan una
referencia diferente, entonces se producirá un corto-circuito interno entre el pin AREF y
la referencia seleccionada (AVCC, por ejemplo).
Los bits REFS1:REFS0 también pueden elegir como Vref+ alguno de los Voltajes de
Referencia Internos que provee el AVR, pero aquí se debe tener en cuenta que sus
valores pueden variar entre las diferentes series de AVR, incluso si son de familias
cercanas. También en este caso la referencia se verá reflejada por el pin AREF, de
modo que deberíamos conectar allí un capacitor de estabilización.

Las siguientes tablas muestran la configuración de los
bits REFS1:REFS0 correspondiente a los AVR de las series ATmegaXX4 y ATmegaXX8.
Podemos observar que la clara divergencia se encuentra en la elección de los Voltajes
de Referencia Internos. Si vamos a utilizar estas referencias en otros AVR será
recomendable revisar su datasheet respectivo.
Tabla REFS1

REFS1 REFS0 Voltaje de Referencia Vref+ para los ATmegaXX4
0

0

Pin AREF

0

1

Pin AVCC con capacitor externo en el pin AREF

1

0

Voltaje de Referencia Interno de 1.1V con capacitor externo en el
Tabla REFS1

REFS1 REFS0 Voltaje de Referencia Vref+ para los ATmegaXX4
pin AREF
1

1

Voltaje de Referencia Interno de 2.56V con capacitor externo en el
pin AREF

Tabla REFS1

REFS1 REFS0 Voltaje de Referencia Vref+ para los ATmegaXX8
0

0

Pin AREF

0

1

Pin AVCC con capacitor externo en el pin AREF

1

0

Reservado

1

1

Voltaje de Referencia Interno de 1.1V con capacitor externo en el
pin AREF

Suponiendo que trabajamos con conversiones de entrada única, esto es, Vrefconectado a tierra, GND = 0V, como se ve en la siguiente figura, entonces una
entrada analógica igual a 0V o inferior se convertirá en 0x000 y las tensiones
analógicas iguales a Vref+ o superiores se convertirán en el valor digital 0x3FF.
Recordando la teoría estudiada al inicio de este capítulo, cualquier otro valor
analógico Vin comprendido entre estos límites estará sujeto a la fórmula.
Voltajes de Referencia del ADC para conversiones de entrada única.
Por otro lado, si nuestro ADC realiza conversiones diferenciales, entonces la máxima
diferencia positiva o negativa que se podrá convertir correctamente será (+/Vref+)/ganancia, siendo esta ganancia igual a 1, 10 ó 200. En este caso el resultado
será un número entero con signo, es decir, positivo o negativo, formateado en
complemento a dos. De acuerdo con esto, la máxima diferencia negativa
corresponderá al valor digital 0x200 = -512 y la máxima diferencia positiva se
convertirá en 0x1FF = +511. Cualquier otra diferencia analógica interpolada se deberá
interpretar con la siguiente fórmula.

Donde Vpos en la entrada diferencial positiva y Vneg es la entrada diferencial negativa.

Voltajes de Referencia del ADC para conversiones de entradas diferenciales.
La primera conversión después de cambiar los voltajes de referencia es imprecisa y se
recomienda descartarla.

Resultado de la Conversión
El resultado de la conversión es una cantidad binaria de 10 bits que se deposita entre
los registros ADCH y ADCL, según la justificación mostrada en la siguiente figura y de
acuerdo con el bit ADLAR (ADC Left Adjust Result) del registro ADMUX.
Registro DMUX

ADMUX

REFS1

REFS0

ADLAR

MUX4

MUX3

MUX2

MUX1

MUX0
Justificación del resultado entre los registros ADCH y ADCL.
Obviamente debemos esperar a que termine una conversión antes de leer un dato
válido de estos registros. Para esto podemos comprobar la activación del flag ADIF (del
registro ADCSRA) o la puesta a cero del bitADSC (también de ADCSRA), si es que el
ADC opera en modo normal.
Los dos registros del resultado son de solo lectura y el acceso a ellos no involucra una
operación atómica. Sin embargo, debemos saber que después de leer el
registro ADCLqueda bloqueada la actualización de los otros registros del ADC hasta
que leamos el registro ADCH. De este modo se asegura que los datos presentes en
estos registros corresponden a una misma conversión. De aquí se desprende que al
terminar una conversión debemos empezar por leer ADCL y luego ADCH, o
simplemente podemos tomar el valor de ADCH y así permitir que se puedan depositar
en ellos los valores de nuevas conversiones.
Cuando se establece la justificación derecha los registros ADCH y ADCL conforman el
registro de 16 bits llamado simplemente ADC y puede ser así reconocido por los
compiladores C puesto que ocupan posiciones contiguas en el espacio de los registros
de E/S.

Reloj del ADC y Tiempo de Conversión
Como todo circuito síncrono, el conversor ADC necesita de una señal de reloj para
dirigir los pasos de su algoritmo de aproximaciones sucesivas, ése que describimos al
principio. Este reloj deriva del oscilador del sistema F_CPU. La frecuencia del reloj del
ADC dependerá de la resolución del resultado que se desee obtener. Por ejemplo, si se
va a trabajar con los 10 bits de resolución , entonces se requerirá de un reloj cuya
frecuencia esté entre 50kHz y 200kHz. Si se requiere de una resolución menor de 10
bits, el reloj del ADC puede superar los 200kHz.
El reloj del ADC es una ramificación del reloj del sistema, F_CPU. De allí proviene y
antes de aplicarse al ADC pasa por un prescaler programable que permite disminuir su
valor. Los factores de división se establecen por los bits ADPS2, ADPS1 y ADPS0, del
registro ADCSRA, de acuerdo con la tabla mostrada más abajo.

Diagrama de la fuente del reloj del ADC.
Tabla ADPS2

ADPS2 ADPS1 ADPS0 Factor de División
0

0

0

2

0

0

1

2

0

1

0

4

0

1

1

8

1

0

0

16

1

0

1

32

1

1

0

64

1

1

1

128
A modo de ejemplo analicemos los valores de ADPS1, ADPS1 y ADPS0 que podríamos
usar suponiendo que trabajamos con nuestro acostumbrado XTAL (F_CPU) de 8MHz.
Si ADPS2:ADPS0 = 111b, el reloj del ADC tendrá una frecuencia de 8MHz/128 =
62.5kHz, valor suficiente para conseguir resultados fiables de 10 bits, tan fiables como
los generados a 125kHz con el factor de prescaler de 64.
Sin embargo, si escogemos el factor de 32, con ADPS2:ADPS0 = 101b, el ADC operará
a 8MHz/32 = 250kHz, que es una frecuencia superior a los 200kHz que garantizan una
buena conversión, de modo que deberemos evitarla, a menos tal vez que solo nos
interesen los 8 bits más significativos del resultado. En este ejemplo, los factores de
prescaler inferiores 32 conllevarán una operación del ADC deficiente e inaceptable.
De nuestras lecciones iniciales del conversor de aproximaciones sucesivas, sabemos
que por cada bit del resultado digital el ADC demora un ciclo del reloj. Esto es, si el
conversor genera datos de 10 bits se requerirán de 10 ciclos de reloj por conversión.
Eso es del todo cierto solo en el cómputo mismo de la conversión o en un ADC ideal;
en un ADC real hay factores de establecimiento que alargan ligeramente el tiempo de
conversión.
En los megaAVR el tiempo de conversión depende del tipo de conversión que se
realiza. La siguiente tabla muestra las cuatro posibilidades.
Tabla Tipo de Conversión

Tipo de Conversión

Muestreo y retención (Ciclos desde Tiempo de
el inicio de la conversión)
Conversión (Ciclos)

Primera conversión

14.5

25

Conversiones Normales, de
entrada única

1.5

13

Conversiones Normales, de
entrada diferencial

1.5/2.5

13/14

Conversiones autodisparadas

2

13.5

La primera conversión en cualquier modo es la que se ejecuta después de habilitar el
ADC, seteando el bit ADEN; siempre demora más debido a que se debe inicializar el
circuito analógico del ADC.
Los ciclos de muestreo y retención se cuentan a partir del momento en que se inicia la
conversión, o sea, después de setear el bit ADSC, del registro ADCSRA. Para
comprender esto debemos saber que el ADC no capta la señal a convertir directamente
del pin ADCx, sino que primero espera que dicho nivel de tensión se deposite en el
capacitor de muestreo y retención
, para luego iniciar la conversión desde allí. El
tiempo que demora este capacitor en cargarse se denomina periodo de muestreo y
retención o a veces tiempo de adquisición. Éste varía principalmente de acuerdo con la
impedancia Rsdel circuito externo al canal del ADC. El ADC del AVR está optimizado
para acoplarse a impedancias de 10K o inferior.

Circuito de entrada del ADC de los megaAVR.
Junto con su resolución el tiempo de conversión es el parámetro más importante de un
ADC. Para fines prácticos esto se calcula como la suma del tiempo de conversión en sí
(el que acabamos de describir) más el tiempo de adquisición. Por ejemplo, según la
tabla de arriba, las conversiones normales de entrada única, que son las más usadas,
demoran 13+1.5 = 14.5 ciclos. Si el ADC trabajara a su máxima frecuencia
recomendada de 200kHz, cada ciclo duraría 1/200kHz = 5us. De aquí concluimos que
cada una de estas conversiones en realidad toma 14.5*5 = 72.5us y que en un
segundo se pueden realizar hasta 1/72.5u = 13793 conversiones => 14 kSPS. Por si
acaso, la unidad kSPS significa kilo Samples Per Second. El datasheet dice que llega a
15 kSPS pero ya ves que según nuestros cálculos no sale así. Como sea, es un
conversor bastante lento para mi gusto.

Interrupción del ADC y Modo Sleep
Recordemos que el modo Sleep es un estado en que se detienen las diversas
ramificaciones del oscilador del sistema y dependiendo de las ramificaciones que se
congelen se pueden conocer varios modos Sleep. Pues bien, son dos los modos Sleep
que nos competen en esta ocasión.
Idle mode. Es el estado de sueño menos profundo que existe en los AVR. Aquí solo se
congelan los relojes del CPU y de la memoria FLASH, permitiendo que los demás
periféricos como el USART, TWI, SPI, los Timers,… y, por supuesto, el ADC, continúen
trabajando normalmente.
ADC Noise Reduction Mode, o modo de reducción de ruido del ADC. Es el segundo
estado de sueño y, por su nombre, fue diseñado para que el ADC opere sin
interferencias, o sea, aunque normalmente se piensa en elmodo Sleep como una forma
de ahorrar energía, el principal objeto de usar el ADC en este estado es tomar la señal
analógica sin presencia del ruido de conmutación inherente de los otros componentes
del microcontrolador. Aparte del CPU en este estado se congelan todos los periféricos
relacionados con las transferencias de datos (USART, SPI, etc.) quedando como
despertadores solo las interrupciones asíncronas, aparte del ADC, por supuesto.
El evento que puede disparar la interrupción del ADC es la conclusión de una
conversión. En ese instante, al mismo tiempo que se limpia el bit ADSC, se activará al
flag ADIF. El bit ADIF se limpia por hardware al ejecutarse la función de interrupción
ISR y si no se va a utilizar dicha función también se puede limpiar por software
escribiendo un uno.
La interrupción del ADC se habilita seteando el bit ADIE, obviamente aparte
de I deSREG.
Registro ADCSRA

ADCSRA ADEN

ADSC

ADATE ADIF

ADIE

ADPS2

ADPS1

ADPS0

Para realizar conversiones en modo ADC Noise Reduction se deben seguir los
siguientes pasos.
El ADC debe estar habilitado, debe estar configurado para conversiones normales, no
debe tener una conversión en curso y debe tener habilitada la interrupción del ADC.
Entrar en modo ADC Noise Reduction o Idle mode. El ADC iniciará la conversión apenas
se detenga el CPU.
Al terminar la conversión, la interrupción del ADC despertará el CPU y se ejecutará la
rutina de interrupción respectiva.
Debemos observar que el ADC no se apagará automáticamente al entrar en otros
modos Sleep que no sean Idle o ADC Noise Reduction. Se recomienda por tanto
apagarlo manualmente escribiendo cero en el bit ADEN para evitar desperdicio de
energía. Si el ADC está habilitado en dichos modos Sleep y se desea realizar
conversiones diferenciales, se recomienda apagar y luego encender el ADC después de
salir del modo Sleep para asegurar que se obtengan resultados válidos.

Registros del Módulo ADC
ADCSRA: ADC Control and Status Register A
Registro ADCSRA

ADCSRA ADEN ADSC

ADATE

ADIF

ADIE

ADPS2

ADPS1

ADPS0

Registro de Microcontrolador

ADEN

ADC Enable
Escribiendo uno en este bit se habilita el ADC. Escribiendo un cero el ADC se
apaga. Si se apaga el ADC cuando hay una conversión en progreso, se interrumpirá
y terminará dicha conversión.

ADSC

ADC Start Conversion
En modo de conversión normal escribir uno en este bit iniciará una conversión. En
modo de conversiones auto-disparadas, escribir uno en este bit iniciará la primera
conversión. La primera conversión después de escribir en ADSC tras iniciar el ADC
o si ADSC fue escrito al mismo tiempo que se habilitó el ADC, tomará 25 ciclos de
reloj del ADC en lugar de los 13 ciclos habituales. La primera conversión conlleva
la inicialización del ADC.
ADSC se leerá como uno mientras la conversión esté en progreso. Al terminar la
conversión este bit retornará a cero. Escribir cero en este bit no tiene efecto.

ADATE

ADC Auto Trigger Enable
Cuando se escribe uno en este bit se habilitan las conversiones automáticas del
ADC. El ADC iniciará una conversión en el flanco positivo de la señal de disparo
seleccionada. La fuente de disparo se selecciona por los bits ADTS en el registro
ADCSRB.

ADIF

ADC Interrupt Flag
Este bit se setea cuando termina una conversión y se actualizan los registros los
registros de datos. La interrupción de Conversión de ADC Completada se ejecuta
cuando valgan uno los bits ADIE e I de SREG.
El flag ADIF se limpiará automáticamente por hardware cuando se ejecute la
correspondiente rutina de interrupción ISR. Alternativamente, ADIF se limpia
escribiéndole un uno. Observa que ejecutar una instrucción de LecturaModificación-Escritura en ADCSRA puede deshabilitar una interrupción pendiente.
Esto también se aplica cuando se usan las instrucciones SBI y CBI.

ADIE

ADC Interrupt Enable
Cuando se escribe uno en este bit y el bit I de SREG vale uno se activa la
interrupción de Conversión de ADC Completada.
ADPS2:0

ADC Prescaler Select Bits
Estos bits determinan el factor de división entre la frecuencia del XTAL y el reloj de
entrada del ADC.
Tabla ADPS2

ADPS2 ADPS1 ADPS0 Factor de División
0

0

0

2

0

0

1

2

0

1

0

4

0

1

1

8

1

0

0

16

1

0

1

32

1

1

0

64

1

1

1

128

ADCSRB: ADC Control and Status Register B
Registro ADCSRB

ADCSRB --- ACME

---

---

---

ADTS2

ADTS1

ADTS0

Registro de Microcontrolador

ACME

Analog Comparator Multiplexer Enable
Al escribir uno en este bit estando el ADC apagado (ADEN en ADCSRA es cero), el
multiplexor del ADC selecciona la entrada negativa del Comparador Analógico.
Cuando se escribe cero en este bit, la entrada negativa del Comparador Analógico
será AIN1.

ADTS2:0

ADC Auto Trigger Source
Si el bit ADATE de ADCSRA vale uno, la configuración de los bits ADTS2:0 selecciona la
fuente de las conversiones auto-disparadas del ADC.
Si ADATE vale cero, la configuración de ADTS2:0 no tendrá efecto. La conversión se
inicia en el flanco de subida de la señal seleccionada. Observa que cambiar de una
fuente de disparo que vale cero a una fuente que vale uno generará un flanco de
subida en la señal de disparo, y si el bit ADEN de ADCSRA vale uno, esto iniciará una
conversión. El cambio al Modo de Corrida Libre (ADTS2:0 = 000) no originará un
evento de disparo, incluso si está activado el flag de interrupción del ADC.
Tabla ADTS2

ADTS2 ADTS1 ADTS0 Fuente de disparo
0

0

0

Modo de Corrida Libre

0

0

1

Comparador Analógico

0

1

0

Interrupción Externa INT0

0

1

1

Coincidencia del Timer/Counter0

1

0

0

Desbordamiento del Timer/Counter0

1

0

1

Coincidencia B del Timer/Counter1

1

1

0

Desbordamiento del Timer/Counter1

1

1

1

Evento de Captura del Timer/Counter1

ADMUX: ADC Multiplexer Selection Register
Registro ADMUX

ADMUX REFS1 REFS0

ADLAR

MUX4

MUX3

MUX2

MUX1

MUX0

Registro de Microcontrolador

REFS1:0

Reference Selection Bits
Estos bits seleccionan los voltajes de referencia del ADC, como se muestra en la
siguiente tabla. Si estos se cambian durante una conversión, el cambio no tendrá
efecto hasta que termine la conversión (hasta que se ponga a uno el flag ADIF en
ADCSRA). Si se usa un voltaje de referencia aplicado al pin AREF, no se podrán usar
las opciones de voltaje de referencia internos.
Nota: Si se seleccionan canales diferenciales, solo se podrá usar 2.56V como
Voltaje de Referencia Interno.
Tabla REFS1

REFS1 REFS0 Voltaje de Referencia Vref+ Seleccionado (ATmegaXX4)
0

Pin AREF, Vref Interno desconectado

0

1

Pin AVCC con capacitor externo en el pin AREF

1

0

Voltaje de Referencia Interno de 1.1V
con capacitor externo en el pin AREF

1
ADLAR

0

1

Voltaje de Referencia Interno de 2.56V
con capacitor externo en el pin AREF

ADC Left Adjust Result
El bit ADLAR afecta la representación del resultado de la conversión en los
registros de datos del ADC. Al escribir uno en ADLAR el resultado se justificará a la
izquierda, de otro modo, el resultado se justifica a la derecha. El cambio del bit
ADLAR afectará inmediatamente los registros de datos del ADC, sin importar si hay
conversiones en curso.

MUX4:0

Analog Channel and Gain Selection Bits
El valor de estos bits selecciona la combinación de las entradas analógicas que se
conectan al ADC. Estos bits también seleccionan la ganancia de los canales
diferenciales. Para más información ver la sección Selección del Canal de
Conversión. Si se cambian estos bits durante una conversión, el cambio no tendrá
efecto hasta que se complete la conversión en curso (hasta que el bit ADIF de
ADCSRA se ponga a uno).

ADCH y ADCL: ADC Data Register
Registro ADCH

ADCH
Registro ADCL

ADCL
Registro de Microcontrolador

ADC Conversion Result
Cuando termine una conversión del ADC el resultado será almacenado en estos dos
registros. Si se usan canales diferenciales, el resultado se presentará en formato de
complemento a dos.
Al leer ADCL los registros de datos del ADC no se actualizan hasta que se lea ADCH. Como
consecuencia, si el resultado se justifica a la izquierda y si no se requiere de más de 8 bits de
precisión, bastará con leer ADCH. De otro modo, ADCL se deberá ser el primer registro en
leer.
El bit ADLAR del ADMUX y los bits MUXn de ADMUX afectan el modo en que se lee el
resultado de estos registros. Si ADLAR vale uno, el resultado se justifica a la izquierda. Si
ADLAR vale cero (valor por defecto), el resultado se justifica a la derecha.

DIDR0: Digital Input Disable Register 0
Registro DIDR0

DIDR0 ADC7D ADC6D

ADC5D

ADC4D

ADC3D

ADC2D

ADC1D

ADC0D

Registro de Microcontrolador

Bit 7:0

ADC7D..ADC0D: ADC7:0 Digital Input Disable
Cuando se escribe uno en este bit, se deshabilita el buffer de entrada digital
correspondiente al pin ADCx. Si este bit vale uno, el correspondiente bit del
registro PIN se leerá siempre como cero. Cuando se aplica una señal analógica al
pin ADC7.0 y no se necesita la entrada digital de este pin, este bit se debería setear
para reducir el consumo de energía en el buffer de entrada digital.

Práctica: Conversiones Normales
Se convierte los valores de hasta 4 potenciómetros y se visualiza en la consola RS232.
El canal a leer se selecciona por medio del teclado.
Circuito para usar el ADC del microcontrolador AVR.

El código fuente
/******************************************************************************
* FileName: main.c
* Purpose: Uso del ADC: Conversiones Normales de Entrada Única
* Processor: ATmega164P
* Compiler: AVR IAR C y AVR GCC (WinAVR)
* Author:

Shawn Johnson. http://www.cursomicros.com.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License: Se permiten el uso y la redistribución de este código con
*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y la nota de autor de arriba.

*****************************************************************************/

#include "avr_compiler.h"
#include "usart.h"

voidadc_setup(void);
unsignedintadc_read(charchannel);

intmain()
{
charc;unsignedintn;

usart_init();// 9600 - 8N1

adc_setup();

printf("r Test del ADC r");
printf("r Ingrese un canal [0..4] rr");

printf(" Canal Valor decimal Valor realr");
printf(" ===== ============= ==========r");
while(1)
{
if(kbhit())
{
c=getchar();
if((c<='3')&&(c>='0'))// Filtrar ingreso
{
n=adc_read(c-'0');
// Visualizar el resultado
printf(" ADC%c %10d %11.3f Vr",c,n,n*5.0/1023);
}
}
}
}

//****************************************************************************
// Configurar el conversor ADC
//****************************************************************************
voidadc_setup(void)
{
// Vref+ y Vref-

= VCC y GND

// Justificación de resultado = Derecha

ADMUX=0x40;

// Reloj de ADC

= F_CPU/64 = 125kHz

// Estado del conversor

= Habilitado

// Modo de conversión

= Manual

ADCSRA=0x86;
ADCSRB=0x00;
}

//****************************************************************************
// Lee el canal 'channel' del conversor ADC
//****************************************************************************
unsignedintadc_read(charchannel)
{
ADMUX&=0xF8;//
ADMUX|=channel;// Seleccionar canal

ADCSRA|=(1<<ADSC);// Iniciar conversión

while(ADCSRA&(1<<ADSC));// Esperar a que termine la conversión

returnADC;// Retornar resultado de conversión
}

Introducción
Los Timers son módulos que trabajan en paralelo con el procesador, permitiendo que
las operaciones de temporización y conteo se puedan llevar a cabo de manera
eficiente, mientras el procesador se ocupa de otras tareas.
Normalmente los megaAVR cuentan con tres Timers, llamados Timer0, Timer1 y
Timer2. A veces desaparece el Timer2 y otras veces aparece adicionalmente el Timer3
o el Timer4. Todos los Timers pueden trabajar en modo de PWM pero esta
funcionalidad está mejor implementada en unos Timers (1 y 3) que en otros. Por su
relativa limitación, el Timer0 está más destinado a las temporizaciones y otro tanto a
los conteos. El Timer2 por su parte fue diseñado con la característica adicional para
trabajar con un XTAL de reloj externo, de 32kHz.
A pesar de sus diferencias operativas, la configuración y control de los Timers son muy
similares en todos los casos, así que por más que sean varios aprenderemos a
controlarlos todos con el menor esfuerzo. Bueno, eso espero.
Supongo que lo común debe ser conocer los Timer de a uno, empezando por el Timer0
hasta llegar al Timer3 o 4, si es que se llega. Pero después de revisar los diversos
datasheets, creo que un mejor enfoque será abarcarlos de a dos, según su número de
bits. Esto nos permitirá evitar las redundancias.
En primer lugar nos ocuparemos de los Timers de 8 bits, o sea, del Timer0 y del
Timer2.
Luego estudiaremos los Timers de 16 bits, que son el Timer1 y el Timer3. De hecho,
solo trataremos el Timer1 porque el Timer3 es su perfecto clon, si vale la expresión.
Como habrán notado, había tratado hasta donde me posible de describir las
características de los periféricos de los megaAVR pensando también en los que la
propia Atmel denomina “viejos AVR”, dado que aún son muy usados en la actualidad y
hay muchos proyectos de interés que se basan en esos microcontroladores. Pero creo
que en esta ocasión dejaré de lado ese enfoque múltiple para no recargar el tema
demasiado.
Como había dicho hay mucha similitud entre todos los Timers y bastaría con conocer
bien el funcionamiento de uno de ellos para poder comprender los demás. Con todo
eso, el estar mencionando a cada rato que tal bit tiene otro nombre en el otro AVR o
que está pequeña función no está presente en aquel otro,... puede hacer tediosa el
estudio. Suficiente será con comparar los diversos Timers que mencionamos. No
obstante, puesto que todo el control y configuración de un módulo queda reflejado en
sus registros de E/S, creo que una buena idea poner al final la descripción respectiva
de los registros de los Timers de los “viejos megaAVR”. Yo creo que eso será más que
suficiente para programarlos bien, sin necesidad de empezar a describirlos por
separado.

El Timer0 y el Timer2
Dada la paridad de características entre el Timer0 y el Timer2, no las vamos a
mencionar simultáneamente para no fatigarnos de términos. Simplemente vamos a
referirnos al Timer0 y se dará por hecho que lo mismo es aplicable para el Timer2,
salvo que se indique explícitamente lo contario. Las principales diferencias se dejarán
notar solo al inicio y al final, luego el tratamiento será muy parejo.
Naturalmente, el control de cada Timer a nivel de programación requiere del trato
específico de sus registros de configuración. Pero tampoco esto es de preocupación
pues lo único que cambia es el número 0 por el número 2 en cada registro y bit de
registro. Por ejemplo, los registros de E/S del Timer0 son:
TCNT0. TCCR0A, TCCR0B, OCR0A, OCR0B, TIMSK0 y TIFR0.
En tanto que para el Timer2 son:
TCNT2. TCCR2A, TCCR2B, OCR2A, OCR2B, TIMSK2 y TIFR2.
Aparte de ellos, tenemos al registro GTCCR, el cual es de uso común para todos los
Timers desde el Timer0 hasta el Timer3, y el registro ASSR, que es de uso exclusivo
del Timer2 y que controla las características distintivas del funcionamiento asíncrono
de este Timer.
Del mismo modo, en los nombres de los bits de cada registro, solo cambian el número
0 por el 2. Por ejemplo, los mapas de bits de los registros TCCR0A y TCCR2A son,
respectivamente:
Registro TCCR0A

TCCR0A

COM0A1

COM0A0

COM0B1

COM0B0

---

---

WGM00

ADPS0

COM2A0

COM2B1

COM2B0

---

---

WGM21

WGM20

Registro TCCR2A

TCCR2A

COM2A1

Queda claro entonces que bastará con referirnos al Timer0, entendiendo que las
mismas características, ventajas y limitaciones citadas serán igualmente aplicables al
Timer2, salvo, repito, que se indique lo contrario. Empecemos, entonces.
El nombre completo del Timer0 es Timer/Counter0 o Temporizador/Contador 0, pero
por comodidad nos referimos a él simplemente como Timer0.
Dicen que un diagrama vale más que mil palabras, así que el siguiente esquema nos
ayudará para entender la operación común del Timer0. Las operaciones específicas de
cada modo particular las describiremos en su momento.
Diagrama de bloques del Timer0.
El elemento central del Timer0 es su contador, que es el mismo registro TCNT0. Como
es un registro de 8 bits, decimos que el Timer0 es de 8 bits. El Timer0 puede avanzar
hacia adelante o hacia atrás, según se programe, impulsado por la señal de su reloj, el
cual puede ser interno o externo. Cuando nos referirnos al avance del Timer en
realidad nos referimos al avance de su contador, el registro TCNT0.
Con sus 8 bits, el Timer0 puede contar en todo su rango, o sea, entre 0 y 255. Cuando
el Timer0 opera solo en modo ascendente y llega a su valor máximo de 255,
continuará después contando desde 0 otra vez, cíclicamente. Esta transición de 255 a
0 es el famoso Desbordamiento y es un concepto clave en los Timers. El
desbordamiento del Timer0 activa el bit de flag TOV0. También es posible hacer que el
Timer0 cuente solo hasta un tope establecido por el registro OCR0A.
El Timer0 tiene dos comparadores que en todo momento están comparando el valor
del registro TCNT0 con los registros OCR0A y OCR0B. Cada igualdad detectada entre
los registros indicados se conoce como Coincidencia y es el segundo concepto clave de
los Timers del AVR. La coincidencia entre TCNT0 y OCR0A activa el bit de flag OCF0A y
la coincidencia entre TCNT0 y OCR0B activa el bit de flag OCF0B.
El Desbordamiento del Timer0 y cada una de sus dos Coincidencias se pueden
programar para disparar interrupciones. Los detalles de las interrupciones serán vistos
con paciencia en su sección respectiva.
Desde el punto de vista de la programación, podemos controlar el Timer0 con tres
tipos de bits:
Los bits CS (de Clock Select). Los bits CS02, CS01 y CS00 se encargan de configurar
todo lo relacionado con el reloj y el prescaler del Timer.
Los bits WGM (de Waveform Generator Mode). Los bits WGM02, WGM01 y WGM00
trabajan con los comparadores para producir ondas cuadradas de acuerdo con la
configuración de los bits. En realidad, su función implica más que eso, pues establecen
el modo en que operará el Timer0, ya sea modo Normal, CTC o PWM.
Los bits COM (de Compare Output Mode). Son los bits COM0A1 y COM0A0 los que en
última instancia deciden si las ondas generadas por los comparadores salen o no por
los pines OC0A y OC0B del AVR. El tipo de onda más popular es PWM y es
habitualmente el único caso en que se dejan salir las ondas. Cuando el Timer0 va a
trabajar como simple contador o temporizador, los bits COM quedan con su valor por
defecto de 0, con lo cual los pines OC0A y OC0B quedan desconectados del Timer y se
pueden seguir usando como puertos de E/S generales.

El Reloj del Timer0 y del Timer2
La similitud entre el Timer0 y el Timer2 se comprueba fácilmente examinando sus
correspondientes registros de control. Es en esta sección donde nos ocuparemos de las
pocas diferencias entre ellos.
El reloj del Timer0 es la señal digital, periódica o no, cuyos pulsos hacen avanzar el
Timer. La fuente de reloj del Timer0 puede ser interna o externa.
Reloj Interno. Aquí el reloj del Timer0 deriva del mismo oscilador interno del sistema
F_CPU. Como se ve en la figura, en este caso la señal pasa previamente por el
prescaler, que puede dividir la frecuencia de F_CPU por un valor seleccionado por
nosotros. Los prescalers del Timer0 y del Timer2 no son idénticos, aunque tengan los
bits de control similares. Pero siendo este reloj el que se usa con regularidad, ya sea
para las temporizaciones o para generar ondas PWM, sobrará espacio para
familiarizarnos con estas ligeras diferencias.
Reloj Externo. He aquí la brecha más grande que separa al Timer0 del Timer2. La
forma como la señal externa se aplica al microcontrolador depende de cada Timer.
En el Timer0 la señal externa se conecta al pin T0 del megaAVR. Con esto el
programador decide si el Timer0 avanzará con cada flanco de subida o de bajada
detectado en dicho pin. Notemos en el diagrama que la señal externa no pasará por su
prescaler.
En el Timer2 su reloj externo puede ser de dos tipos: o es una señal aplicada al
pinTOSC1 del megaAVR, en cuyo caso el Timer2 avanzará con cada flanco de bajada
de ese pin; o es la señal de un oscilador de XTAL conectado entre los
pines TOSC1y TOSC2 del megaAVR. En ambos casos, la señal de reloj pasará por el
prescaler del Timer2.
El modo donde el Timer0/2 trabaja con un reloj externo aplicado al pin T0 (para el
Timer0) o TOSC1 (para el Timer2) se conoce como modo Contador porque de alguna
forma el Timer contará los pulsos detectados en dicho pin. Sin embargo, el hecho de
que el reloj provenga de una fuente externa no le quita sus otras funcionalidades,
como por ejemplo, poder generar ondas PWM, interrupciones, etc., claro que sería
conveniente que para tal caso la señal fuera periódica.

Contador del Timer0 con su fuente de reloj.

Contador del Timer2 con su fuente de reloj.

El Prescaler del Timer0 y del Timer2
El prescaler es un circuito contador por el que se puede hacer pasar el reloj del Timer
para dividir su frecuencia. De ese modo el Timer avanzará más lento, según las
necesidades del diseñador.
El prescaler es parte del reloj del Timer, así que para configurarlo se usan los bits de
Selección de Reloj o bits CS (por Clock Select).
Registro TCCR0B

TCCR0B

FOC0A

FOC0B

---

---

WGM02

CS02

CS01

CS00
Registro TCCR2B

TCCR2B

FOC2A

FOC2B

---

---

WGM22

CS22

CS21

CS20

Como puedes ver, los bits CS se encuentran en los registros TCCRxB de cada Timer,
sin embargo, por más que sean casi iguales, tiene efectos diferentes debido a que los
prescalers son diferentes. El prescaler del Timer2 es más sencillo y completo, pero
empezaremos por explicar el prescaler del Timer0.
El prescaler del Timer0 es compartido con el Timer1 (¿y qué tiene que ver en todo esto
el Timer1?). De acuerdo con la figura, es posible que los dos Timers operen
simultáneamente con el prescaler y utilizando diferentes factores de división puesto
que cada Timer tiene sus propios bits CS (de Clock Select). El único reparo sería que
se debe tener cuidado al resetear el prescaler porque para esto se dispone de una
única señalPSRSYNC. Es un reset SYNCrono porque el Timers0 y el Timer1 trabajan
siempre sincronizados con el reloj del sistema F_CPU, hasta cuando su reloj proviene
de los pinesT0 o T1, respectivamente. El bit PSRSYNC se encuentra en el
registro GTCCR.

Prescaler y Relojes del Timer0 y del Timer1.
Notemos que el prescaler divide el reloj del sistema por 8, 64, 256 ó 1024. Estos
divisores se conocen como factores de prescaler. Observemos además que de usar un
reloj proveniente del pin T0, entonces no será posible usar el prescaler.
Registro TCCR0B

TCCR0B

FOC0A

FOC0B

---

---

WGM02

CS02 CS01

CS00

Tabla CS02

CS02

CS01 CS00 Fuente de reloj del Timer0

0

0

0

Sin fuente de reloj (Timer0 detenido)

0

0

1

F_CPU (Sin prescaler)

0

1

0

F_CPU/8 (con prescaler)

0

1

1

F_CPU/64 (con prescaler)

1

0

0

F_CPU/256 (con prescaler)

1

0

1

F_CPU/1024 (con prescaler)

1

1

0

Reloj externo en pin T0.
El Timer0 avanza con el flanco de bajada.

1

1

1

Reloj externo en pin T0.
El Timer0 avanza con el flanco de subida.

Ahora revisemos el prescaler del Timer2. Este prescaler ofrece más factores de
división, con lo que las temporizaciones podrán ser más flexibles. A diferencia del
Timer0/1, si optamos por un reloj externo aplicado al pin TOSC1, dicha señal sí podrá
pasar por el prescaler. Esta vez el prescaler se puede resetear con la señal PSRASY. Su
nombre indica que se trata de naturaleza ASYncrona porque si el reloj del Timer2 viene
del exterior, no habrá circuito que lo sincronice con el reloj del sistema F_CPU. Los
bits AS2 y PSRASYse encuentran en el registro GTCCR.
Prescaler y Reloj del Timer2.
Registro TCCR2B

TCCR2B

FOC2A

FOC2B

---

---

WGM22

CS22

CS21

CS20

En la siguiente tabla la señal clkT2S puede ser F_CPU o el reloj proveniente del
exterior.
Tabla CS22

CS22 CS21 CS20 Fuente de reloj del Timer2
0

0

0

Sin fuente de reloj (Timer detenido).

0

0

1

clkT2S (Sin prescaler)

0

1

0

clkT2S/8 (Desde el prescaler)

0

1

1

clkT2S/32 (Desde el prescaler)

1

0

0

clkT2S/64 (Desde el prescaler)

1

0

1

clkT2S/128 (Desde el prescaler)

1

1

0

clkT2S/256 (Desde el prescaler)

1

1

1

clkT2S/1024 (Desde el prescaler)

Registro GTCCR
Tabla CS22

CS22 CS21 CS20 Fuente de reloj del Timer2
GTCCR TSM

---

---

---

--- ---

PSRASY

PSRSYNC

Modos de Operación del Timer0 y Timer2
En general existen 3 modos en que pueden trabajar los Timers:
Modo Normal
Modo CTC
Modo PWM
Cada modo tendrá sus variantes dependiendo del Timer. Por ejemplo, en el Timer1
existen hasta 12 modos PWM, pero bueno, de eso nos ocuparemos en su momento.

Diagrama de bloques del Timer0.
La figura nos resalta que esta vez vamos a trabajar con los bits WGM. Su nombre
viene de Waveform Generation Mode porque estos bits pre-establecen el tipo de onda
que podrá generar el Timer0 por los pines OC0A yOC0B. En la práctica es raro utilizar
otras formas de onda que no sean de tipo PWM, así que el nombre no parece muy
apropiado.
En la figura también se aprecia que los GENERADORES DE ONDA también dependen de
los bits COM (de Compare Output Mode). Estos bits establecen el modo en que
finalmente saldrán las ondas por los pinesOC0A y OC0B, es decir, pueden salir
normales, invertidas, o pueden simplemente no salir y dejar los pines OC0x libres para
otras tareas. A lo que quiero llegar es que al menos en cursomicros los bits COM solo
se usan en modo PWM. En los modos Normal y CTC nos olvidamos de ellos.
Tabla Modos de Operación de Timer0

WGM02 WGM01 WGM00

Modo de Operación de
Timer0

Inicio del
Conteo

Tope del
Conteo

0

0

0

Normal

0x00

0xFF

0

0

1

PWM de Fase Correcta

0x00

0xFF

0

1

0

CTC

0x00

OCR0A

0

1

1

Fast PWM

0x00

0xFF

1

0

0

Reservado

0x00

---

1

0

1

PWM de Fase Correcta

0x00

OCR0A

1

1

0

Reservado

0x00

---

1

1

1

Fast PWM

0x00

OCR0A

Los bits WGM están distribuidos en los dos registros de control del
Timer0, TCCR0A yTCCR0B. El hecho de que el bit WFG02 esté en TCCR0B y no junto
con sus pares enTCCR0A habiendo espacios vacíos allí se debe a cuestiones de
compatibilidad con otros AVR.
Registro TCCR0A

TCCR0A COM0A1 COM0A0 COM0B1 COM0B0 ---

---

WGM01 WGM00

Registro TCCR0B

TCCR0B FOC0A

FOC0B

---

---

WGM02 CS02

CS01

CS00
El Timer0 y el Timer2 en Modo Normal
Este modo queda seleccionado cuando todos los bits WGM valen 0, es decir, es el
modo por defecto del Timer0. De hecho, lo es en todos los Timers.
Tabla WGM02

WGM02 WGM01 WGM00 Modo de Operación de Timer0 Inicio del Conteo Tope del Conteo
0

0

0

Normal

0x00

0xFF

En modo Normal el Timer0, habilitado, avanza libre y cíclicamente en todo su rango, es
decir, su registro TCNT0 cuenta desde 0x00 hasta 0xFF, luego se desborda para volver
a iniciar desde 0x00.
El desbordamiento del Timer activa el flag TOV0 del registro TIFR0 el cual puede
programarse para disparar interrupciones. Como el registro TCNT0 es de lectura y
escritura podemos en cualquier momento modificar su valor y así recortar los periodos
de conteo para calibrar o ajustar las temporizaciones.
Registro TCCR0A

TCCR0A

COM0A1

COM0A0

COM0B1

FOC0B

---

COM0B0

---

---

WGM01

WGM00

Registro TCCR0B

TCCR0B

FOC0A

---

WGM02

CS02

CS01

CS00

OCF0A

TOV0

Registro TIFR0

TIFR0

---

---

---

---

---

OCF0B

El Timer0 siempre inicia detenido, así que para que se cumpla todo lo descrito primero
habrá echarlo a andar configurando los bits de reloj CS, según lo estudiado en El Reloj
del Timer0 y del Timer2.
Recordemos que los comparadores del Timer0 pueden sacar por los
pines OC0A y OC0Bunas señales que se pueden configurar con los bits COM. En los
modos Normal o CTC esta señal se forma poniendo a cero, a uno, o conmutando el
valor de OC0A/OC0B. Todas las opciones posibles se muestran en la siguiente tabla.
Para temas de temporización, que es normalmente el propósito del modo Normal o
CTC, debemos escoger la primera opción, que es la predeterminada y que nos dejará
los pines OC0A/OC0B libres para seguir usándolos como puertos de E/S generales.
Tabla COM0A1

COM0A1 COM0A0 Descripción
Tabla COM0A1

COM0A1 COM0A0 Descripción
0

0

Pin OC0A desconectado. Operación normal del pin

0

1

Pin OC0A conmuta en Coincidencia entre TCNT0 yOCR0A

1

0

Pin OC0A se limpia en Coincidencia entre TCNT0 yOCR0A

1

1

Pin OC0A se setea en Coincidencia entre TCNT0 yOCR0A

La tabla solo muestra la configuración de la onda generada para el pin OC0A pero es la
misma que se obtiene para el pin OC0B con los bits COM0B1 y COM0B0.

Cálculo de la Temporización en Modo Normal
Temporizar con el Timer0 implica cargar su registro TCNT0 con un valor adecuado y
dejar que siga contando hasta que se desborde. Es el tiempo que demora en
desbordarse lo que nos interesa conocer para aplicarlo a nuestras necesidades; y son
el cálculo y la programación de ese tiempo el objetivo de esta sección.
Bueno, asumo que en este momento ya sabemos cómo configurar el reloj del Timer0
(bits CS), que sabemos cómo programar el Timer0 en modo Normal (bits WGM) y que
entendemos su operación en ese modo. Ahora aprenderemos a escoger la opción de
reloj más adecuada para nuestras temporizaciones.
Para empezar, debemos usar el reloj interno derivado de F_CPU (cuyo valor es
teóricamente igual a la frecuencia del XTAL del megaAVR.), salvo que tengamos una
señal externa periódica. Como sabemos, si la fuente de reloj es interna, el Timer0 y el
Timer2 se programan igual. Lo único que cambiará serán los factores de prescaler.
Tabla CS02

CS02 CS01 CS00 Fuente de reloj del Timer0
0

0

0

Sin fuente de reloj (Timer0 detenido)

0

0

1

F_CPU (Sin prescaler)

0

1

0

F_CPU/8 (con prescaler)

0

1

1

F_CPU/64 (con prescaler)
Tabla CS02

CS02 CS01 CS00 Fuente de reloj del Timer0
1

0

0

F_CPU/256 (con prescaler)

1

0

1

F_CPU/1024 (con prescaler)

1

1

0

Reloj externo en pin T0. El Timer0 avanza con el flanco de bajada.

1

1

1

Reloj externo en pin T0. El Timer0 avanza con el flanco de subida.

En primer lugar veamos cómo avanza el Timer0. Por ejemplo, si tenemos un XTAL de 8
MHz y no usamos prescaler, entonces el reloj del Timer0 será de 8 MHz y el
registroTCNT0 se incrementará cada 1/8MHz = 128ns, lo mismo que un ciclo de
instrucción básica. Pero si usamos el factor de prescaler 8, TCNT0 avanzará cada 1us.
Si usamos el factor de prescaler de 256, TCNT0 avanzará cada 32us. Y si cambiamos
de XTAL, los tiempos serán otros.
Ahora entonces, suponiendo que seguimos con nuestro XTAL de 8MHz, el
registro TCNT0avanzará desde 0 hasta 255 en 32us (sin prescaler). Pero si cargamos
TCNT0 con 200, llegará al desbordamiento después de 7us; y si usamos prescaler de
8, lo hará después de 7×8 = 56us.
Al inicio todos vemos en esto un enredo de números. Parece complejo pero solo es
cuestión de encontrar el hilo de la madeja para suspirar diciendo ¡Ah…, era así de fácil!
Sin embargo, hay quienes se rinden y prefieren usar fórmulas y cálculos directos como
los descritos a continuación.
Bueno, vamos al grano. El Tiempo que pasará el Timer0 contando desde un valor
inicialTCNT0 hasta 255 y se produzca el desbordamiento está dado por la fórmula:

Donde:
Tabla de Temporización en Modo CTC con Timer0

Tiempo

= Valor de la temporización.

F_CPU

= Frecuencia del XTAL del megaAVR.

N

= Factor de prescaler (1, 8, 64, 256 ó
1024).
TCNT0

= Valor de inicio del registro TCNT0.

Tabla de Temporización en Modo CTC con Timer0

Nota: los factores de prescaler N del Timer2 son 1, 8, 32, 64, 128, 256 y 1024. Eso podría
dar otras soluciones para N y TCNT2.
Como ves, ésta es una ecuación con dos incógnitas (N y TCNT0) y es posible encontrar
más de una solución para ambas. Sin embargo, no todas serán igualmente adecuadas.
Los valores más apropiados serán los que nos permitan realizar un mejor posterior
ajuste de precisión. Si eres ducho resolviendo ecuaciones de Diofanto, puedes trabajar
con esa fórmula.
Pero si no quieres ir tanteando, puedes emplear las siguientes dos fórmulas: (He
borrado todas sus deducciones para no alargar más esta sección.)

Lo más probable es que el valor obtenido con esta fórmula no esté disponible como
factor de prescaler válido (1, 8, 64, 256 ó 1024 para el Timer0 o 1, 8, 32, 64, 128,
256 y 1024 para el Timer2). En tal caso deberemos tomar el factor superior más
cercano (“redondear” para arriba). La otra fórmula es:

Como antes, si el resultado no fuera un número entero, habría que redondearlo para
arriba.
Si el factor de prescaler obtenido estuviera fuera del rango permitido (más alto que
1024), se puede optar por buscar otro camino, como fragmentar la temporización. Por
otro lado, si la temporización es muy fina, puede que sea necesario subir un poquito el
valor de inicio del TCNT0 para realizar una calibración añadiendo algunas instrucciones
de relleno comonops. Estas dos situaciones las veremos en las prácticas; así que
pierde cuidado si no las dejé muy claro.
A modo de ejemplo, hallemos el factor de prescaler N y el valor de inicio
de TCNT0 para generar una temporización de 5 ms si el megaAVR trabaja con un XTAL
de 10 MHz.

Y el valor de inicio del registro TCNT0 será:
La secuencia de conteo resultaría así:

Otro ejemplo. ¿Cuáles son la razón de prescaler y el valor inicial de TCNT0 para
conseguir una temporización de 200 µs si nuestro megaAVR tiene un XTAL de 4 MHz?
El factor de prescaler N sería:

Y el valor inicial de TCNT0 será:

Luego, la secuencia de conteo quedaría así:

Finalmente, ¿cuáles son la razón de prescaler y el valor inicial de TCNT0 para conseguir
una temporización de 50 ms si se tiene un megaAVR con un XTAL de 20 MHz?
El factor de prescaler sería:

¿Y ahora de dónde vamos a sacar un factor de prescaler mayor que 3906.25 si el
máximo es de 1024? ¿Buscamos otro Timer? Bueno, quizá podríamos temporizar 10
veces 5 ms.

El Timer0 y el Timer2 en Modo CTC
Tabla WGM02

WGM02 WGM01 WGM00
0

1

0

Modo de Operación de
Timer0

Tope del
Conteo

0x00

CTC

Inicio del
Conteo

OCR0A

Registro TCCR0A

TCCR0A COM0A1 COM0A0 COM0B1 COM0B0 --- ---

WGM01 WGM00

Registro TCCR0B

TCCR0B FOC0A

FOC0B

---

---

WGM02 CS02

CS01

CS00

Su nombre es el acrónimo de Clear Timer onCompare Match y significa que el
Timer se limpia cuando se produce una Coincidencia en la comparación de los
registros TCNT0 yOCR0A.
En este modo el Timer0 (su registro TCNT0) también empieza a contar desde 0x00 y
se incrementa hasta que su valor sea igual al del registro OCR0A, en ese momento el
registroTCNT0 se resetea y vuelve a contar desde 0x00. La Coincidencia también
activa el flagOCF0A, el cual se puede usar para programar interrupciones. El
registro OCR0A también es de lectura y escritura de modo que podemos establecer el
tope hasta donde contará el Timer0. De cierta forma el auto-reseteo del Timer0
equivale a una auto-recarga. El diagrama indica la operación descrita.

Diagrama simplificado del Timer0 para temporizaciones en modo CTC.
En términos generales todo lo que puede hacer el registro OCR0A también lo puede
hacer su gemelo OCR0B, pero en este caso en especial la regla se rompe: Aunque los
comparadores trabajen en todo momento y hay una Coincidencia por cada uno de
ellos, el modo CTC está reservado para operar únicamente con el registro OCR0A, es
decir, aunque es posible que se activen los flags OCF0A y/o OCF0B, y se disparen
inclusive sus interrupciones correspondientes, el Timer0 solo se resetea en su
Coincidencia conOCR0A.
Las aplicaciones del modo CTC son similares a las del modo Normal, o sea, las
temporizaciones. Sin embargo, hay una diferencia que representa una ventaja o
desventaja, según se vea. La auto-recarga del Timer0 en modo CTC es un suceso
hardware que genera las más precisas temporizaciones independientemente de si el
código está escrito en ensamblador, en un compilador o en otro. Pero la no necesidad
de posteriores calibraciones software también implica que si la temporización obtenida
no es la deseada, entonces no será posible ajustar su valor, a menos que volvamos a
modificar el registro TCNT0, lo cual desnaturaliza este modo.

Cálculo de la Temporización en Modo CTC
La fórmula que nos da el tiempo entre coincidencia y coincidencia es la siguiente.
Recuerda que en modo CTC está fórmula solo vale para el registro OCR0A y no
paraOCR0B.

Donde:
Tabla de Temporización en Modo CTC con Timer0

Tiempo

= Valor de la temporización.

F_CPU

= Frecuencia del XTAL del megaAVR.

N

= Factor de prescaler (1, 8, 64, 256 ó 1024).

OCR0A

= Valor de inicio del registro OCR0A.

Tabla de Temporización en Modo CTC con Timer0

Nota: los factores de prescaler N del Timer2 son 1, 8, 32, 64, 128, 256 y 1024. Eso podría dar otras
soluciones para N y TCNT2.
De nuevo, esta ecuación tiene dos incógnitas (N y OCR0A). Como antes, es posible
descomponerla en dos ecuaciones de una variable. La primera, para hallar N, es igual
que en el modo Normal. De no resultar un valor exacto, también aquí debemos tomar
el factor de prescaler superior más cercano.
La segunda fórmula se obtiene despejando OCR0A de la fórmula inicial. Se presupone
que para esto ya debimos haber hallado el valor de N.

Si conseguimos para OCR0A una solución entera válida, será genial y no tendremos
que recurrir a posteriores ajustes de precisión, sin importar en qué lenguaje o
compilador se programe. De lo contrario, no habrá calibración en OCR0A que valga, de
modo que será de poco alivio para el diseñador redondearlo a su valor más cercano,
superior o inferior. En ese caso y si la precisión fuera realmente importante, se puede
optar por cambiar de XTAL.
Ya no vamos a poner ejemplos porque estas fórmulas se resuelven exactamente igual
que en el Cálculo de las Temporizaciones en Modo Normal.

Interrupciones del Timer0 y el Timer2
Esta vez el diagrama del Timer0 nos indica que debemos concentrarnos en los bits de
flag que pueden disparar las interrupciones del Timer0.
Flags de Desbordamiento y de Coincidencias del Timer0.
La real potencia del Timer0 se deja apreciar al emplear su característica más notable:
las interrupciones. El Timer0 tiene dos tipos de interrupciones: una por
el desbordamiento de su registro TCNT0 y dos en las coincidencias de su
registro TCNT0 con los registrosOCR0A y OCR0B. Estas interrupciones se controlan por
los bits de los registros TIMSK0y TIFR0:
TIMSK0 (Timer Interrupt Mask Register 0). Contiene los bits Enable de interrupciones.
TIFR0 (Timer Interrupt Flags Register 0). Contiene los bits de Flag de interrupciones.
Para quienes aún trabajan con los viejos megaAVR, ellos no tienen interrupción en
coincidencia B, los bits de la coincidencia A son simplemente OCIE0 y OCF0, y los
siguientes registros se llaman TIMSK y TIFR. No llevan el 0 porque también controlan
las interrupciones del Timer1 y del Timer2.
Registro TIMSK0

TIMSK0

---

---

---

---

---

OCIE0B

OCIE0A

TOIE0

---

---

---

---

---

OCF0B

OCF0A

TOV0

Registro TIFR0

TIFR0
Interrupción por Desbordamiento del Timer0. El evento que puede disparar esta
interrupción es el desbordamiento del registro TCNT0, o sea, la transición de 255 a 0.
Esto implica la operación incremental del Timer0, sin importar si está contando en
modo Normal, CTC o Fast PWM. En modo PWM de Fase Correcta el Timer0 cuenta en
sube y baja sin pasar por la transición 255 a 0, así que en este modo no hay
desbordamiento.
El desbordamiento de Timer0 activará el flagTOV0 y si la interrupción está habilitada,
se disparará. El bit TOV0 se limpia automáticamente al ejecutarse su función de
interrupción ISR, pero también se puede limpiar por software, como de costumbre,
escribiéndole un 1 y sin usar instrucciones de lectura-modificación-escritura como las
generadas por las sentencias con el operador OR binario (|).
Para habilitar la interrupción por Desbordamiento del Timer0 se setean los bitsTOIE0 y
obviamente, el bit enable general de interrupciones I, del registro SREG. La instrucción
del ensamblador dedicada a esta operación es SEI y que en lenguaje C se puede
incrustar mediante la función macro del mismo nombre sei(). (No sé por qué repito
estas cosas.)
Interrupción en Coincidencia del Timer0. Como sabemos, los comparadores del Timer0
son circuitos que en todo momento están comparando los valores del
registro TCNT0 con los registros OCR0A y OCR0B. Pues bien, el evento que puede
disparar esta interrupción es la coincidencia entre los registros mencionados. Como
puede haber dos coincidencias, aquí podemos tener hasta dos interrupciones.
Especificando, cuando se detecte la igualdad entre TCNT0 y OCR0A se activará el flag
OCF0A (Output Compare Flag 0 A), y cuando sean iguales TCNT0 y OCR0B se activará
el flag OCF0B (Output Compare Flag 0 A). De nuevo, los flags se ponen a 1
independientemente de si sus interrupciones están habilitadas o no. Si lo están, se
dispararán sus interrupciones, se ejecutarán las funciones ISR respectivas y los
flagsOCF0A y/o OCF0B se limpiarán por hardware. Ya sobra decir que también se
pueden limpiar por software escribiéndoles un uno.
Ambas interrupciones son gemelas pero no son siamesas, es decir, funcionan
exactamente igual pero no necesariamente se tienen que habilitar las dos al mismo
tiempo. Se habilitan por separado seteando el bit OCIE0A para una y OCIE0B para la
otra.
Una observación: el circuito comparador (llamado Output Compare) trabaja siempre
sin importar en qué modo está operando el Timer0 (Normal, CTC o PWM), aunque las
implicancias no serán las mismas. Explico: una coincidencia en modo CTC resetea el
registro TCNT0, mientras que en los otros modos el registro TCNT0 sigue su marchar
sin hacer caso. Si captaste mi cháchara, habrás descubierto que es posible temporizar
con la Interrupción en Coincidencia incluso si el Timer trabaja en modo PWM.

El Timer0 y el Timer2 Como Contadores
El modo Contador de los Timers es una variante de su operación en modo Normal. Se
dice contador porque cuenta los pulsos (o flancos) de la señal aplicada en un pin del
megaAVR. Esto nos lleva de regreso a la configuración del reloj del Timer y también a
las diferencias entre el Timer0 y el Timer2 cuando sus relojes son externos.

Antiguamente el Timer2 estaba más bien pensado para ser usado con un XTAL externo
de reloj (de 32 kHz) conectado a los pinesTOSC1 y TOSC2 del AVR. De ahí sus
características para trabajar asíncronamente con el reloj del sistema. Esa era su única
forma de soportar un reloj externo, es decir, no podía usarse para contar pulsos o
flancos sueltos. En los viejos AVR el Timer2 es de ese tipo.
Por fortuna, el Timer2 de los nuevos AVR viene mejor equipado y ofrece la opción
adicional de trabajar con una señal externa (sin XTAL) aplicada al pin TOSC1. De ese
modo el Timer2 se pone a nivel del Timer0/1. En realidad tiene una limitación y una
mejora al respecto.
La limitación es que el Timer2 solo se incrementa con los flancos negativos de la señal
externa, en tanto que el Timers0/1 puede programarse para que su avance sea con los
flancos de bajada o de subida.
La mejora es que el reloj externo del Timer2 sí pasa por el prescaler. En el Timer0/1
esto no es así por lo que el registro TCNT0 se incrementa o decrementa en uno por
cada flanco de la señal externa. Se puede programar el factor de prescaler del Timer2
para que tenga un avance personalizado. Por ejemplo, si escogemos un factor de 8,
entonces el registro TCNT0 podrá incrementarse cada 8 pulsos detectados en el
pin TOSC1.
Puedes ir a la sección El Prescaler del Timer0 y del Timer2 para revisar la estructura de
los prescalers.
En el Timer0 debemos configurar los bits de CS02:CS00 a 110 ó 111.
Registro TCCR0B

TCCR0B

FOC0A

FOC0B

---

---

WGM02 CS02

CS01

Tabla CS02

CS02

CS01 CS00

Fuente de reloj del Timer0

1

1

0

Reloj externo en pin T0. El Timer0 avanza en el
flanco de bajada.

1

1

1

Reloj externo en pin T0. El Timer0 avanza en el
flanco de subida.

CS00
En el Timer2 los bits CS nos dan más opciones.
Registro TCCR2B

TCCR2B

FOC2A

FOC2B

---

---

WGM22 CS22

CS21

CS20

Tabla CS22

CS22

CS21 CS20

Fuente de reloj del Timer2

0

0

1

clkT2S (Sin prescaler)

0

1

0

clkT2S/8 (Desde el
prescaler)

0

1

1

clkT2S/32 (Desde el
prescaler)

1

0

0

clkT2S/64 (Desde el
prescaler)

1

0

1

clkT2S/128 (Desde el
prescaler)

1

1

0

clkT2S/256 (Desde el
prescaler)

1

1

1

clkT2S/1024 (Desde el
prescaler)

Tengamos en cuenta que, siendo el modo Contador una interpretación particular del
modo Normal, siguen latentes todas las funciones de temporización del Timer0,
incluyendo las interrupciones en el Desbordamiento y en las Coincidencias. De hecho,
también es posible temporizar con la señal externa, siempre que sea una onda
cuadrada periódica, claro está. La única consideración es que la señal
de T0 o T1 jamás debería superar, o igualar siquiera, la performance del reloj del
sistema F_CPU, para permitir la sincronización entre ellas. Por otro lado, el reloj
externo del timer2 no está sincronizado y en ese caso la manipulación de sus registros
de datos puede requerir el uso del registro ASSR.

El Timer0 y el Timer2 en Modo PWM
Inicialmente era el Timer1 el que fue plenamente equipado para generar ondas PWM.
Si bien los otros Timers también ofrecen esa funcionalidad, aún tienen muchas
limitaciones.
El Timer0 de los AVR actuales puede producir hasta dos canales PWM, por los
pines OC0Ay OC0B. El Timer2 hace lo propio por los pines OC2A y OC2B. Estos pines
deberán estar configurados como salida para dar salida a las señales PWM. Esta es una
característica que distingue a los módulos PWM de los demás periféricos del megaAVR
donde la configuración y control de los pines involucrados quedan a cargo del módulo
respectivo.
Dada la paridad entre los dos Timers y entre los dos canales PWM, volvemos a aclarar
que la siguiente exposición está relacionada solo al canal A del Timer0, dando por
sentado que es también aplicable al canal B, tanto del Timer0 como del Timer2.
Tabla WGM02

WGM02 WGM01 WGM00 Modo de Operación del Timer0 Inicio del Conteo Tope del Conteo
0

0

1

PWM de Fase Correcta

0x00

0xFF

0

1

1

Fast PWM

0x00

0xFF

1

0

1

PWM de Fase Correcta

0x00

OCR0A

1

1

1

Fast PWM

0x00

OCR0A

La tabla de arriba nos muestra que el Timer0 puede generar dos tipos de ondas
PWM, Fast PWM y PWM de Fase Correcta, según la forma como avanza el
registro TCNT0. Cada uno de estos modos tiene a su vez una variante (aparecen
sombreados en la tabla), donde el tope del conteo es el valor del registro OCR0A.
Estos modos son especiales que suelen entorpecer la comprensión del modo PWM de
los Timers. Así que los ignoraremos por el momento y en adelante asumiremos que el
valor tope del Timer0 es siempre de 255. Con eso en mente podemos seguir.

Timer0 en modo Fast PWM
El modo Fast PWM se conoce también como PWM de pendiente única porque el
registroTCNT0 avanza siempre hacia arriba, es decir, cuenta desde 0 hasta un
valor TOPE que es255, después de lo cual se resetea y vuelve a empezar desde 0. Si
graficamos este progreso, la curva resulta siendo efectivamente una escalera de
pendiente única, como se ve abajo.
Como se indica en la figura, la onda Fast PWM es generada por la conmutación del
pinOC0A cada vez que el registro TCNT0 llega al tope y cada vez que TCNT0 coincide
con el registro OCR0A. En la gráfica las coincidencias se señalan con pequeñas líneas
rojas horizontales sobre la escalera. Según la configuración de los
bits COM0A1 y COM0A0(del registro TCCR0A), la onda PWM puede ser invertida o no
invertida.
Si los bits COM0A1:0 valen 0b10 se establece una onda no-invertida, esto es, el
pin OC0A se setea cuando TCNT0 llega al tope y se limpia cuando TCNT0 coincide con
el registro OCR0A.
Si los bits COM0A1:0 valen 0b11 se establece una onda invertida, esto es, el
pinOC0A se limpia cuando TCNT0 llega al tope y se setea cuando TCNT0 coincide con el
registro OCR0A.
Puesto que el Timer0 siempre cuenta en todo su rango de 0 a 255, se deduce que el
periodo, y por ende la frecuencia, de la onda PWM también serán constantes. Esa es la
limitación del modo PWM de los Timers de 8 bits. Por otro lado, sí es posible modificar
el valor del registro OCR0A, lo cual nos permitirá controlar el duty cycle de la onda
PWM.
En la siguiente imagen se indican las fórmulas para calcular el periodo y el duty cycle
de una onda Fast PWM no-invertida. Como siempre, F_CPU es la frecuencia del
procesador yN es el factor del prescaler. Recuerda que N tiene más posibles valores en
el Timer 2 que en el Timer0. Para más detalles puedes revisar la sección relojes del
Timer0 y del Timer2.

Periodo y Duty cycle de una onda Fast PWM no-invertida.
La frecuencia de la onda Fast PWM se obtiene invirtiendo la fórmula del periodo:

Y la fórmula que nos da el duty cycle expresado en porcentaje la obtenemos dividiendo
las fórmulas del duty cycle y del periodo:

En esta fórmula podemos observar que si el registro OCR0A toma su valor máximo de
255, entonces el duty cycle será del 100%, lo cual significa que el estado del
pin OC0Aserá de 1 lógico constante. En cambio, no hay ningún para el
registro OCR0A que nos dé un duty cycle del 0%. Si el registro OCR0A vale 0 la salida
será de unos pequeños picos que representan un duty cycle cercano al 0.4%. Ésa es la
característica que diferencia al modo Fast PWM de los demás modos PWM.

Timer0 en modo PWM de Fase Correcta
El modo PWM de Fase Correcta se conoce también como PWM de doble pendiente
porque el registro TCNT0 cuenta en sube y baja, es decir, primero avanza
desde 0 hasta un valorTOPE (255) y después regresa de 255 hasta 0. Este proceso se
repite cíclicamente y si lo graficamos, la curva resulta siendo efectivamente una
escalera de doble pendiente, como se ve abajo.
Como se indica en la figura, la onda PWM de Fase Correcta es generada por la
conmutación del pin OC0A cada vez que el registro TCNT0 coincide con el
registroOCR0A. En la gráfica las coincidencias se señalan con pequeñas líneas rojas
horizontales sobre la escalera. Según la configuración de los
bits COM0A1 y COM0A0 (del registroTCCR0A), la onda PWM puede ser invertida o no
invertida.
Si los bits COM0A1:0 valen 0b10 se establece una onda no-invertida, esto es, el
pin OC0A se setea cuando TCNT0 coincide con el registro OCR0A en su conteo
descendente, y se limpia cuando TCNT0 coincide con el registro OCR0A en su conteo
ascendente.
Si los bits COM0A1:0 valen 0b11 se establece una onda invertida, esto es, el
pinOC0A se setea cuando TCNT0 coincide con el registro OCR0A en su conteo
ascendente, y se limpia cuando TCNT0 coincide con el registro OCR0A en su conteo
descendente.
Puesto que el Timer0 siempre cuenta en todo su rango de 0 a 255, se deduce que el
periodo, y por ende la frecuencia, de la onda PWM también serán constantes. Esa es la
limitación del modo PWM de los Timers de 8 bits. Por otro lado, sí es posible modificar
el valor del registro OCR0A, lo cual nos permitirá controlar el duty cycle de la onda
PWM.
En la siguiente imagen se indican las fórmulas para calcular el periodo y el duty cycle
de una onda PWM de Fase Correcta no-invertida. Como siempre, F_CPU es la
frecuencia del procesador y N es el factor del prescaler. Recuerda que N tiene más
posibles valores en el Timer 2 que en el Timer0. Para más detalles puedes revisar la
sección relojes del Timer0 y del Timer2.

Periodo y Duty cycle de una onda PWM de Fase Correcta no-invertida.
La frecuencia de la onda PWM de Fase Correcta se obtiene invirtiendo la fórmula del
periodo. Observa que la doble pendiente de este modo hace que la máxima frecuencia
obtenida sea la mitad de la frecuencia máxima que brinda el modo Fast PWM.

Y la fórmula que nos da el duty cycle expresado en porcentaje la obtenemos dividiendo
las fórmulas del duty cycle y del periodo:

A diferencia del modo Fast PWM en esta fórmula podemos observar que el duty cycle
puede abarcar todo su rango desde 0% (con OCR0A = 0) hasta el 100%
(con OCR0A = 255). El 0% significa que la salida será un constante 0 lógico y un
100%, que la salida será un estado alto constante. Ésa es la razón por la que se
denomina de fase correcta.

Práctica: Temporización con Sondeo del Timer0
El programa genera una onda cuadrada conmuta cada 2.5 ms, pero como ya estamos
en temas serios, la temporización debe ser lo más precisa posible, ni 1 µs más ni 1 µs
menos. Visto de otro modo, el programa genera una señal de onda cuadrada de 200
Hz.
Circuito para probar el Timer0 del microcontrolador AVR.

El código fuente
/******************************************************************************
* FileName: main.c
* Purpose: Timer0 - Operación en modo Temporizador
* Processor: ATmega164P
* Compiler: AVR IAR C y AVR GCC (WinAVR)
* Author:

Shawn Johnson. http://www.cursomicros.com.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License: Se permiten el uso y la redistribución de este código con
*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y la nota de autor de arriba.

*****************************************************************************/
#include "avr_compiler.h"

voidPause(void);// Prototipo de función

//************************************************************************
// Función principal
//************************************************************************
intmain(void)
{
DDRA=0x01;// PA0 salida de señal

/* Configuración del Timer0
* Modo de operación = Normal
* Factor de prescaler = 256
*/
TCCR0A=0x00;
TCCR0B=(1<<CS02);

while(1)// Loop forever
{
PINA=0x01;// Conmutar pin PA0
Pause();// Delay de 2499.55 us
}
}
//************************************************************************
// Produce 2499.55 µs exactamente
// Con el Prescaler de 256 y con con XTAL de 8 MHz el Timer0 se incrementa
// cada 256/8 = 32 us. Por tanto, para alcanzar 2499.55 µs se requieren de
// 2499.55/32 = 78.1 ticks. Ergo, TCNT0 se debe cargar con 256-78.1 = 178
//************************************************************************
voidPause(void)
{
GTCCR=(1<<PSRSYNC);// Resetaer prescaler
TCNT0=178;// Cargar registro TCNT0

TIFR0=(1<<TOV0);// Limpiar flag de desbordamiento del Timer0

while((TIFR0&(1<<TOV0))==0);// Esperar hasta que ocurra el desbordamiento
nop();nop();
nop();nop();
nop();nop();
nop();nop();// NOPs para ajustar la precisión
nop();nop();
nop();nop();
nop();nop();
nop();nop();
nop();
}

Descripción del programa
El punto crucial del programa es el cálculo de la temporización. Según mi código, para
que la señal cambie de nivel cada 2.5 ms, la funciónPause debería tomar 2499.55µs,
ya que el bucle llega a dicha llamada cada 0.45 µs (lo vi en el simulador de Atmel
Studio 6). Por supuesto, este valor puede variar de un compilador a otro porque cada
cual compila a su modo. Inclusive varía en un mismo compilador según el nivel de
optimización establecido o según el microcontrolador usado. Esta exposición la hago
habiendo compilado el código con AVR GCC con nivel de optimización –Os.

while(1)// Loop forever
{
PINA=0x01;// Conmutar pin PA0
Pause();// Delay de 2499.55 us
}
El factor de prescaler sería:

Y el valor inicial del TCNT0 es:

El ajuste de la temporización se ha conseguido añadiendo algunos nops en Pause. Esto
es tiempo muerto pero son solo 2 us, nada comparado con los 2500 us del total. Para
calibrar estas precisiones es aconsejable recurrir al Cronómetro de Proteus o
alStopwatch de Atmel Studio 6.
Quizá te puedas preguntar ¿qué gracia tiene realizar temporizaciones de este modo, si
bien se pueden usar los delays? En primer lugar, los conocidos delays nunca son
precisos de por sí y, en segundo lugar, son muy susceptibles de sufrir dilataciones
debido a las interrupciones. Además, significan tiempo muerto que impiden que el CPU
ejecute otras tareas. Por ejemplo, ¿cómo harías si quisieras revisar el estado del
puerto serie continuamente pero solo por 1 segundo?, ¿acaso le pondrías un delay de
1s? No, ¿verdad? Bueno, puede haber varias soluciones pero la más recomendada
suele ser usando delays a base de Timers, en especial con sus interrupciones.

Práctica: Interrupción del Timer0
Se genera una onda cuadrada de frecuencia 500 Hz. Quizá creas que no es una
práctica muy provechosa pero en realidad en la gran mayoría de aplicaciones el Timer0
funciona como se verá aquí. Dependerá del diseñador saber qué hacer con esta señal.
Por ejemplo, aquí generaremos una onda PWM y al mismo tiempo una señal para
bascular un LED en intervalos largos de tiempo, más allá de lo que permite el
prescaler. Esta onda PWM se genera a nivel software y no tiene nada que ver con el
modo PWM de los Timer0 (generado por hardware y con muchísima mejor
performance). Esto es solo una demostración y hasta parece que el mismo datasheet
recomienda no hacerlo. Pero como decía, se podrán encontrar otras aplicaciones donde
la señal generada sea más útil, como las ondas PRM para controlar la velocidad de los
motores de sus primeros robots que utilizaba Dale Heatherington.
La onda PWM será de 500 Hz y con duty cycle variable mediante las teclas + y – del
teclado. A la salida se puede conectar un pequeño motor DC o simplemente un LED,
cuya intensidad de brillo variará.
Por otro lado, el programa también hará parpadear un LED cada 500 ms, un tiempo
que a priori no se puede conseguir así nada más. Esto para demostrar que las
temporizaciones no tienen que ser tareas exclusivas.
En el circuito el IRF730 puede ser sustituido por un IRF720, un IRF540 o cualquier otro
similar.

Circuito para probar el Timer0 del microcontrolador AVR.
El código fuente
/******************************************************************************
* FileName: main.c
* Purpose: Interrupción del Timer0 en Modo Normal
* Processor: ATmega164P
* Compiler: AVR IAR C y AVR GCC (WinAVR)
* Author:

Shawn Johnson. http://www.cursomicros.com.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License: Se permiten el uso y la redistribución de este código con
*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y la nota de autor de arriba.

*****************************************************************************/

#include "avr_compiler.h"
#include "usart.h"

#define MaxDuty 20

// Máximo duty cycle

volatileunsignedcharDuty;// Duty cycle actual

/******************************************************************************
* Gestor de Interrupción por Desbordamoento del Timer0.
* Esta interrupción se dispara cada 100 µs exactamente.
* Genera una señal PWM con una frecuencia de 500 Hz
*****************************************************************************/
ISR(TIMER0_OVF_vect)
{
staticunsignedcharDgen=1;// Duty generator. Rango = [1:MaxDuty]
staticunsignedintticks=0;

TCNT0+=157;// Reponer registro TCNT0

if(Dgen<=Duty)
PORTA|=(1<<0);// 1-> Salida de señal PWM
else
PORTA&=~(1<<0);// 0-> Salida de señal PWM

if(++Dgen>MaxDuty)//
Dgen=1;// Rango de Dgen es [1:MaxDuty]

if(++ticks==5000)// Sig. bloque se ejecuta cada 5000×100u = 500ms
{
PINA=(1<<1);// Conmutar pin PA0 (LED)
ticks=0;
}
}

/******************************************************************************
* Main Function
*****************************************************************************/
intmain(void)
{
DDRA=(1<<1)|(1<<0);// PA0 -> Salida de señal PWM
// PA1 -> Salida LED

Duty=0;// Rango = [0 : MaxDuty]

usart_init();
puts("rn Interrupción del Timer0 en Modo Normal r");
puts("rn Control de Motor DC ");
puts("rn (+) Aumentar velocidad");
puts("rn (-) Disminuir velocidad");

/* Configuración del Timer0
* - Modo de operación = Normal
* - Factor de prescaler = 8
*/
TCCR0A=0x00;
TCCR0B=(1<<CS01);

/* Habilitar Interrupción por Desbordamiento del Timer0 */
TIMSK0=(1<<TOIE0);
sei();

for(;;)
{
if(kbhit())
{
chark=getchar();

// El valor de Duty está limitado al rango [0 : MaxDuty]

if((k=='+')&&(Duty<MaxDuty)){
Duty++;
}
elseif((k=='-')&&(Duty>0)){
Duty--;
}
}
}
}

Descripción del programa
El principio el Timer0 trabaja en modo Normal como en el programa anterior, solo que
se le configura para que dispare interrupciones cada 100 µs. Ahora no interesa por qué
100 µs. El hecho es que para tal cometido los cálculos indicaban que el factor de
prescaler Ndebía ser de 8 y el valor a recargar en TCNT0 debía ser de 156, tal como se
ve abajo.

Y el valor inicial del TCNT0 será:

A pesar de que el cálculo para TCNT0 resultó exacto, en el programa tuve que
utilizar 157para conseguir los 100µs buscados. Como se predijo en la teoría, esto se
debe a que la temporización es muy fina. Pero no solo se usa 157 en vez de 156 sino
que en vez de una recarga directa como TCNT0 = 157 se utiliza una
suma, TCNT0 += 157. Para las temporizaciones periódicas por interrupciones esta
suma ayuda a disminuir las instrucciones de relleno que se deben colocar la ajustar la
precisión. De hecho, como se ve, no tuve que añadir ni siquiera un nop como en la
anterior práctica. Las interrupciones se disparan con total precisión cada 100us.
El hecho de tener que calibrar manualmente estás temporizaciones a pesar de que los
cálculos eran precisos se debe a que la sentencia de recarga del registro TCNT0 no se
produce en el preciso instante en que se desborda el Timer0, puesto que la ejecución
de lafunción de interrupción ISR implica la ejecución de cierto código como el salto a
dicha función y el almacenamiento temporal que se hace de algunos datos del
programa. Todo esto es código oculto y se ejecuta muy rápido, pero no tanto como
para pasar desapercibido ante temporizaciones pequeñas del orden de los
microsegundos. Mientras se ejecuta ese pequeño código el Timer sigue avanzando de
modo que el cálculo teórico no siempre se reflejará en la práctica.
Pero no todo es mala noticia. Aún nos falta experimentar con el modo CTC. En las
siguientes prácticas veremos que el Timer en modo CTC utiliza su “recarga automática”
para temporizar mejor sin necesidad de este tipo de ajustes.
Ahora toca pensar en la temporización de 500 ms. Con nuestro XTAL de 8MHz y el
máximo factor de prescaler para el Timer0 solo llegaríamos a 32.768 ms, que está
bastante lejos de lo que buscamos. Aunque en las mismas condiciones el Timer1 puede
temporizar hasta 8 388 608 ms, aquí vemos que no es necesario recurrir a él y así lo
reservamos para su trabajo por excelencia que son las ondas PWM.
Bueno, lo que hice en el programa es usar la anterior temporización de 100 µs para
incrementar el contador ticks. Entonces deducimos que para alcanzar 500
ms, ticksdeberá llegar a 5000. Y allí lo tenemos.

if(++ticks==5000)// Sig. bloque se ejecuta cada 5000×100u = 500ms
{
PINA=(1<<1);// Conmutar pin PA0 (LED)
ticks=0;
}
Lo hecho equivale a hacer una temporización grande a base de varias temporizaciones
menores. Otro ejemplo podría ser temporizar 50 segundos partiéndolos en 50000.
Habíamos hablado de esto en las secciones teóricas, cuando veíamos los ejemplos
cuyos cálculos no daban soluciones. También se pueden añadir más contadores para
multiplicar más temporizaciones, o emplear más variables como Duty y Dgen para
sacar otros canales PWM y controlar varios motores... Las ideas sobran.
No voy a explicar el algoritmo de generación de la onda PWM porque creo que no viene
al caso y porque es fácil de deducir. Posteriormente estudiaremos los Timers en modos
PWM para generar ondas PWM de alta frecuencia y a nivel hardware.
Una observación final: en la función principal el programa sondea el puerto serie para
ver si llegaron datos. También se pudo utilizar la interrupción de recepción del USART
para esta tarea y así poner al AVR en algún modo Sleep para ahorrar energía. Quizá se
deba hacer en una aplicación final, pero como estas prácticas son de ejemplo,…

Práctica: El Timer0 en Modo CTC
Se utiliza el Timer0 para incrementar dos contadores, uno cada 1ms y el otro cada
100ms. Los contadores se pueden usar para establecer el tiempo de ejecución de otras
rutinas. A manera de simple ejemplo, aunque no le encuentro mucha gracia, aquí se
espera por 5 segundos a que el usuario ingrese una contraseña por el puerto serie
para ahorrar en el circuito, pero que bien podría ser el prototipo de acceso a un
sistema digitando la clave en un teclado matricial por un tiempo establecido. El tiempo
se mide a partir del momento en que se presiona el primer dígito.
Recuerdo haber hecho la pregunta ¿cómo haríamos para ejecutar una tarea durante
cierto tiempo?, ya que, obviamente, para esto no se pueden recurrir a los típicos
delays porque son tiempo muerto y poco precisos. Bueno, pues, en esta práctica
veremos una solución.

El circuito.

El código fuente
/******************************************************************************
* FileName: main.c
* Purpose: Temporización con el Timer0 en Modo CTC usando Interrupciones
* Processor: ATmega164P
* Compiler: AVR IAR C y AVR GCC (WinAVR)
* Author:

Shawn Johnson. http://www.cursomicros.com.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License: Se permiten el uso y la redistribución de este código con
*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y la nota de autor de arriba.

*****************************************************************************/

#include "avr_compiler.h"
#include "usart.h"

charGetNumStr(char*,unsignedchar);

volatileunsignedcharticks_ms;
volatileunsignedcharticks_100ms;

/******************************************************************************
* Gestor de Interrupción en Coincidencia del Timer0.
* Esta función se ejecuta cada 1 ms exactamente.
*****************************************************************************/
ISR(TIMER0_COMPA_vect)
{
staticunsignedchari;

ticks_ms++;
if(++i>=100)
{
// Este bloque se ejecuta cada 100 ms
ticks_100ms++;
i=0;
}
}

/******************************************************************************
* Main Function
*****************************************************************************/
intmain(void)
{
constcharpsw[11]="1234";// password actual
charbuffer[11];

DDRA=(1<<0);// PA0 -> Salida LED

usart_init();
puts("rn Temporización con el Timer0 en Modo CTC usando Interrupciones");
/* Configuración del Timer0
* - Modo de operación

= CTC

* - Factor de prescaler = 64
* - Periodo de auto-reset = 1 ms
*/
TCCR0A=(1<<WGM01);
TCCR0B=(1<<CS00)|(1<<CS01);
OCR0A=124;// Límite de TCNT0

/* Habilitar Interrupción en Coincidencia del Timer0 */
TIMSK0=(1<<OCIE0A);
sei();

for(;;)
{
start:
GetNumStr(buffer,0);// Reset internal counter
puts("rr Ingrese su password r ");
while(kbhit()==0);// Esperar a que lleguen datos al puerto serie
GTCCR=(1<<PSRSYNC);// Resetaer prescaler
TCNT0=0;// Resetear registro TCNT0
ticks_100ms=0;

do{
// Estas rutinas se ejecutan durante 5 segundos.
// Hasta que ticks_100ms = 50 (50×100ms = 5 segundos).
if(GetNumStr(buffer,10))// Si se leyeron hasta 10 números
{
if(strcmp(buffer,psw)==0){
puts(" Password OK");
PORTA|=(1<<0);// Prender LED de PA0
puts("r Presione una tecla para salir");
getchar();
PORTA&=(~1<<0);// Apagar LED de PA0
gotostart;
}
else{
puts(" Error");
gotostart;
}
}
}while(ticks_100ms<50);
puts("r Timer out ");
}
}

//****************************************************************************
// Lee una cadena de texto de 'len' números
// El tamaño de 'buffer' debe ser mayor que 'len'.
//****************************************************************************
charGetNumStr(char*buffer,unsignedcharlen)
{
charc;staticunsignedchari=0;
if(len==0)i=0;
if(kbhit()){
c=getchar();
if((c<='9'&&c>='0')&&(i<len)){// Si c está entre 0 y 9
buffer[i++]=c;// Guardar en buffer
putchar(c);// Eco
}
elseif((c=='b')&&(i)){// Si c es RETROCESO y si i>0
i--;//
putchar(c);// Eco
}
elseif((c=='r')&&(i)){// Si c es ENTER y si i>0
buffer[i]='0';// Poner un 0x00 (fin de cadena)
putchar(c);// Eco
i=0;// Resetear contador
return1;// Retornar con 1
}
}
return0;// Retornar con 0
}

Descripción del programa
La Interrupción CTC o en Coincidencia del Timer0 incrementa los
contadores ticks_ms yticks_100ms. Creo que es obvio cada cuánto tiempo se
incrementan. Para generar el tiempo base de 1ms los valores de prescaler N del
registro OCR0A los obtuve con las formulas presentadas en la sección Cálculo de la
Temporización en Modo CTC.

Observa que el cálculo salió preciso y esos mismos valores son los que aparecen en el
programa. La temporización es precisa y no hay que preocuparse por recargas ni
ajustes, ni nada. Genial, ¿verdad?
Para terminar esto quiero mencionar que el factor de prescaler usado es bastante
grande como para que el prescaler avance unos varios ticks que pueden adelantar la
temporización. En la simulación vi que en vez de la espera de 5 segundos solo se
cronometraban 4.973 segundos, así que tuve que resetear el prescaler y con eso el
problema se arregló.

GTCCR=(1<<PSRSYNC);// Resetaer prescaler
Por supuesto que he dramatizado el tema, pero quise destacarlo para recordar que si
bien puede haber otras aplicaciones donde sí resulte realmente serio, se debe pensar
bien antes de hacerlo porque el prescaler es también compartido por el Timer1. Por
ejemplo, si tuviera el Timer1 trabajando en modo PWM, yo no me atrevería a resetear
el prescaler.

Práctica: Control de Potencia
Un poco más adelante nos dedicaremos a los motores DC. Pero antes me parece
interesante controlar la velocidad de un pequeño motor AC monofásico o de algún
dispositivo de potencia que no genere mucho ruido como por ejemplo una típica
bombilla de 100W. Bueno yo sé que en estos tiempos eso ya no es nada típico, pero la
idea es variar la alimentación de 220V o 110V de la red doméstica para controlar un
dispositivo de ese calibre.
El circuito no está blindado para filtrar los ruidos que pueden generar los motores AC
de cierta magnitud, así que es recomendable que no sea muy grande. Aunque el
programe funcione bien, los ruidos grandes “sacuden” al microcontrolador y perturban
su operación.
El circuito.

El código fuente
/******************************************************************************
* FileName: main.c
* Purpose: Timer0 en Modo CTC
* Processor: ATmega164P
* Compiler: AVR IAR C y AVR GCC (WinAVR)
* Author:

Shawn Johnson. http://www.cursomicros.com.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License: Se permiten el uso y la redistribución de este código con
*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y la nota de autor de arriba.

*****************************************************************************/

#include "avr_compiler.h"
#include "usart.h"

#define MOC_OFF() (PORTA |= (1<<0))
#define MOC_ON() (PORTA &= ~(1<<0))

volatileunsignedchartop=0;
volatileunsignedcharduty=0;
volatileunsignedcharticks=0;

//*****************************************************************************
// Gestor de Interrupción INT0.
// Esta función se ejecuta cuando se detectan flancos de subida en el pin INT0.
//*****************************************************************************
ISR(INT0_vect)
{
staticunsignedchari=0;
staticunsignedintavr=0;

if(duty)
{
MOC_ON();
}
avr+=ticks;
if(++i>=4){
i=0;
avr=avr/4;
top=(unsignedchar)avr+1;
avr=0;
}
ticks=0;
}

//*****************************************************************************
// Gestor de Interrupción en Coincidencia del Timer0.
// Esta función se ejecuta cada 200 us exactamente.
//*****************************************************************************
ISR(TIMER0_COMPA_vect)
{
if(++ticks>=duty)
{
if(duty<top)
{
MOC_OFF();
}
}
}
/******************************************************************************
* Main Function
*****************************************************************************/
intmain(void)
{
DDRA=(1<<0);// PA0 -> Salida MOC

usart_init();
printf("rn Control de Potencia ");
printf("rn (+) Subir duty cycle ");
printf("rn (-) Bajar duty cycle r");

/* Configuración del Timer0
* - Modo de operación

= CTC

* - Factor de prescaler = 8
* - Periodo de auto-reset = 200 us
*/
TCCR0A=(1<<WGM01);
TCCR0B=(1<<CS01);
OCR0A=199;// Límite de TCNT0

/* Habilitar parcialmente la Interrupción en Coincidencia del Timer0 */
TIMSK0=(1<<OCIE0A);

/* Configurar y habilitar parcialmnete la interrupción INT0 para que se
* dispare con cualquier flanco (de subida y/o de bajada) detectado en el
* pin INTx
*/
EIMSK=(1<<INT0);// Habilitar INT0
EICRA=(1<<INT0*2);// Elegir flanco de bajada/subida (modo 1)

sei();// Habilitación general de interrupciones

for(;;)
{
if(kbhit())
{
chark=getchar();

if(top==0){
printf("r No AC signal detected");
}
elseif((k=='+')&&(duty<top))
{
printf("r duty = %d",++duty);
}
elseif((k=='-')&&(duty))
{
printf("r duty = %d",--duty);
}
}
}
}

Descripción del programa
No hay mucho que explicar. Los cálculos de la temporización se realizaron
exactamente como en la anterior práctica. Salieron precisos y no fue necesario realizar
calibraciones ni nada.

La Interrupción INT0 se estudió tan ampliamente como lo estamos haciendo con los
Timers. Su función en este programa es detectar los cruces por cero de la señal alterna
AC. En ese momento se activa el opto-acoplador y también se reinicia el contador
ticks, el cual tiene la función de medir el tiempo que dura cada semiperiodo de la señal
alterna. En el código se promedian cuatro de estos tiempos, aunque creo que hubiera
sido mejor añadirle un filtro software para los picos.
La Interrupción en Coincidencia del Timer0pone fin al tiempo que el opto-acoplador
permanece activo. Este tiempo está determinado por el valor de la variable ticks, el
cual a su vez se incrementa desde 0 hasta duty, que es el duty cycle establecido por el
usuario mediante la consola del puerto serie.
No sé si seguir explicando o sugerirte que mejor vieras el resultado en un osciloscopio,
aunque sea en Proteus. Se ve impresionante. La siguiente figura es una captura de la
onda alterna de 220V solo que recortada con un duty cycle de 50%. Como siempre la
práctica lleva su archivo de simulación de Proteus y la puedes descargar haciendo clic
en la imagen del circuito.
Práctica: El Timer0/2 Como Contador
La práctica no puede ser más sencilla: el Timer0 contará los flancos de bajada
generados por un pulsador conectado al pin T0 del AVR. El valor del
registro TCNT0 será enviado al terminal serial. No se pondrá ningún mecanismo antirebote. Así que el Timer0 se incrementará con todo y los rebotes.
Circuito para probar el Timer0 del microcontrolador AVR.

El código fuente
/******************************************************************************
* FileName: main.c
* Purpose: Timer0 en Modo Normal como Contador
* Processor: ATmega164P
* Compiler: AVR IAR C y AVR GCC (WinAVR)
* Author:

Shawn Johnson. http://www.cursomicros.com.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License: Se permiten el uso y la redistribución de este código con
*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y la nota de autor de arriba.

*****************************************************************************/
#include "avr_compiler.h"
#include "usart.h"

/******************************************************************************
* Main Function
*****************************************************************************/
intmain(void)
{
unsignedchartcnt0;

PORTB|=(1<<0);// Activar pull-up de pin PB0/T0

usart_init();
printf("rn Timer0 como Contador de Pulsos r");

/* Configuración del Timer0
* - Modo de operación = Normal
* - Fuente de reloj = Pin T0
* El registro TCNT0 se incrementa con los flancos de bajada del pin T0
*/
TCCR0A=0X00;
TCCR0B=(1<<CS02)|(1<<CS01);

/* Resetear TCNT0 */
TCNT0=0x00;
tcnt0=~TCNT0;

for(;;)
{
if(tcnt0!=TCNT0)
{
tcnt0=TCNT0;
printf("r Valor del registro TCNT0 = %d",tcnt0);
}
}
}

Práctica: Teclado Alfanumérico basado en teclado matricial de 4x4
El ejemplo más notorio de este tipo de teclados lo tenemos en los teléfonos celulares.
Allí vemos cómo unas pocas teclas se multiplican para permitir el ingreso de un
conjunto amplio de caracteres entre letras, números y símbolos ortográficos o
matemáticos. En esta práctica aprenderemos una forma de implementarlo.
Había intentado más de una vez escribir este programa prescindiendo de las
interrupciones del Timer y así incluirlo en un capítulo previo, pero había fracasado. Fue
recién después de terminarlo como está ahora que hallé el algoritmo que buscaba.
Pero bueno, aquí va de todas formas (con interrupción del Timer0).
Emularemos el modo de ingreso de texto en un celular donde cada tecla vale por
varios dígitos. Cuando pulsamos seguidamente una tecla se nos van mostrando los
caracteres que representa mientras no pasemos un tiempo de ventana entre pulsada y
pulsada. El carácter escogido será el que dejemos permanecer por más de ese tiempo.
El teclado aceptará todos los caracteres ASCII visibles. Los caracteres validados se irán
almacenando en un buffer. Por defecto las letras se presentan en minúsculas. Para
pasar a mayúsculas debemos presionar la tecla MAY. Eso además encenderá un LED.
Para regresar al modo de minúsculas presionamos MAY otra vez. Las otras teclas de
función son:
DEL: permite borrar el carácter actual del buffer.
RST: Borra todas los caracteres del buffer.
ENT: Reproduce todo el contenido del buffer.
El enunciado suena interesante pero no se deja apreciar a plenitud en el circuito que
armaremos donde como siempre y para facilitar su implementación usaremos la
interface con la computadora para visualizar los datos en ella. Ahora que si alguien
tiene un poco más de paciencia para añadirle un LCD puede descargar esta práctica.
Los resultados se ven mucho mejor en un LCD. El pequeño problema es que en ese
programa la rutina de ingreso de símbolos me quedó incompleta. Será para una
próxima actualización.

Circuito para teclado alfanumérico basado en teclado matricial de 4x4.

Los códigos fuente
El programa utiliza la interrupción del Timer0 para incrementar un contador que a su
vez usaremos para medir el tiempo entre las pulsadas de una tecla. La temporización
se reinicia si se presiona otra tecla a la vez que se valida el carácter de la tecla previa.
La temporización se detiene mientras una tecla permanezca presionada.
El código del teclado ha sido encerrado en una librería de archivos keypad.h y
keypad.c. Con eso el código fuente principal queda así:
/******************************************************************************
* FileName: main.c
* Purpose: Teclado alfanumérico basado en teclado matricial de 4x4
* Processor: AVR ATmegaXX4
* Compiler: AVR IAR C & AVR GCC (WinAVR)
* Author:

Shawn Johnson. http://www.cursomicros.com.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License: Se permiten el uso y la redistribución de este código con
*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y las notas de autor y copyright de arriba.

*****************************************************************************/

#include "avr_compiler.h"
#include "usart.h"
#include "keypad.h"

/******************************************************************************
* Mapa de teclas
*****************************************************************************/
PROGMEMconstcharsymbol[]={'*','!','"','#','$','%','&',''','(',
')','+','-','/',':',';','<','=','>','?',
'@','[','',']','^','_','{','|','}','~'};

PROGMEMconstcharkeys[16][5]={
{'1','a','b','c',4},{'2','d','e','f',4},{'3','g','h','i',4},{'R',0,0,0,1},
{'4','j','k','l',4},{'5','m','n','o',4},{'6','p','q','r',4},{'D',0,0,0,1},
{'7','s','t','u',4},{'8','v','w','x',4},{'9','y','z',' ',3},{'M',0,0,0,1},
{'*',0,0,0,sizeof(symbol)},{'0','.',',',0,3},{' ',0,0,0,1},{'E',0,0,0,1}};

volatileunsignedcharticks_4ms;

/******************************************************************************
* Gestor de 'Interrupción en Coincidencia del Timer0'.
* Esta función se ejecuta cada 4 ms exactamente.
*****************************************************************************/
ISR(TIMER0_COMPA_vect)
{
if(ticks_4ms<255)
ticks_4ms++;
}

/******************************************************************************
* Función principal
*****************************************************************************/
intmain(void)
{
unsignedcharkey,old_key;// Desplazamientos de teclas
charckey;// valor ASCCI de tecla
unsignedcharold_ticks_4ms;
unsignedcharidx,index;
staticcharmessage[64];// El mensaje a recibir puede ser de hasta 64 caracteres

DDRD|=(1<<PD5);// Pin PA0 = salida para LED indicador de mayúsculas
PORTD&=~(1<<PD5);// Iniciar con LED apagado (minúsculas)

usart_init();// Inicializar USART0 @ 9600 N 1

puts("rn Teclado Alfanumérico rn");

/******************************************************************
* Configuración del Timer0
* - Bits WGM: Modo de operación = CTC
* - Bits COM: Operación normal de pines OC0A y OC0B
* - Bits CS: Fuente de reloj = F_CPU/256 (Prescaler = N = 256)
*****************************************************************/
TCCR0A=(1<<WGM01);
TCCR0B=(1<<CS02);
OCR0A=124;// Límite de TCNT0

/* Habilitar 'Interrupción en Coincidencia del Timer0' */
TIMSK0=(1<<OCIE0A);
sei();

index=0;
message[index]=0x00;
while(1)
{
key=keypad_read();// Leer teclado
if(key<16)// Sí hubo Tecla pulsada
{
/*****************************************************************/
switch(pgm_read_byte(&(keys[key][0])))
{
case'E':// Si es tecla ENT
message[index]=0x00;// Poner fin de cadena
puts(message);// Mostrar el mensaje
putchar('r');
keypad_released();
break;

case'R':// Si es tecla RST
while(index)
message[--index]=0x00;// Borrar todo lo escrito
keypad_released();
break;

case'D':// Si es tecla DEL
if(index)
message[--index]=0x00;// Borrar última tecla
keypad_released();
break;
case'M':// Si es tecla MAY
PIND|=(1<<PD5);// Conmutar LED = flag de mayúsculas
keypad_released();
break;

default:// Las demás teclas son multiplexadas
/*********************************************************/
ticks_4ms=0;// Iniciar temporización
idx=0;// Iniciar índice idx
do{
if(key<16)
{
/************************************************
* Obtener valor ASCCI de la tecla desde el array
* symbol o desde el array keys según sea el caso.
************************************************/
if(pgm_read_byte(&(keys[key][0]))=='*')
ckey=pgm_read_byte(&(symbol[idx]));
else
ckey=pgm_read_byte(&(keys[key][idx]));
/************************************************
* Si está encendido el LED de mayúsculas y si es
* una letra minúscula, convertirla en mayúscula.
************************************************/
if((PIND&(1<<5))&&('a'<=ckey)&&(ckey<='z'))
ckey=ckey-32;
/************************************************
* Visualizar el carácter, actualizar old_key y
* validar key.
************************************************/
putchar(ckey);
old_key=key;
key=16;
/************************************************
* Detener la temporización mientras la tecla
* esté pulsada.
************************************************/
old_ticks_4ms=ticks_4ms;
keypad_released();
ticks_4ms=old_ticks_4ms;
}

/************************************************
* Volver a leer el teclado.
************************************************/
key=keypad_read();

if(key<16)// Si hubo tecla pulsada
{
if(key==old_key)
{
/**********************************************
* Si es la misma tecla, avanzamos el índice idx
* cíclicamente y reniciamos la temporización
**********************************************/
if(idx<(pgm_read_byte(&(keys[key][4]))-1))
idx++;
else
idx=0;
ticks_4ms=0;
}
else
{
/**********************************************
* Si es otra tecla, forzamos a que termine
* la temporización.
**********************************************/
ticks_4ms=255;
}
if(pgm_read_byte(&(keys[key][4]))==1)
{
/**********************************************
* Si es una tecla mono-función, forzamos a que
* termine la temporización para salir.
**********************************************/
ticks_4ms=255;
}
}
}while(ticks_4ms<150);// Mientras no pasen 150*4 = 0.6 segundos

if(index<(sizeof(message)-1))// Si queda espacio en buffer
{
message[index++]=ckey;// Almacenar tecla
putchar('r');// Salto de línea
}
/*********************************************************/
}
}
}
}
En seguida tenemos los archivos de la librería del teclado. Las funciones proveídas
devuelven un código entre 0 y 15 si se encuentra alguna tecla pulsada; es el programa
principal el que se encarga de interpretar qué dígito de la tecla se valida.

/******************************************************************************
* FileName: keypad.h
* Purpose: Librería para Teclado matricial de 4x4
* Processor: AVR ATmegaXX4
* Compiler: AVR IAR C & AVR GCC (WinAVR)
* Author:

Shawn Johnson. http://www.cursomicros.com.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License: Se permiten el uso y la redistribución de este código con
*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y las notas de autor y copyright de arriba.

*****************************************************************************/

#include "avr_compiler.h"

#include "avr_compiler.h"

//****************************************************************************
// Configuración del puerto de interface
//****************************************************************************
#define kpd_PORT PORTB // Port write
#define kpd_PIN PINB // Port read
#define kpd_DDR DDRB // Dirección de puerto

//****************************************************************************
// Prototipos de funciones
//****************************************************************************
charkeypad_read(void);// Retorna el valor ASCII de la tecla presionada
// o retorna 0x00 si no se presionó nada
voidkeypad_released(void);// Espera hasta que el teclado esté libre
charkeypad_scan(void);// Escanea el teclado

/******************************************************************************
* FileName: keypad.c
* Purpose: Librería para Teclado matricial de 4x4
* Processor: AVR ATmegaXX4
* Compiler: AVR IAR C & AVR GCC (WinAVR)
* Author:

Shawn Johnson. http://www.cursomicros.com.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License: Se permiten el uso y la redistribución de este código con
*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y las notas de autor y copyright de arriba.

*****************************************************************************/

#include "avr_compiler.h"
#include "keypad.h"

/*****************************************************************************
* Mapa de desplazamientos de las teclas
*

===================

* | 0|1 |2 |3 |
*

-------------------

* | 4|5 |6 |7 |
*

-------------------

* | 8 | 9 | 10 | 11 |
*

-------------------

* | 12 | 13 | 14 | 15 |
*

===================

*****************************************************************************/

//****************************************************************************
// Escanea el teclado y retorna desplazamiento de la tecla presionada por
// al menos 25ms. En otro caso retorna 0xFF.
//****************************************************************************
charkeypad_read(void)
{
unsignedchari,k;
k=keypad_scan();// Escanear teclado
if(k<16)// Si hubo alguna tecla pulsada
{
for(i=0;i<25;i++)
{
if(k!=keypad_scan())
return0xFF;
delay_us(1000);
}
}
returnk;
}

//****************************************************************************
// Espera hasta que el teclado quede libre.
//****************************************************************************
voidkeypad_released(void)
{
delay_us(10);//
while(keypad_scan()!=0xFF)// Mientras se detecte alguna tecla pulsada
continue;// seguir escaneando.
}

//****************************************************************************
// Escanea el teclado y retorna el desplazamiento de la primera tecla que
// encuentre pulsada. De otro modo retorna 0xFF
//****************************************************************************
charkeypad_scan(void)
{
unsignedcharCol,Row;
charRowMask,ColMask;

kpd_DDR=0x0F;// Nibble alto entrada, nibble bajo salida
kpd_PORT=0xF0;// Habilitar pull-ups del nibble alto

RowMask=0xFE;// Inicializar RowMask a 11111110

for(Row=0;Row<4;Row++)
{
kpd_PORT=RowMask;//
delay_us(10);// Para que se estabilice la señal
ColMask=0x10;// Inicializar ColMask a 00010000
for(Col=0;Col<4;Col++)
{
if((kpd_PIN&ColMask)==0)// Si hubo tecla pulsada
{
kpd_DDR=0x00;// Todo puerto entrada otra vez
return4*Row+Col;// Retornar desplazamiento de tecla pulsada
}

ColMask<<=1;// Desplazar ColMask para escanear
}// siguiente columna

RowMask<<=1;// Desplazar RowMask para escanear
RowMask|=0x01;// siguiente fila
}

// Se llega aquí si no se halló ninguna tecla pulsada
kpd_DDR=0x00;// Todo puerto entrada otra vez
return0xFF;// Retornar Código de no tecla pulsada
}

Registros del Timer0
Registro TCCR0A

TCCR0A

COM0A1

Registro TCCR0B

COM0A0

COM0B1

COM0B0

---

---

WGM01

WGM00
TCCR0B

FOC0A

FOC0B

---

---

---

---

---

---

---

---

---

TSM

---

---

WGM02

CS02

CS01

CS00

---

OCIE0B

OCIE0A

TOIE0

---

---

OCF0B

OCF0A

TOV0

---

---

---

PSRASY

PSRSYNC

Registro TCNT0

TCNT0
Registro OCR0A

OCR0A
Registro OCR0B

OCR0B
Registro TIMSK0

TIMSK0
Registro TIFR0

TIFR0
Registro GTCCR

GTCCR

TCCR0A – Timer/Counter Control Register 0 A
Registro TCCR0A

TCCR0A

COM0A1 COM0A0

COM0B1

COM0B0

---

---

WGM01

WGM00

Registro de Microcontrolador

COM0A1:
0

Compare Match Output A Mode
Estos bits controlan el comportamiento del pin Output Compare (OC0A). Si
uno o los dos bits COM0A1:0 están seteados, la salida OC0A tiene prioridad
sobre la funcionalidad normal del pin al que está conectado. Sin embargo,
note que el bit del registro DDR correspondiente al pin OC0A debe estar
configurado como salida para habilitar el driver de salida.
Cuando OC0A está conectado al pin la función de los bits COM0A1:0 depende
de la configuración de los bits WGM02:0. La siguiente tabla muestra la
funcionalidad de los bits COM0A1:0 cuando los bits WGM02:0 están
configurados en modo Normal oCTC (no PWM).
Tabla COM0A1
COM0A1 COM0A0 Descripción
0

0

OC0A desconectado. Operación normal del pin

0

1

OC0A conmuta en Coincidencia de TCNT0 y OCR0A

1

0

OC0A se limpia en Coincidencia de TCNT0 y OCR0A

1

1

OC0A se setea en Coincidencia de TCNT0 y OCR0A

La siguiente tabla muestra la funcionalidad de los bits COM0A1:0 cuando los
bits WGM02:0 están configurados en modo Fast PWM.
Nota: ocurre un caso especial cuando OCR0A es el tope del conteo y el bit
COM0A1 vale uno. En este caso se ignora la Coincidencia, pero la puesta a
cero o a uno de OC0A se produce al llegar a 0x00.
Tabla COM0A1

COM0A1 COM0A0 Descripción
0

0

OC0A desconectado. Operación normal del pin

0

1

WGM02 = 0: OC0A Desconectado, Operación normal
del pin.
WGM02 = 1: OC0A conmuta en Coincidencia.

1

0

OC0A se limpia en la Coincidencia, y se setea al
llegar a 0x00 (modo no-invertido)

1

1

OC0A se setea en la Coincidencia, y se limpia al
llegar a 0x00 (modo invertido).

La siguiente tabla muestra la funcionalidad de los bits COM0A1:0 cuando los
bits WGM02:0 están configurados en modo PWM de Fase Correcta.
Nota: ocurre un caso especial cuando OCR0A es el tope del conteo y el bit
COM0A1 vale uno. En este caso se ignora la Coincidencia, pero la puesta a
cero o a uno de OC0A se produce al llegar al tope.
Tabla COM0A1

COM0A1 COM0A0 Descripción
0

0

1

1

COM0B1:
0

0

OC0A desconectado. Operación normal del pin

1

WGM02 = 0: OC0A Desconectado, Operación normal
del pin.
WGM02 = 1: OC0A conmuta en Coincidencia.

0

OC0A se limpia en la Coincidencia cuando se cuenta
hacia arriba, y se setea en la Coincidencia cuando se
cuenta hacia abajo.

1

OC0A se setea en la Coincidencia cuando se cuenta
hacia arriba, y se limpia en la Coincidencia cuando se
cuenta hacia abajo.

Compare Match Output B Mode
Estos bits controlan el comportamiento del pin Output Compare (OC0B). si
uno o los dos bits COM0B1:0 están seteados, la salida OC0B tiene prioridad
sobre la funcionalidad normal del pin al que está conectado. Sin embargo,
note que el bit del registro DDR correspondiente al pin OC0B debe estar
configurado como salida para habilitar el driver de salida.
Cuando OC0B está conectado al pin la función de los bits COM0B1:0 depende
de la configuración de los bits WGM02:0. La siguiente tabla muestra la
funcionalidad de los bits COM0B1:0 cuando los bits WGM02:0 están
configurados en modo Normal oCTC (no PWM).
Tabla COM0B1

COM0B1 COM0B0 Descripción
0

0

OC0B desconectado. Operación normal del pin

0

1

OC0B conmuta en Coincidencia de TCNT0 y OCR0B

1

0

OC0B se limpia en Coincidencia de TCNT0 y OCR0B

1

1

OC0B se setea en Coincidencia de TCNT0 y OCR0B

La siguiente tabla muestra la funcionalidad de los bits COM0B1:0 cuando los
bits WGM02:0 están configurados en modo Fast PWM.
Nota: ocurre un caso especial cuando OCR0B es el tope del conteo y el bit
COM0B1 vale uno. En este caso se ignora la Coincidencia, pero la puesta a
cero o a uno de OC0B se produce al llegar a 0x00.
Tabla COM0B1

COM0B1 COM0B0 Descripción
0

0

OC0B desconectado. Operación normal del pin

0

1

Reservado

1

0

OC0A se limpia en la Coincidencia, y se setea al llegar
a 0x00 (modo no-invertido)

1

1

OC0A se setea en la Coincidencia, y se limpia al llegar
a 0x00 (modo invertido).

La siguiente tabla muestra la funcionalidad de los bits COM0B1:0 cuando los
bits WGM02:0 están configurados en modo PWM de Fase Correcta.
Nota: ocurre un caso especial cuando OCR0B es el tope del conteo y el bit
COM0B1 vale uno. En este caso se ignora la Coincidencia, pero la puesta a
cero o a uno de OC0B se produce al llegar al tope.
Tabla COM0B1

COM0B1 COM0B0 Descripción
0

0

OC0B desconectado. Operación normal del pin

0

1

Reservado

0

OC0B se limpia en la Coincidencia cuando se cuenta
hacia arriba, y se setea en la Coincidencia cuando se
cuenta hacia abajo.

1

OC0B se setea en la Coincidencia cuando se cuenta
hacia arriba, y se limpia en la Coincidencia cuando se
cuenta hacia abajo.

1

1

Bits 3:2

Reservados
Estos bits están reservados en los ATmega164A/PA, ATmega324A/PA,
ATmega644A/PA y ATmega1284/P, y siempre se leerán como cero.
WGM01:0

Waveform Generation Mode
Estos bits se combinan con el bit WFGM02 del registro TCCR0B. Juntos
controlan la secuencia de conteo del contador, el valor máximo del conteo (el
tope) y el tipo de la forma de onda que se generará. Los modos operación
que soporta la unidad del Timer/Counter son: modo Normal (contador),
modo Clear Timer on Compare Match (CTC), y dos tipos de Modulación de
Ancho de Pulso (PWM).
Tabla WGM02

WGM02 WGM01 WGM00 Modo de Operación del Timer0 Tope del Conteo
0

0

0

Normal

0xFF

0

0

1

PWM de Fase Correcta

0xFF

0

1

0

CTC

OCR0A

0

1

1

Fast PWM

0xFF

1

0

0

Reservado

---

1

0

1

PWM de Fase Correcta

OCR0A

1

1

0

Reservado

---

1

1

1

Fast PWM

OCR0A

TCCR0B – Timer/Counter Control Register 0 B
Registro TCCR0B

TCCR0B FOC0A FOC0B

---

---

WGM02

CS02

CS01

CS00

Registro de Microcontrolador

FOC0A

Force Output Compare A
El bit FOC0A solo está activo cuando los bits WGM establecen un modo no PWM.
Sin embargo, para asegurar la compatibilidad con futuros dispositivos, este bit se
debe mantener en cero al escribir en TCCRB cuando el Timer está operando en
uno de los modos PWM. Si se escribe un uno en el bit FOCA, se fuerza una
Coincidencia inmediata en la Unidad Generadora de Forma de Onda. La salida de
OC0A cambia de acuerdo con la configuración de los bits COM0A1:0. Note que el
bit FOC0A se implementa como strobe. Así que es el valor presente en los
COM0A1:0 los que determinan el efecto de la Coincidencia forzada.
Un strobe de FOC0A no generará ninguna interrupción ni tampoco reseteará el
Timer en modo CTC si se usa OCR0A como tope del conteo.
El bit FOC0A siempre se lee como cero.
FOC0B

Force Output Compare B
El bit FOC0B solo está activo cuando los bits WGM establecen un modo no PWM.
Sin embargo, para asegurar la compatibilidad con futuros dispositivos, este bit se
debe mantener en cero al escribir en TCCRB cuando está operando en modo
PWM. Si se escribe un uno en el bit FOC0B, se fuerza una Coincidencia inmediata
en la Unidad Generadora de Forma de Onda. La salida de FOC0B cambia de
acuerdo con la configuración de los bits COM0B1:0. Note que el bit FOC0B se
implementa como strobe. Así que es el valor presente en los COM0B1:0 los que
determinan el efecto de la Coincidencia forzada.
Un strobe de FOC0B no generará ninguna interrupción ni tampoco reseteará el
Timer en modo CTC si se usa OCR0B como tope del conteo.
El bit FOC0B siempre se lee como cero.

Bits 5:4

Reservados
Estos bits están reservados y siempre se leerán como cero.

WGM02

Waveform Generation Mode
Ver la descripción de los bits WGM01:0 del registro TCCR0A.

CS02:0

Clock Select
Los tres bits de Clock Select seleccionan la fuente de reloj que usará el
Timer/Counter.
Si se usan los modos de pin externo para el Timer/Counter0, las transiciones en el
pin T0 harán el contador incluso si el pin está configurado como salida. Esta
característica permite el control software del contador.
Tabla CS02
CS02 CS01 CS00 Fuente de reloj del Timer0
0

0

0

Sin fuente de reloj (el Timer0 está detenido)

0

0

1

F_CPU (Sin prescaler)

0

1

0

F_CPU/8 (con prescaler)

0

1

1

F_CPU/64 (con prescaler)

1

0

0

F_CPU/256 (con prescaler)

1

0

1

F_CPU/1024 (con prescaler)

1

1

0

Reloj externo en pin T0.
El Timer0 avanza con el flanco de bajada.

1

1

1

Reloj externo en pin T0.
El Timer0 avanza con el flanco de subida.

TCNT0 – Timer/Counter 0 Register
Registro TCNT0

TCNT0
Registro de Microcontrolador

Bits 7:0

TCNT0[7:0]
El Registro Timer/Counter da acceso directo al contador de 8 bits del Timer/Counter
para las operaciones de lectura y escritura. La escritura en el registro TCNT0 bloquea
(quita) la Coincidencia en el siguiente ciclo de reloj del Timer. La modificación de
TCNT0 cuando el contador está corriendo conlleva un riesgo de perder una
Coincidencia entre los registros TCNT0 y OCR0x.

OCR0A – Output Compare Register 0 A
Registro OCR0A

OCR0A
Registro de Microcontrolador

Bits 7:0

OCR0A[7:0]
El registro Output Compare A contiene un valor de 8 bits que es continuamente
comparado con el valor del contador (TCNT0). Se puede usar una Coincidencia para
disparar una Interrupción en Coincidencia, o para generar una onda por el pin OC0A.
OCR0B – Output Compare Register 0 B
Registro OCR0B

OCR0B
Registro de Microcontrolador

Bits 7:0

OCR0B[7:0]
El registro Output Compare B contiene un valor de 8 bits que es continuamente
comparado con el valor del contador (TCNT0). Se puede usar una Coincidencia para
disparar una Interrupción en Coincidencia, o para generar una onda por el pin OC0B.

TIMSK0 – Timer/Counter Interrupt Mask 0 Register
Registro TIMSK0

TIMSK0 --- ---

---

---

---

OCIE0B

OCIE0A

TOIE0

Registro de Microcontrolador

Bits 7:3

Reserved
Estos bits están reservados y siempre se leerán como cero.

OCIE0B

Timer/Counter Output Compare Match B Interrupt Enable
Cuando se escribe uno en el bit OCIE0B y el bit I del registro SREG vale uno, se
habilita la Interrupción en Coincidencia B del Timer/Counter0. Si ocurre una
Coincidencia entre TCNT0 y OCR0B se ejecutará la función de interrupción
correspondiente, esto es, cuando se active el flag OCF0B del registro TIFR0.

OCIE0A

Timer/Counter Output Compare Match A Interrupt Enable
Cuando se escribe uno en el bit OCIE0A, y el bit I del registro SREG vale uno, se
habilita la Interrupción en Coincidencia A del Timer/Counter0. Si ocurre una
Coincidencia entre TCNT0 y OCR0A, se ejecutará la función de interrupción
correspondiente, esto es, cuando se active el flag OCF0A del registro TIFR0.

TOIE0

Timer/Counter0 Overflow Interrupt Enable
Cuando se escribe uno en el bit TOIE0, y el bit I del registro SREG vale uno, se habilita
la Interrupción por Desbordamiento del Timer/Counter0. Si ocurre un
Desbordamiento en el registro TCNT0, se ejecutará la función de Interrupción
correspondiente, esto es, cuando se active al flag TOV0 del registro TIFR0.
TIFR0 – Timer/Counter Interrupt Flag 0 Register
Registro TIFR0

TIFR0 --- ---

---

---

---

OCF0B

OCF0A

TOV0

Registro de Microcontrolador

Bits
7:3

Reserved

OCF0B

Timer/Counter 0 Output Compare B Match Flag

Estos bits están reservados y siempre se leerán como cero.

El bit OCF0B se setea cuando ocurre una Coincidencia entre los datos de los registros
TCNT0 y OCR0B. El flag OCF0B se limpia por hardware al ejecutarse su
correspondiente función de interrupción. Alternativamente se puede limpiar
escribiendo en él un uno lógico. Cuando valgan uno el bit I de SREG, el bit de enable
OCIE0B y el bit de flag OCF0B, entonces se ejecutará la Interrupción en Coincidencia B
del Timer/Counter0.
OCF0A

Timer/Counter 0 Output Compare A Match Flag
El bit OCF0A se setea cuando ocurre una Coincidencia entre los datos de los registros
TCNT0 y OCR0A. El flag OCF0A se limpia por hardware al ejecutarse su
correspondiente función de interrupción. Alternativamente se puede limpiar
escribiendo en él un uno lógico. Cuando valgan uno el bit I de SREG, el bit de enable
OCIE0A y el bit de flag OCF0A, entonces se ejecutará la Interrupción en Coincidencia A
del Timer/Counter0.

TOV0

Timer/Counter0 Overflow Flag
El bit TOV0 se setea cuando ocurre un Desbordamiento del registro TCNT0. El flag
TOV0 se limpia por hardware al ejecutarse su correspondiente función de
interrupción. Alternativamente se puede limpiar escribiendo en él un uno lógico.
Cuando valgan uno el bit I de SREG, el bit de enable OCIE0A y el bit de flag OCF0A,
entonces se ejecutará la Interrupción por Desbordamiento del Timer/Counter0.
La activación de este flag depende de la configuración de los bits WGM02:0.

GTCCR – General Timer/Counter Control Register
Registro GTCCR
GTCCR

TSM ---

---

---

---

---

PSRASY

PSRSYNC

Registro de Microcontrolador

TSM

Timer/Counter Synchronization Mode
Al escribir uno en el bit TSM se activa el modo de Sincronización del
Timer/Counter. En este modo, se mantendrán los valores que se escriban en los
bits PSRASY y PSRSYNC, para mantener activadas las señales de reset del prescaler
correspondiente. Esto asegura que los correspondientes Timers/Counters estén
detenidos y se puedan configurar al mismo valor sin correr el riesgo de que uno de
ellos avance durante la configuración.
Si se escribe un cero en el bit TSM, los bits PSRASY y PSRSYNC se limpian por
hardware y los Timers/Counters empiezan a contar simultáneamente.

PSRASY

Prescaler Reset Timer/Counter2
Cuando este bit vale uno, el prescaler del Timer/Counter2 se reseteará.
Normalmente este bit se limpia de inmediato por hardware. Si se escribe en este
bit cuando el Timer/Counter2 está trabajando en modo asíncrono, el bit
permanecerá en uno hasta que se resetee el prescaler. El bit no se limpiará por
hardware si el bit TSM vale uno.

PSRSYNC

Prescaler Reset
Si este bit vale uno, el prescaler del Timer/Counter0 y el Timer/Counter1 se
reseteará. Normalmente este bit se limpia de inmediato por hardware, excepto
cuando el bit TSM valga uno. Note que el Timer/Counter0 y el Timer/Counter1
comparten el mismo prescaler y el reset de este prescaler afecta a ambos Timers.

Registros del Timer2
Registro TCCR2A

TCCR2A

COM2A1

COM2A0

COM2B1

FOC2B

---

COM2B0

---

---

WGM21

WGM20

Registro TCCR2B

TCCR2B
Registro TCNT2

TCNT2

FOC2A

---

WGM22

CS22

CS21

CS20
Registro OCR2A

OCR2A
Registro OCR2B

OCR2B
Registro TIMSK2

TIMSK2

---

---

---

---

---

OCIE2B

OCIE2A

TOIE2

---

---

---

---

---

OCF2B

OCF2A

TOV2

TSM

---

---

---

---

---

Registro TIFR2

TIFR2
Registro GTCCR

GTCCR

PSRASY

PSRSYNC

Registro de Microcontrolador

CS22:0

Clock Select
Los tres bits de Clock Select seleccionan la fuente de reloj que usará el
Timer/Counter2.
Tabla CS22

CS22 CS21 CS20 Fuente de reloj del Timer2
0

0

0

Sin fuente de reloj (Timer/Counter2 detenido).

0

0

1

clkT2S (Sin prescaler)

0

1

0

clkT2S/8 (Desde el prescaler)

0

1

1

clkT2S/32 (Desde el prescaler)

1

0

0

clkT2S/64 (Desde el prescaler)

1

0

1

clkT2S/128 (Desde el prescaler)

1

1

0

clkT2S/256 (Desde el prescaler)

1

1

1

clkT2S/1024 (Desde el prescaler)
ASSR – Asynchronous Status Register
Si se efectúa una escritura en cualquiera de los cinco registros del Timer/Counter2
cuando su correspondiente flag de busy (ocupado) vale uno, el valor actualizado puede
resultar corrompido y se podría disparar una interrupción inintencionada.
El mecanismo para leer los registros TCNT2, OCR2A, OCR2B, TCCR2A y TCCR2B es
diferente. Al leer TCNT2 se obtiene el valor actual del Timer. Al leer OCR2A, OCR2B,
TCCR2A y TCCR2Bse obtiene el valor del registro de almacenamiento temporal.
Registro ASSR

ASSR

--- EXCLK AS2

TCN2UB

OCR2AUB

OCR2BUB

TCR2AUB

TCR2BUB

Registro de Microcontrolador

EXCLK

Enable External Clock Input
Al escribir uno en el bit EXCLK, y se tiene seleccionado el reloj asíncrono, se habilita
el buffer de entrada de reloj externo y se puede poner un reloj externo al pin Timer
Oscillator 1 (TOSC1) en vez del XTAL de 32kHz. La escritura en EXCLK se debería
realizar antes de seleccionar la operación asíncrona.
Note que el oscilador de XTAL solo trabajará cuando este bit vale cero.

AS2

Asynchronous Timer/Counter2
Al escribir cero en este bit, el reloj del Timer/Counter2 provendrá del reloj del
sistema F_CPU. Al escribir uno en AS2, el reloj del Timer/Counter2 derivará de un
XTAL externo conectado al pin Timer Oscillator 1 (TOSC1). Si se cambia el valor de
AS2, se podrían corromper los contenidos de los registros TCNT2, OCR2A, OCR2B,
TCCR2A y TCCR2B.

TCN2UB

Timer/Counter2 Update Busy
Cuando el Timer/Counter2 opera en modo asíncrono y se escribe en el registro
TCNT2, este bit se activa a uno. Este bit se limpiará por hardware cuando TCNT2 se
actualice desde el registro de almacenamiento temporal. Un cero lógico en este bit
indica que el registro TCNT2 está listo para ser actualizado con un nuevo valor.

OCR2AUB

Output Compare Register2 A Update Busy
Cuando el Timer/Counter2 opera en modo asíncrono y se escribe en el registro
OCR2A, este bit se activa a uno. Este bit se limpiará por hardware cuando OCR2A se
actualice desde el registro de almacenamiento temporal. Un cero lógico en este bit
indica que el registro OCR2A está listo para ser actualizado con un nuevo valor.
OCR2BUB

Output Compare Register2 B Update Busy
Cuando el Timer/Counter2 opera en modo asíncrono y se escribe en el registro
OCR2B, este bit se activa a uno. Este bit se limpiará por hardware cuando OCR2B se
actualice desde el registro de almacenamiento temporal. Un cero lógico en este bit
indica que el registro OCR2B está listo para ser actualizado con un nuevo valor.

TCR2AUB

Timer/Counter Control Register2 A Update Busy
Cuando el Timer/Counter2 opera en modo asíncrono y se escribe en el registro
TCCR2A, este bit se activa a uno. Este bit se limpiará por hardware cuando TCCR2A
se actualice desde el registro de almacenamiento temporal. Un cero lógico en este
bit indica que el registro TCCR2A está listo para ser actualizado con un nuevo valor.

TCR2BUB

Timer/Counter Control Register2 B Update Busy
Cuando el Timer/Counter2 opera en modo asíncrono y se escribe en el registro
TCCR2B, este bit se activa a uno. Este bit se limpiará por hardware cuando TCCR2B
se actualice desde el registro de almacenamiento temporal. Un cero lógico en este
bit indica que el registro TCCR2B está listo para ser actualizado con un nuevo valor.

El Timer1 y el Timer3
Si antes habíamos estudiado a la par el Timer0 y el Timer2 por ser muy simulares,
esta vez la referencia al Timer3 se reduce solo a su mención en los títulos. Sucede que
el Timer3 es completamente idéntico al Timer1. La mala noticia es que solo está
disponible en los ATmega128xy :-(
El Timer/Counter1, o simplemente Timer1, es de 16 bits. En principio, opera de la
misma forma en que lo hace el Timer0, solo que utilizando sus registros de datos de
16 bits. Esto lo podremos comprobar al notar la gran similitud que tienen sus
diagramas de bloques.
Como en los megaAVR cada registro de E/S es de 8 bits, en realidad los registros de
datos del Timer1 están compuestos por la unión de dos registros de 8 bits. Por
ejemplo,TCNT1 se forma uniendo los registrosTCNT1H y TCNT1L. Lo mismo sucede
con los siguientes registros
OCR1A = unión de OCR1AH y OCR1AL
OCR1B = unión de OCR1BH y OCR1BL
ICR1 = unión de ICR1H e ICR1L
Los registros de control del Timer1 siguen siendo de 8
bits: TCCR1A, TCCR1B, TCCR1C,TIMSK1, TIFR1 y GTCCR.
Diagrama de bloques del Timer1.
Este diagrama del Timer2 es una adaptación que nos facilitará su descripción funcional.
Es una descripción corta para no redundar demasiado sobre lo explicado para el
Timer0. Bueno, empecemos:
Los bits WGM configuran en gran medida el modo de operación del Timer2. Con ellos
podemos escoger entre los modos Normal, CTC o PWM. Notemos que a diferencia del
Timer0, ahora son 4 bits WGM. Esto es porque el Timer2 ofrece muchas variantes de
los modos CTC y sobre todo PWM. Hay 16 modos en total, pero muchos de ellos son
redundantes y de poca utilidad.
Tabla WGM13

WGM13 WGM12 WGM11 WGM10

Modo de Operación del
Timer1

Inicio de
Conteo

Tope de
Conteo

0

0

0

0

Normal

0x00

0xFFFF

0

0

0

1

PWM de Fase Correcta,
8 bits

0x00

0x00FF
Tabla WGM13

WGM13 WGM12 WGM11 WGM10

Modo de Operación del
Timer1

Inicio de
Conteo

Tope de
Conteo

0

0

1

0

PWM de Fase Correcta,
9 bits

0x00

0x01FF

0

0

1

1

PWM de Fase Correcta
10 bits

0x00

0x03FF

0

1

0

0

CTC

0x00

OCR1A

0

1

0

1

Fast PWM, 8 bits

0x00

0x00FF

0

1

1

0

Fast PWM, 9 bits

0x00

0x01FF

0

1

1

1

Fast PWM, 10 bits

0x00

0x03FF

1

0

0

0

PWM de Fase y
Frecuencia Correctas

0x00

ICR1

1

0

0

1

PWM de Fase y
Frecuencia Correctas

0x00

OCR1A

1

0

1

0

PWM de Fase Correcta

0x00

ICR1

1

0

1

1

PWM de Fase Correcta

0x00

OCR1A

1

1

0

0

CTC

0x00

ICR1

1

1

0

1

Reservado

0x00

–

1

1

1

0

Fast PWM

0x00

ICR1

1

1

1

1

Fast PWM

0x00

OCR1A

Los bits CS (Clock Select), como indica su nombre, son para configurar la fuente de
reloj del Timer2. El reloj del Timer2 es idéntico al del Timer0. Recordemos que
comparten el mismo prescaler y aunque también trabajan con los mismos divisores o
factores de prescaler, su configuración no tiene que ser la misma, es decir, podemos
emplear nuestro Timer0 con un factor de 8 y el Timer2 con el factor 1024. Para mayor
información puedes revisar la sección los prescalers del Timer0 y del Timer2.
Tabla CS12

CS12 CS11 CS10 Fuente de reloj del Timer1
0

0

0

Sin fuente de reloj (el Timer1 está detenido)

0

0

1

F_CPU (Sin prescaler)

0

1

0

F_CPU/8 (con prescaler)

0

1

1

F_CPU/64 (con prescaler)

1

0

0

F_CPU/256 (con prescaler)

1

0

1

F_CPU/1024 (con prescaler)

1

1

0

Reloj externo en pin T1. El Timer1 avanza en el flanco de bajada.

1

1

1

Reloj externo en pin T1. El Timer1 avanza en el flanco de subida.

Los bits COM. Hasta ahora los habíamos estado dejando de lado. Como su nombre lo
indica, estos bits controlan en modo de salida de los comparadores (Compare Output
Mode). Los habíamos ignorado porque no tiene mucho sentido usarlos en los modos
Normal o CTC. En cambio ahora que entraremos de lleno en el modo PWM, serán más
que necesarios. Desde el momento en que configuremos estos bits para sacar las
ondas (PWM o no) por los pines OC1A y/o OC1B el Timer2 asumirá el control sobre
ellos y ya no podremos usarlos como puertos de E/S generales (mediante el registro
PORTx respectivo). Sin embargo, todavía será necesario configurarlos como pines de
salida en sus correspondientes bits del registro DDRx. Los bits COM son detestables
porque tienen diferente efecto dependiendo de si el Timer opera en modo Normal y
CTC, en modo Fast PWM o en modo PWM de Fase correcta y PWM de Fase y Frecuencia
correctas. Tan solo mencionar estos términos marea un poco, ¿verdad? Por eso voy a
reservar las tablas correspondientes para otro momento.
El Registro ICR1. Como te habrás dado cuenta, este registro es nuevo; es propio del
Timer1. Su nombre ICR1 es el acrónimo de Input Capture Register 1 y tiene dos
funciones. En primer lugar sirve para capturar el valor del registro TCNT1 justo en el
momento en que el bloque Fuente de Captura (ver la figura) le dé la señal. Esta
función es útil en aplicaciones que necesitan medir la frecuencia de alguna señal
externa, pero no tiene nada que ver con el modo PWM así que la ignoraremos por el
momento. Sin embargo, el registro ICR1 también puede trabajar como el tope del
conteo del Timer1 en los modos PWM. Ésta es su faceta en que más se le emplea.
El bloque FUENTE DE CAPTURA. Se trata de un circuito que selecciona la señal de
captura. Según su diagrama mostrado abajo, se disponen de dos opciones: o es el
pin ICPdel megaAVR o es la salida del Comparador Analógico. La elección se hace
mediante el bit ACIC, del registro ACRS. El bit ICNC1 (del registro TCCR1B) puede
activar el eliminador de ruido para evitar falsos disparos causados por los picos del
ruido. En la etapa final, el bit ICES1 (también del registro TCCR1B) establece si el
disparo se dará en el flanco de subida o de bajada de la fuente seleccionada. Una vez
detectada la señal elegida, se activará el flag ICF1 y el registro ICR1 le tomará una
fotografía al registroTCNT1. Este evento se puede aprovechar para programar y
disparar su interrupción.

Diagrama de bloques de la Entrada de Captura.

El Timer1 y el Timer3 en Modos Normal y CTC
Con su registro TCNT1 de 16 bits, el Timer1 puede contar desde 0x0000 hasta 0xFFFF.
En modo Normal y en modo CTC avanza siempre en modo ascendente. En modo
Normal llega hasta 0xFFFF y en modo CTC avanza hasta que coincida con el valor del
registroOCR1A o ICR1 que también son de 16 bits. El conteo es siempre cíclico, o sea
que después de llegar a su valor tope, la cuenta se reinicia desde 0. Notamos que el
modo CTC con tope de conteo establecido por registro ICR1 no era una opción
disponible en los Timers de 8 bits. Este modo se selecciona con los bits WGM13:0 =
1100b.

Cualquiera que sea su modo de operación, el registro de conteo TCNT1 siempre es
comparado con los registros OCR1A yOCR1B. Las Coincidencias detectadas activarán
los flags OCF1A y OCF1B, respectivamente (ver el diagrama de bloques) y se pueden
usar estos eventos para programar las interrupciones de Coincidencias setenado los
bits del registroTIMSK1 OCIE1A para la coincidencia entreTCNT1 y OCR1A,
y OCIE1B para la coincidencia entre TCNT1 y OCR1B.
Si bien pueden usarse las Coincidencias como señales para setear, limpiar o conmutar
el estado de los pines OC1A y/o OC1B, según la configuración de los bits COM, nunca
he visto una aplicación que saque provecho de esa característica. El Timer1 se reserva
de forma casi exclusiva para el modo PWM y es raro verlo trabajar incluso en los
modos Normal o CTC. Con todo, si hubiera que hacerlo, no hay mucho que añadir
sobre la operación de los Timers0 o 2 en ese modo. Obviamente, las fórmulas de
temporización adoptarán nuevas formas, teniendo en cuenta los 16 bits del Timer1.
El Tiempo que pasará entre la carga del Timer1 con un valor inicial TCNT1 hasta su
desbordamiento está dado por:

Donde:
Tabla de Temporización en Modo CTC con Timer0

Tiempo

= Valor de la temporización.

F_CPU

= Frecuencia del XTAL del megaAVR.

N

= Factor de prescaler (1, 8, 64, 256 ó 1024).

TCNT1

= Valor de inicio del registro TCNT1.

Las siguientes formulas nos dan un camino para hallar por partes las dos incógnitas de
la primera fórmula.

Si el Timer1 está programado en modo CTC, el tiempo entre coincidencia y
coincidencia de TCNT1 con TOPE podemos calcular utilizando la fórmula.

Donde:
Tabla de Temporización en Modo CTC con Timer0

Tiempo

= Valor de la temporización.

F_CPU

= Frecuencia del XTAL del megaAVR.

N

= Factor de prescaler (1, 8, 64, 256 ó 1024).
TOPE

= Valor del registro OCR1A o del registro ICR1, según el modo CTC

En este caso la fórmula se puede descomponer en las siguientes dos fórmulas de una
sola variable.

Si no tienes idea de cómo usarlas o de cuál modo (Normal o CTC) es mejor para
temporizar, puedes revisar la teoría dedicada al Timer0 y al Timer2.

El Timer1 y el Timer3 en Modo PWM
Los Timers 1 y 3 generan mejores ondas PWM que los Timers 0 y 2. Además de
obtener señales con resolución de hasta 16 bits, la frecuencia de la señal PWM es
completamente controlable.
El Timer1 de los AVR puede producir hasta dos canales PWM, por los
pines OC1A yOC1B. El Timer3 hace lo propio por los pinesOC3A y OC3B. Estos pines
deberán estar configurados como salida para dar salida a las señales PWM. Esta es una
caractrística que distingue a los módulos PWM de los otros periféricos del megaAVR
donde la configuración y control de los pines involucrados quedan a cargo del módulo
respectivo.
El mecanismo como los Timers de 16 bits generan ondas PWM es similar a como lo
hacen los Timers de 8 bits, de manera que nos debe ser familiar trabajar con los
registros de control. La única novedad es la aparición en escena del nuevo registro de
16 bits ICR1, cuya función en modo PWM es establecer el tope de conteo opcional. El
registro ICR1 es en realidad la unión de los registros de 8 bits ICR1H e ICR1L.
Dada la semejanza entre los dos Timers y entre los dos canales PWM, volvemos a
aclarar que la siguiente exposición está relacionada solo al canal A del Timer1, dando
por sentado que es también aplicable al canal B, tanto del Timer1 como del Timer3. El
Timer3 no está disponible en muchos AVR.
Tabla WGM13

WGM13 WGM12 WGM11 WGM10 Modo de Operación del Timer1

Inicio de
Conteo

Tope de
Conteo

0

0

0

1

PWM de Fase Correcta, 8 bits

0x00

0x00FF

0

0

1

0

PWM de Fase Correcta, 9 bits

0x00

0x01FF
Tabla WGM13

WGM13 WGM12 WGM11 WGM10 Modo de Operación del Timer1

Inicio de
Conteo

Tope de
Conteo

0

0

1

1

PWM de Fase Correcta, 10 bits

0x00

0x03FF

0

1

0

1

Fast PWM, 8 bits

0x00

0x00FF

0

1

1

0

Fast PWM, 9 bits

0x00

0x01FF

0

1

1

1

Fast PWM, 10 bits

0x00

0x03FF

1

0

0

0

PWM de Fase y Frecuencia
Correctas

0x00

ICR1

1

0

0

1

PWM de Fase y Frecuencia
Correctas

0x00

OCR1A

1

0

1

0

PWM de Fase Correcta

0x00

ICR1

1

0

1

1

PWM de Fase Correcta

0x00

OCR1A

1

1

1

0

Fast PWM

0x00

ICR1

1

1

1

1

Fast PWM

0x00

OCR1A

La tabla de arriba nos muestra que el Timer1 puede generar tres tipos de ondas PWM,
según la forma como avanza el registro TCNT1:
Fast PWM
PWM de Fase Correcta y
PWM de Fase y Frecuencia Correctas
Cada uno de estos modos tiene a su vez otras variantes dependiendo del valor tope
hasta donde puede contar el Timer1. Son especiales los modos que en la tabla
aparecen sombreados (donde el tope del conteo es el valor del registro OCR1A) porque
suelen entorpecer la comprensión del modo PWM de los Timers. Así que los
ignoraremos por el momento y en adelante asumiremos que tope del Timer1 puede ser
de 0x00FF, 0x01FF, 0x03FF o el valor del registro ICR1. Con eso aclarado podemos
seguir.

Timer1 en modo Fast PWM
El modo Fast PWM se conoce también como PWM de pendiente única porque el
registroTCNT1 avanza siempre hacia arriba, es decir, cuenta desde 0 hasta un
valor TOPE(0x00FF, 0x01FF, 0x03FF o el valor del registro ICR1), después de lo cual se
resetea y vuelve a empezar desde 0. Si graficamos este progreso, la curva resulta
siendo efectivamente una escalera de pendiente única, como se ve abajo.

La figura mostrada corresponde al modo Fast PWM que tiene al registro ICR1 como
tope de conteo por ser el caso más usual. Según la figura, la onda Fast PWM es
generada por la conmutación del pin OC1A cada vez que el registro TCNT1 llega al tope
y cada vez queTCNT1 coincide con el registro OCR1A. En la gráfica las coincidencias se
señalan con pequeñas líneas rojas horizontales sobre la escalera. Según la
configuración de los bitsCOM1A1 y COM1A0 (del registro TCCR1A), la onda PWM puede
ser invertida o no invertida.
Si los bits COM1A1:0 valen 0b10 se establece una onda no-invertida, esto es, el
pin OC1A se setea cuando TCNT1 llega al tope y se limpia cuando TCNT1 coincide con
el registro OCR1A.
Si los bits COM1A1:0 valen 0b11 se establece una onda invertida, esto es, el
pinOC1A se limpia cuando TCNT1 llega al tope y se setea cuando TCNT1 coincide con el
registro OCR1A.
Puesto que el Timer1 puede contar hasta varios valores TOPE (0x00FF, 0x01FF,
0x03FFo el valor del registro ICR1), se deduce que el periodo, y por ende la frecuencia,
de la onda PWM también serán variables. Esta flexibilidad lo distingue del modo PWM
de los Timers de 8 bits, donde el periodo es siempre constante. También es posible
modificar el valor del registro OCR1A, lo cual nos permitirá controlar el duty cycle de la
onda PWM.
En la siguiente imagen se indican las fórmulas para calcular el periodo y el duty cycle
de una onda Fast PWM no-invertida. Como siempre, F_CPU es la frecuencia del
procesador yN es el factor del prescaler. Recuerda que el Timer1 comparte el mismo
prescaler con el Timer0, aunque ello no significa que tengan que trabajar con el mismo
factor de prescaler. Para más detalles puedes revisar la sección relojes del Timer0 y
del Timer2.

Periodo y Duty cycle de una onda Fast PWM no-invertida.
La frecuencia de la onda Fast PWM se obtiene invirtiendo la fórmula del periodo.
Recordemos que TOPE puede valer 0x00FF, 0x01FF, 0x03FF o el valor del
registro ICR1. En el caso de que escojamos la última opción podemos cargar
en ICR1 cualesquiera valores siempre que sean mayores que 2. El máximo valor es
desde luego 0xFFFF = 65535. Luego estudiaremos los otros modos PWM y al hacer
comparaciones veremos que esta fórmula nos permite obtener frecuencias más altas
(hasta el doble de lo que se puede conseguir con los otros PWM). Eso explica la razón
de su nombre: Fast PWM = PWM rápido.

Y la fórmula que nos da el duty cycle expresado en porcentaje la obtenemos dividiendo
las fórmulas del duty cycle y del periodo.
Puesto que el registro OCR1A tiene el mismo rango de valores que TOPE, esta fórmula
nos permite observar que el duty cycle puede ser del 100% (si OCR1A = TOPE), lo cual
significa que el estado del pin OC0A será de 1 lógico constante. En cambio, no hay
ningún valor para el registro OCR1A que nos dé un duty cycle del 0%. Si el
registro OCR1A vale 0 la salida será de unos pequeños picos que representan un duty
cycle cercano al 0.0015%. Ésa es la característica que diferencia al modo Fast PWM de
los demás modos PWM. No pierdas de vista que que este duty cycle corresponde a una
onda PWM no invertida. Si la onda es invertida, el duty cycle será el complemento a
100, por ejemplo, un duty cycle de 30% de un PWM invertido equivale a un duty cycle
de 70% de un PWM no invertido.

Timer1 en modo PWM de Fase Correcta
El modo PWM de Fase Correcta se conoce también como PWM de doble pendiente
porque el registro TCNT1 cuenta en sube y baja, es decir, primero avanza
desde 0 hasta el valorTOPE (0x00FF, 0x01FF, 0x03FF o el valor del registro ICR1), y
después regresa desdeTOPE hasta 0. Este proceso se repite cíclicamente y si lo
graficamos, la curva resulta siendo efectivamente una escalera de doble pendiente,
como se ve abajo.

Como se indica en la figura, la onda PWM de Fase Correcta es generada por la
conmutación del pin OC1A cada vez que el registro TCNT1 coincide con el
registroOCR1A. En la gráfica las coincidencias se señalan con pequeñas líneas rojas
horizontales sobre la escalera. Según la configuración de los
bits COM1A1 y COM1A0 (del registroTCCR1A), la onda PWM puede ser invertida o no
invertida.
Si los bits COM1A1:0 valen 0b10 se establece una onda no-invertida, esto es, el
pin OC1A se setea cuando TCNT1 coincide con el registro OCR1A en su conteo
descendente, y se limpia cuando TCNT1 coincide con el registro OCR1A en su conteo
ascendente.
Si los bits COM1A1:0 valen 0b11 se establece una onda invertida, esto es, el
pinOC1A se setea cuando TCNT1 coincide con el registro OCR1A en su conteo
ascendente, y se limpia cuando TCNT1 coincide con el registro OCR1A en su conteo
descendente.
Puesto que el Timer1 puede contar hasta varios valores TOPE (0x00FF, 0x01FF,
0x03FFo el valor del registro ICR1), se deduce que el periodo, y por ende la frecuencia,
de la onda PWM también serán variables. Esta flexibilidad lo distingue del modo PWM
de los Timers de 8 bits, donde el periodo es siempre constante. También es posible
modificar el valor del registro OCR1A, lo cual nos permitirá controlar el duty cycle de la
onda PWM.
En la siguiente imagen se indican las fórmulas para calcular el periodo y el duty cycle
de una onda PWM de Fase Correcta no-invertida. Como siempre, F_CPU es la
frecuencia del procesador y N es el factor del prescaler. Recuerda que el Timer1
comparte el mismo prescaler con el Timer0, aunque ello no significa que tengan que
trabajar con el mismo factor de prescaler. Para más detalles puedes revisar la
sección relojes del Timer0 y del Timer2.

Periodo y Duty cycle de una onda PWM de Fase Correcta no-invertida.
La frecuencia de la onda PWM de Fase Correcta se obtiene invirtiendo la fórmula del
periodo. Recordemos que TOPE puede valer 0x00FF, 0x01FF, 0x03FF o el valor del
registro ICR1. En el caso de que escojamos la última opción podemos cargar en el
registro ICR1 cualesquiera valores siempre que sean mayores que 2. El máximo valor
es desde luego 0xFFFF = 65535.
Y la fórmula que nos da el duty cycle expresado en porcentaje la obtenemos dividiendo
las fórmulas del duty cycle y del periodo:

A diferencia del modo Fast PWM en esta fórmula podemos observar que el duty cycle
puede abarcar todo su rango desde 0% (con OCR1A = 0) hasta el 100%
(con OCR1A =TOPE). El 0% significa que la salida será un constante 0 lógico y un
100%, que la salida será un estado alto constante. Ésa es la razón por la que se
denomina de fase correcta. No pierdas de vista que que este duty cycle corresponde a
una onda PWM no invertida. Si la onda es invertida, el duty cycle será el complemento
a 100, por ejemplo, un duty cycle de 30% de un PWM invertido equivale a un duty
cycle de 70% de un PWM no invertido.

PWM de Fase y Frecuencia Correctas
Este modo es igual al modo PWM de Fase Correcta en cuanto al mecanismo de
generación de la onda PWM y al cálculo de su frecuencia y duty cycle. Todas las
fórmulas presentadas en esa sección són también válidas en este modo, así que se
recomienda revisarla para no seguir redundando. En su lugar vamos a aprovechar este
apartado para conocer algunos puntos que no fueron tratados antes por requerir
precisamente de una visión más general de todos los modos PWM.
El modo PWM de Fase y Frecuencia Correctas es idéntico al modo PWM de fase
Correctaen todos los aspectos, excepto por el momento en que se actualiza el
registro OCR1A. Si no le diste importancia a los recuadros amarillos que aparecen
encima de cada una de las gráficas de pendiente mostradas anteriormente, éste es el
momento de echar la mirada atrás porque las vamos a comparar con la gráfica de
doble pendiente correspondiente a la onda PWM de Fase y Frecuencia Correctas que se
muestra abajo.
Para comprender el concepto de actualización del registro OCR1A primero debemos
saber que en cualquiera de los modos PWM este registro trabaja con doble buffer, es
decir, cuando escribimos un dato en OCR1A en realidad estamos enviando el dato a su
buffer. Luego el dato será transferido al verdadero registro OCR1A, justo cuando el
registroTCNT1 llegue al valor 0x0000 o el valor de TOPE, según el modo PWM. A dicha
transferencia es que nos referimos cuando hablamos de actualización del
registro OCR1A. Recuerda que este registro lo usamos para controlar el duty cycle de
la onda PWM.
En el modo PWM de Fase y Frecuencia Correctas el registro OCR1A se actualiza cuando
el registro TCNT1 llega a 0x0000. Si observas bien la gráfica mostrada arriba, verás
que este hecho permite que los pulsos de la onda PWM sean simétricos en todos sus
periodos, sin importar el momento en que cambia su duty cycle. Por esa razón se
llama de Frecuencia Correcta, o sea, si todo anda bien en el periodo, también anda
bien en la frecuencia.
En todos los otros modos PWM el registro OCR1A se actualiza cuando el
registroTCNT1 alcanza el valor de TOPE (0x00FF, 0x01FF, 0x03FF o el valor del
registroICR1). Si observas las gráficas de pendiente de los modos Fast PWM y PWM de
Fase Correcta, notarás que esto producirá una onda cuyo duty cycle cambia
brúscamente al modificar el registro OCR1A. Piensa en esto: Si deseamos modificar el
duty cycle de nuestro PWM, lo ideal sería que ese cambio tenga efecto a partir del
siguiente periodo de la onda como en el anterior caso, ¿verdad? Pues ésa es la única y
acaso irrelevante característica que diferencia al modo PWM de Fase y Frecuencia
Correctas del resto de modos PWM.
Registros del Timer1
Registro TCCR1A

TCCR1A

COM1A1

COM1A0

COM1B1

COM1B0

---

---

WGM11

WGM10

Registro TCCR1B

TCCR1B

ICNC1

ICES1

FOC1A

FOC1B

---

WGM13

WGM12

CS12

CS11

CS10

---

---

---

Registro TCCR1C

TCCR1C
Registro TCNT1H

TCNT1H
Registro TCNT1L

TCNT1L
Registro OCR1AH

OCR1AH
Registro OCR1AL

OCR1AL
Registro OCR1BH

OCR1BH
Registro OCR1BL

OCR1BL
Registro ICR1H

ICR1H
Registro ICR1L

ICR1L
Registro TIMSK1

---

---

---
TIMSK1

---

---

ICIE1

---

---

OCIE1B

OCIE1A

TOIE1

---

---

ICF1

---

---

OCF1B

OCF1A

TOV1

TSM

---

---

---

---

---

Registro TIFR1

TIFR1
Registro GTCCR

GTCCR

PSRASY

PSRSYNC

WGM11

WGM10

TCCR1A – Timer/Counter Control Register 1 A
Registro TCCR1A

TCCR1A

COM1A1 COM1A0

COM1B1

COM1B0

---

---

Registro de Microcontrolador

COM1A1:
0

Compare Output Mode for Channel A

COM1B1:
0

Compare Output Mode for Channel B
Los bits COM1A1:0 y COM1A1:0 controlan el comportamiento de los pines
Output Compare (OC1A y OC1B, respectivamente). Si uno o los dos bits
COM1A1:0 están seteados, la salida OC0A tiene prioridad sobre la
funcionalidad normal del pin al que está conectado. Si uno o los dos bits
COM1B1:0 están seteados, la salida OC0B tiene prioridad sobre la
funcionalidad normal del pin al que está conectado. Sin embargo, note que el
bit del registro DDR correspondiente al pin OC0A o OC0B debe estar
configurado como salida para habilitar el driver de salida.
Cuando OC0A o OC0B está conectado al pin la función de los bits COM1x1:0
depende de la configuración de los bits WGM13:0. La siguiente tabla muestra
la funcionalidad de los bits COM1x1:0 cuando los bits WGM13:0 están
configurados en modo Normal oCTC (no PWM).
Tabla COM1A1/COM1B1

COM1A1/ COM1A0/
Descripción
COM1B1 COM1B0
0

0

OC0A/OC0B desconectado. Operación normal de
pin

0

1

OC0A/OC0B conmuta en Coincidencia
1

0

OC0A/OC0B se limpia en Coincidencia

1

1

OC0A/OC0B se setea en Coincidencia

La siguiente tabla muestra la funcionalidad de los bits COM1A1:0 cuando los
bits WGM13:0 están configurados en modo Fast PWM.
Nota: ocurre un caso especial cuando OCR1A/OCR1B es el tope del conteo y
el bit COM1A1/COM1B1 vale uno. En este caso se ignora la Coincidencia, pero
la puesta a cero o a uno de OC0A/OC0B se produce al llegar a 0x0000.
Tabla COM1A1/ COM1B1

COM1A1/
COM1B1

COM1A0/
COM1B0

0

0

OC1A/OC1B desconectado. Operación
normal del pin

1

WGM13:0 = 14 o 15: OC1A conmuta en
Coincidencia, OC1B desconectado
(operación normal de pin).
Para las demás configuraciones de WGM1,
OC1A/OC1B desconectado (operación
normal de pin).

1

0

OC1A/OC1B se limpia en la Coincidencia, y
se setea al llegar a 0x0000 (modo noinvertido)

1

1

OC1A/OC1B se setea en la Coincidencia, y se
limpia al llegar a 0x00 (modo invertido).

0

Descripción

La siguiente tabla muestra la funcionalidad de los bits COM1A1:0 cuando los
bits WGM13:0 están configurados en modo PWM de Fase Correcta o PWM de
Fase y Frecuencia Correctas.
Nota: ocurre un caso especial cuando OCR1A es el tope del conteo y el bit
COM1A1/COM1B1 vale uno...
Tabla COM1A1/ COM1B1
COM1A1/
COM1B1

COM1A0/
COM1B0

0

0

OC1A/OC1B desconectado. Operación normal
del pin

1

WGM13:0 = 9 u 11: OC1A conmuta en
Coincidencia, OC1B desconectado (operación
normal de pin).
Para las demás configuraciones de WGM1,
OC1A/OC1B desconectado (operación normal
de pin).

0

OC1A/OC1B se limpia en la Coincidencia
cuando se cuenta hacia arriba, y se setea en
la Coincidencia cuando se cuenta hacia abajo.

1

OC1A/OC1B se setea en la Coincidencia
cuando se cuenta hacia arriba, y se limpia en
la Coincidencia cuando se cuenta hacia abajo.

0

1

1

Bits 3:2

Descripción

Reservados
Estos bits están reservados en los ATmega164A/PA, ATmega324A/PA,
ATmega644A/PA y ATmega1284/P, y siempre se leerán como cero.

WGM11:0

Waveform Generation Mode
Estos bits se combinan con los bits WFGM13:2 del registro TCCR1B. Juntos
controlan la secuencia de conteo del contador, el valor máximo del conteo (el
tope) y el tipo de la forma de onda que se generará. Los modos operación
que soporta la unidad del Timer/Counter son: modo Normal (contador),
modo Clear Timer on Compare Match (CTC), y tres tipos de Modulación de
Ancho de Pulso (PWM).
Tabla WGM13

WGM13 WGM12 WGM11 WGM10
0

0

0

0

Modo de Operación del
Timer1
Normal

Tope del
Conteo
0xFFFF
0

0

0

1

PWM de Fase Correcta,
8 bits

0x00FF

0

0

1

0

PWM de Fase Correcta,
9 bits

0x01FF

0

0

1

1

PWM de Fase Correcta,
10 bits

0x03FF

0

1

0

0

CTC

OCR1A

0

1

0

1

Fast PWM, 8 bits

0x00FF

0

1

1

0

Fast PWM, 9 bits

0x01FF

0

1

1

1

Fast PWM, 10 bits

0x03FF

1

0

0

0

PWM de Fase y
Frecuencia Correctas

ICR1

1

0

0

1

PWM de Fase y
Frecuencia Correctas

OCR1A

1

0

1

0

PWM de Fase Correcta

ICR1

1

0

1

1

PWM de Fase Correcta

OCR1A

1

1

0

0

CTC

ICR1

1

1

0

1

Reservado

–

1

1

1

0

Fast PWM

ICR1

1

1

1

1

Fast PWM

OCR1A

TCCR1B – Timer/Counter Control Register 1 B
Registro TCCR1B

TCCR1B

ICNC1 ICES1

Registro de Microcontrolador

---

WGM13

WGM12

CS12

CS11

CS10
ICNC1

Input Capture Noise Canceler
La escritura de uno en este bit activa el circuito eliminador de ruido Input
Capture Noise Canceler del Timer1. Cuando el eliminador de ruido está activado,
la entrada del pin Input Capture (ICP1) será filtrada. La función del filtro requiere
de la igualdad de cuatro muestreos del pin ICP1 para cambiar su estado. Debido
a esto, cuando el eliminador de ruido está habilitado, la unidad de Input Capture
se retrasa por cuatro ciclos del oscilador.

ICES1

Input Capture Edge Select
Este bit selecciona el flanco en el pin Input Capture (ICP1) que se usará para
disparar el evento de captura. Cuando el bit ICES1 vale uno, el disparo se dará
en el flanco de bajada, y si ICES1 vale cero, el disparo se dará en el flanco de
subida.
Cuando se dispara una captura de acuerdo con la configuración del bit ICES1, el
valor del contador se copiará al registro Input Capture Register (ICR1). El evento
también seteará el flag Input Capture (ICF1), y se puede usar para generar una
Interrupción de Input Capture, si esta interrupción está habilitada.
Cuando se usa como el tope de conteo el registro ICR1 (ver la descripción de los
bits WGM13:0 ubicados en los registros TCCR1A y TCCR1B), ICP1 queda
desconectado y por tanto se deshabilita la función de Input Capture.

Bits 5

Reservado
Este bit está reservado para usos futuros. Al escribir en TCCR1B, este bit se debe
mantener en cero para asegurar la compatibilidad con futuros dispositivos.

WGM13:
2

Waveform Generation Mode

CS12:0

Clock Select

Ver la descripción de los bits WGM11:0 del registro TCCR1A.

Los tres bits de Clock Select seleccionan la fuente de reloj que usará el
Timer/Counter.
Si se usan los modos de pin externo para el Timer/Counter1, las transiciones en
el pin T1 harán el contador incluso si el pin está configurado como salida. Esta
característica permite el control software del contador.
Tabla CS12

CS12 CS11 CS10 Fuente de reloj del Timer1
0

0

0

Sin fuente de reloj (el Timer1 está detenido)

0

0

1

F_CPU (Sin prescaler)

0

1

0

F_CPU/8 (con prescaler)

0

1

1

F_CPU/64 (con prescaler)

1

0

0

F_CPU/256 (con prescaler)

1

0

1

F_CPU/1024 (con prescaler)

1

1

0

Reloj externo en pin T1.
El Timer1 avanza en el flanco de bajada.

1

1

1

Reloj externo en pin T1.
El Timer1 avanza en el flanco de subida.

TCCR1C – Timer/Counter Control Register 1 C
Registro TCCR1C

TCCR1C FOC1A FOC1B

---

---

---

---

---

---

Registro de Microcontrolador

FOC1A

Force Output Compare A

FOC1B

Force Output Compare B
Los bits FOC1A/FOC1B solo están activos cuando los bits WGM13:0 establecen un
modo no PWM.
Sin embargo, para asegurar la compatibilidad con futuros dispositivos, estos bits
se deben mantener en cero al escribir en TCCR1C cuando el Timer está operando
en uno de los modos PWM. Si se escribe un uno en el bit FOC1A/FOC1B, se fuerza
una Coincidencia inmediata en la Unidad Generadora de Forma de Onda. La
salida de OC1A/OC1B cambia de acuerdo con la configuración de los bits
COM1A1:0/COM1B1:0. Note que los bits FOC1A/FOC1B se implementan como
strobes. Así que es el valor presente en los bits COM1A1:0/COM1B1:0 los que
determinan el efecto de la Coincidencia forzada.
Un strobe de FOC1A/FOC1B no generará ninguna interrupción ni tampoco
reseteará el Timer en modo CTC si se usa OCR1A como tope del conteo.
Los bits FOC1A/FOC1B siempre se leen como cero.
Bits 5:0

Reservados
Estos bits están reservados y siempre se leerán como cero.

TCNT1H y TCNT1L – Timer/Counter 1 Register
Registro TCNT1H

TCNT1H
Registro TCNT1L

TCNT1L
Registro de Microcontrolador

Bits
15:0

TCNT1H[7:0] y TCNT1L[7:0]
Los dos registros TCNT1H y TCNT1L combinados, dan acceso directo al contador de 16
bits del Timer/Counter para las operaciones de lectura y escritura. Para asegurar que
los dos registros se lean y escriban al mismo tiempo, el acceso se realiza utilizando un
registro temporal de 8 bits para almacenar el byte alto (TCNT1H). Este registro
temporal se comparte con todos los otros registros de 16 bits.
La modificación de TCNT1 cuando el contador está corriendo conlleva un riesgo de
perder una Coincidencia entre los registros TCNT1 y OCR1A/ OCR1B.
La escritura en el registro TCNT0 bloquea (quita) la Coincidencia en el siguiente ciclo
de reloj del Timer para todas las unidades de comparación.

OCR1AH y OCR1AL – Output Compare Register 1 A
Registro OCR1AH

OCR1AH
Registro OCR1AL

OCR1AL
Registro de Microcontrolador

Bits
15:0

OCR1AH[7:0] y OCR1AL[7:0]
Los dos registros Output Compare A contienen un valor de 16 bits que es
continuamente comparado con el valor del contador (TCNT1). Se puede usar una
Coincidencia para disparar una Interrupción en Coincidencia, o para generar una
onda por el pin OC1A.
Para asegurar que los dos registros Output Compare A se escriban simultáneamente
cuando el CPU escribe en estos registros, el acceso se realiza utilizando un registro
temporal de 8 bits para almacenar el byte alto (OCR1AH). Este registro temporal es
compartido por todos los demás registros de 16 bits.
OCR1BH y OCR1BL – Output Compare Register 1 B
Registro OCR1BH

OCR1BH
Registro OCR1BL

OCR1BL
Registro de Microcontrolador

Bits
15:0

OCR1BH[7:0] y OCR1BL[7:0]
Los dos registros Output Compare B contienen un valor de 16 bits que es
continuamente comparado con el valor del contador (TCNT1). Se puede usar una
Coincidencia para disparar una Interrupción en Coincidencia, o para generar una
onda por el pin OC1B.
Para asegurar que los dos registros Output Compare B se escriban simultáneamente
cuando el CPU escribe en estos registros, el acceso se realiza utilizando un registro
temporal de 8 bits para almacenar el byte alto (OCR1BH). Este registro temporal es
compartido por todos los demás registros de 16 bits.

ICR1H y ICR1L – Input Capture Register 1
Registro ICR1H

ICR1H
Registro ICR1L

ICR1L
Registro de Microcontrolador

Bits
15:0

ICR1H [7:0] y ICR1L [7:0]
El registro Input Capture se actualiza con el valor del contador (TCNT1) cada vez que
ocurre un evento en el pin ICP1 (u opcionalmente en la salida del Comparador
Analógico que va al Timer/Counter1). Se puede usar el registro Input Capture para
definir el valor tope del conteo.
El registro Input Capture es de 16 bits. Para asegurar que sus dos bytes se escriban
simultáneamente cuando el CPU escribe en estos registros, el acceso se realiza
utilizando un registro temporal de 8 bits para almacenar el byte alto (ICR1H). Este
registro temporal es compartido por todos los demás registros de 16 bits.
TIMSK1 – Timer/Counter Interrupt Mask 1 Register
Registro TIMSK1

TIMSK1 --- ---

ICIE1

---

---

OCIE1B

OCIE1A

TOIE1

Registro de Microcontrolador

Bits 7:6

Reserved

Bits 4:3

Reserved
Estos bits están reservados y siempre se leerán como cero.

ICIE1

Timer/Counter1, Input Capture Interrupt Enable
Al escribir uno en este bit, y el bit enable general I del registro SREG vale uno, se
habilita la Interrupción Input Capture del Timer/Counter1. Al activarse el flag ICF1,
del registro TIFR1, se ejecutará su respectiva función de interrupción ISR.

OCIE1B

Timer/Counter1, Output Compare B Match Interrupt Enable
Al escribir uno en este bit, y el bit enable general I del registro SREG vale uno, se
habilita la Interrupción en Coincidencia B del Timer/Counter1. Al activarse el flag
OCF1B, del registro TIFR1, se ejecutará su respectiva función de interrupción ISR.

OCIE1A

Timer/Counter1, Output Compare A Match Interrupt Enable
Al escribir uno en este bit, y el bit enable general I del registro SREG vale uno, se
habilita la Interrupción en Coincidencia A del Timer/Counter1. Al activarse el flag
OCF1A, del registro TIFR1, se ejecutará su respectiva función de interrupción ISR.

TOIE1

Timer/Counter1 Overflow Interrupt Enable
Cuando se escribe uno en el bit TOIE1, y el bit I del registro SREG vale uno, se habilita
la Interrupción por Desbordamiento del Timer/Counter1. Si ocurre un
Desbordamiento en el registro TCNT1, se ejecutará la función de Interrupción
correspondiente, esto es, cuando se active al flag TOV1 del registro TIFR1.

TIFR1 – Timer/Counter Interrupt Flag 1 Register
Registro TIFR1

TIFR1

---

---

ICF1

---

---

OCF1B

OCF1A

TOV1

Registro de Microcontrolador

Bits 7:6

Reserved

Bits 4:3

Reserved
Estos bits están reservados y siempre se leerán como cero.

ICF1

Timer/Counter1, Input Capture Flag
Este flag se activa cuando ocurre un evento en el pin ICP1. Si la configuración de los
bits WGM13:0 establecen el registro Input Capture como tope del conteo, el flag
ICF1 se activará cuando el contador alcance el valor tope.
El bit ICF1 se limpia automáticamente al ejecutarse la función de Interrupción
respectiva. Alternativamente, ICF1 se puede limpiar por software escribiendo sobre
él un uno lógico.

OCF1B

Timer/Counter1, Output Compare B Match Flag
Este flag se activa en el ciclo de reloj del Timer después de que el valor del contador
(TCNT1) coincida con el registro Output Compare B (OCR1B).
Note que una Coincidencia forzada con el bit FOC1B no activará el flag OCF1B.
El flag OCF1B se limpia por hardware al ejecutarse su correspondiente función de
interrupción. Alternativamente se puede limpiar escribiendo en él un uno lógico.

OCF1A

Timer/Counter1, Output Compare A Match Flag
Este flag se activa en el ciclo de reloj del Timer después de que el valor del contador
(TCNT1) coincida con el registro Output Compare A (OCR1A).
Note que una Coincidencia forzada con el bit FOC1A no activará el flag OCF1A.
El flag OCF1A se limpia por hardware al ejecutarse su correspondiente función de
interrupción. Alternativamente se puede limpiar escribiendo en él un uno lógico.

TOV1

Timer/Counter1, Overflow Flag
La activación de este flag depende de la configuración de los bits WGM13:0. En los
modos Normal y CTC el flag TOV1 se activa cuando se desborda el Timer1.
El flag TOV1 se limpia por hardware al ejecutarse su correspondiente función de
interrupción. Alternativamente se puede limpiar escribiendo en él un uno lógico.
GTCCR – General Timer/Counter Control Register
Registro GTCCR

GTCCR

TSM ---

---

---

---

---

PSRASY

PSRSYNC

Registro de Microcontrolador

TSM

Timer/Counter Synchronization Mode
Al escribir uno en el bit TSM se activa el modo de Sincronización del
Timer/Counter. En este modo, se mantendrán los valores que se escriban en los
bits PSRASY y PSRSYNC, para mantener activadas las señales de reset del prescaler
correspondiente. Esto asegura que los correspondientes Timers estén detenidos y
se puedan configurar al mismo valor sin correr el riesgo de que uno de ellos
avance durante la configuración.
Si se escribe un cero en el bit TSM, los bits PSRASY y PSRSYNC se limpian por
hardware y los Timers/Counters empiezan a contar simultáneamente.

PSRASY

Prescaler Reset Timer/Counter2
Cuando este bit vale uno, el prescaler del Timer/Counter2 se reseteará.
Normalmente este bit se limpia de inmediato por hardware. Si se escribe en este
bit cuando el Timer/Counter2 está trabajando en modo asíncrono, el bit
permanecerá en uno hasta que se resetee el prescaler. El bit no se limpiará por
hardware si el bit TSM vale uno.

PSRSYNC

Prescaler Reset
Si este bit vale uno, el prescaler del Timer/Counter0 y el Timer/Counter1 se
reseteará. Normalmente este bit se limpia de inmediato por hardware, excepto
cuando el bit TSM valga uno. Note que el Timer/Counter0 y el Timer/Counter1
comparten el mismo prescaler y el reset de este prescaler afecta a ambos Timers.

Registros del Timer3
Registro TCCR3A

TCCR3A

COM3A1

COM3A0

COM3B1

COM3B0

---

---

WGM31

WGM30

Registro TCCR3B

TCCR3B

ICNC3

ICES3

---

WGM33

WGM32

CS32

CS31

CS30
Registro TCCR3C

TCCR3C

FOC3A

FOC3B

---

---

---

---

---

---

Registro TCNT3H

TCNT3H
Registro TCNT3L

TCNT3L
Registro OCR3H

OCR3H
Registro OCR3AL

OCR3AL
Registro OCR3BH

OCR3BH
Registro OCR3BL

OCR3BL
Registro ICR3H

ICR3H
Registro ICR3L

ICR3L
Registro TIMSK3

TIMSK3

---

---

ICIE3

---

---

OCIE3B

OCIE3A

TOIE3

---

---

ICF3

---

---

OCF3B

OCF3A

TOV3

TSM

---

---

---

---

---

Registro TIFR3

TIFR3
Registro GTCCR

GTCCR

PSRASY

PSRSYNC
Práctica: El Timer1 en Modo PWM
Se generan ondas PWM de Fase y Frecuencia correctas por los dos canales, A y B. Se
pueden configurar la Frecuencia y el Duty cycle de la dos señales desde la consola
serial.

Circuito para probar el Timer1 del microcontrolador AVR.

El código fuente
/******************************************************************************
* FileName: main.c
* Purpose: Timer1 - Operación en modo PWM
* Processor: ATmega164P
* Compiler: AVR IAR C y AVR GCC (WinAVR)
* Author:

Shawn Johnson. http://www.cursomicros.com.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License: Se permiten el uso y la redistribución de este código con
*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y la nota de autor de arriba.

*****************************************************************************/

#include "avr_compiler.h"
#include "usart.h"

charGetNumStr(char*buffer,unsignedcharlen);

intmain(void)
{
unsignedintreg;
chark,buffer[10];

usart_init();
puts("rn Timer1 en Modo PWM r");

/* Configuración del Timer1
* - Bits WGM: PWM de Fase y Frecuencia Correctas con tope de conteo = ICR1
* - Bits COM: PWM no-invertida en los dos canales A y B
* - Bits CS: Fuente de reloj = F_CPU/8
*/
TCCR1A=(1<<COM1A1)|(1<<COM1B1);
TCCR1B=(1<<WGM13)|(1<<CS11);
DDRD=(1<<4)|(1<<5);// Pines OC1A y OC1B salidas PWM
/* Poner algunos valores iniciales */
ICR1=200;// frecuencia PWM = 1/(2*200 ) = 2.5 kHz
OCR1A=100;// Duty cycle canal A = 50%
OCR1B=50;// Duty cycle canal B = 25%

puts("r Escoja el registro a modificar ");
puts("r [1] ICR1 -> Frecuencia");
puts("r [2] OCR1A -> Duty cycle A");
puts("r [3] OCR1B -> Duty cycle Br");
while(1)
{
do{
k=getchar();
}while(!((1<='k')&&(k<='3')));

switch(k)
{
case'1':puts("r ICR1 = ");break;
case'2':puts("r OCR1A = ");break;
case'3':puts("r OCR1B = ");break;
}
while(GetNumStr(buffer,9)==0);
reg=atoi(buffer);
switch(k)
{
case'1':ICR1=reg;break;
case'2':OCR1A=reg;break;
case'3':OCR1B=reg;break;
}
}
}

//****************************************************************************
// Lee una cadena de texto de 'len' números
// El tamaño de 'buffer' debe ser mayor que 'len'.
//****************************************************************************
charGetNumStr(char*buffer,unsignedcharlen)
{
charc;staticunsignedchari=0;
if(kbhit()){
c=getchar();
if((c<='9'&&c>='0')&&(i<len)){// Si c está entre 0 y 9
buffer[i++]=c;// Guardar en buffer
putchar(c);// Eco
}
elseif((c=='b')&&(i)){// Si c es RETROCESO y si i>0
i--;//
putchar(c);// Eco
}
elseif((c=='r')&&(i)){// Si c es ENTER y si i>0
buffer[i]='0';// Poner un 0x00 (fin de cadena)
putchar(c);// Eco
i=0;// Resetear contador
return1;// Retornar con 1
}
}
return0;// Retornar con 0
}

Reproductor de Ringtones en formato RTTTL
De todos los proyectos con microcontroladores creo que los relacionados con las luces
y el sonido son las más atractivas. El procesamiento del sonido dependerá de la calidad
de como lo queramos oír. Y si deseamos música, un simple microcontrolador de 8 bits
nos puede resultar corto. Un proyecto más a nuestro alcance sería un reproductor de
ringtones, por tratarse de la música que viene en los formatos más sencillos.
El conocido formato RTTTL (RingTone Text Transfer Language) fue desarrollado por
Nokia hace muchísimos años para sus productos celulares y por la popularidad
alcanzada se ha convertido en el más usado para los juguetes, tarjetas navideñas,
cajitas de música, etc.
Como su nombre indica, el formato RTTTL se basa en un archivo de texto y su
estructura luce más o menos así:
estructura de un ringtone en formato RTTTL

Newyear:d=4,o=5,b=125:a4,d.,8d,d,f#,e.,8d,e,8f#,8e,d.,8d,f#,a,2b.,b,a.,8f#,f#,d,e.,8d,e,8f#,8e,d.,
8b4,b4,a4,2d,16p
Se trata de una simple línea de texto que se divide en tres secciones. Cada sección se
separa de la otra por el carácter dos puntos (:). Eso de los colores lo puse yo para su
mejor identificación.
El título del ringtone. Es un texto cualquiera que no debería superar los 10 caracteres.
Los parámetros por defecto. Establecen los valores para d, o, b, s y l que serán
tomados en cuenta en la sección de notas. Al mismo tiempo si aquí no se
especificand (duration), o (octava) y b (beats por minuto), se deben tomar los valores
por defecto d=4, o=6 y b=63. Describimos estos parámetros más abajo. s y l son
raramente usados (de hecho, yo nunca los vi).
s. Indica el estilo en que se toca el ringtone.
l. Indica las veces que se reproducirá el ringtone actual. Puede valer entre 0 y 15. El
15 significa que el ringtone se reproduce indefinidamente.
Los comandos de las notas. Los comandos deben estar separados por comas. Cada
comando representa un sonido diferente y debe presentar el siguiente esquema:
<duración><nota><escala><duración-especial>
donde solo nota es necesaria. Los otros parámetros son opcionales.

La nota y su escala 'o'
Es el único campo necesario. Las 12 notas se identifican
por C, C#, D, D#, E, F, F#, G, G#,A y A#. Además está la pausa identificada por P. En
los ringtones se suelen escribir en minúsculas.
El formato RTTTL solo soporta 4 escalas, que corresponden a las octavas 4, 5, 6 y 7.
Es fácil notar en la siguiente tabla que la frecuencia de cada nota en una escala es el
doble de su valor en la escala previa. Esta tabla se obtiene teniendo como base A4 =
440 Hz, es decir, cuando la nota A en la octava 4 vale 440 Hz
(fuente: www.music.sc.edu). Sobra decir que la frecuencia de la pausa es 0.
Tabla Nota

Nota Frecuencia en Octava 4 Frecuencia en Octava 5 Frecuencia en Octava 6 Frecuencia en Octava 7
C

261.63

523.25

1046.50

2093.00

C#

277.18

554.37

1108.73

2217.46

D

293.66

587.33

1174.66

2349.32

D# 311.13

622.25

1244.51

2489.02

E

329.63

659.26

1318.51

2637.02

F

349.23

698.46

1396.91

2793.83

F#

369.99

739.99

1479.98

2959.96

G

392.00

783.99

1567.98

3135.96

G# 415.30

830.61

1661.22

3322.44

A

880.00

1760.00

3520.00

440.00
Tabla Nota

Nota Frecuencia en Octava 4 Frecuencia en Octava 5 Frecuencia en Octava 6 Frecuencia en Octava 7
A# 466.16

932.33

1864.66

3729.31

B

987.77

1975.53

3951.07

493.88

La duración 'd' y la duración-especial '.'
Representa la fracción de tiempo que debe sonar una nota. Su valor por defecto es 4,
es decir, si no se indica otro valor, la nota debería durar la cuarta parte de una nota
entera. Los otros valores para la duración son 1, 2, 4, 8, 16 y 32.
La duración-especial se representa por el carácter punto (.) y significa que el tiempo
establecido por la duración d se debe prolongar en un 50%. El estándar RTTTL también
contempla los caracteres ; y & como duraciones especiales pero no se suelen usar en
la práctica.

Los beats por minuto 'b'
Habíamos dicho previamente que la duración d divide el tiempo que suena una nota
entera. Pues bien, una nota entera debe durar 4 beats. El valor de cada beat se calcula
por medio del parámetro b, que indica los beats por minuto a que se toca el ringtone.
Por ejemplo, si b=125, cada beat dura 60/125 = 0.48 segundos.
Los valores posibles de b son 25, 28, 31, 35, 40, 45, 50, 56, 63, 70, 80, 90, 100, 112,
125, 140, 160, 180, 200, 225, 250, 285, 320, 355, 400, 450, 500, 565, 635, 715, 800
y 900. Si no se especifica se debe tomar su valor por defecto b=63.

La práctica
Por más que el formato RTTTL sea muy sencillo, al principio cuesta un poco asimilarlo.
Por eso en esta práctica no solo reproduciremos ringtones con nuestro AVR, sino que
podremos visualizar las notas que se están tocando en tiempo real. El programa nos
permitirá inclusive ver los parámetros decodificados de cada nota.
Desde el teclado se puede avanzar o retroceder por los 63 ringtones disponibles,
mediante las teclas (+) y (-). Si presionamos la tecla (*) también podremos ver las
notas que están sonando. Y si presionamos la tecla (.) descubriremos la diferencia que
hay entre un ringtone cuyas notas suenan todo su periodo y cuando suena solo las 7/8
partes de su periodo. Esto último se describe mejor al final.
Circuito para el reproductor de Ringtones en formato RTTTL
Este programa lo compilé para un ATmega324P para albergar todos los ringtones que
quize probar. Si tienes un megaAVR con memoria FLASH inferior a 16K puedes puedes
quitar algunos ringtones y recompilar el programa.

/******************************************************************************
* FileName: main.c
* Purpose: Reproductor de Ringtones en formato RTTTL de Nokia.
* Processor: ATmel AVR
* Compiler: AVR IAR C y AVR GCC (WinAVR)
* Author:

Shawn Johnson. http://www.cursomicros.com.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License: Se permiten el uso y la redistribución de este código con
*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y las notas de autor y copyright de arriba.

*****************************************************************************/

#include "avr_compiler.h"
#include "usart.h"
#include <ctype.h>

voidSetupTimers(void);
unsignedintGetDefault(PGM_Pr,charpar);
unsignedintGetFreq(char*n,unsignedcharoctave);
unsignedintGetLeng(char*n,unsignedintduration,unsignedintbpm);
voidPlayNote(unsignedintf_pwm,unsignedintt);

/* Fuentes de ringtones
* http://ez4mobile.com/nokiatone/rtttf.htm
* http://www.free-nokia-ring-tones-4u.com/
* http://www.free-ringtones.eu.com/
*/
/* Films */
PROGMEMconstcharrt01[]="Stars
Wars:d=4,o=5,b=180:8f,8f,8f,2a#.,2f.6,8d#6,8d6,8c6,2a#.6,f.6,8d#6,8d6,8c6,2a#.6,f.6,8d#6,8d6,8d
#6,2c6,p,8f,8f,8f,2a#.,2f.6,8d#6,8d6,8c6,2a#.6,f.6,8d#6,8d6,8c6,2a#.6,f.6,8d#6,8d6,8d#6,2c6,p";
PROGMEMconstcharrt02[]="Star Wars
End:d=4,o=5,b=225:2c,1f,2g.,8g#,8a#,1g#,2c.,c,2f.,g,g#,c,8g#.,8c.,8c6,1a#.,2c,2f.,g,g#.,8f,c.6,8g#,1f
6,2f,8g#.,8g.,8f,2c6,8c.6,8g#.,8f,2c,8c.,8c.,8c,2f,8f.,8f.,8f,2f";
PROGMEMconstcharrt03[]="Star Wars
Imp:d=4,o=5,b=112:8d.,16p,8d.,16p,8d.,16p,8a#4,16p,16f,8d.,16p,8a#4,16p,16f,d.,8p,8a.,16p,8a.,
16p,8a.,16p,8a#,16p,16f,8c#.,16p,8a#4,16p,16f,d.,8p,8d.6,16p,8d,16p,16d,8d6,8p,8c#6,16p,16c6,
16b,16a#,8b,8p,16d#,16p,8g#,8p,8g,16p,16f#,16f,16e,8f,8p,16a#4,16p,2c#";// Dude
PROGMEMconstcharrt04[]="Exorcist:
d=4,o=5,b=130:8e6,8a6,8e6,8b6,8e6,8g6,8a6,8e6,8c7,8e6,8d7,8e6,8b6,8c7,8e6,8a6,8e6,8b6,8e6,
8g6,8a6,8e6,8c7,8e6,8d7,8e6,8b6,8c7,8e6,8b6,8e6,8a6,8e6,8b6,8e6,8g6,8a6,8e6,8c7,8e6,8d7,8e6
,8b6,8c7";
PROGMEMconstcharrt05[]="The
Godfather:d=8,o=5,b=56:g,c6,d#6,d6,c6,d#6,c6,d6,c6,g#,a#,2g,g,c6,d#6,d6,c6,d#6,c6,d6,c6,g,f#,2f,
f,g#,b,2d6,f,g#,b,2c6,c,d#,a#,g#,g,a#,g#,g#,g,g,b,2c6";
PROGMEMconstcharrt06[]="Indiana
Jones:d=4,o=5,b=250:e,8p,8f,8g,8p,2c.6,8p.,d,8p,8e,1f,p.,g,8p,8a,8b,8p,2f.6,p,a,8p,8b,2c6,2d6,2e6
,e,8p,8f,8g,8p,1c6,p,d6,8p,8e6,1f6,g,8p,8g,e.6,8p,d6,8p,8g,e.6,8p,d6,8p,8g,f.6,8p,e6,8p,8d6,1c6";
PROGMEMconstcharrt07[]="James Bond:
d=4,o=5,b=140:8e,16f#,16f#,8f#,f#,8e,8e,8e,8e,16g,16g,8g,g,8f#,8f#,8f#,8e,16f#,16f#,8f#,f#,8e,8e,
8e,8e,16g,16g,8g,g,8f#,8f,8e,8d#6,2d.6,8b,8a,1b";
PROGMEMconstcharrt08[]="Mission
Impossible:d=4,o=6,b=100:32d,32d#,32d,32d#,32d,32d#,32d,32d#,32d,32d,32d#,32e,32f,32f#,32g
,16g,8p,16g,8p,16a#,16p,16c,16p,16g,8p,16g,8p,16f,16p,16f#,16p,16g,8p,16g,8p,16a#,16p,16c,16
p,16g,8p,16g,8p,16f,16p,16f#,16p,16a#,16g,2d,32p,16a#,16g,2c#,32p,16a#,16g,2c,16p,16a#5,16c"
;
PROGMEMconstcharrt09[]="20th Century
Fox:d=16,o=5,b=140:b,8p,b,b,2b,p,c6,32p,b,32p,c6,32p,b,32p,c6,32p,b,8p,b,b,b,32p,b,32p,b,32p,b
,32p,b,32p,b,32p,b,32p,g#,32p,a,32p,b,8p,b,b,2b,4p,8e,8g#,8b,1c#6,8f#,8a,8c#6,1e6,8a,8c#6,8e6,
1e6,8b,8g#,8a,2b";
PROGMEMconstcharrt10[]="Jurasic
Park:d=4,o=6,b=180:a5,2g5,d5,2a5,8a5,8b5,c.,8c,b5,g5,2a.5,2p,c,b5,g5,g_.5,8e5,8a5,8b5,c,2c.5,d5
,2e5,d.5,p,c5,2g5,d5,2a5,8a5,8b5,c.,8c,b5,g5,2a.5,2p,c,b5,g5,g_.5,8e5,8a5,8b5,c,2c.5,d5,2e5,d.5";
PROGMEMconstcharrt11[]="Lord of the
rings:d=4,o=6,b=140:c5,d5,2e5,2g5,2e.5,d5,1c.5,e5,g5,2a5,2c,2b.5,g5,2e.5,8f5,8e5,2d5,c5,d5,2e5,
2g5,2e.5,d5,1c.5,e5,g5,1a5,a5,g5,e5,1d.5,2p,1c5";
PROGMEMconstcharrt12[]="Terminator:d=4,o=6,b=40:32d#6,16f6,4f#6,16f.6,16c#6,4f#5,32d#6,1
6f6,4f#6,16f.6,16c#6,4a#6,4g#6,16d#6,16f6,4f#6,16f.6,16c#6,4g#5,4f#5,16d#5,4f#5,4f5,32d#6,16f
6,4,f#6,2f#.5";
PROGMEMconstcharrt13[]="Robocop:d=8,o=5,b=140:f,g#,f,4g,4a#.,32p,f,g#,f,2c#,p,f,g#,f,4g,4a#,2
d#.,4p,f,g#,f,4g,4a#.,32p,a#,c#6,a#,2f6,p,f6,g#6,f6,4c7,4g#6,2a#6.,p,16a#6,16a#6,2c7.";
PROGMEMconstcharrt14[]="Rocky:d=4,o=6,b=125:16e5,8g5,16p,2a5,8p,16a5,8b5,16p,2e5,16p,32
p,16e5,8g5,16p,2a5,16p,32p,16a5,8b5,16p,1e5,8p,16p,16d,16c,8d,16p,16c,16d,e,p,16c,16c,8b5,1
6b5,8a5,16a5,g5,8f,1e";
PROGMEMconstcharrt15[]="The Good The Bad And The
Ugly:d=8,o=5,b=125:16a,16d6,16a,16d6,2a,4f,4g,2d,4p,16a,16d6,16a,16d6,2a,4f,4g,2c6,4p,16a,16
d6,16a,2d6,4f6,e6,d6,2c6,4p,16a,16d6,16a,16d6,4a.,4g,d,2d";
PROGMEMconstcharrt16[]="Top
Gun:d=4,o=5,b=100:32p,8g#,8g#,8g#,8g#,g#,8d#,g,8g,8g,8g,g,8d#,8f.,16d#,c,p,8g#,8g#,8g#,8g#,g#,
8d#,g,8g,8g,8g,g,8g#,8f.,16d#,c,p,8a#,8a#,8a#,8a#,a#,8f,g#,8g#,8g#,8g#,g#,8a#,8g.,16f,d#,p,8g#,8g
#,8g#,8g#,g#,8d#,g,8g,8g,8g,g,8g#,8f,16d#,c#.,p,8f,2d#,8d#,8f,8g#,8a#,2g#";
/* Tv */
PROGMEMconstcharrt17[]="Knight
Rider:d=4,o=5,b=63:16e,32f,32e,8b,16e6,32f6,32e6,8b,16e,32f,32e,16b,16e6,d6,8p,p,16e,32f,32e,
8b,16e6,32f6,32e6,8b,16e,32f,32e,16b,16e6,f6,p";
PROGMEMconstcharrt18[]="Knight Rider
2:d=4,o=5,b=125:16e,16p,16f,16e,16e,16p,16e,16e,16f,16e,16e,16e,16d#,16e,16e,16e,16e,16p,16
f,16e,16e,16p,16f,16e,16f,16e,16e,16e,16d#,16e,16e,16e,16d,16p,16e,16d,16d,16p,16e,16d,16e,1
6d,16d,16d,16c,16d,16d,16d,16d,16p,16e,16d,16d,16p,16e,16d,16e,16d,16d,16d,16c,16d,16d,16d
";
PROGMEMconstcharrt19[]="Hawaii
50:d=4,o=6,b=80:16a5,16a5,16c,16e.,8d.,a5,16a5,16a5,16g5,16c.,a.5,16a5,16a5,16c,16e.,8d.,a,16
g,16g,16e,16c.,2a,16c7,16a#,16a,16g,16f,16e,16d,16e,16c,d,8d,16a#,16g#,16g,16e,16d,16c,16d,16
e.,16d,16c.,8d,a,16g,16g,16e,16c.,2d";
PROGMEMconstcharrt20[]="Charlie's
Angels:d=4,o=5,b=125:32p,c#,8d,2c#,b4,e,d,c#,8d,2c#,8d,8b4,8c#,8d,8e,f#,8g,2f#,e,a,g,2f#,16a4,1
6b4,16c,16d,16e,16f#,16g,16a,b,8c6,2b,8a,8f#,g,8a,b,8c6,2b,a,d6,c6,2b";
PROGMEMconstcharrt21[]="The Adams
Family:d=4,o=5,b=160:8c,f,8a,f,8c,b4,2g,8f,e,8g,e,8e4,a4,2f,8c,f,8a,f,8c,b4,2g,8f,e,8c,d,8e,1f,8c,8d,
8e,8f,1p,8d,8e,8f#,8g,1p,8d,8e,8f#,8g,p,8d,8e,8f#,8g,p,8c,8d,8e,8f";
PROGMEMconstcharrt22[]="The Xfiles:d=4,o=5,b=125:e,b,a,b,d6,2b.,1p,e,b,a,b,e6,2b.,1p,g6,f#6,e6,d6,e6,2b.,1p,g6,f#6,e6,d6,f#6,2b.
,1p,e,b,a,b,d6,2b.,1p,e,b,a,b,e6,2b.,1p,e6,2b.";
PROGMEMconstcharrt23[]="I Dream Of
Jeanie:d=8,o=6,b=250:4g.5,p,d,4p,d,p,c,p,e,d,p,c,p,4f.5,p,d,4p,d,p,c,p,e,d,p,c,p,4g.5,p,d,4p,d,p,c,p,
e,d,p,c,p,2f.5,f,f,f,1p,4f.,p,f,4p,f,p,f,p,f,d#,p,c#,p,4d#.,p,c,4p,d#,p,d#,p,d#,c#,p,c,p,4c#.,p,a#5,4p,c#,
p,c#,p,c#,c,p,a#5,p,1c,p,d,p,c,a#5,p,a5";
/* Cartoons */
PROGMEMconstcharrt24[]="Dragon
ball:d=4,o=4,b=56:16f5,16f5,16d#5,32c5,4f5,32f5,32f5,16f5,16d#5,32c5,4f5,32a#4,32a#4,32a#4,3
2a#4,16a#4,16g#4,32a#4,32a#4,32a#4,32a#4,16a#4,16g#4,2c5,16c5,16d#5,8f5,8f5,8f5,16d#5,16c
5,16d#5,16c5,16d#5,32d#5,8f.5,16c5,16d#5,4f5,16g#5,16g5,16f5,8d#.5,16g#5,16g5,8f5,8d#5,2f5";
PROGMEMconstcharrt25[]="Transformers:d=32,o=6,b=45:p,f5,16a#5,c,4c#,16a#5,c,16c#,f#5,4f#5,
a#5,c,16c#,16c#,16d#,16f,f#,16d#,c#,16d#,f,16c#,c,16c#,d#,16c.,a#5,a5,c,a#5,8a#5";
PROGMEMconstcharrt26[]="Looney
Toons:d=4,o=5,b=140:32p,c6,8f6,8e6,8d6,8c6,a.,8c6,8f6,8e6,8d6,8d#6,e.6,8e6,8e6,8c6,8d6,8c6,8e
6,8c6,8d6,8a,8c6,8g,8a#,8a,8f";
PROGMEMconstcharrt27[]="De
Smurfe:d=4,o=5,b=112:g,8c.6,16g,8a,8f,d,8g.,16c,8d,8e,2d,g,8c.6,16g,8a,8f,d,8g.,16e,8f,8d,c,p,g,8c
.6,16g,8a,8f,d,8g.,16c,8d,8e,2d,g,8c.6,16g,8a,8f,d,8g.,16e,8f,8d,c,p,g,8c.6,16g,8a,8f,d,8g.,16c,8d,8
e,2d,g,8c.6,16g,8a,8f,d,8g.,16e,8f,8d,c";
PROGMEMconstcharrt28[]="Animaniacs:d=4,o=5,b=160:d#6,d6,d#6,f.6,8d#6,d.6,8d#6,c6,2p,8d6,8
d#6,f.6,8d#6,d.6,8d#6,a#,2p,8c6,8a#,8g#,8g#,8c6,8d#6,g#6,8p,8g#6,8a#.6,16g#6,8g6,8g#6,f6,8p,8f
6,d#6,c7,a#.6,8g#6,g#6";
PROGMEMconstcharrt29[]="Inspector
Gadget:d=4,o=5,b=320:c#,8d#,e,8f#,g#,8p,8e,p,g,8p,8d#,p,f#.,8e,p,c#,8d#,e,8f#,g#,8p,8c#6,p,2c.6,
p,2p,c#,8d#,8e,8p,8f#,g#,8p,8e,p,g,8p,8d#,p,f#.,e.,c#,1p,2p,8p,c.,c#,2p,c#,8d#,e,8f#,g#,8p,8e,p,g,8
p,8d#,p,f#.,8e,p,c#,8d#,e,8f#,g#,8p,8c#6,p,2c.6,p,2p,c#,8d#,8e,8p,8f#,g#,8p,8e,p,g,8p,8d#,p,f#.,e.,
c#,1p,2p,8p,c.,c#";
PROGMEMconstcharrt30[]="The
Simpsons:d=4,o=5,b=160:c.6,e6,f#6,8a6,g.6,e6,c6,8a,8f#,8f#,8f#,2g,8p,8p,8f#,8f#,8f#,8g,a#.,8c6,8c
6,8c6,c6";// Check
PROGMEMconstcharrt31[]="Barbie
girl:d=4,o=5,b=125:8g#,8e,8g#,8c#6,a,p,8f#,8d#,8f#,8b,g#,8f#,8e,p,8e,8c#,f#,c#,p,8f#,8e,g#,f#,8g#,
8e,8g#,8c#6,a,p,8f#,8d#,8f#,8b,g#,8f#,8e,p,8e,8c#,f#,c#,p,8f#,8e,g#,f#";
PROGMEMconstcharrt32[]="Superman:d=4,o=6,b=200:8d5,8d5,8d5,8g.5,16p,8g5,2d,8p,8d,8e,8d,
8c,1d,8p,8d5,8d5,8d5,8g.5,16p,8g5,2d,8d,8d,8e,8c,8g5,8e,2d.,p,8g5,8g5,8g5,2f#.,d.,8g5,8g5,8g5,2
f#.,d.,8g5,8g5,8g5,8f#,8e,8f#,2g.,8g5,8g5,8g5,2g.5";
PROGMEMconstcharrt33[]="Popeye:d=4,o=5,b=225:8a#,8a#,8a#,8a#,8g#,8p,8g,2a#,8p,8a#,8c6,8g
#,8c6,8d#6,8p,8c6,2a#,8p,8a#,8c6,8g#,8c6,8d#6,8d6,8c6,8a#,8c6,8a#,8g,8d#,8a#,8a#,8a#,8a#,8c6,
8p,8d6,2d#6";
PROGMEMconstcharrt34[]="The Pink
Panther:d=4,o=6,b=70:32f#.5,g5,32a.5,a#5,32f#.5,16g.5,32a.5,16a#.5,32d#.,16d.,32g.5,16a#.5,32d
.,2c#,32c.,32a#.5,32g.5,32f.5,g.5,32p,16g.,32f.,16d.,32c.,16a#.5,32g.5,32c#,8c,32c#,8c,32c#,8c,32c
#,8c,32a#.5,32g.5,32f.5,16g.5,g.5";
PROGMEMconstcharrt35[]="Cantina:d=4,o=5,b=250:8a,8p,8d6,8p,8a,8p,8d6,8p,8a,8d6,8p,8a,8p,8
g#,a,8a,8g#,8a,g,8f#,8g,8f#,f.,8d.,16p,p.,8a,8p,8d6,8p,8a,8p,8d6,8p,8a,8d6,8p,8a,8p,8g#,8a,8p,8g,
8p,g.,8f#,8g,8p,8c6,a#,a,g";
PROGMEMconstcharrt36[]="Flinstones:d=4,o=5,b=40:32p,16f6,16a#,16a#6,32g6,16f6,16a#.,16f6,
32d#6,32d6,32d6,32d#6,32f6,16a#,16c6,d6,16f6,16a#.,16a#6,32g6,16f6,16a#.,32f6,32f6,32d#6,32
d6,32d6,32d#6,32f6,16a#,16c6,a#,16a6,16d.6,16a#6,32a6,32a6,32g6,32f#6,32a6,8g6,16g6,16c.6,3
2a6,32a6,32g6,32g6,32f6,32e6,32g6,8f6,16f6,16a#.,16a#6,32g6,16f6,16a#.,16f6,32d#6,32d6,32d6
,32d#6,32f6,16a#,16c.6,32d6,32d#6,32f6,16a#,16c.6,32d6,32d#6,32f6,16a#6,16c7,8a#.6";
PROGMEMconstcharrt37[]="Woody
Woodpecker:d=16,o=6,b=125:c,f,a,8c7,8a,4p,p,c,f,a,8c7,8a,4p,c7,p,a,8a#,c7,8a#.,8a.,8g,4f,4p,8p,c,
f,a,8c7,8a,4p,p,c,f,a,8c7,8a,4p,c7,p,a,8a#,c7,8a#.,8a.,8g,4c7";
PROGMEMconstcharrt38[]="Batman:d=4,o=5,b=200:8f,8f,8e,8e,8d#,8d#,8e,8e,8f,8f,8e,8e,8d#,8d
#,8e,8e,8f,8f,8e,8e,8d#,8d#,8e,8e,g#,2g#,p,8f,8f,8e,8e,8d#,8d#,8e,8e,8f,8f,8e,8e,8d#,8d#,8e,8e,8f,
8f,8e,8e,8d#,8d#,8e,8e,g#,2g#,p,8f,8f,8e,8e,8d#,8d#,8e,8e,8f,8f,8e,8e,8d#,8d#,8e,8e,8f,8f,8e,8e,8
d#,8d#,8e,8e,g#,2g#,p,f";
/* Classics */
PROGMEMconstcharrt39[]="Beethoven - Fur Elise:
d=4,o=5,b=140:8e6,8d#6,8e6,8d#6,8e6,8b,8d6,8c6,a,8p,8c,8e,8a,b,8p,8e,8g#,8b,c6,p,8e,8e6,8d#6
,8e6,8d#6,8e6,8b,8d6,8c6,a,8p,8c,8e,8a,b,8p,8e,8c6,8b,2a";
PROGMEMconstcharrt40[]="Beethoven - 5th
Symphony:d=4,o=5,b=180:8f,8f,8f,1c#,8p,8d#,8d#,8d#,1c,8p,8f,8f,8f,8c#,8f#,8f#,8f#,8f,8c#6,8c#6,
8c#6,2a#,8p,8f,8f,8f,8c,8f#,8f#,8f#,8f,8d#6,8d#6,8d#6,1c6,8f6,8f6,8d#6,8c#6,8c#,8c#,8d#,8f,8f6,8f
6,8d#6,8c#6,8c#,8c#,8d#,8f,8f6,8f6,8d#6,c#6,p,a#,p,2f6";
PROGMEMconstcharrt41[]="Vivaldi - 4 Seasons Summer:d=16,o=6,b=120:8e,8g#,8g#,8g#,f#,e,4b.,b,a,8g#,8g#,8g#,f#,e,4b.,b,a,8g#,a,b,8a,8g#,8f#,8
d#,4b.,8e,8g#,8g#,8g#,f#,e,4b.,b,a,8g#,8g#,8g#,f#,e,4b.,b,a,8g#,a,b,8a,8g#,8f#,8d#,4b.";
PROGMEMconstcharrt42[]="Mozart:d=4,o=6,b=120:16f#5,16e5,16d#5,16e5,g5,16a5,16g5,16f#5,1
6g5,b5,16c,16b5,16a#5,16b5,16f#,16e,16d#,16e,16f#,16e,16d#,16e,g,8e,8g,32d,32e,16f#,8e,8d,8e
,32d,32e,16f#,8e,8d,8e,32d,32e,16f#,8e,8d,8c#,b5";
PROGMEMconstcharrt43[]="Mozart - The Nutcracker
Suite:d=16,o=5,b=225:8d6,4p,d6,p,d6,p,d6,p,8e6,4p,8e6,4p,8f#6,4p,8d6,4p,2e.6,8p,8d6,4p,d6,p,d
6,p,d6,p,8e6,4p,8e6,4p,8f#6,4p,8d6,4p,2e.6,8e,c6,8p,8d6,c6,8p,8b,a,8p,8g,f#,8p,8a,d6,8p,8e6,d6,
8p,8c6,b,8p,8a,g,8p,8b,4e6,8d6,4c6,8e6,4f#6,8e6,4d6,8f#6,4g6,8f#6,4e6,8f#6,8g6,4p,4g.6";
PROGMEMconstcharrt44[]="Beethoven - Ode To
Joy:d=4,o=6,b=100:a5,a5,a#5,c,c,a#5,a5,g5,f5,f5,g5,a5,a.5,8g5,2g5,a5,a5,a#5,c,c,a#5,a5,g5,f5,f5,g5
,a5,g.5,8f5,2f5,g5,g5,a5,f5,g5,8a5,8a#5,a5,f5,g5,8a5,8a#5,a5,g5,f5,g5,c5,2a5,a5,a#5,c,c,a#5,a5,g5,f
5,f5,g5,a5,g.5,8f5,2f5";
PROGMEMconstcharrt45[]="Bach - Fugue In D
Minor:d=32,o=6,b=45:d#,c#,d#,b5,d#,a#5,d#,g#5,d#,g5,d#,g#5,d#,a#5,d#,b5,d#,d#5,d#,f5,d#,g5,d#
,g#5,d#,g5,d#,g#5,d#,a#5,d#,8b.5,16c,8c#.,16d,8d#.,16d#,8f.,16f,16f#,16a#5,16b5,16d#,8g#.5,8b5,
16a#5,16g#5,16c#,8f#5";
PROGMEMconstcharrt46[]="Bach Badinerie:d=16,o=6,b=125:8b,d7,b,8f#,b,f#,8d,f#,d,4b5,f#5,b5,d,b5,c#,b5,c#,b5,a#5,c#,e,c#,8d,8b5
,8b,d7,b,8f#,b,f#,8d,f#,d,4b5,8d,8d,8d,8d,8b,8d,32d,32c#,32d,32c#,8c#,8f#,8f#,8f#,8f#,8d7,8f#,32f
#,32f,32f#,32f,8f,c#,f#,a,f#,g#,f#,g#,f#,f,g#,b,g#,a,g#,a,g#,f#,a,f#,f,f#,b,f#,f,f#,c#7,f#,f,f#,d7,f#,f,f#,d
7,c#7,b,c#7,a,g#,f#,8a,8g#,4f#";// Dude
/* Christmas */
PROGMEMconstcharrt47[]="Silent
Night:d=4,o=5,b=112:g.,8a,g,2e.,g.,8a,g,2e.,2d6,d6,2b.,2c6,c6,2g.,2a,a,c6.,8b,a,g.,8a,g,2e.,2a,a,c6.,
8b,a,g.,8a,g,2e.,2d6,d6,f6.,8d6,b,2c6.,2e6.,c6,g,e,g.,8f,d,2c.";
PROGMEMconstcharrt48[]="Jingle
Bells:d=8,o=6,b=125:g5,e,d,c,2g5,g5,e,d,c,2a5,a5,f,e,d,b5,g5,b5,d,g.,16g,f,d,2e,g5,e,d,c,2g5,16f#5,
g5,e,d,c,2a5,a5,f,e,d,g,16g,16f#,16g,16f#,16g,16g#,a.,16g,e,d,4c,4g,e,e,e.,16d#,e,e,e.,16d#,e,g,c.,1
6d,2e,f,f,f.,16f,f,e,e,16e,16e,e,d,d,e,2d";
PROGMEMconstcharrt49[]="We Wish
You:d=4,o=5,b=170:d,g,8g,8a,8g,8f#,e,e,e,a,8a,8b,8a,8g,f#,d,d,b,8b,8c6,8b,8a,g,e,d,e,a,f#,2g,d,g,8
g,8a,8g,8f#,e,e,e,a,8a,8b,8a,8g,f#,d,d,b,8b,8c6,8b,8a,g,e,d,e,a,f#,1g,d,g,g,g,2f#,f#,g,f#,e,2d,a,b,8a,8
a,8g,8g,d6,d,d,e,a,f#,2g";
PROGMEMconstcharrt50[]="Santa
Clauss:d=4,o=5,b=160:16c,8e.,16f,g,2g,16g,8a.,16b,c6,2c6,8e.,16f,g,g,g,8a.,16g,f,2f,e,g,c,e,d,2f,b4,
1c.,16c,8e.,16f,g,2g,16g,8a.,16b,c6,2c6,8e.,16f,g,g,g,8a.,16g,f,2f,e,g,c,e,d,2f,b4,1c.6";
PROGMEMconstcharrt51[]="Rudolph:d=16,o=6,b=100:32p,g#5,8a#5,g#5,8f5,8c#,8a#5,4g#.5,g#5,a
#5,g#5,a#5,8g#5,8c#,2c,f#5,8g#5,f#5,8d#5,8c,8a#5,4g#.5,g#5,a#5,g#5,a#5,8g#5,8a#5,2f5,g#5,8a#5
,a#5,8f5,8c#,8a#5,4g#.5,g#5,a#5,g#5,a#5,8g#5,8c#,2c,f#5,8g#5,f#5,8d#5,8c,8a#5,4g#.5,g#5,a#5,g#
5,a#5,8g#5,8d#,2c#";
PROGMEMconstcharrt52[]="Deck The
Halls:d=8,o=6,b=140:4g.,f,4e,4d,4c,4d,4e,4c,d,e,f,d,4e.,d,4c,4b5,2c,4d.,e,4f,4d,4e.,f,4g,4d,e,f#,4g,a
,b,4c7,4b,4a,4g";
PROGMEMconstcharrt53[]="So this is
christmass:d=4,o=6,b=160:c5,f5,g5,a5,f5,1c5,p,2p,c5,f5,g5,a5,8g5,8f5,1d5,p,2p,d5,g5,a5,a#5,a5,2g
.5,1p,c5,a5,c,a5,8a5,8g5,1f5,p,2p,f5,d,d,d,c,1a#5,p,2p,f5,a#5,c,d,1c,1p,f5,c,d,d#,d,1c,p,2p,f5,d,f,d,
8a#5,8g5,2f.5";
/* Traditional */
PROGMEMconstcharrt54[]="Lord of the
rings:d=4,o=6,b=125:8c5,8p,d#,d#,d,d,p,8c5,8p,d#,d#,d,d,p,8c5,8p,d#,d#,d,2d#,2f,d#,2d.,8c5,8p,d
#,d#,d,d,p,8c5,8p,d#,d#,d,d,p,8c5,8p,d#,d#,d,2d#,2f,d#,2d.,c5";
PROGMEMconstcharrt55[]="Camptown
Races:d=4,o=5,b=180:g,g,e,g,a,g,2e,e,2d.,e,2d.,g,g,e,g,a,g,2e,2d,e,d,1c,c.,8c,e,g,1c6,a.,8a,c6,a,1g,g,
g,8e,8e,8g,8g,a,g,2e,d,8e,8f,e,8e,8d,2c,2p";
PROGMEMconstcharrt56[]="Circus:d=32,o=6,b=45:16c#,16c,b5,c,b5,a#5,16a5,16g#5,16g5,16g#5,1
6a#5,16a5,g#5,a5,g#5,g5,16f#5,16f5,16e5,16f5,16g#5,f#5,f#5,16d5,16d#5,16g#5,f#5,f#5,16d5,16d
#5,c,c#,d,d#,e,f,f#,g,g#,a,a#,b,16a#,16g#,16c#,16c,b5,c,b5,a#5,16a5,16g#5,16g5,16g#5,16a#5,16a
5,g#5,a5,g#5,g5,16f#5,16f5,16e5,16f5,16e5,e5,e5,8g5,g#5,a#5,g#5,g5,16f5,16g#5,16c,c,c,16c,16c,
c,c,c,c,c,c,c";
PROGMEMconstcharrt57[]="Pop Goes The
Weasel:d=4,o=6,b=200:8g5,c,8c,d,8d,8e,8g,8e,c,8g5,c,8c,d,8d,e.,c,8g5,c,8c,d,8d,8e,8g,8e,c.,a.,d,8f,
e.,c.,c,8c,a5,8c,8b5,8d,8b5,g.5,c,8c,a5,8c,b.5,g.5,f5,8e5,f5,8g5,a5,8b5,c,8d,g.,d,8f,e.,c.";
PROGMEMconstcharrt58[]="Rock A Bye Baby
(Lullaby):d=4,o=5,b=90:c,8d#,c6,2a#,g#,c,d#,g#,2g.,c#.,8d#,8c#.6,2c6,a#,a#,g#,f,2d#,8p,c.,8d#,c6,2
a#,g#,c,d#,g#,2g,f,d#,g#,c#6,c6,g#.,a#,f,g#,2g#";
PROGMEMconstcharrt59[]="Sailors
Hornpipe:d=8,o=5,b=200:f6,e6,4f6,f.,32p,4f,c6,a#,a,c6,f6.,32p,4f6,g6,f#6,4g6,g.,32p,4g,g6,f6,e6,d6
,c6.,32p,4c6,d6,e6,f6,e6,d6,c6,d6,c6,a#,a,a#,a,g,f,g,f,e,d,c,d,e,f,g,a#,a,g,4a,4f,4f.";
PROGMEMconstcharrt60[]="Yankee
Doodle:d=16,o=6,b=50:d.,g,g,a,b,g,b,a,d,g,g,a,b,8g,f#,d,g,g,a,b,c7,b,a,g,f#,d,e,f#,8g,8g,e.,32f#.,e,d,
e,f#,8g,d.,32e.,d,c,8b5,8d,e.,32f#.,e,d,e,f#,g,e,d,g,f#,a,8g,8g";
PROGMEMconstcharrt61[]="Halloween:d=4,o=5,b=180:32p,8d6,8g,8g,8d6,8g,8g,8d6,8g,8d#6,8g,8
d6,8g,8g,8d6,8g,8g,8d6,8g,8d#6,8g,8c#6,8f#,8f#,8c#6,8f#,8f#,8c#6,8f#,8d6,8f#,8c#6,8f#,8f#,8c#6,
8f#,8f#,8c#6,8f#,8d6,8f#";
PROGMEMconstcharrt62[]="Happy
Birthday:d=4,o=5,b=125:8g.,16g,a,g,c6,2b,8g.,16g,a,g,d6,2c6,8g.,16g,g6,e6,c6,b,a,8f6.,16f6,e6,c6,d
6,2c6,8g.,16g,a,g,c6,2b,8g.,16g,a,g,d6,2c6,8g.,16g,g6,e6,c6,b,a,8f6.,16f6,e6,c6,d6,2c6";
PROGMEMconstcharrt63[]="Newyear:d=4,o=5,b=125:a4,d.,8d,d,f#,e.,8d,e,8f#,8e,d.,8d,f#,a,2b.,b,a
.,8f#,f#,d,e.,8d,e,8f#,8e,d.,8b4,b4,a4,2d,16p";

PROGMEMPGM_Pconstringtones[]={
rt01,rt02,rt03,rt04,rt05,rt06,rt07,rt08,rt09,rt10,
rt11,rt12,rt13,rt14,rt15,rt16,rt17,rt18,rt19,rt20,
rt21,rt22,rt23,rt24,rt25,rt26,rt27,rt28,rt29,rt30,
rt31,rt32,rt33,rt34,rt35,rt36,rt37,rt38,rt39,rt40,
rt41,rt42,rt43,rt44,rt45,rt46,rt47,rt48,rt49,rt50,
rt51,rt52,rt53,rt54,rt55,rt56,rt57,rt58,rt59,rt60,
rt61,rt62,rt63
};

constunsignedintNUM_RT=sizeof(ringtones)/2;// Número de ringtones disponibles
volatileunsignedintticks_ms;// Contador de milisegundos
volatileunsignedintsilence_ms;// Número de milisegundos de los silencios
volatileunsignedcharsilence;// Flag para habilitar los silensios
volatileunsignedcharskip;// Flag para detener y saltear el ringtone
volatileunsignedcharrIndex;// Indice de ringtone
volatileunsignedchardisplay_notes;// Flag para mostrar notas

/******************************************************************************
* Gestor de Interrupción en Coincidencia del Timer0.
* Esta función se ejecuta cada 1 ms exactamente.
*****************************************************************************/
ISR(TIMER0_COMPA_vect)
{
if(ticks_ms)
{
ticks_ms--;
if((ticks_ms<silence_ms)&&(silence==1))
OCR1A=0;// Poner Duty cycle = 0 para apagar el buzzer
}
}

/*****************************************************************************
* Gestor de Interrupción de Recepción Completada.
* Esta función se dispara ante la presencia de algún dato en el USART.
* Nota:
* Para el ATmega324P (entre algunos otros) el nombre del Vector de esta
* Interrupción debería ser USART0_RX_vect (con guiones bajo simples) en lugar
* de USART0__RX_vect. Debe ser un error del archivo "iom324p.h" que
* probablemenete se corrija en el futuro. Entre tanto se debe tener cuidado
* porque AVR GCC no da error. Para IAR C se cambia por USART0_RX_vect.
*****************************************************************************/
#ifdef USART0__RX_vect
ISR(USART0__RX_vect)
#else
ISR(USART0_RX_vect)
#endif
{
chark=UDR0;// Leer dato llegado
if(k=='-')// Tocar el ringtone anterior
{
if(rIndex>0){
rIndex--;
ticks_ms=0;// Detener la nota actual
skip=1;// Detener y saltear el ringtone actual
}
}
elseif(k=='+')// Tocar el ringtone siguiente
{
if(rIndex<(NUM_RT-1)){
rIndex++;
ticks_ms=0;// Detener la nota actual
skip=1;// Detener y saltear el ringtone actual
}
}
elseif(k=='*')// Visualizar u ocultar las notas
{
if(display_notes==0)display_notes=1;
elsedisplay_notes=0;
}
elseif(k=='.')// Habilitar o deshabilitar silencios
{
if(silence==0)silence=1;
elsesilence=0;
}
}

/******************************************************************************
* Main function
*****************************************************************************/

intmain(void)
{
unsignedintFrecuencia,Longitud;
unsignedintb,d,o;
charc,cad[50];
unsignedcharcounter,i,index_bk;
PGM_PpRingtone;

usart_init();// 9600 - 8N1
/* Habilitar Interrupción de 'Recepción Completada' del USART */
UCSR0B|=(1<<RXCIE0);

printf("r Ringtones player ");
printf("r ================ r");
printf("r %d ringtones available",NUM_RT);
printf("r Press (+) for next ringtone");
printf("r Press (-) for previous ringtone");
printf("r Press (*) to show/hide playing notes");
printf("r Press (.) to enable/disable silence r");

SetupTimers();// Configurar los Timers 0 y 1

rIndex=0;// Iniciar con el primer ringtone
display_notes=0;// No visualizar las notas
silence=1;// No tocar silencios

while(1)
{
/* Apuntar al ringtone número rIndex */
index_bk=rIndex;
pRingtone=(PGM_P)pgm_read_word(&ringtones[index_bk]);

/* Extraer el título del ringtone.
* El bucle copia los primeros caracteres hasta llegar al primer ':'
*/
i=0;
while((cad[i++]=pgm_read_byte(pRingtone++))!=':');
cad[i-1]='0';// Poner final
/* Obtener parámetros de las notas por defecto */
b=GetDefault(pRingtone,'b');// Beats per minute
d=GetDefault(pRingtone,'d');// Duration
o=GetDefault(pRingtone,'o');// Octave

/* Mostrar el título y los parámetros de control obtenidos */
printf("r [%d] %s",rIndex+1,cad);
//printf("r b = %d d = %d o = %d ", b, d, o);

/* Avanzar el puntero hasta la primera nota (pasar el segundo ':') */
while(pgm_read_byte(pRingtone++)!=':');

counter=0;
if(index_bk==rIndex)
skip=0;// Activar flag para ejecutar ringtone

/* Bucle while para leer y ejecutar todas las notas del ringtone actual */
do{
/* Leer próxima nota del ringtone
* Los caracteres se copian en el array cad hasta encontrar ',' o hasta
* llegar al final de la ringtone.
*/
i=0;
do{
c=pgm_read_byte(pRingtone++);
cad[i++]=c;
}while((c!=',')&&(c!='0'));
cad[i-1]='0';// Poner final

/* Decodificar la nota. Esto es obtener su Frecuencia y su Longitud */
Frecuencia=GetFreq(cad,o);
Longitud=GetLeng(cad,d,b);

/* Tocar la nota con los parámetros de Frecuencia y Longitud enviados */
PlayNote(Frecuencia,Longitud);

/* Visualizar la nota a tocar si está activo el flag display_notes */
if(display_notes==1){
//printf("r N =%5s F =%5d D =%5d S =%3d", cad, Frecuencia, Longitud, silence_ms);
if(((counter++)%16)==0)printf("r

%s",cad);

elseprintf(" %s",cad);
}
}while((c!=0)&&(skip==0));
PlayNote(0,3000);// Esto dará una pausa de 3000 ms
PlayNote(0,0);// Esperar a que termine la pausa
}
}

/*****************************************************************************
* Devuelve el valor numérico que sigue al parámetro par.
* par es un carácter que puede valer:
* 'b': Beats Per Minute del ringtone.
* 'o': Octava del ringtone.
* 'd': Duración del ringtone.
* 'l': Repeticiones de la nota.
****************************************************************************/

unsignedintGetDefault(PGM_Pr,charpar)
{
charc,num[5];unsignedchari=0;

/* Avanzar el puntero hasta llegar al parámetro par
* Se asume que r ya está apuntando al primer carácter de la sección que
* contiene los parámetros a usar por defecto
*/
while(pgm_read_byte(r++)!=par);

/* Copiar número subsiguiente hasta encontrar ',' o el siguiente ':' */
do{
c=pgm_read_byte(r);
if(isdigit(c))// Filtar los números
num[i++]=c;
r++;
}while((c!=':')&&(c!=','));
num[i]='0';// Poner final
returnatoi(num);// Retornar cadena convertida en número
}
/*****************************************************************************
* Devuelve el valor de la frecuencia de la nota en Hz.
* La nota se pasa en n como una cadena terminada en nulo.
****************************************************************************/

unsignedintGetFreq(char*n,unsignedcharoctave)
{
// C C# D D# E F F# G G# A A# B Pausa
unsignedintNoteFreqs[]={262,277,294,311,330,349,370,392,415,440,466,494,000};
unsignedintFrecuencia;
unsignedchari=0;

/* Avanzar el puntero salteando los caracteres numéricos de la duración */
while(isdigit(*n))
n++;

/* Hallar la frecuencia base de la nota
* Para esto buscamos el índice del array NoteFreqs que la contiene
*/
switch(*n++)
{
case'c':i=0;break;
case'd':i=2;break;
case'e':i=4;break;
case'f':i=5;break;
case'g':i=7;break;
case'a':i=9;break;
case'b':i=11;break;
case'p':i=12;break;
}
// Ver si la nota es en realidad de la forma 'x#'
if(*n=='#'){
i++;
n++;
}

Frecuencia=NoteFreqs[i];// Obtener la frecuencia del array NoteFreqs

/* Avanzar el puntero hasta el final buscando un carácter numérico que debe
* ser la octava. Si se le encuentra se le convierte en número (asumiendo
* que vale entre 4 y 7) y se actualiza la variable octave.
*/
while(*n!='0'){
if(isdigit(*n))
octave=*n-'0';// Ascii a integer
n++;
}

Frecuencia=Frecuencia<<(octave-4);// Aplicar la octava
returnFrecuencia;
}
/*****************************************************************************
* Devuelve el valor de la Longitud de la nota en ms.
* La nota se pasa en n como una cadena terminada en nulo.
****************************************************************************/

unsignedintGetLeng(char*n,unsignedintduration,unsignedintbpm)
{
unsignedintLongitud;

/* Procesar la duración de la nota actual, si es que hay. De no haber, luego
* se utilizará la duración por defecto recibida como parámetro.
*/
if(isdigit(*n))
duration=*n++-'0';
if(isdigit(*n))
duration=duration*10+*n++-'0';

/* Calcular la longitud de la nota en milisegundos, en base a su
* duración y bpm.
*/
Longitud=60000/bpm;// Duración de un beat
Longitud*=4;// Duración de una nota entera
Longitud/=duration;// Duración de la nota actual

/* Avanzamos el puntero hasta el final buscando el caracter '.'
* Si se le encuentra, la longitud de la nota se debe extender en 50%.
*/
while(*n!='0'){
if(*n++=='.')
Longitud+=Longitud/2;
}
returnLongitud;
}

/*****************************************************************************
* Para tocar la nota en el formato RTTTL esta función solo necesita su
* frecuencia en Hz y el tiempo de su duración en ms.
* La señal es generada por el Timer1 trabajando en modo PWM. La frecuencia
* de la onda PWM está dada por la fórmula. F_PWM = F_CPU/(2*ICR1).
* El volumen de la nota no es considerado en el formato RTTTL de Nokia.
****************************************************************************/

voidPlayNote(unsignedintf_pwm,unsignedintt)
{
unsignedinttime;
do{
cli();

time=ticks_ms;
sei();
}while(time>0);// Esperar a que termine el tiempo de la nota previa
if(skip==0){
cli();
ticks_ms=t;// Poner el tiempo que se tocará la nueva nota
silence_ms=t/8;// Poner el tiempo que se tocará el silencio
sei();
}

if((f_pwm==0)||(skip==1))
OCR1A=0;// Poner Duty cycle = 0 para apagar el buzzer
else{
ICR1=(unsignedint)(F_CPU/(2*f_pwm));
OCR1A=ICR1/2;// Poner Duty cycle = 50%
}
}

/*****************************************************************************
* Configuración de los Timers 0 y 1.
* El Timer0 trabajará en modo CTC para generar interrupciones cada 1ms. Esto
* servirá para medir el tiempo que debe durar la ejecución de cada nota.
* El Timer1 funcionará en modo PWM para generar por el pin OC1A/PD5 del
* megaAVR la señal que se aplicará al buzzer.
****************************************************************************/

voidSetupTimers(void)
{
/* Configuración del Timer1
* - Bits WGM: PWM de Fase y Frecuencia Correctas con tope de conteo = ICR1
* - Bits COM: PWM no-invertida en el canal A
* - Bits CS: Fuente de reloj = F_CPU (sin prescaler)
*/
TCCR1A=(1<<COM1A1);
TCCR1B=(1<<WGM13)|(1<<CS10);
DDRD=(1<<PD5);// Pin OC1A/PD5 salida PWM

/* Configuración del Timer0
* - Bits WGM: Modo de operación = CTC
* - Bits CS: Fuente de reloj = F_CPU/64 (Factor de prescaler N = 64)
* - Periodo de auto-reset = 1 ms
*/
TCCR0A=(1<<WGM01);
TCCR0B=(1<<CS00)|(1<<CS01);
OCR0A=124;// Límite de TCNT0

/* Habilitar Interrupción en Coincidencia del Timer0 */
TIMSK0=(1<<OCIE0A);
sei();
}

Descripción del programa
Este programa lo creé hace varios años intrigado por los malos resultados que obtuvo
Craig Peacok en su reproductor de ringtones RTTTL. Es decir, cuando vi el proyecto de
Craig Peacok, que era uno de mis autores referentes en programación de PICs de ese
entonces, de inmediato quize escuchar cómo sonaban los famosos rongtones en un
microcontrolador. Grabé el PIC con el mismo archivo HEX de su web y, para mi gran
decepción, sonaba pésimo. Yo había escuchado mejores melodías reproducidas por
códigos muy sencillos como por ejemplo la función tune del PICAXE y no podía creer
que el programa aparentemente tan sofisticado de Craig funcionara tan mal.
Así que antes de averiguar qué andaba mal en el proyecto de Craig, decidí escribir un
reproductor de ringtones RTTTL desde cero para ver si el error era de los ringtones o
del programa de Craig. Al final descubriría que había errores de ambos lados.
En última instancia cada nota musical del ringtone no es más que un sonido de cierta
frecuencia y con una determinada duración. De esto se encargaría la función PlayNote,
que emplea el Timer1 para generar ondas PWM con duty cycle de 50% pero de
frecuencia variable. Esta frecuencia, como sabemos, viene dada por la fórmula F_PWM
= F_CPU / (2 * ICR1). El Timer1 era el único que podía generar fácilmente todas las
frecuencias deseadas. Los Timers 0 y 2 también pueden generar ondas PWM pero con
un rango de frecuencias muy limitado.
Si bien se podía controlar la duración de cada nota musical usando simples delays
(como en la siguiente práctica), decidí emplear el Timer0 para que dicha duración
fuera inmune a la ejecución del resto del código, especialmente a las rutinas de
depuración y logging como la función printf. La función del Timer0 es decrementar
mediante interrupciones el contador ticks_ms.

/*****************************************************************************
* Configuración de los Timers 0 y 1.
* El Timer0 trabajará en modo CTC para generar interrupciones cada 1ms. Esto
* servirá para medir el tiempo que debe durar la ejecución de cada nota.
* El Timer1 funcionará en modo PWM para generar por el pin OC1A/PD5 del
* megaAVR la señal que se aplicará al buzzer.
****************************************************************************/

voidSetupTimers(void)
{
/* Configuración del Timer1
* - Bits WGM: PWM de Fase y Frecuencia Correctas con tope de conteo = ICR1
* - Bits COM: PWM no-invertida en el canal A
* - Bits CS: Fuente de reloj = F_CPU (sin prescaler)
*/
TCCR1A=(1<<COM1A1);
TCCR1B=(1<<WGM13)|(1<<CS10);
DDRD=(1<<PD5);// Pin OC1A/PD5 salida PWM

/* Configuración del Timer0
* - Bits WGM: Modo de operación = CTC
* - Bits CS: Fuente de reloj = F_CPU/64 (Factor de prescaler N = 64)
* - Periodo de auto-reset = 1 ms
*/
TCCR0A=(1<<WGM01);
TCCR0B=(1<<CS00)|(1<<CS01);
OCR0A=124;// Límite de TCNT0

/* Habilitar Interrupción en Coincidencia del Timer0 */
TIMSK0=(1<<OCIE0A);
sei();
}
Aunque el estándar RTTTL de Nokia esablece que los parámetros de cada nota deben
seguir el orden estudiado antes, es decir, <duración><nota><escala><duraciónespecial>, el logging me hizo ver que había ringtones que no respetaban ese orden.
Algunas veces ponían la duración-especial al final y otras veces la ponían antes que
laescala. Por eso escribí mi función GetLeng para que buscara los parámetros de
duración sin tener en cuenta su ubicación.

/*****************************************************************************
* Devuelve el valor de la Longitud de la nota en ms.
* La nota se pasa en n como una cadena terminada en nulo.
****************************************************************************/
unsignedintGetLeng(char*n,unsignedintduration,unsignedintbpm)
{
unsignedintLongitud;

/* Procesar la duración de la nota actual, si es que hay. De no haber, luego
* se utilizará la duración por defecto recibida como parámetro.
*/
if(isdigit(*n))
duration=*n++-'0';
if(isdigit(*n))
duration=duration*10+*n++-'0';

/* Calcular la longitud de la nota en milisegundos, en base a su
* duración y bpm.
*/
Longitud=60000/bpm;// Duración de un beat
Longitud*=4;// Duración de una nota entera
Longitud/=duration;// Duración de la nota actual

/* Avanzamos el puntero hasta el final buscando el caracter '.'
* Si se le encuentra, la longitud de la nota se debe extender en 50%.
*/
while(*n!='0'){
if(*n++=='.')
Longitud+=Longitud/2;
}
returnLongitud;
}
Terminado mi programa vi que Craig se había equivocado al considerar que las
frecuencias de las notas empezaban por A y no por C, como se indica
enwww.music.sc.edu. Definitivamente mi reproductor de ringtones funcionaba mucho
mejor pero aún había algo extraño. Y es que si bien algunos ringtones como Star
wars sonaban muy bien, otros como Rudolph sonaban sin inflexiones.
De nuevo, no podía creer que el Rudolph que viene en los ejemplos de PICAXE sonara
mejor que mi Rudoph. Mi Silent night también se escuchaba pésimo. ¿Cómo era
posible que las simples tarjetitas navideñas con su controlador de juguete podían tocar
mejor música que un potente microcontrolador de 20 MIPS (millones de instrucciones
por segundo)? Yo estaba casi convencido de que el problema estaba otra vez en los
ringtones. Hallé varios ringtones de Rudolph en Internet pero ninguno sonaba parecido
al Rudoph de PICAXE. Así que decidí ver el manual de PICAXE y descubrí algo que no
se especifica en el estándar RTTTL o que por lo menos no es obvio para quienes no
conocemos esos detalles de la cultura músical. Descubrí que cada nota debe sonar en
realidad solo los 7/8 de su periodo y la fracción restante de 1/8 debe ser de un
silencio.
Dada la estructura de mi código, fue muy fácil modificarlo para que la nota deje de
sonar cuando falte la octava parte de su duración. Este tiempo está dado por la
variablesilence_ms. Y para ir un poco más allá decidí que el programa ofreciera la
opción de tocar los ringtones con los silencios descritos o sin ellos. Debemos presionar
la tecla punto (.) para conmutar entre ambos modos de reproducción.

/******************************************************************************
* Gestor de Interrupción en Coincidencia del Timer0.
* Esta función se ejecuta cada 1 ms exactamente.
*****************************************************************************/
ISR(TIMER0_COMPA_vect)
{
if(ticks_ms)
{
ticks_ms--;
if((ticks_ms<silence_ms)&&(silence==1))
OCR1A=0;// Poner Duty cycle = 0 para apagar el buzzer
}
}
Ahora sí, mi Rudolph me hace evocar mis mejores momentos de navidad y mi Silent
nightsuena celestialmente mejor. Dragon ball y Transformers suenan genial,
The Godfather yExorcist se oyen casi mejor que en el cine. Definitivamente, quedó
bien.

Simulación del reproductor de ringtones RTTTL
Éste debe uno de los proyectos más grandes en código pero que trabajan en el
hardware más sencillo, así que será muy fácil ponerlo en práctica. Pero si deseas
escucharlo previamente en una simulación también suena bien en Proteus.
Simulación del reproductor de ringtones en formato RTTTL

Reproductor de Ringtones en formato PICAXE
El compilador Basic de los microcontroladores PICAXE ofrece el comando Tune para
tocar ringtones en formato RTTTL. El formato de Tune tiene tres variantes, para ser
usados según las limitaciones del modelo de PICAXE. A continuación se muestra el más
básico.
TUNE pin, speed, (note, note, note...)
En pin se indica el pin por donde saldrá la onda cuadrada que exitará el piezo-buzzer,
enspeed se especifica el tempo, es decir, losbeats por minuto a que se reproduce el
ringtone, y por último, encerrados entre paréntesis, se encuentran todas las notas del
ringtone, expresadas todas en bytes hexadecimales. Un ejemplo se su uso sería más o
menos así.
TUNE 3, 4,
($69,$02,$41,$42,$EB,$2C,$6B,$04,$42,$6B,$E9,$2C,$69,$02,$41,$42,$2B,$67,
$2B,$2B,$69,$69,$6B,$69,$27,$67,$A6)
Vamos viendo que este formato no es precisamente el que se indica en el estándar
RTTTL de Nokia. Lo denominé formato RTTTL de PICAXE por ser una adaptación del
RTTTL original que la gente de PICAXE desarrolló para reducir en lo posible el
procesamiento y la memoria utilizada por los ringtones originales. Para decirlo de una
forma simple, las notas del ringtone ya están preprocesadas. De modo que decodificar
la frecuencia y la duración de cada una de ellas será mucho más fácil para el
programa. Parece una gran idea de los ingenieros de PICAXE y fue ésa la razón inicial
que me motivo a realizar este programa. Sin embargo, creo que la compresión se les
fue un poco de la mano, ya que el sonido del ringtone pierde bastante calidad. Eso lo
comprobaremos al final.
En primer lugar hablemos de los beats por minuto (b o bpm) a que suena el ringtone.
El valor de speed en el comando es en realidad un índice a una tabla que contiene los
verdaderos valores de bpm. La tabla de abajo nos indica que speed solo puede
conducir a 15 valores de bpm, con lo cual se dejan de lado la mitad de los posibles
valores que el estándar RTTTL original establece para este parámetro.
Tabla Nota

speed bpm (beats por minuto)
1

812

2

406

3

270

4

203

5

162

6

135

7

116
Tabla Nota

speed bpm (beats por minuto)
8

101

9

90

10

81

11

73

12

67

13

62

14

58

15

54

Ahora sigamos con los bytes hexadecimales que representan las notas. Como vemos
abajo, cada byte se divide entres campos. Hay dos bits que marcan la duración de la
nota actual. Aquí notamos que faltan las fracciones 1/16 y 1/32 que contempla el
estándar de Nokia. Eso es todo en cuanto a la duración, es decir, no se considera la
duración especial.
Las notas sí están completas, al menos en nombres (desde C hasta B). Lo que falta
ahora es la escala que corresponde a la octava 7. Los tonos agudos por tanto no se
dejarán escuchar en el ringtone. Recordemos que en cada escala la frecuencia de la
nota se duplica. Puedes revisar la tabla de frecuencias presentada en la práctica
anterior si es necesario.
Bueno, creo que eso es todo lo que necesitamos saber para ponernos a escribir el
programa.
El comando TUNE permite especificar un pin cualquiera del PICAXE al cual se conectará
el buzzer. Podemos hacer lo mismo, considerando que la onda cuadrada se puede
generar usando delays en vez de algún Timer. De hecho, es así como trabaja el
comando TUNE de PICAXE. Eso también le suma las dilataciones de hasta 8/9 (en vez
de los 7/8) de periodo que debería sonar cada nota, según advierte su manual.
Así que ante tantas limitaciones de este formato RTTTL de PICAXE no vamos a
molestarnos en emular la función TUNE «al pie de la letra». Tomaremos nuestro
programa anterior y le haremos las modificaciones necesarias. El circuito desde luego
será el mismo.
Circuito para el reproductor de Ringtones en formato RTTTL
/************************************************************************
******
* FileName:

main.c

* Purpose:

Reproductor de Ringtones en formato RTTTL de PICAXE

* Processor:

ATmel AVR

* Compiler:

AVR IAR C y AVR GCC (WinAVR)

* Author:

Shawn Johnson. http://www.cursomicros.com.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License:

Se permiten el uso y la redistribución de este código con

*

modificaciones o sin ellas, siempre que se mantengan esta
*

licencia y las notas de autor y copyright de arriba.

*************************************************************************
****/

#include "avr_compiler.h"
#include "usart.h"

void SetupTimers(void);
void Tune(PGM_P pRingtone);
void delay_ms(unsignedint t);

/* Fuente de ringtones
* http://www.picaxe.com/downloads/
*/
PROGMEMconstchar
rt01[]={6,0x27,0x69,0x27,0xE4,0x27,0x69,0x27,0xE4,0xC2,0x02,0xEB,0xC0,0x0
0,0xE7,0xE9,0x29,0x00,0x6B,0x29,0x27,0x69,0x27,0xE4,0xE9,0x29,0x00,0x6B,0
x29,0x27,0x69,0x27,0xE4,0xC2,0x02,0x05,0x42,0x2B,0xC0,0xC4,0x00,0x27,0x24
,0x27,0x65,0x22,0xE0,0x30};
PROGMEMconstchar
rt02[]={6,0x67,0x44,0x42,0x40,0xE7,0x67,0x44,0x42,0x40,0xE9,0x69,0x45,0x4
4,0x42,0x6B,0x67,0x6B,0x42,0x47,0x47,0x45,0x42,0xC4,0x67,0x44,0x42,0x40,0
xE7,0x66,0x67,0x44,0x42,0x40,0xE9,0x69,0x45,0x44,0x42,0x47,0x47,0x46,0x47
,0x46,0x47,0x48,0x49,0x47,0x44,0x42,0x00,0x07,0x44,0x44,0x44,0x43,0x44,0x
44,0x44,0x43,0x44,0x47,0x40,0x42,0xC4,0x45,0x45,0x45,0x45,0x45,0x44,0x44,
0x44,0x44,0x44,0x42,0x42,0x44,0xC2,0x30};
PROGMEMconstchar
rt03[]={5,0x22,0x27,0x67,0x69,0x67,0x66,0x24,0x20,0x24,0x29,0x69,0x6B,0x6
9,0x67,0x26,0x22,0x26,0x2B,0x6B,0x40,0x6B,0x69,0x27,0x24,0x62,0x62,0x24,0
x29,0x26,0xE7,0x30};// we wish you
PROGMEMconstchar
rt04[]={4,0x22,0x27,0x67,0x69,0x67,0x66,0x24,0x24,0x24,0x29,0x69,0x6B,0x6
9,0x67,0x26,0x22,0x22,0x2B,0x6B,0x40,0x6B,0x69,0x27,0x24,0x22,0x24,0x29,0
x26,0xE7,0x22,0x27,0x67,0x69,0x67,0x66,0x24,0x24,0x24,0x29,0x69,0x6B,0x69
,0x67,0x26,0x22,0x22,0x2B,0x6B,0x40,0x6B,0x69,0x27,0x24,0x22,0x24,0x29,0x
26,0xA7,0x22,0x27,0x27,0x27,0xE6,0x26,0x27,0x26,0x24,0xE2,0x29,0x2B,0x69,
0x69,0x67,0x67,0x02,0x22,0x22,0x24,0x29,0x26,0xE7,0x30};
PROGMEMconstchar
rt05[]={5,0x07,0x45,0x04,0x02,0x00,0x02,0x04,0x00,0x42,0x44,0x45,0x42,0x0
4,0x42,0x00,0x2B,0xC0,0x02,0x44,0x05,0x02,0x04,0x45,0x07,0x02,0x44,0x46,0
x07,0x49,0x4B,0x10,0x0B,0x09,0x07,0x30};
PROGMEMconstchar
rt06[]={6,0x60,0x60,0x40,0x40,0x2A,0x29,0x27,0x25,0xE0,0x60,0x60,0x27,0x6
5,0x27,0x65,0x24,0xE0,0x22,0x42,0x42,0x00,0x2A,0x29,0xE7,0x44,0x42,0x00,0
x40,0x6A,0x29,0x6A,0x69,0xE5,0x20,0x40,0x40,0x2A,0x29,0x27,0x25,0xE0,0x60
,0x60,0x27,0x65,0x27,0x65,0x24,0xE0,0x22,0x42,0x42,0x00,0x2A,0x29,0xE7,0x
44,0x42,0x00,0x40,0x6A,0x29,0x69,0x67,0xE5,0x30};// let it snow

/************************************************************************
******
* Main function

*************************************************************************
****/
int main(void)
{
SetupTimers();// Configurar Timers

1

usart_init();// Inicializar USART a 9600 - 8N1

printf("r Ringtones player ");
printf("r ================ r");

while(1)
{
printf("r Deck the halls");
Tune(rt05);
delay_ms(2000);

printf("r Jingle bells");
Tune(rt02);
delay_ms(2000);

printf("r We wish you a merry christmas");
Tune(rt04);
delay_ms(2000);

printf("r Silent night");
Tune(rt01);
delay_ms(2000);
}
}

/************************************************************************
*****
* Toca las notas del ringtone apuntado por pRingtone.
*************************************************************************
***/
void Tune(PGM_P pRingtone)
{// C

C#

D

D#

E

F

F#

G

G#

A

A#

B

staticPROGMEMconstunsignedint
NoteFreqs[]={262,277,294,311,330,349,370,392,415,440,466,494};
staticPROGMEMconstunsignedchar Octaves[]={6,7,5};
staticPROGMEMconstunsignedint
Bpms[]={0,812,406,270,203,162,135,116,101,90,81,73,67,62,58,54};
staticPROGMEMconstunsignedchar Durations[]={4,8,1,2};

unsignedint Frecuencia, Longitud, bpm;
unsignedchar speed, note, octave, duration;

/* Obtener speed */
speed = pgm_read_byte(pRingtone++);

/* Bucle para leer y tocar todas las notas del ringtone actual */
while((note = pgm_read_byte(pRingtone++))!=0x30)
{
/******************************************************
* Obtener la Frecuencia de la nota en Hz
*****************************************************/
octave = pgm_read_byte(&Octaves[(note&0x30)>>4]);
Frecuencia =0;// Frecuencia por defecto, pausa
if((note&0x0F)<12)
Frecuencia = pgm_read_word(&NoteFreqs[note&0x0F]);
Frecuencia <<=(octave-5);// Aplicar la octava

/******************************************************
* Obtener la Longitud de la nota en milisegundos
*****************************************************/
duration = pgm_read_byte(&Durations[(note&0xC0)>>6]);
bpm = pgm_read_word(&Bpms[speed]);
Longitud =60000/bpm;// Duración de un beat
Longitud *=4;// Duración de una nota entera
Longitud /= duration;// Duración de la nota actual

/*******************************************************
* La señal de la nota es generada por el Timer1
* trabajando en modo PWM. La frecuencia de la onda PWM
* está dada por la fórmula. F_PWM = F_CPU/(2*ICR1).
******************************************************/
if( Frecuencia ==0)
OCR1A =0;// Poner Duty cycle = 0 para apagar el buzzer
else{
ICR1 =(unsignedint)(F_CPU/(2*Frecuencia));
OCR1A = ICR1/2;// Poner Duty cycle = 50%
}
delay_ms((Longitud*7)/8);// Tiempo que se tocará la nueva nota
OCR1A =0;// Poner Duty cycle = 0 para apagar el buzzer
delay_ms(Longitud/8);// Tiempo que se tocará el silencio
}
}

/************************************************************************
*****
* Configuración de los Timers
* El Timer1 funcionará en modo PWM para generar por el pin OC1A/PD5 del
* megaAVR la señal que se aplicará al buzzer.

*************************************************************************
***/
void SetupTimers(void)
{
/* Configuración del Timer1
* - Bits WGM: PWM de Fase y Frecuencia Correctas con tope de conteo
= ICR1
* - Bits COM: PWM no-invertida en el canal A
* - Bits CS:

Fuente de reloj = F_CPU (sin prescaler)

*/
TCCR1A =(1<<COM1A1);
TCCR1B =(1<<WGM13)|(1<<CS10);
DDRD =(1<<PD5);// Pin OC1A/PD5 salida PWM
}

void delay_ms(unsignedint t)
{
while(t--)
delay_us(1000);
}

Descripción del programa
En este programa también se utiliza el Timer1 para generar la onda PWM con duty
cycle de 50% que exitará el buzzer. El tiempo que dura cada nota en cambio se basa
en delays y ya no en el Timer0. Comparado con la anterior práctica este programa es
muchísimo más sencillo, así que no queda mucho que explicar.
Cada cadena de ringtone tuvo que ser modificada para los requerimientos del lenguaje
C. Un numero hexadecimal en C empieza por 0x en vez del $ de Basic. Es solo cosa de
usar la herramienta reemplazar de cualquier editor de texto.
Otro punto que quiero destacar es que estos ringtones de PICAXE no son cadenas
terminadas en nulo. El byte 0x00 de hecho representa una nota musical más. Pero sí
hay un byte que no se usa como nota y es el 0x30. Recuerda que esos dos bits iguales
a 1 son los que no se usan en las octavas. Así que empleé ese caracter como
delimitador de la cadena. La función Tune reproduce cada nota del ringtone hasta
encontrar el 0x30.
Ante todas las limitaciones descritas antes de este simplificado formato RTTTL de
PICAXE, no debe extrañarnos que los ringtones suenen muy pobres al lado de los
originales ringtones RTTTL.
Si al final te llega a interesar este formato RTTTL alternativo, puedes encontrar más
ringtones similares en la web de PICAXE. También vas a encontrar varios paquetes de
ellos al descargar los archivos de esta práctica. Y por si fuera poco el software de
PICAXE también incluye una utilidad para convertir los ringtones originales en
ringtones PICAXE:

Simulación del reproductor de ringtones de PICAXE
Aunque esta práctica es bastante sencilla de implementar, puede que primero desees
escuchar cómo suenan los ringtones PICAXE en una simulación en Proteus.
Simulación del reproductor de ringtones en formato de PICAXE

Protocolo del Bus I2C
El I2C (Inter Integrated Circuits) es un bus de comunicaciones serial síncrono de dos
líneas que fue originalmente desarrollado porPhilips Semiconductors (ahora nxp
semiconductors) desde los inicios de los „80. Hoy es un estándar aceptado y
respaldado por los fabricantes de dispositivos semiconductores.
El bus I2C permite la comunicación entre múltiples dispositivos (en teoría más de
1000), todos conectados paralelamente a las dos líneas. Las transferencias de datos
siempre se realizan entre dos dispositivos a la vez y en una relación maestro –
esclavo.
Los dispositivos maestros son normalmente los microcontroladores y los dispositivos
esclavos pueden ser memorias, conversores DAC y ADC, controladores de LCD,
sensores de todos los tipos, etc.
Ahora bien, para que todos los dispositivos se puedan comunicar sin entorpecerse unos
y otros, sin que haya pérdidas o colisiones en las transferencias de datos, sin que los
dispositivos rápidos se desentiendan de los dispositivos lentos, etc., se deben de seguir
ciertas reglas estándar, cierto protocolo.
Todas las especificaciones software y hardware del protocolo del bus I2C están
descritas en el I2C-bus specification and user manual. Es un documento PDF de 50
páginas (la versión que veo en este momento: rev. 03, de 2007). No obstante, si
reducimos las funciones del bus I2C a redes donde solo haya un dispositivo maestro y
uno o varios (hasta 112) dispositivos esclavos, si limitamos la velocidad de
transferencia a un máximo de 1 Mbit/s (que no es poca cosa, ¿verdad?), entonces el
estándar I2C se simplifica más o menos a las siguientes cinco páginas.

Topología del bus I2C.
Consideremos entonces las siguientes características:
Las transferencias de datos se llevan a cabo mediante dos líneas: línea serial de datos
SDA y línea serial de reloj SCL. Ambas son bidireccionales. SDA se encarga de conducir
los datos entre el dispositivo maestro y los esclavos. SCL es la señal de reloj que
sincroniza los datos que viajan por la línea SDA.
El dispositivo maestro (microcontrolador) es quien siempre tiene la iniciativa de la
comunicación: el maestro genera la señal de reloj y controla cuando se transmiten o
reciben los datos.
Puede haber varios esclavos en la red I2C, pero el maestro solo se comunica con uno a
la vez. Por eso cada dispositivo esclavo debe ser identificado por una dirección única.
Hay muchos conceptos más en el estándar. Algunos serán descritos en lo sucesivo y
otros no nos conciernen directamente. Ante cualquier duda no resuelta aquí, puedes
revisar el documento de la especificación citado antes.

Transferencias de Datos
Los datos que se transfieren por el bus I2C deben viajar ―para decirlo fácil― en forma
de paquetes, aquí llamados transferencias. Como se ve en la siguiente figura,
unatransferencia empieza con un START y termina con un STOP. Entre estas señales
van los datos propiamente dichos. Cada dato debe ser de 8 bits (1 byte) y debe ir
seguido de un noveno bit, llamado bit de reconocimiento (ACK o NACK).

Transferencias de datos sobre el bus I2C.
La transferencia mostrada arriba tiene dos bytes pero puede varios más (sin
restricción) o puede haber un solo byte por paquete.
Los datos son transferidos por la línea SDA y son acompañados y sincronizados por los
pulsos de reloj de la línea SCL. Para transmitir un bit primero hay que poner la
línea SDA a 1 ó 0 según sea el caso, y luego colocar un pulso en la línea SCL.
Los datos pueden viajar de ida y de vuelta porSDA sin colisionar porque es el maestro
quien controla cuándo se transmite o recibe un dato. De ese modo, el control
de SDA puede ser asumido tanto por el maestro como por el esclavo y ambos
dispositivos podrán intercambiar los roles de transmisor o receptor. Eso sí, en
cualquier caso, el control de la línea SCL siempre (excepto en el Clock Stretching) es
asumido por el maestro.

Condiciones START, STOP y START Repetida
Como dijimos, los paquetes de datos transferidos por el bus I2C deben ir enmarcados
por un Start y un Stop. Ambas señales son generadas por el maestro.
Una condición START es una transición de Alto a Bajo en la línea SDA cuando SCLestá
en Alto. Se le representa por la letra S. Después de Start el bus se considera ocupado.
Una condición STOP es una transición de Bajo a Alto en la línea SDA mientras SCLestá
en Alto. Está simbolizada por la letra P. Después de Stop las dos líneas están en Alto y
el bus se considera libre. Se usa Stop para cerrar la transferencia de un paquete de
datos o para abortar una transferencia previa que quedó truncada.

Condiciones START y STOP.
La señal de una condición START repetida es exactamente igual a la de START. La
diferencia es de tipo “ocasional”: aunque en principio cada transferencia debe ir
enmarcada por un Start y un Stop, el estándar contempla la posibilidad de iniciar una
nueva transferencia sobre una anterior que no ha sido cerrada con un Stop. El Startde
la nueva transferencia se llama entonces Start Repetida y su símbolo es Rs. Este punto
lo entenderemos mejor en las prácticas.

El Bit de Reconocimiento (ACK o NACK)
Según las figuras mostradas, cada byte transferido debe ir seguido de un noveno bit,
llamado Acknowledge bit (bit de reconocimiento, en inglés). Este bit siempre debe ser
devuelto por el dispositivo receptor (maestro o esclavo) tras cada byte recibido.
Si el bit de reconocimiento es 0 significa que el dato fue reconocido y aceptado. Este
bit se denomina ACK (Acknowledge).
Si el bit de reconocimiento es 1 significa que el dato recibido aún no es aceptado. Se
usa este mecanismo para indicar que el receptor está ocupado realizando alguna tarea
interna. Este bit se denomina NACK (Not Acknowledge).

El Byte de Control
Como se sabe, las comunicaciones por el bus I2C se llevan a cabo siguiendo la
relaciónmaestro – esclavo. Eso significa que es el maestro (microcontrolador) quien
ordena con cuál esclavo se va a comunicar o si los siguientes datos se van a transmitir
o recibir; el esclavo solo obedece. Pues bien, esa orden viaja en el primer byte de
cada transferencia y es más conocido como byte de control.
Según lo mostrado en la siguiente figura, 7 bits del byte de control contienen la
dirección del esclavo con el cual se desea entablar la comunicación y el
bit R/W establece si los siguientes bytes serán de lectura o escritura. Como
siempre, R/W = 0 indica una escritura yR/W = 1 indica una lectura.

Formato del byte de control (primer byte).
Todos los esclavos deben recibir el byte de control, pero solo el que halle su dirección
en él será el que prosiga la comunicación. Los demás esclavos se deben mantener al
margen hasta un nuevo aviso (otra condición de Start).

Velocidad de Transferencia de Datos
Como cada bit de dato transferido sobre la línea SDA debe ser validado por la señal de
reloj SCL, podemos deducir que la velocidad de transferencia está determinada por la
frecuencia de la señal de SCL. Por ejemplo, una velocidad de 100 kbits/s implica que
cada bit se transmite en 1s/100k = 10µs, lo que nos dice que cada semiperiodo de la
señal de reloj vale en promedio 5 µs. Estos datos se detallan en el Estándar I2C y
también suelen ir indicados en los datasheets de los dispositivos I2C.
El estándar del bus I2C soporta cuatro modos de operación:
Standard Mode, con una velocidad de hasta 100 kbit/s.
Fast mode, con una velocidad de hasta 400 kbit/s.
Fast mode plus, con una velocidad de hasta 1 Mbit/s.
High-speed mode, con una velocidad de hasta 3.4 Mbit/s.
Los valores límites implican que los dispositivos más rápidos son compatibles con los
dispositivos más lentos; lo contrario, por supuesto, no es factible. Así, entenderemos
que todos ellos podrían trabajar en una misma red si operan, por ejemplo, a 20 kHz,
50 kHz ó 100 kHz.

El Módulo TWI de los AVR
TWI es la sigla de Two Wire Interface (Interface serial de dos líneas). Es el periférico
de los AVR que realiza las funciones del protocolo I2C a nivel hardware. El
módulo TWI soporta los tres siguientes modos de operación, de los cuales ahora
estudiaremos el primero:
Modo I2C de maestro único. El microcontrolador trabaja como maestro, controlando
uno o varios dispositivos esclavos como EEPROMs, termómetros, RTCs, etc.
Modo I2C de esclavo. El microcontrolador trabajará como esclavo frente a algún otro
microcontrolador que hace de maestro.
Modo I2C de maestros múltiples. Es una extensión del primer modo, solo que ahora el
microcontrolador compartirá la red I2C con otros microcontroladores maestros. Puede
inclusive alternar su rol entre maestro y esclavo.
A diferencia de otros microcontroladores, en los AVR no hay que configurar
previamente uno de estos modos de operación. En cualquier momento el AVR puede
pedir el control de bus enviando una condición START, y si lo obtiene, trabajará como
maestro (transmisor o receptor).
El módulo TWI alcanza velocidades de hasta 400 k-bits/s (esto es Fast mode)

Control del Módulo TWI
Estos son los principales Registros de E/S que conducen las operaciones del módulo
TWI:
TWDR (Registro de Datos del TWI). Es el registro donde se cargan los datos a
transmitir y donde se depositan los datos que llegan.
Como el bus I2C es half dúplex , los datos pueden viajar en ambas direcciones pero no
al mismo tiempo. El maestro debe controlar el tráfico.
TWBR. Es el registro que establece la velocidad de transferencia de datos. Actúa con
algunos bits del registro TWSR.
TWCR (Registro de Control del TWI). Con este registro se puede habilitar el módulo
TWI, se pueden generar las condiciones Start y Stop, se puede dar inicio a una
transferencia de datos, se pueden configurar las interrupciones del módulo TWI, etc.
TWSR(Registro de Estado del TWI). Este registro se actualiza automáticamente para
indicar el resultado de la última operación I2C ejecutada. Por ejemplo, leyendo este
registro podremos saber si un dato fue enviado satisfactoriamente o no.
TWAR (Registro de Dirección del TWI). La dirección a que hace alusión es solo para
cuando el microcontrolador trabaja en modo esclavo.
TWAMR (Registro de máscara de dirección del TWI). Sirve para enmascarar la
dirección contenida en el registro TWAR.
En seguida presentamos los mapas de bits de los registros de control y estado a los
que nos referiremos en adelante. Sus nombres no reflejan del todo sus funciones.
Veremos luego que hay algunos bits de control en el registro de estado y viceversa.
Los bits sin nombre no tienen efecto.
Registro TWCR

TWCR

TWINT

TWEA

TWSTA

TWSTO

TWWC

TWS7

TWS6

TWS5

TWS4

TWS3

TWEN

---

TWIE

TWPS1

TWPS0

Registro TWSR

TWSR

---

En lo sucesivo trabajaremos constantemente con el registro de control TWCR. Su uso
es un poco especial debido al bit TWINT. El flag TWINT se activa por hardware para
indicar la culminación de alguna operación, pero como todo flag del AVR, TWINT se
debe limpiar por software escribiendo un 1.
Otra consecuencia de la naturaleza del bit TWINT es que no se deberían usar las
instrucciones de lectura-modificación-escritura. Por eso, aunque solo tengamos que
acceder a un bit de TWCR, debemos escribir el registro completo. Lo malo de esto es
que debemos recordar los valores relevantes que los otros bits de TWCR tenían antes
para volverlos a escribir.
Por ejemplo, para una condición START se debe setear el bit TWSTA y limpiar el
bitTWINT. La siguiente sentencia lo hace pero sin olvidar que el bit TWEN valía 1.

/* Iniciar Condición START */
TWCR=(1<<TWSTA)|(1<<TWINT)|(1<<TWEN);

Velocidad de Transferencias de Datos
Recordemos que cada bit de dato se valida con un pulso del reloj. Por tanto la
velocidad de transferencia de datos será igual a la frecuencia de la señal de SCL, que
es controlada por maestro. Esta frecuencia depende del valor de los
registros TWBR, TWSR y por supuesto de la frecuencia del reloj del sistema, que es la
misma frecuencia del procesador definida como F_CPU en archivo avr_compiler.h.

#ifndef F_CPU
/* Define la frecuencia de CPU (en Hertz) por defecto, si aún no ha
* sido definida.
*/
#define F_CPU 8000000UL
#endif

// XTAL de 8 MHz
Se calcula con la siguiente fórmula:

Donde:
TWBR es el valor del registro TWBR.
TWPS es el valor conformado por los bits de prescaler TWPS1:TWPS0, contenidos en el
registro TWSR. Por ser dos bits, TWPS puede valer 0, 1, 2 ó 3.
Observa que la Frecuencia de SCL es inversamente proporcional a TWBR y TWPS. Para
valores “altos” de TWPS la Frecuencia de SCL disminuirá exponencialmente. Si
ponemos TWPS a 0, es decir, si no utilizamos el prescaler, la fórmula original se reduce
a

Sin el prescaler, ahora la Frecuencias de SCL estará “limitada” a valores relativamente
altos. Por ejemplo, la Frecuencia más baja que podríamos conseguir con el XTAL más
alto (de 20MHz) sería 20MHz/(16+2*255) = 38 kHz. Esta frecuencia no es tan baja
considerando que todos los dispositivos I2C debieran operar al menos a 100 kHz.
Con la fórmula reducida, podemos despejar TWBR para calcular su valor.

El código de inicialización del módulo TWI (I2C) también es simplifica.

#define I2C_BAUD 100000UL

// 100khz. Valor por defecto

//****************************************************************************
// Inicializar el módulo TWI.
// Configurar Frecuencia de reloj a I2C_BAUD.
//****************************************************************************
voidi2c_init(void)
{
/* Establecer valor de prescaler de TWI a 0 */
TWSR&=~((1<<TWPS1)|(1<<TWPS0));

/* Establecer Frecuencia de SCL */
TWBR=((F_CPU/I2C_BAUD)-16)/2;

/* Habilitar la interface TWI */
TWCR=(1<<TWEN);
}
Se dice que la Interferencia Electromagnética (EMI) solo afecta las transmisiones
cuando la velocidad del bus es de alrededor de 400 kbps (Fast mode). Para evitar esto,
el módulo TWI tiene incorporado un filtro que adapta ligeramente las señales para que
sean inmunes a dicha interferencia.

Condición Start y Start Repetida
Para enviar una condición START primero se setea el bit TWSTA y luego se limpia el
bitTWINT. También se pueden escribir los dos bits al mismo tiempo pero no invertir el
orden. Si el bus está libre, el Start iniciará normalmente; de lo contrario, si el bus está
acaparado por otro maestro, el módulo TWI esperará a que el bus esté disponible
nuevamente para iniciar la condición Start. Durante todo ese lapso el
bit TWSTApermanecerá seteado y el bit TWINTpermanecerá en 0.
Apenas la condición Start se haya terminado de enviar, el bit TWINT se seteará
automáticamente. Así que se puede sondear este bit para saber si la condición Start
fue bien enviada. La función alternativa del bitTWINT es que al activarse puede
disparar una interrupción si esa interrupción está habilitada. Por otro lado, el
bit TWSTA se debe limpiar por software.
La procedimiento software para generar una condición START repetida es el mismo. No
hay bits de control exclusivos para una START repetida. El hardware del TWI sabrá
diferenciar estos dos eventos por el momento en que ocurren y reportará los
resultados en el registro TWSR.
Tabla Código de estado (TWSR & 0xFC)

Código de estado (TWSR & 0xFC) Estado del bus I2C y del módulo TWI
0x08

Se ha transmitido una condición START
Tabla Código de estado (TWSR & 0xFC)

Código de estado (TWSR & 0xFC) Estado del bus I2C y del módulo TWI
0x10

Se ha transmitido una condición START Repetida

Y finalmente el código para la condición Start podría quedar así:

//****************************************************************************
// Envía una Condición START o START Repetida.
// Retorna 1 si la Condición START se envió satisfactoriamente, 0 si falló.
//****************************************************************************
chari2c_start(void)
{
/* Setear bit TWSTA y limpiar flag TWINT para iniciar Condición START */
TWCR=(1<<TWSTA)|(1<<TWINT)|(1<<TWEN);

/* Esperar a que la Condición START se termine de enviar */
while((TWCR&(1<<TWINT))==0);

/* Limpiar bit TWSTA */
TWCR=(1<<TWEN);

/* Comprobar si la transferencia de START fue satisfactoria */
if(((TWSR&0xFC)==0x08)||((TWSR&0xFC)==0x10))
return1;
return0;
}
Condición Stop
Para enviar una condición Stop primero se setea el bit TWSTO y luego se limpia el
bitTWINT. También se pueden escribir los dos bits al mismo tiempo pero no vale
invertir el orden. La condición Stop se iniciará inmediatamente puesto que se supone
que el bus está en posesión del AVR actual.
Cuando la condición Stop se termine de enviar el bit TWSTO se limpiará
automáticamente y el bit TWINT se seteará al mismo tiempo también
automáticamente. Se puede sondear cualquiera de estos bits para saber si la condición
Stop se terminó de enviar.
Aunque también en este caso se activa el bit TWINT, ello no es condición para poder
disparar una interrupción. La activación de TWINT puede generar una interrupción ante
una condición Stop pero cuando el AVR está trabajando en modo esclavo

//****************************************************************************
// Envía una Condición STOP
//****************************************************************************
voidi2c_stop(void)
{
/* Setear bit TWSTO y limpiar flag TWINT para iniciar Condición STOP */
TWCR=(1<<TWSTO)|(1<<TWINT)|(1<<TWEN);

/* Esperar a que la Condición STOP se termine de enviar */
while(TWCR&(1<<TWSTO));
}

Transmitir Dato y Recibir Bit ACK/NACK
El byte (de dato o de control) a transmitir se debe cargar en el registro TWDR. Se
asume que en este punto el bus está en posesión del AVR y que no hay ninguna
transferencia previa en marcha, o sea que el bit TWINTdebe estar seteado. A
continuación debemos limpiar el bit TWINT para iniciar la transferencia.
Del otro lado, si el esclavo recibe el dato correctamente y lo admite, entonces
responderá afirmativamente enviando un bit 0 llamado ACK (Acknowledge). De lo
contrario, responderá con un bit 1 llamado NACK (Not Acknowledge). Cuando el AVR
reciba esta respuesta seteará el bit TWINT, hecho que puede disparar la interrupción
de TWI si está habilitada.
El resultado del byte enviado y del bit ACK/NACK recibido será registrado en el registro
de estado TWSR. Puesto que el módulo TWI puede reconocer si el dato enviado fue
unbyte de control para escritura (SLA+W), un byte de control para lectura (SLA+R) o
si fue un byte de dato; el registro TWSR podrá contener uno de los siguientes 6
códigos de estado.
Tabla Código de estado (TWSR & 0xFC)

Código de estado
(TWSR & 0xFC)

Estado del bus I2C y del módulo TWI

0x18

Se ha transmitido SLA+W; se ha recibido ACK

0x20

Se ha transmitido SLA+W; se ha recibido NACK

0x40

Se ha transmitido SLA+R; se ha recibido ACK

0x48

Se ha transmitido SLA+R; se ha recibido NACK

0x28

Se ha transmitido un byte de dato; se ha recibido
ACK

0x30

Se ha transmitido un byte de dato; se ha recibido
NACK

Finalmente, con toda la información presentada podemos escribir el código para enviar
un byte por el bus I2C en modo Maestro. Normalmente se asocia el 0 con una
respuesta negativa, pero en mi función el 0 es el valor original del bit ACK asignado
por el protocolo I2C. nota que al final solo se evalúa si se recibió un ACK, sin revisar
explícitamente si el caso contrario se trata de un NACK. De ese modo quise simplificar
el código.
Esta función retorna 1 cuando el esclavo respondió con un bit NACK o cuando hubo un
error de transferencia. Esto último sería muy raro. Un caso en que se podría presentar
es cuando el AVR pierde el control del bus ante otro microcontrolador maestro, pero
como estamos trabajando en un sistema de un solo maestro, tampoco sería caso. En
conclusión, si la función no retorna un 0 (de ACK) es casi seguro que el esclavo
respondió con unNACK. Yo sé que en programación el “casi” nunca se debería pasar
por alto, pero en esta situación en particular la respuesta ante un NACK o un error
desconocido sería la misma: volver a enviar el dato.
//***********************************************************************
*****
// Envía el byte 'data' y devuelve 0 si el esclavo respondió con un bit
ACK
// En otro caso, devuelve 1.
//***********************************************************************
*****
char i2c_write(char data)
{
/* Cargar dato a transmitir */
TWDR = data;

/* Limpiar flag TWINT para iniciar la transferencia */
TWCR =(1<<TWINT)|(1<<TWEN);

/* Esperar a que finalice la transferencia */
while((TWCR &(1<<TWINT))==0);

/* Leer el resultado de la transferencia y retornar 0 si el esclavo
* respondió con un bit ACK.
*/
if(((TWSR&0xFC)==0x18)||((TWSR&0xFC)==0x40)||((TWSR&0xFC)==0x28))
return0;

/* Se llega a este punto si el esclavo respondió con un NACK o si
* hubo un error en la transferencia */
return1;
}

Recibir Dato y Transmitir Bit ACK/NACK
Un esclavo no puede enviar un dato cuando quiera. Es el maestro quien le ordena que
lo haga. Es decir, para recibir un dato asumimos que ya enviamos al esclavo un byte
de control para lectura (por ejemplo, utilizando la funcióni2c_write). Tras recibir esa
orden el esclavo inició la transferencia del dato respectivo.
El AVR acepta el dato enviado y lo deposita en el registro TWDR. En seguida seteará el
ya familiar flag TWINT. También en esta ocasión se puede usar este evento para
disparar una interrupción si está habilitada. Con interrupción o sin ella, ya se puede
leer el registro TWDR.
Puesto que se recibe un dato, ahora es el AVR el que tiene que responder con el bit
ACK o NACK. Para ello primero debemos escribir 0 (para NACK) ó 1 (para ACK) en el
bit TWEA (TWI Enable Acknowledge). Luego podemos iniciar su envío, como siempre,
limpiando el bit TWINT. Por supuesto, aquí también es posible escribir ambos bits al
mismo tiempo. El flag TWINT se seteará automáticamente después de que nuestra
respuesta se haya enviado.
Como de costumbre, al final de todo el registro de estado TWSR reflejará el resultado
de la operación. Asumiendo que no hay ningún otro microcontrolador pugnando por la
posesión del bus I2C, los códigos del registro TWSR se reducen a dos.
Tabla Código de estado (TWSR & 0xFC)

Código de estado
(TWSR & 0xFC)

Estado del bus I2C y del módulo TWI

0x50

Se ha recibido un byte de dato; se ha retornado
ACK

0x58

Se ha recibido un byte de dato; se ha retornado
NACK

Puesto que estos códigos se originan después de haber leído el dato pedido, no tiene
mayor relevancia conocerlos, sobre todo si se retorna un NACK, que es como dar por
terminada la transferencia. Así que no los he incluido mi función i2c_read. De hecho no
los he usado nunca. Pero si tú deseas, puedes adaptar tu propio código.
//***********************************************************************
*****
// Lee un byte de dato y envía el bit ACK/NACK.
// ack = 0 es ACK y ack = 1 es NACK.
//***********************************************************************
*****
char i2c_read(char ack)
{
/* Esperar a que termine de llegar el dato pedido */
while((TWCR &(1<<TWINT))==0);
/* Enviar el bit ACK o NACK al esclavo */
if(ack ==1)
TWCR =(1<<TWINT)|(1<<TWEN);// Esto es NACK
else
TWCR =(1<<TWEA)|(1<<TWINT)|(1<<TWEN);// Esto es ACK

/* Esperar a que se termine de enviar el bit ACK/NACK */
while((TWCR &(1<<TWINT))==0);

/* Retornar el dato que llego anteriormente */
return TWDR;
}

Interrupciones del Módulo TWI
En las comunicaciones RS-232 los datos suelen viajar tan lento que el microcontrolador
puede aprovechar los tiempos que duran las transferencias para ejecutar otras
funciones. En las comunicaciones I2C el uso de las interrupciones para detectar el
inicio o final de los datos solo es beneficioso para el microcontrolador cuando opera en
modo de Esclavo.
La interrupción del módulo TWI puede ser disparada por cualquiera de los eventos que
activa el flag TWINT, del registro TWCR. El bit TWINT es el único que no se limpia
automáticamente al ejecutarse la rutina de interrupción ISR. Pero como los demás
flags, al limpiarse por software se debe escribir un 1.
La interrupción del módulo TWI se habilita seteando los bits TWIE (TWI Interrupt
Enable) de TWCR e I de SREG.

Registros del Módulo TWI
El Registro TWCR
Registro TWCR

TWCR TWINT TWEA

TWSTA

TWSTO

TWWC

TWEN

---

TWIE
Registro de Microcontrolador

TWINT

TWI Interrupt Flag
Este bit se setea por hardware cuando el módulo TWI ha terminado una operación
actual y espera una respuesta del programa. Si los bits I del registro SREG y TWIE
de TWCR están seteados, el programa saltará al Vector de interrupción. Mientras
el flag TWINT valga 1 se extenderá el periodo bajo de SCL (clock stretching).
TWINT se debe limpiar por software escribiendo un 1. Este flag no se limpia
automáticamente por hardware cuando se ejecuta la rutina de interrupción ISR. Al
limpiar este flag se iniciarán las operaciones del TWI, así que los accesos a los
registros TWAR, TWSR y TWDR deberían estar completos antes de limpiar TWINT.

TWEA

TWI Enable Acknowledge Bit
El bit TWEA controla la generación del pulso de reconocimiento. Si se escribe 1 en
el bit TWEA, se generará un pulso ACK en las siguientes condiciones:
1. Se recibió la dirección del microcontrolador en modo esclavo.
2. Se recibió una llamada general cuando el bit TWGCE de TWAR valía 1
3. Se recibió un byte de dato o de control, actuando el AVR como maestro o
esclavo.
Si se escribe 0 en TWEA, el AVR se desconecta virtualmente del bus I2C, porque si
algún otro maestro trata de contactarlo se le responderá con puros NACK.
Después se podrá setear TWEA nuevamente para que el AVR pueda volver a
reconocer su dirección.

TWSTA

TWI START Condition Bit
Se debe escribir en el bit TWSTA cuando se desea tomar el bus I2C, para actuar
como maestro. El hardware del TWI revisa si el bus está disponible y genera una
condición START si está libre. Pero si el bus no está libre, el TWI espera hasta que
detecte una condición STOP, y luego genera una nueva condición START para
tomar el control del bus.
El bit TWSTA se debe limpiar por software después de transmitir la condición
START.

TWSTO

TWI STOP Condition Bit
Cuando se escribe 1 en el bit TWSTO en modo maestro se generará una condición
STOP en el bus I2C. Cuando se ejecute la condición STOP el bit TWSTOP se
limpiará automáticamente.
En modo esclavo, se puede setear el bit TWSTO para recuperarse de una
condición de error. Esto no generará una condición STOP, sino hará que el módulo
TWI regrese a su estado de esclavo no direccionado y liberará las líneas SCL y SDA.
TWWC

TWI Write Collision Flag
El bit TWWC se setea cuando se trata de escribir en el registro TWDR cuando
TWINT valga 0. Este flag se limpia al escribir un dato en TWDR cuando TWINT
valga 1.

TWEN

TWI Enable Bit
El bit TWEN habilita las operaciones de la interface TWI.
Cuando se escribe 1 en TWEN, el TWI toma el control de los pines de E/S SCL y
SDA, se habilitan los filtros de picos y los limitadores slew-rate.
Si se escribe 0 en TWEN, se apaga el TWI y se terminarán todas las transmisiones
I2C, sin importar las operaciones en curso.

TWIE

TWI Interrupt Enable
Cuando se escribe 1 en este bit, y el bit I del registro SREG vale1, la interrupción
de TWI se reactivará mientras el flag TWINT permanezca activo.

El Registro TWSR
Registro TWCR

TWCR TWINT TWEA

TWSTA

TWSTO

TWWC

TWEN

---

TWIE

Registro de Microcontrolador

Bits
7:3

TWI Status

Bits
1:0

TWPS: TWI Prescaler Bits

Estos 5 bits reflejan el estado del módulo TWI y de la lógica I2C. Al revisar estos
bits de estado el programa debería enmascarar con 0 los dos bits de prescaler
para que la revisión sea independiente de la configuración del prescaler.

Estos bits son de lectura y escritura y controlan el prescaler del generador de
baudios del TWI.
Tabla TWPS1
TWPS1 TWPS0 Valor del prescaler
0

0

1

0

1

4

1

0

16

1

1

64

Librería Para Bus I2C en los AVR
Se usará esta librería en todas las prácticas con bus I2C, desde las memorias
EEPROM, pasando por los sensores de temperatura, los relojes RTC y los expansores
de E/S. La librería consta de los archivos i2c.h e i2c.c. i2c.c recopila las funciones del
módulo TWI elaboradas anteriormente.
i2c.h contiene los prototipos de las funciones y macros de configuración. En realidad la
única macro que se podría editar es la directiva que define la frecuencia del bus I2C.

//***********************************************************************
*****
//

CONFIGURACIÓN DE LA FRECUENCIA DEL BUS I2C

//***********************************************************************
*****
#ifndef I2C_BAUD
/* Define la velocidad de transmisión del módulo TWI (en bits/segundo),
si
* aún no ha sido definida.
*/
#define I2C_BAUD
#endif

100000UL

// 100khz. Valor por defecto
Por defecto está establecido a 100000UL = 100kHz, pero se puede cambiar por otro
valor ya sea aquí mismo o poniendo el define en el archivo principal del proyecto.
El valor que se indique no necesariamente será la frecuencia real. La
función i2c_init del archivo i2c.c utiliza la fórmula estudiada en la sección Velocidad de
Transferencias de Datos y establecerá la frecuencia que resulte más cercana al valor
indicado, con redondeo hacia abajo.
Por supuesto, esta frecuencia también dependerá del reloj del sistema. Por ejemplo,
para un XTAL de 8MHz la frecuencia I2C resultante sí será exactamente de 100kHz,
bueno, al menos en teoría, porque en la práctica los dispositivos tienen la facultad de
frenar la velocidad del bus.
/************************************************************************
******
* FileName:
* Purpose:
Master

i2c.h
Librería de funciones para el módulo TWI en modo I2C

* Processor:

ATmel AVR con módulo TWI

* Compiler:

AVR IAR C y AVR GCC (WinAVR)

* Author:

Shawn Johnson. http://www.cursomicros.com.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License:

Se permiten el uso y la redistribución de este código con

*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y las notas de autor y copyright de arriba.

*************************************************************************
****/

#include "avr_compiler.h"

//***********************************************************************
*****
//

CONFIGURACIÓN DE LA FRECUENCIA DEL BUS I2C

//***********************************************************************
*****
#ifndef I2C_BAUD
/* Define la velocidad de transmisión del módulo TWI (en bits/segundo),
si
* aún no ha sido definida.
*/
#define I2C_BAUD

100000UL

// 100khz. Valor por defecto

#endif

//***********************************************************************
*****
//

PROTOTIPOS DE FUNCIONES

//***********************************************************************
*****
void i2c_init(void);// Inicializa el bus I2C
char i2c_start(void);// Envía un START
void i2c_stop(void);// Envía un STOP
char i2c_write(char data);// Envía un byte y recibe el bit ACK/NACK
char i2c_read(char ack);// Recibe un byte y envía el bit ACK/NACK

#define i2c_restart

i2c_start

/************************************************************************
******
* FileName:

i2c.c
* Purpose:
Master

Librería de funciones para el módulo TWI en modo I2C

* Processor:

ATmel AVR con módulo TWI

* Compiler:

AVR IAR C y AVR GCC (WinAVR)

* Author:

Shawn Johnson. http://www.cursomicros.com.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License:

Se permiten el uso y la redistribución de este código con

*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y las notas de autor y copyright de arriba.

*************************************************************************
****/

#include "i2c.h"

//***********************************************************************
*****
// Inicializar el módulo TWI.
// Configurar Frecuencia de reloj a I2C_BAUD.
//***********************************************************************
*****
void i2c_init(void)
{
/* Establecer valor de prescaler de TWI a 0
TWSR &=~((1<<TWPS1)|(1<<TWPS0));

/* Establecer Frecuencia de SCL */
TWBR =((F_CPU/I2C_BAUD)-16)/2;

*/
/* Habilitar la interface TWI */
TWCR =(1<<TWEN);
}

//***********************************************************************
*****
// Envía una Condición START o START Repetida.
// Retorna 1 si la Condición START se envió satisfactoriamente, 0 si
falló.
//***********************************************************************
*****
char i2c_start(void)
{
/* Setear bit TWSTA y limpiar flag TWINT para iniciar Condición START */
TWCR =(1<<TWSTA)|(1<<TWINT)|(1<<TWEN);

/* Esperar a que la Condición START se termine de enviar */
while((TWCR &(1<<TWINT))==0);

/* Limpiar bit TWSTA */
TWCR =(1<<TWEN);

/* Comprobar si la transferencia de START fue satisfactoria */
if(((TWSR&0xFC)==0x08)||((TWSR&0xFC)==0x10))
return1;
return0;
}
//***********************************************************************
*****
// Envía una Condición STOP
//***********************************************************************
*****
void i2c_stop(void)
{
/* Setear bit TWSTO y limpiar flag TWINT para iniciar Condición STOP */
TWCR =(1<<TWSTO)|(1<<TWINT)|(1<<TWEN);

/* Esperar a que la Condición STOP se termine de enviar */
while( TWCR &(1<<TWSTO));
}

//***********************************************************************
*****
// Envía el byte 'data' y devuelve 0 si el esclavo respondió con un bit
ACK
// En otro caso, devuelve 1.
//***********************************************************************
*****
char i2c_write(char data)
{
/* Cargar dato a transmitir */
TWDR = data;

/* Limpiar flag TWINT para iniciar la transferencia */
TWCR =(1<<TWINT)|(1<<TWEN);

/* Esperar a que finalice la transferencia */
while((TWCR &(1<<TWINT))==0);

/* Leer el resultado de la transferencia y retornar 0 si el esclavo
* respondió con un bit ACK.
*/
if(((TWSR&0xFC)==0x18)||((TWSR&0xFC)==0x40)||((TWSR&0xFC)==0x28))
return0;

/* Se llega a este punto si el esclavo respondió con un NACK o si
* hubo un error en la transferencia */
return1;
}

//***********************************************************************
*****
// Lee un byte de dato y envía el bit ACK/NACK.
// ack = 0 es ACK y ack = 1 es NACK.
//***********************************************************************
*****
char i2c_read(char ack)
{
/* Esperar a que termine de llegar el dato pedido */
while((TWCR &(1<<TWINT))==0);

/* Enviar el bit ACK o NACK al esclavo */
if(ack ==1)
TWCR =(1<<TWINT)|(1<<TWEN);// Esto es NACK
else
TWCR =(1<<TWEA)|(1<<TWINT)|(1<<TWEN);// Esto es ACK
/* Esperar a que se termine de enviar el bit ACK/NACK */
while((TWCR &(1<<TWINT))==0);

/* Retornar el dato que llego anteriormente */
return TWDR;
}

Memorias EEPROM Seriales 24xxx
Las EEPROM seriales que estudiaremos en esta ocasión pertenecen a la familia 24xxxx.
Hay varias compañías que fabrican muchos de estos modelos. De hecho, la misma
Microchip también provee los suyos.
Tabla de memorias seriales eeprom i2c

Capacidad
Dispositivo

Conexión en cascada
Bits Bytes

24xx01

1K

128 No

24xx02

2K

256 No

24xx04

4K

512 No

24xx08

8K

1 K No

24xx16

16 K 2 K No

24xx32

32 K 4 K Sí

24xx64

64 K 8 K Sí

24xx128

128 K 16 K Sí

24xx256

256 K 32 K Sí
Tabla de memorias seriales eeprom i2c

Capacidad
Dispositivo

Conexión en cascada
Bits Bytes

24xx512

512 K 64 K Sí

En la tabla mostrada arriba se aprecia la diversidad de memorias disponibles (y todavía
es posible hallar unas cuantas más, aunque menos usuales). Lo que se deduce de
inmediato es que el número final de la identificación de cada dispositivo es la
capacidad de la EEPROM en bits. Por ejemplo, la EEPROM 24xx64 tiene 64 Kbits = 8
Kbytes de memoria.
También se puede notar que la capacidad deconexión en cascada divide a las EEPROMs
en dos grupos: las que la tienen y las que no. Las que la tienen pueden reconfigurar su
dirección de esclavo de modo que se puedan colgar varias EEPROMs de ese tipo en la
misma red I2C. Por ejemplo, es posible tener hasta 8 EEPROMs 24xx256 para juntar
un total de 128 KBytes.
La división indicada coincide con el modo de acceso al contenido de las EEPROMs. Por
ejemplo, la forma de leer o escribir un byte en una 24xx16 difiere ligeramente de la
forma de hacerlo en una 24xx32. A mí me parece que es más fácil empezar con las
EEPROMs del segundo grupo (de 32 Kbits para arriba). Así que para nuestras prácticas
vamos a tomar una de éstas, y para que no sea ni mucho ni poco elegiremos el modelo
24xx128.
Lo olvidaba: el significado de las xx suele variar según el fabricante. En los modelos de
Microchip estas xx pueden ser AA, LC o FC y distinguen básicamente la velocidad de
reloj y tensión de operación del dispositivo.

La EEPROM Serial 24xx128
Esto es parte del datasheet. Sus principales características son:
Los tres modelos 24AA128, 24LC128 y 24FC128 se diferencian únicamente por operar
a diferente velocidad.
Memoria de 128 Kbits = 16 Kbytes.
Frecuencia máxima de reloj de 400 KHz (Full Speed mode).
Tiempo de escritura máximo de 5 ms.
Retención de datos > 200 años.
1 000 000 de ciclos de lectura escritura.
Modo de escritura por página de 64 bytes.
Conexión en cascada hasta de 8 dispositivos.
Tensión de operación entre 2.5 V y 5.5 V.

Descripción de Pines

Diagrama de pines de la EEPROM 24xx128.
A0, A1 y A2. Pines que establecen parte de la dirección de esclavo de este dispositivo.
Leer siguiente sección.
Vss y Vcc. Tierra y alimentación del dispositivo.
SDA y SCL. Línea serial de datos y línea serial de reloj.
El pin WP (Write Protection) o protección de escritura. Conectado a tierra desactiva la
protección de escritura, es decir, el contenido de la memoria podrá ser leído y escrito.
Si WP se conecta a VCC, la memoria se podrá leer pero no escribir.

Dirección del Dispositivo
Recordemos que cada dispositivo esclavo conectado al bus I2C debe estar identificado
por una dirección de 7 bits. Pues bien, en la gran mayoría de los esclavos parte de esa
dirección suele ser fija y la otra parte, reconfigurable vía hardware.
Por ejemplo, en las EEPROMs seriales de la familia 24xxx, la dirección de esclavo, en
binario, es 1010xxx, siendo xxx la parte reconfigurable por los pines A2, A1 y A0 del
dispositivo. Así se podrán formar 8 direcciones diferentes para conectar hasta 8
EEPROMs de este tipo.
Como sabemos, la dirección de esclavo debe estar contenida en el byte de control de
cada paquete de datos transmitido. El bit restante de dicho byte (R/W) indica si los
siguientes bytes serán de lectura (R/W = 1) o de escritura (R/W = 0).
El byte de control (Dirección de esclavo + bit R/W).

Lectura y Escritura Aleatorias de Bytes
Hay dos formas de realizar operaciones de lectura y escritura de datos en las EEPROMs
24xxx: una es byte por byte (acceso a un byte por cada paquete transmitido) y la otra
es en bloques (varios bytes por cada paquete transmitido). En este apartado nos
enfocamos a la primera forma, conocida como acceso aleatorio, porque se debe
especificar la dirección de cada byte de dato accedido.
La locación de memoria a acceder depende de un registro interno llamado Puntero de
memoria, el cual puede llegar a ser de 16 bits (2 bytes). Tras cada lectura el Puntero
de memoria se incrementa en 1. Para las escrituras funciona ligeramente diferente.

Secuencia de escritura de un byte en la EEPROM 24xx128.
La figura de arriba indica que para escribir un Data Byte en la dirección Address de la
memoria se debe seguir la siguiente secuencia:
Enviar una Condición START (iniciar transferencia).
Enviar el byte de control para escritura (Slave address + Write).
Enviar el byte alto de Address.
Enviar el byte bajo de Address.
Enviar el Data Byte.
Enviar una Condición STOP (cerrar transferencia).
Tras la Condición STOP empieza el ciclo de escritura interno del dato enviado. Este
ciclo dura a lo mucho 5 ms. Hay que poner un delay.
Ahora pasemos al proceso de lectura de una posición aleatoria de la EEPROM 24128.
De nuevo, solo seguimos los pasos que nos indica el datasheet, graficados en el
siguiente esquema:

Secuencia de lectura de un byte de la EEPROM 24xx128.
El esquema indica que para leer un Data Byte de la dirección Address de la memoria se
deben seguir los siguientes pasos:
Enviar una condición START.
Enviar el byte de control para escritura (Slave address + Write).
Enviar el byte bajo de Address.
Enviar el byte alto de Address.
Enviar una condición START (llamada START repetida aunque sea lo mismo).
Enviar el byte de control para lectura (Slave address + Read).
Leer el byte de dato y devolver un NACK.
Enviar una condición STOP.
Nota que, aunque vayamos a leer un dato de la EEPROM, primero debemos especificar
de qué dirección, es decir, debemos escribir su dirección (en el Puntero de memoria).
Por eso el primer byte de control es para escritura. Luego se vuelve a enviar el Byte de
Control, esta vez para la lectura del dato en sí.

Práctica: Acceso Aleatorio a la EEPROM 24xxx
Lo que hace el programa es grabar cada uno de los caracteres de una cadena de texto
en las primeras posiciones de la EEPROM serial y a continuación los lee todos de allí y
los visualiza en el programa terminal.
La EEPROM puede ser una de las que Microchip denomina Smart o de alta densidad. Es
decir, vale desde una 24xx32 hasta una 24xx512.
El pin WP (protección contra escritura) está conectado a GND ya que la memoria será
accedida para lectura y escritura.
Según el datasheet, el valor recomendable de las resistencias de pull-up para
velocidades de bus menores o iguales a 100KHz es de 10K más o menos. No es fácil
dar un valor exacto porque dependerá tanto de la velocidad del bus como de su
capacitancia. Este último parámetro a su vez depende en gran medida del circuito (ni
siquiera es lo mismo un circuito de placa impresa que uno montado en un
breadboard).

Circuito para la EEPROM I2C y el microcontrolador AVR.
Nota: las versiones anteriores de Proteus VSM requerirán que las resistencias sean
digitales. Se pueden escoger las partes llamadas PULLUP o cambiar una resistencia
analógica a digital editando su ventana de propiedades, tal como se ve abajo. De lo
contrario, las simulaciones producirán errores o consumirán más ciclos de CPU de lo
necesario. Algunas resistencias tienen una ventana de propiedades diferente y para
cambiar su naturaleza analógica será necesario sobrescribirla en el cuadro de texto
que aparece al hacer clic en Edit all properties as text.
Sobre el programa, la documentación en línea de la función main no deja nada más
que decir. Y sobre las funciones write_24xxx y read_24xxx tampoco hay mayor
misterio: son traducciones casi literales de los procedimientos descritos antes.
Tras cada byte enviado con write_24xxx se recibe el correspondiente bit de
Acknowledge, aunque no se tomen en cuenta porque se da por hecho que se tratan de
bits ACK (que el esclavo reconoce todos los bytes).
No debemos confundir las direcciones de los datos dentro de la memoria con la
dirección del dispositivo. Esta última está contenida en la constante 0xA0 = 10100000.
Los tres bits 3, 2 y 1 valen 0 porque en el circuito los pines A2, A1 y A0 están
conectados a GND. El bit 0 vale inicialmente 0 para indicar una escritura y se pone a 1
con 0xA0 |0x01 para cuando se desee una lectura.

/******************************************************************************
* FileName: main.c
* Purpose: Acceso aleatorio a la EEPROM serial 24xxx (>=32 kBits)
* Processor: ATmel AVR con módulo TWI
* Compiler: AVR IAR C y AVR GCC (WinAVR)
* Author:
*

Shawn Johnson. http://www.cursomicros.com.
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License: Se permiten el uso y la redistribución de este código con
*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y las notas de autor y copyright de arriba.

*****************************************************************************/

#include "avr_compiler.h"
#include "usart.h"
#include "i2c.h"

voidwrite_24xxx(unsignedintaddress,chardata);
charread_24xxx(unsignedintaddress);

intmain(void)
{
charc;unsignedinta;
charcad[]=" Este texto fue escrito en la EEPROM y leído de ella ";

unsignedintlon=strlen(cad);// Obtener longitud de cad

i2c_init();// 100 kHz
usart_init();// 9600 - 8N1

puts("nr Test de la EEPROM serial 24xxx ");
puts("nr ============================== nrnr");
// Escribir todos los caracteres de 'cad' en las primeras
// posiciones de la EEPROM serial
for(a=0;a<lon;a++)
{
c=cad[a];// Obtener elemento a de cad
write_24xxx(a,c);// Escribir c en dirección a
}

// Leer las 'lon' primeras posiciones de la EEPROM y mostrarlas
// en el terminal serial RS232
for(a=0;a<lon;a++)
{
c=read_24xxx(a);// Leer dirección a
putchar(c);// Enviar a terminal
}

sleep_enter();// Entrar en modo Standby
}

//****************************************************************************
// Escribe el dato 'data' en la dirección 'address' de la EEPROM serial.
//****************************************************************************
voidwrite_24xxx(unsignedintaddress,chardata)
{
i2c_start();// START
i2c_write(0xA0);// Slave address + Write
i2c_write(address>>8);// Address high byte
i2c_write(address);// Address low byte
i2c_write(data);// Data to EEPROM
i2c_stop();// STOP
delay_us(5000);// Tiempo de ciclo de escritura interno
}

//****************************************************************************
// Lee un byte de dato de la dirección 'address' de la EEPROM serial.
//****************************************************************************
charread_24xxx(unsignedintaddress)
{
chardata;
i2c_start();// START
i2c_write(0xA0);// Slave address + Write
i2c_write(address>>8);// Address high byte
i2c_write(address);// Address low byte
i2c_restart();// Repeated START
i2c_write(0xA0|0x01);// Slave address + Read
data=i2c_read(1);// Read data & send NACK
i2c_stop();// STOP
returndata;
}
Acknowledge Polling
Con lo visto hasta ahora puede ser más que suficiente para usar una EEPROM serial en
cualquier aplicación. Sin embargo, quienes tenemos esta manía por la microelectrónica
siempre queremos saber algo más. Así que, tras hurgar en el datasheet, encontré la
sección Acknowledge Polling o sondeo del bit de reconocimiento.
Allí dice que mientras la EEPROM esté ocupada responderá con un NACK a los bytes
que se le envíen. Cuando responda con unACK significa que la EEPROM está lista de
nuevo.
Podemos usar esa característica para optimizar los accesos a la memoria. Por ejemplo,
en vez de esperar 5 ms para que la EEPROM termine de grabar un dato, se podría
poner el siguiente procedimiento.

Diagrama de flujo del bucle Acknowledge Polling.
El código quedaría así:

// ...
i2c_stop();// Cierre de algún paquete previo
// El siguiente bucle envía un START y el Byte de Control (con RW = 0)
// hasta que se obtenga un ACK (0) como respuesta. Entonces la EEPROM
// estará lista para la siguiente operación.
do{
i2c_restart();// START condition
ack=i2c_write(0xA0);// Control Byte
}while(ack!=0);
// Siguiente operación
// ...
En este código también se toma la dirección de esclavo 0xA0, asumiendo que los pines
A2, A1 y A0 de la EEPROM I2C están conectados a GND. Se pone la
función i2c_restarten vez del i2c_start presupuesto para asegurar que se inicia otra
transferencia.

Práctica: Uso de Bucle Acknowledge Polling
El programa recibe los datos llegados del terminal RS232 y actúa según lo siguiente:
Si es un carácter imprimible (letra, número, espacio, etc.) o la tecla Intro, se guarda
su valor en la EEPROM en posiciones consecutivas a partir de la dirección 0x0000.
Si es la tecla de Retroceso, se “borra” el carácter anterior.
Si es la tecla Escape, se escribe un 0x00 (como fin de cadena) y se procede a leer todo
lo grabado.
Es el mismo circuito de la práctica previa.
Circuito para la EEPROM I2C y el microcontrolador AVR.
Lo que hace la función principal main es lo de menos. De nuevo vamos a
concentrarnos enwrite_24xxx y read_24xxx. Estas funciones son similares a las de la
práctica anterior. De hecho, si observas bien, sus accesos a la EEPROM son
completamente equivalentes cuando ella está libre; es decir, el código del
bucle Acknowledge Polling:
do{// Bucle Acknowledge Polling
i2c_restart();//
ack = i2c_write(0xA0);//

Enviar START y
Byte de control (para escritura)

}while(ack!=0);// Hasta que sea reconocido
se reduce a la siguiente rutina:
i2c_start();//

Enviar START y

ack = i2c_write(0xA0);//

Byte de control (para escritura)

y el resto se equipara por sí solo.
Muchos suelen usar el bucle Acknowledge Polling al final de la función de escritura, en
sustitución del acostumbrado delay de 5 ms (de hecho, así lo sugiere el datasheet).
Sin embargo, en tal caso el programa se quedará en el bucle casi el mismo tiempo
esperando la escritura completada.
En ciertas aplicaciones sería mejor que todo o parte de dicho tiempo pasara mientras
el procesador ejecuta otras tareas. Eso es lo que se consigue poniendo el bucle al inicio
y testeando la disponibilidad de la EEPROM justo antes de una nueva operación de
lectura o escritura.
/************************************************************************
******
* FileName:
* Purpose:
Polling
* Processor:
* Compiler:
* Author:

main.c
Acceso aleatorio a la EEPROM 24xxx + bucle Acknowledge

ATmel AVR con módulo TWI
AVR IAR C y AVR GCC (WinAVR)
Shawn Johnson. http://www.cursomicros.com.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License:

Se permiten el uso y la redistribución de este código con

*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y las notas de autor y copyright de arriba.

*************************************************************************
****/

#include "avr_compiler.h"
#include "usart.h"
#include "i2c.h"
#include <ctype.h>

void write_24xxx(unsignedint address,char data);
char read_24xxx(unsignedint address);

int main(void)
{
char c;unsignedint a=0;

i2c_init();// 100 kHz
usart_init();// 9600 - 8N1

puts("nr www.cursomicros.com nr");

puts("nr Lo que escriba se irá grabando en la EEPROM nr");
puts("nr Pulse Escape para leer en contenido grabado... nrr");

while(1)
{
if(kbhit())// Si llegó algún dato
{
c = getchar();// Leer dato

if(isprint(c)||(c=='r'))// Si c es imprimible o si es Enter
{
write_24xxx(a++, c);
putchar(c);
}
elseif((c=='b')&&(a))// Si c es Retroceso y si a es > 0
{
putchar(c);
a--;
}
elseif(c==27)// Si c es tecla Escape,...
{
write_24xxx(a++, '0');// Almacenar un 0x00 (fin de
cadena)
a =0;// Resetear puntero de memoria
while( c=read_24xxx(a++))// Volcar los datos de la EEPROM
putchar(c);// hasta encontrar un 0x00
a =0;
}
}
}
}

//***********************************************************************
*****
// Escribe el dato 'data' en la dirección 'address' de la EEPROM serial.
//***********************************************************************
*****
void write_24xxx(unsignedint address,char data)
{
char ack;

do{// Bucle Acknowledge Polling
i2c_restart();//

Enviar START y

ack = i2c_write(0xA0);//

Byte de control (para escritura)

}while(ack!=0);// Hasta que sea reconocido

i2c_write(address >>8);// Byte alto de address
i2c_write(address);// Byte bajo de address
i2c_write(data);// Escribir dato
i2c_stop();//
}
//***********************************************************************
*****
// Lee un byte de dato de la dirección 'address' de la EEPROM serial.
//***********************************************************************
*****
char read_24xxx(unsignedint address)
{
char data, ack;

do{// Bucle Acknowledge Polling
i2c_restart();//

Enviar START y

ack = i2c_write(0xA0);//

Byte de control (para escritura)

}while(ack!=0);// Hasta que sea reconocido

i2c_write(address >>8);// Byte alto de address
i2c_write(address);// Byte bajo de address
i2c_restart();//
i2c_write(0xA0|0x01);// Byte de control (para lectura)
data = i2c_read(1);// Leer y enviar un NACK
i2c_stop();//
return data;
}

Lectura Secuencial y Escritura por Páginas
Por lo visto previamente, para mover un byte de dato a/desde la 24xxx el paquete
transferido debía incluir algunos bytes extras, como el byte de control (dos veces en
las lecturas) y de dirección de memoria (hasta dos bytes). Este proceso puede ser
pesado para algunas aplicaciones, donde se transfieran grandes cantidades de datos.
Afortunadamente, también es posible mover varios datos por paquete. A eso se le
llamalectura secuencial y escritura por páginas. En general, bastará con especificar la
dirección de la primera locación a acceder. Después de cada lectura o escritura,
el puntero de memoria se incrementará automáticamente para acceder a la siguiente
locación.
Como lo evidencian los diagramas de tiempos, los procedimientos software seguidos
en ambos casos es muy similar a como se hacía con un único byte: empieza igual y
termina igual, solo varía la cantidad de data bytes transferidos y un “detallito” que a
continuación se describe.
Por ejemplo, en la lectura la diferencia es que cada byte leído debe ser respondido con
unACK, salvo el último, el cual debe ser respondido con un NACK. Esto es
perfectamente compatible con la lectura de un solo byte ya que ahí el único byte es a
la vez el último.

Esquema de una lectura secuencial de N+1 datos.
Por otro lado, para escribir un bloque de bytes se sigue el mismo procedimiento que
para escribir un solo byte. El obstáculo ahora radica en que el número de bytes
enviados por paquete es limitado y en rangos restringidos, según el espacio de las
páginas. Por eso se llama escritura por páginas. Ahora, ¿qué son las páginas?
Esquema de una escritura por páginas.
Antes, las EEPROMs I2C poseen buffers internos donde reciben temporalmente los
datos. En la 24xx128 este buffer es de 64 bytes, lo que le permite recibir hasta 64
bytes seguidos. Al cerrar el paquete (con el STOP) el buffer entero será volcado a las
celdas de la memoria y empezará el ciclo de escritura interno. Lo bueno es que este
ciclo durará lo mismo que cuando se escribe un solo byte, o sea, 5 ms a lo sumo.
Ahora bien, según el tamaño del buffer interno, el cuerpo de la 24xxxx se puede dividir
en bloques o páginas. Para la 24xx128 estamos hablando de 16KB/64 = 512 páginas;
cadapágina empieza en una dirección múltiplo de 64 y termina en una múltiplo de 64
menos 1.
El punto es que una vez establecido el puntero de memoria, su valor se incrementará
tras cada byte enviado, hasta llegar al límite de la página actual, luego de lo cual
volverá a apuntar al inicio de la página. Como consecuencia, los siguientes bytes
enviados sobrescribirían los datos allí presentes. No olvides esa precaución.

Práctica: Acceso Secuencial a la 24xxx
Supongamos que queremos copiar todo el contenido de una 24xx128 a otra. Si
utilizáramos el direccionamiento aleatorio, por cada byte de dato copiado habría que
transferir otros 3 ó 4 (de control y dirección), con el consiguiente desperdicio de ciclos
de CPU. Además, si cada byte de dato se graba en cerca de 5 ms, los 16384 bytes de
la 24xx128 tardarían más de 80 segundos en grabarse. En esta práctica veremos cómo
optimizar ese trabajo.
En una EEPROM se ha conectado el pin WP a GND para habilitar la escritura en ella y
en la otra dicho pin está sujetada a VCC porque será usada como de solo lectura. Son
detalles.
En cambio, sí es crucial la diferencia en las conexiones de los pines A2, A1 y A0 entre
las dos EEPROMs.
Habrás notado que el valor de las resistencias de pull-up del bus I2C ha disminuido.
Esto revela, aunque grosso modo, su comportamiento ante el incremento tanto de la
velocidad del bus como de su capacitancia debido al aumento de dispositivos I2C
colgados.

Circuito para la EEPROM I2C y el microcontrolador AVR.
En vez de mover los bytes de datos uno por uno de una EEPROM a la otra, el programa
mueve 512 bloques de 32 bytes cada uno, aunque hubiera sido mejor que cada bloque
fuera de 64 bytes para aprovechar todo el tamaño del buffer de página de la 24xx128.
Creo que eso lo podrías hacer tú.
Los pasos de las funciones EEPROM1_read y EEPROM2_write son muy similares a los
deread_24xxx y write_24xxx estudiadas en la práctica anterior. Los pequeños cambios
ya fueron descritos en la teoría y también se repiten en los comentarios del código.

/******************************************************************************
* FileName: main.c
* Purpose: EEPROMs 24xxx: Escritura por Páginas & Lectura Secuencial.
* Processor: ATmel AVR con módulo TWI
* Compiler: AVR IAR C y AVR GCC (WinAVR)
* Author:

Shawn Johnson. http://www.cursomicros.com.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License: Se permiten el uso y la redistribución de este código con
*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y las notas de autor y copyright de arriba.

*****************************************************************************/

#include "avr_compiler.h"
#include "usart.h"
#include "i2c.h"

voidEEPROM2_write(unsignedintaddress,char*p,unsignedcharsize);
voidEEPROM1_read(unsignedintaddress,char*p,unsignedcharsize);

intmain(void)
{
unsignedinti,Address;
unsignedchark;
charbuf[32];// Buffer de 32 bytes

i2c_init();// 100 kHz
usart_init();// 9600 - 8N1
printf("nr 24XX128 cloner");

while(1)
{
printf("nrr Press ENTER to start ");
while(getchar()!='r')// Esperar mientras carácter leído
continue;// no sea 'r' = 0x0D = ENTER

Address=0x0000;
k=0;

for(i=0;i<512;i++)// 512 bloques de 32 bytes
{
EEPROM1_read(Address,buf,32);// Leer bloque de 32 bytes
EEPROM2_write(Address,buf,32);// Grabar bloque de 32 bytes
Address+=32;
if(++k>=32){// Dar mensaje cada 1 KB copiado
printf("rn%5u bytes copiados",Address+1);
k=0;
}
}
printf("rnTerminado ");
}
}
//****************************************************************************
// Escribe los 'size' primeros bytes del array 'p' en la EEPROM serial 2
// a partir de la direccion 'address'.
// Nota: los 'size' bytes no deben rebasar la página actual.
//****************************************************************************
voidEEPROM2_write(unsignedintaddress,char*p,unsignedcharsize)
{
unsignedcharack,i;

do{// Bucle Acknowledge Polling
i2c_restart();// Enviar START y
ack=i2c_write(0xA1);// Slave address + Write
}while(ack!=0);// Hasta recibir ACK

i2c_write(address>>8);// Address high byte
i2c_write(address);// Address low byte

for(i=0;i<size;i++)// Bucle para enviar size bytes de datos
i2c_write(*p++);// Escribir dato apuntado por p

i2c_stop();// Iniciar escritura de bloque
}

//****************************************************************************
// Lee del array 'p' los 'size' bytes consecutivos de la EEPROM serial 1
// a partir de la dirección 'address'.
// Tras cada byte leído se envía un ACK, salvo en el último, donde se envía
// un NACK.
//****************************************************************************
voidEEPROM1_read(unsignedintaddress,char*p,unsignedcharsize)
{
unsignedcharack,i;

do{// Bucle Acknowledge Polling
i2c_restart();// Enviar START y
ack=i2c_write(0xA0);// Slave address + Write
}while(ack!=0);// Hasta recibir ACK

i2c_write(address>>8);// Address high byte
i2c_write(address);// Address low byte
i2c_restart();//
i2c_write(0xA0|0x01);// Slave address + Read

for(i=0;i<size-1;i++)// Bucle para leer size-1 datos
*p++=i2c_read(0);// Leer y enviar un ACK

*p=i2c_read(1);// Leer y enviar un NACK
i2c_stop();//
}

Memorias 24xxx de Baja Densidad
Cuando empezamos con las EEPROM seriales I2C vimos que había diversos modelos.
No solo se diferencian por su capacidad de almacenamiento, sino también por el
tamaño de sus páginas internas y soporte de conexión en cascada. Los dos últimos
aspectos ya los vimos de cerca y los pusimos en práctica.
Recordemos la siguiente tabla.
Tabla de memorias seriales eeprom i2c

Capacidad
Dispositivo

Conexión en cascada
Bits Bytes

24xx01

1K

128 No

24xx02

2K

256 No

24xx04

4K

512 No

24xx08

8K

1 K No

24xx16

16 K 2 K No

24xx32

32 K 4 K Sí

24xx64

64 K 8 K Sí

24xx128

128 K 16 K Sí

24xx256

256 K 32 K Sí

24xx512

512 K 64 K Sí

Las EEPROMs que pueden conectarse en cascada corresponden al grupo de las
llamadasSmart o de alta densidad y las que no, pertenecen al grupo Standard o
memorias de baja densidad. Parece solo una cuestión de denominación, pero no. La
principal diferencia es el modo de direccionamiento.
Como sabemos, la dirección de esclavo de todos estos dispositivos es 1010xxx.
En el caso de las EEPROMs de alta densidadlas xxx se configuran por los pines A2, A1 y
A0 para así permitir la conexión en cascada de varios dispositivos iguales.
Por otro lado, al no soportar conexión en cascada, en las EEPROMs de baja
densidadesos pines no significan nada. ¿Entonces qué pasa con las xxx? Forman parte
de la dirección de los datos.
O sea, mientras que la dirección de un dato en una EEPROM de alta densidad se
especifica en dos bytes separados, en las EEPROMs de baja densidad dicha dirección va
en un byte y se complementa con los tres bits de esas xxx, esto es, los tres bits altos
de la dirección se hallan incrustados en el byte de control, tal como se ven en las
siguientes figuras. Con los 11 bits en total se logra abarcar un rango de hasta 2048
bytes de datos (los de una 24xx16).
Obviamente, el esquema de direccionamiento descrito también se debe cumplir en los
accesos secuenciales de las EEPROM de baja densidad.

Escritura de 1 byte de dato en una EEPROM de baja densidad.

Lectura de 1 byte de dato de una EEPROM de baja densidad.

Práctica: Detección Automática de Memoria
Aparte de sus memorias de programa, de datos EEPROM, etc., sabemos que los AVR
guardan en su chip códigos de ID(Identificación de Dispositivo) que nos permiten
reconocerlos, por ejemplo, desde un programador.
Si hubiera algo parecido en las EEPROM I2C, se podrían hacer programas con accesos
de escritura optimizados aprovechando el tamaño completo de los buffers de página.
Desafortunadamente, no hay.
La presente práctica es una versión en lenguaje C del algoritmo propuesto y
desarrollado en ensamblador por Lucio Di Jasio en la nota de aplicación AN690
deMicrochip Technology Inc. (Lucio Di Jasio también es autor de algunos muy buenos
libros de microcontroladores.)
El lugar de la EEPROM corresponde más bien a un zócalo de 8 pines. Se supone que el
programa debería reconocer a cualquier dispositivo que allí pusiéramos.

Circuito para la EEPROM I2C y el microcontrolador AVR.
La estructura de las funciones de escritura y lectura de datos ha variado respecto de
nuestros códigos anteriores, aunque aún se puede notar una clara equivalencia. Los
cambios más notorios son que el bucle Acknowledge Polling se coloca después de
escrito el dato en WriteByte y que la rutina de direccionamiento se empaqueta aparte
en la funciónSetAddress. Como sea, son rutinas accesorias.
La función clave del programa es MemDetect. Lo primero que hace es detectar si la
24xxx es de tipo Smart o Standard. El mecanismo de Lucio Di Jasio no está
estrictamente sustentado pero funciona. La idea es ésta:
La escritura de un 1 en la dirección 0x000 de la EEPROM asumiendo que se trata de
unSmart produce dos posibles resultados.
WriteByte(1,0x000,1); // Escribir 1 en dirección 0x000 (como Smart)
Si la EEPROM es una Smart, se escribe un 1 en 0x000. Eso es obvio, ¿verdad?
Si la EEPROM es una Standard, se escribe un 0 en 0x000 y un 1 en 0x001. ¿Por qué?
Porque la operación es interpretada como una escritura por páginas de dos bytes.
A continuación se ejecuta una lectura de la dirección 0x000, pero esta vez asumiendo
que la memoria es del tipo Standard.
dato = ReadByte(0,0x000); // Leer dirección 0x000 (como Standard)
De nuevo hay dos posibles resultados:
Si la EEPROM es una Standard, la lectura será correcta y se obtendrá el 0.
Si la EEPROM es una Smart, se consigue leer el 1 de 0x000, pese a ser una operación
con direccionamiento incompleto. Éste era el punto ambiguo.
Así que en este punto ya sabemos de qué tipo de memoria se trata, si es de las
grandes o de las pequeñas. Lo que falta ahora es encontrar su tamaño exacto. El truco
para hallarla es saber que en una EEPROM de N bytes (con direcciones
desde 0 hasta N-1) la dirección N equivale otra vez a la dirección 0; la dirección N+1,
a la 1, y así. Es como si se desbordara el puntero de memoria.
Entonces lo que sigue en el código de MemDetect es buscar que se cumpla esa
condición. Se comparan los datos de las direcciones 0x00 y size. Si son iguales, se
escribe un dato diferente (temp+1) en 0x00 y se vuelve a comparar con el dato
de size. Si son iguales otra vez, es que en efecto la dirección 0x00 corresponde a la
dirección size.
El programa trabaja asumiendo que el Watchdog está habilitado. No está habilitado por
software, así que se debería habilitar por hardware mediante su fuse correspondiente.
/************************************************************************
******
* FileName:

main.c

* Overview:

Programa para detectar automáticamente la capacidad de una

*

EEPROM serial 24xxxx conectada al bus I2C.

* Processor:

ATmel AVR con módulo TWI

* Compiler:

AVR IAR C y AVR GCC (WinAVR)

* Author:

Shawn Johnson. http://www.cursomicros.com.

*
Jasio.

Basado en el AN690 de Microchip Technology, por Lucio Di

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License:

Se permiten el uso y la redistribución de este código con

*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y las notas de autor y copyright de arriba.

*************************************************************************
****/

#include "avr_compiler.h"
#include "usart.h"
#include "i2c.h"

char ReadByte(char smart,int address);
void WriteByte(char smart,int address,char dato);
char SetAddress(char smart,int address);
unsignedint MemDetect(void);

int main(void)
{
unsignedint size;

i2c_init();// 100 kHz
usart_init();// 9600 - 8N1

wdt_reset();
printf("nr Automatic detection of memory size");
printf("nr ==================================");
wdt_reset();
size = MemDetect();
printf("nrr Esta EEPROM es de %u bytes", size);
while(1)
{
wdt_reset();// Pxq el WDT está habilitado
}
}

/************************************************************************
*****
*

Detección automática del tamaño de la memoria 24XXX.

*

Los tamaños de las memorias corresponden a los siguientes modelos.

*
*

SIZE

*

MODEL

|

128

24xx01/21/41

|

256

24xx02/62

|

512

24xx04

*

|

1024

24xx08

*

|

2048

24xx16/164

|

4096

24xx32

|

8192

24xx65/64

*
*

Standard
I2C

*
*
*

Smart

*

Serial

*

| 16384

24xx128

| 32768

24xx256

*************************************************************************
***/
unsignedint MemDetect(void)
{
char dato, temp, smart, type;
unsignedint size;

WriteByte(1,0x000,1);// Escribir 1 en dirección 0x000 (como Smart)

dato = ReadByte(0,0x000);// Leer dirección 0x000 (como Standard)

if(dato==0)// Si dato leído es 0
{
smart =0;// Es una EEPROM Standard
size =128;// size = 128 byte
type =01;// Empezar con type = 24xx01
}
else// De otro modo
{
smart =1;// Sí es una EEPROM Smart
size =4096;// size = 4096 bytes
type =32;// Empezar con type = 24xx32
}

/*** El siguiente código busca el tamaño de la EEPROM ***/

temp = ReadByte(smart,0x00);// Leer dirección 0x000
do{
dato = ReadByte(smart, size);// Leer dirección size
if(dato==temp)// Si los datos leídos son iguales
{
WriteByte(smart,0x00, temp+1);// Escribir temp+1 en dirección
0x00
dato = ReadByte(smart, size);// Leer dirección size
if(dato==(temp+1))// Si aún son iguales, eureka!
break;// Salir del bucle do-while
}
size *=2;// Duplicar size
type *=2;// Duplicar type
}while((type&0x10)==0);
return size;
}

/************************************************************************
*****
*

Lee un byte de la dirección 'address' de la EEPROM serial.

*

El tipo de direccionamiento se pasa en la variable 'smart'.

*************************************************************************
***/
char ReadByte(char smart,int address)
{
char ctrbyte, data;
ctrbyte = SetAddress(smart, address);// set address pointer
/* enter here for sequential reading */
i2c_restart();
i2c_write(ctrbyte|0x01);// Es un comando de lectura
data = i2c_read(1);// Leer y enviar NACK
i2c_stop();
return data;
}

/************************************************************************
*****
* Escribe el byte de 'dato' en la dirección 'address' de la EEPROM
serial.
*

El tipo de direccionamiento se pasa en la variable 'smart'.

*************************************************************************
***/
void WriteByte(char smart,int address,char dato)
{
unsignedchar ack, i=128;
SetAddress(smart, address);// set address pointer
i2c_write(dato);// output DATO
i2c_stop();
do{// Bucle Acknowledge Polling
wdt_reset();
i2c_restart();// i2c_start ???
ack = i2c_write(0xA0);// Un comando de escritura
}while((ack!=0)&&(--i));
i2c_stop();
}

/************************************************************************
*****
*

Establece el Puntero de Direcciones de la EEPROM a 'address'.

*

El parámetro 'smart' determina lo siguiente:

*

- Si smart = 1 -> la EEPROM es es direccionada como Smart.

*

- Si smart = 0 -> la EEPROM es es direccionada como Standard.

*

Retorna el Byte de Control usado. Servirá en la función 'ReadByte'.

*************************************************************************
***/
char SetAddress(char smart,int address)
{
char ctrbyte;
i2c_start();
if(smart)
{
ctrbyte =0xA0;// Guardar Byte de control
i2c_write(ctrbyte);// Enviar byte de control
i2c_write(address>>8);// Enviar MSByte de address
}
else
{
ctrbyte =((unsignedchar)(address>>8))<<1;
ctrbyte |=0xA0;// Sumar MSByte de address con 0xA0
i2c_write(ctrbyte);// Enviar byte de control
}
i2c_write(address);// Enviar LSByte de address
return ctrbyte;
}

El Reloj de Tiempo Real DS1307
¿Para qué queremos un reloj-calendario en chip separado? ¿Acaso no es a fin de
cuentas un simple contador de segundos que se puede implementar cómodamente con
un Timer cualquiera de un microcontrolador cualquiera?
No es tan difícil construir un completo RTC con un microcontrolador. La parte del reloj
es la más simple; solo ajustamos bien la temporización y listo. Luego implementamos
los algoritmos del calendario para reconocer los meses que tienen 28, 29, 30 ó 31
días, los algoritmos para realizar las compensaciones de los años bisiestos,...
Los relojes-calendario no suelen ser el elemento principal en los dispositivos
electrónicos, sino un aditamento que es preferible que opere independientemente,
incluso con su propia fuente de alimentación. Por ejemplo, el procesador de tu
computadora podrá ser muy potente pero aun así el reloj del sistema va en otro chip y
usa su propia batería.
Justificada la necesidad de los RTCs, veremos que existe una casi incalculable cantidad
de RTCs (de diversas marcas, de diversas características). De los relojes de Dallas
semiconductors con interface I2C tomaremos en primer lugar el DS1307. Con decirte
que es como empezar con el PIC16F84A en el mundo de los microcontroladores...

Características del DS1307
El DS1307 es uno de los RTCs I2C más fáciles de usar debido en parte a sus
limitaciones. Aun así, sus características son muy provechosas y son principalmente las
que, con pocas variaciones, se repiten en los modelos de la serie
DS1337, DS1338,DS1339, DS1340, DS1375. Veamos:
Computa los segundos, minutos, horas, días de la semana, días del mes, meses y años
(de 2000 hasta 2099).
Aparte de los registros de hora y fecha del RTC, ofrece una SRAM de 56 bytes que se
podrían usar como RAM extendida del microcontrolador.
Provee por el pin SQW/OUT una señal cuadrada de frecuencia programable.
Alimentación alterna usando una batería. En ausencia o deficiencia de la alimentación
principal de Vcc, el DS1307 pasa automáticamente a alimentarse de la batería.
Soporta el protocolo I2C en Standard Mode (máxima frecuencia de reloj de 100 kHz)

Descripción Funcional de Pines del DS1307
Tabla Pines del DS1307

Diagrama de pines del RTC DS1307 en PDIP
SDA y SCL. Pines de interface I2C.
Vcc y GND. Pines de alimentación. Vcc es típicamente de 5 V.
X1 y X2. Pines para conectar un XTAL de cuarzo estándar externo de 32.768 KHz.Los
capacitores para estabilizar el circuito oscilador se incluyen internamente.
Vbat. Pin para conectar opcionalmente una batería de 2.0 a 3.5 V. Normalmente el
DS1307 operará con su fuente del pin Vcc. En ausencia de dicha tensión o cuando su
nivel caiga por debajo de Vbat, el DS1307 empezará a trabajar con la batería.
SQW/OUT. Por aquí el DS1307 puede sacar una onda cuadrada de cuatro frecuencias:
1 Hz, 4.096 kHz, 8.192 kHz ó 32.768 kHz. Se configura con el registro de control. Es
un pin de drenador abierto y por tanto necesitará de una pull-up si se usa.

Diagrama de Bloques del DS1307

Diagrama de bloques simplificado del DS1307
Algunas de las partes que se aprecian en el esquema de arriba ya fueron aludidas
antes. Ahora citaremos los elementos más relevantes que en adelante nos servirán
para entender mejor el funcionamiento del DS1307:
Los registros de fecha y hora del RTC.
El registro de control. Configura la señal del pin SQW/OUT.
El puntero de registros. Contiene la dirección del registro a acceder.
Los 56 registros de propósito general que se pueden usar como RAM libre de usuario.

Los Registros de Hora y Fecha del DS1307
El primer segmento de la SRAM corresponde a los registros de función del RTC y son
conocidos como Timekeeping registers o Timekeeper registers. Allí se almacenan los
datos de hora y fecha en formato BCD, como en la mayoría de los RTCs.
Tabla de Registros de Hora y Fecha del DS1307

Dirección Registro

Rango
Nibble alto

Nibble bajo

0x00

CH 10 segundos

segundos

00 – 59

0x01

10 minutos

minutos

00 – 59

0x02

0 12/24

0x03

0000

día (de semana) 1 – 7

0x04

10 día

día (de mes)

1 – 31

0x05

10 mes

mes

1 – 12

0x06

10 año

año

00 – 99

10HR
10HR horas
AM/PM

1-12 / 0-23

Como se ve, en general, el nibble bajo de cada registro contiene el dígito de las
unidades y el nibble alto contiene el dígito de las decenas de cada dato BCD. Las
visibles excepciones son los nibbles altos de los segundos y de las horas.
En el registro de segundos el bit 7 es CH(Clock Halt o reloj detenido). No sé qué hace
allí habiendo espacio en el registro de control. Pero bueno, yo no diseñé este
dispositivo.
El bit CH indica si el oscilador del DS1307 está en marcha (0) o está detenido (1).
Siendo de lectura o escritura, significa que:
Si escribimos un 0 en CH, arrancamos el DS1307.
Si escribimos un 1 en CH, detenemos el oscilador del DS1307 y su operación. Sirve
para ahorrar energía cuando no se le necesite. Es como “enviarlo a un modoSleep”.
El valor inicial de CH tras conectar la alimentación es 1. Es decir, si vemos un 1
en CHcuando no debería, tal vez sea porque falló la alimentación y el dispositivo se
“reinició”. La validez de los datos de fecha y hora, por tanto, no estará garantizada. No
olvides eso.
El registro de las horas también es algo tedioso. Su bit 6 (12/24) establece si el
DS1307 operará en modo de 12 horas o de 24 horas si vale 1 ó 0, respectivamente.
En modo 24 horas el bit 5 (10HR / AMPM) complementa el dígito de decenas de
horas. En modo 12 horas el bit 5 señala AM (0) o PM (1).

El Registro de Control del DS1307
Además de los registros de fecha y hora, los RTCs cuentan con registros de controlstatus para las funcionalidades extra que ofrecen: registros para programar alarmas (si
las hubiera), registros para configurar algunas interrupciones, registros para configurar
otros timers (si los hubiera), etc.
Solemos empezar a trabajar con el DS1307 diciendo que es de los más fáciles porque
solo tiene un registro de control, y de pocos bits efectivos :) Su función básica es
configurar la onda cuadrada que saldrá por el pin SQW/OUT del DS1307.
Registro Registro de Control del DS1307

OUT

---

---

SQWE

---

---

RS1

RS0

Registro de Microcontrolador

SQWE

Square Wave Enable
1 = Por el pin SQW/OUT sale una onda cuadrada cuya frecuencia se determina
por los bits RS1 y RS0
0 = El estado del pin SQW/OUT depende del bit OUT

OUT

Output Control
Siendo el bit SQWE = 0:
1 = El pin SQW/OUT se queda en 1 lógico constante
0 = El pin SQW/OUT se queda en 0 lógico constante

BORF

Brown-out Reset Flag
Este bit se pone a uno cuando se produce un Reset Brown-out. Este bit se pone a
cero por un reset Power-on o escribiendo un cero lógico en el flag.
Tabla RS1 RS0

RS1 RS0 Frecuencia de onda del pin SQW/INT
00

1 Hz

01

4.096 kHz

10

8.192 kHz

11

32.768 kHz
Acceso a los Registros del DS1307
Todos los registros del DS1307, ya sean de fecha y hora, el de control o los registros
de propósito general, se acceden igual. Primero escribimos en el puntero de
registros la dirección del registro a acceder y luego efectuamos la lectura o escritura
del registro. Cada transferencia debe seguir las normas del protocolo I2C, empezando
con un STARTy terminando con un STOP.
Tras cada lectura o escritura el puntero de registros se incrementa automáticamente
en 1 para apuntar al siguiente registro, lo cual nos permite acceder a varios registros
secuencialmente por cada transferencia.
En los siguientes diagramas verás que leer y escribir los registros de un RTC es más
simple que leer y escribir datos en una EEPROM I2C, tanto en modo individual como en
bloques. Después de todo, el protocolo I2C es único. Además, ahora las direcciones de
registros son de un byte, no hacen falta rutinas para comprobar la disponibilidad del
dispositivo, etc.

Secuencia de escritura de uno o varios registros en el DS1307.
El procedimiento descrito paso a paso es:
Enviar una Condición START.
Enviar el byte de control (dirección de esclavo con R/W = 0, para escritura).
Enviar la dirección del primer (o único) registro a escribir, Register Address en la
imagen.
Escribir uno o tantos registros como se desee, Register Data 0, 1, x en la imagen.
Enviar una Condición STOP.
Ahora revisemos el procedimiento para leer uno varios registros del DS1307.
Secuencia de lectura de uno o varios registros del RTC DS1307.
Ésta es la secuencia disgregada:
Enviar una condición START.
Enviar el byte de control (dirección de esclavo con R/W = 0, para escritura).
Enviar la dirección del primer (o único) registro a leer, Register Address en la imagen.
Enviar una condición START (llamada START repetida aunque sea lo mismo).
Enviar el byte de control (dirección de esclavo con R/W = 1, para lectura).
Leer uno o tantos registros como se desee. A cada registro leído se responde con
unACK, excepto al último, al cual se le devuelve un NACK. Un único dato es el último.
Enviar una condición STOP.

Dirección de Esclavo del DS1307
Todos los RTC I2C de Dallas Semiconductor tienen la misma dirección de esclavo, que
es fija e igual a 1101000. Sin opción a reconfiguración, implica que solo se puede tener
una de estas partes en un bus I2C. ¡Para qué más!
Recordemos que la dirección de esclavo viaja en el primer byte o byte de control y va
acompañado por el bit R/W para indicar si los siguientes bytes serán de lectura (R/W =
1) o de escritura (R/W = 0).

El byte de control (dirección de esclavo + bit R/W) para el DS1307.
Práctica: Uso del RTC DS1307
Lo que hace el programa es mostrar la fecha y hora en el terminal serial cada segundo.
En el arranque se pide la puesta de fecha y hora iniciales. Como hacer parpadear un
LED, ¿verdad? ¡Qué más se puede hacer con un RTC!
Nota que la resistencia de pull-up del pin SQW/OUT del DS0307 no tiene restricciones
y en su lugar bien se podría activar la pull-up interna del AVR.

Circuito para el RTC DS1307 y el microcontrolador AVR.
Las funciones ds1307_SetTimeDate y ds1307_GetTimeDate acceden a todos los
registros de fecha y hora del DS1307 de manera secuencial para acelerar la
transferencia. Como es obvio, también se les puede acceder individualmente como
al Registro de Controlen

/*** Configurar el Registro de Control para generar una onda cuadrada
de 1 Hz por el pin SQW/OUT ***/
i2c_start();// START
i2c_write(0xD0);// Slave address + Write
i2c_write(0x07);// Dirección de Registro de Control
i2c_write(0b00010000);// Escribir en Registro de Control
i2c_stop();// STOP
Todos los RTCs suelen tener un pin por el que sacan una onda cuadrada o una señal de
interrupción que les permite tener una mejor comunicación con el microcontrolador. De
ese modo el microcontrolador ya no tendrá que sondear el RTC para ver si los datos se
actualizaron.
En el caso del DS1307, este pin es SQW/OUT. En el programa se le ha configurado
para que saque onda cuadrada de 1 Hz, la cual por supuesto está sincronizada con el
progreso del reloj. Creo que no lo dije antes, pero los registros de fecha y hora del
DS1307 se actualizan en el flanco de bajada de dicha onda.
Con ese propósito se ha conectado el pin SQW/OUT al pin INT2 del AVR y se ha
habilitado la interrupción INT2 con el flanco de bajada. No hay ningún inconveniente si
se prefiere usar las interrupciones INT0 o INT1.

/* Configurar y habilitar la Interrupción Externa INT2
* para que se dispare en cada flanco de bajada del pin INT2
*/
EICRA=0x20;
EIMSK=0x04;
sei();
Pasando a otro tema, recordemos que el registro de horas tiene dos posibles formatos:
el de 12 horas (con el bit6 = 1) y el de 24 horas (con el bit6 = 0). Como en el
programa no se activa dicho bit, queda claro que se trabaja en modo de 24 horas.
Y para terminar, habrás notado que mi función getbcd no restringe el ingreso de datos
en ningún caso. Por ejemplo, podrías poner un 80 en los segundos. En tal caso el RTC
funcionará “graciosamente” hasta que los desbordamientos de los registros los lleven a
estados válidos. No quise escribir más código para no agrandarlo demasiado.

/******************************************************************************
* FileName: main.c
* Purpose: Uso del reloj/calendario I2C DS1307
* Processor: ATmel AVR con módulo TWI
* Compiler: AVR IAR C y AVR GCC (WinAVR)
* Author:

Shawn Johnson. http://www.cursomicros.com.
*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License: Se permiten el uso y la redistribución de este código con
*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y las notas de autor y copyright de arriba.

*
* Disclaimer: El uso de este software queda bajo su completa responsabilidad.
*****************************************************************************/

#include "avr_compiler.h"
#include "usart.h"
#include "i2c.h"

/*** Prototipos de funciones ***/
voidds1307_setup(void);
voidds1307_SetTimeDate(char,char,char,char,char,char,char);
voidds1307_GetTimeDate(char*,char*,char*,char*,char*,char*,char*);
voidDisplayTimeDate(void);
chargetbcd(void);

//****************************************************************************
// ISR o manejador de interrupción INT2.
// Esta interrupción INT2 se dispara con el flanco de bajada.
//****************************************************************************
ISR(INT2_vect)
{
DisplayTimeDate();
}

/*** Función principal ***/
intmain(void)
{
i2c_init();// 100 kHz
usart_init();// 9600 - 8N1

printf("nr DS1307: 64 x 8, Serial, I2C Real-Time Clock");
printf("nr ===========================================nr");

ds1307_setup();

/* Configurar y habilitar la Interrupción Externa INT2
* para que se dispare en cada flanco de bajada del pin INT2
*/
EICRA=0x20;
EIMSK=0x04;
sei();

while(1)
{
/* Entrar en modo sleep (Idle Mode).
* Power-down takes the USART to crashed, why?
*/
SMCR=0x01;
sleep_enter();
}
}

//****************************************************************************
// Inicializa la fecha y hora del DS1307 y lo configura para generar una
// onda cuadrada de 1 Hz por el pin SQW/OUT.
//****************************************************************************
voidds1307_setup(void)
{
charsegs,mins,hors,diasem,dia,mes,anio;

// Escribir valores iniciales de fecha y hora en el DS1307
ds1307_SetTimeDate(segs,mins,hors,diasem,dia,mes,anio);

/*** Configurar el Registro de Control para generar una onda cuadrada
de 1 Hz por el pin SQW/OUT ***/
i2c_start();// START
i2c_write(0xD0);// Slave address + Write
i2c_write(0x07);// Dirección de Registro de Control
i2c_write(0x10);// Escribir en Registro de Control 00010000
i2c_stop();// STOP

/*** Poner valores iniciales de fecha y hora del DS1307 ***/
printf("nr Pon año [0..99]: ");anio=getbcd();
printf("nr Pon mes [1..12]: ");mes=getbcd();
printf("nr Pon día [1..31]: ");dia=getbcd();
printf("nr Pon día [1.. 7]: ");diasem=getbcd();
printf("nr Pon hora [0..23]: ");hors=getbcd();
printf("nr Pon minutos [0..59]: ");mins=getbcd();
printf("nr Pon segundos [0..59]: ");segs=getbcd();
printf("nr");
}

//****************************************************************************
// Escribe los registros de Hora y Fecha del DS1307.
// Los parámetros deben estar en formato BCD.
//****************************************************************************
voidds1307_SetTimeDate(charseg,charmin,charhor,chardia,chardds,charmes,charanio)
{
i2c_start();// START
i2c_write(0xD0);// Slave address + Write
i2c_write(0x00);// Dirección de registro Seconds
i2c_write(seg);// Escribir en registro
i2c_write(min);// " " "
i2c_write(hor);// " " "
i2c_write(dia);// " " "
i2c_write(dds);// " " "
i2c_write(mes);// " " "
i2c_write(anio);// " " "
i2c_stop();// STOP
}

//****************************************************************************
// Lee los registros de Hora y Fecha del DS1307.
// Los parámetros salen en formato BCD.
//****************************************************************************
voidds1307_GetTimeDate(char*seg,char*min,char*hor,char*dds,char*dia,char*mes,char*ani)
{
i2c_start();// START
i2c_write(0xD0);// Slave address + Write
i2c_write(0x00);// Dirección de registro Seconds
i2c_restart();// Repeated START
i2c_write(0xD0|0x01);// Slave address + Read
*seg=i2c_read(0);// Leer registro y enviar ACK
*min=i2c_read(0);// " " "
*hor=i2c_read(0);// " " "
*dds=i2c_read(0);// " " "
*dia=i2c_read(0);// " " "
*mes=i2c_read(0);// " " "
*ani=i2c_read(1);// Leer último registro y enviar NACK
i2c_stop();// STOP
}

//****************************************************************************
// Lee los registros de fecha y hora del DS1307 y los visualiza en el
// terminal serial.
// Formato de salida de visualización: 14/03/2009 05:32:45
//****************************************************************************
voidDisplayTimeDate(void)
{
charseg,min,hor,diasem,dia,mes,ani;

/* Leer registros del DS1307 */
ds1307_GetTimeDate(&seg,&min,&hor,&diasem,&dia,&mes,&ani);

if(seg&0x80)// Si el reloj se detuvo (CH = 1?)
{
printf("nr El RTC se detuvo");
}
else
{
/* Mostrar los datos leídos */
printf("rn %2x/%02x/20%02x %02x:%02x:%02x",dia,mes,ani,hor,min,seg);
}
}

//****************************************************************************
// Lee un número BCD de dos dígitos del terminal serial.
//****************************************************************************
chargetbcd(void)
{
unsignedcharc,buff[3],i=0;
while(1){
c=getchar();
if(('0'<=c)&&(c<='9')&&(i<2)){// Si c es dígito 0..9 y si i>0
buff[i++]=c;// Guardar en buffer
putchar(c);// Eco
}
elseif((c=='b')&&(i)){// Si c es BACKSPACE y si i>0
i--;//
putchar(c);// Eco
}
elseif((c=='r')&&(i))// Si c es ENTER y si i>0
break;// Salir del bucle
}
c=buff[0]-'0';
if(i>1){// (i==2)
c<<=4;
c|=(buff[1]-'0');
}
returnc;
}

El RTC con Trickle Charger DS1340
Como dije antes, en los RTCs I2C de Dallas el DS1307 es como el modelo base.
Muchas de sus capacidades, como el uso de una batería de respaldo por si falla la
alimentación principal, se repiten en otras partes.
Así que, asumiendo que al menos conoces elDS1307 y sabes cómo controlarlo, esta
presentación del DS1340 será lo más somera posible, enfocando sobre todo sus
características más distintivas. Algunas de ellas son:
Tiene un Cargador Trickle, que permite al DS1340 ir recargando la batería cuando no
esté operando con la alimentación principal de Vcc.
Tiene un calibrador del oscilador, con el que se le puede dar una mayor precisión al
RTC.
Soporte del protocolo I2C a velocidad Fast Mode (hasta 400 kHz).
No tiene memoria SRAM genérica.

Diagrama de pines del DS1340 (Varía en el DS1340C).
Al ver el dibujo del DS1340 puedes notar su gran similitud con el DS1307. El pin de
batería alterna ahora se llama VBACKUP. Aquí la mayor diferencia sería el pin FT/OUT.
Con ayuda de una pull-up externa, este pin puede quedarse en un estado fijo o sacar
una onda cuadrada de 512 Hz. La frecuencia de esta onda podía ser de 4 frecuencias
en elDS1307.
Los RTCs de la serie DS1340C vienen en encapsulados de 16 pines, aunque la gran
mayoría es del tipo NC (sin conexión). Sucede que este modelo incluye el XTAL
internamente.

Los Registros del DS1340
La siguiente tabla muestra todos los registros del DS1340. Puedes ver que hay dos
grupos: los registros de fecha y hora, y los registros de control-estado (Control, Trickle
charger y Flag). Este modelo no tiene registros SRAM de propósito general.
Tabla Registros del DS1340

Contenido
Dirección Nombre

Rango
Nibble alto

0x00

Seconds

Nibble bajo

EOSC 10 segundos

segundos

00 –59
Tabla Registros del DS1340

Contenido
Dirección Nombre

Rango
Nibble alto

Nibble bajo

10 minutos

minutos

0x01

Minutes

0x02

Century/hours CEB CB 10 horas horas

0-1/0-23

0x03

Day

0000

día (de semana)

1–7

0x04

Date

10 día

día (de mes)

1 – 31

0x05

Month

10 mes

mes

1 – 12

0x06

Year

10 año

año

00 – 99

0x07

Control

OUT FT

0x08

Trickle charger TCS3 TCS2 TCS1 TCS0 DS1 DS0 ROUT1 ROUT0 ---

0x09

Flag

OSF 0

S

0

00–59

CAL4 CAL3 CAL2 CAL1 CAL0 ---

0

0

0

0

0

---

Los bits EOSC (del registro seconds) y OSF (del registro Flags) son parientes.
EOSC (Enable Oscillator) arranca el oscilador (cuando se le escribe un 0) o lo detiene
(cuando se le escribe un 1).
OSF (Oscillator Stop Flag) es un flag que se activa a 1 cuando el oscilador se detiene
por algún motivo, como una falla de la alimentación. Se debe limpiar por software.
Nota también que el registro de horas solo acepta el formato de 24 horas, aunque
ahora se le han sumado los bits CEB y CB. CB bascula cuando se pasa del año 2099 al
año 2000, siempre que en CEB se haya escrito 1. Para nuestros tiempos podemos
dejar CEB en 0 y simplemente nos olvidamos del resto.
Bien, ahora solo nos queda hablar de los registros Control y Trickle charger.

El Registro CONTROL
Control register tiene dos funciones: configurar la señal del pin FT/OUT y calibrar el
oscilador del DS1340.
Solemos empezar a trabajar con el DS1307 diciendo que es de los más fáciles porque
solo tiene un registro de control, y de pocos bits efectivos :) Su función básica es
configurar la onda cuadrada que saldrá por el pin SQW/OUT del DS1307.
Registro CONTROL del DS1307

OUT

FT

S

CAL4

CAL3

CAL2

CAL1

CAL0

Registro de Microcontrolador

bits 5-0

S – CAL4 – CAL3 – CAL2 – CAL1 – CAL0
Estos bits representan un número relacionado con la cantidad de ciclos de reloj
que se añadirán o restarán en el circuito del oscilador. Es una calibración manual
que puede ignorarse dejando todos estos bits a 0 (su valor por defecto).

FT

Frecuency Test
1 = Por el pin FT/OUT sale una señal que bascula a 512 Hz.
0 = El estado del pin FT/OUT depende del bit OUT

OUT

Output Control
Siendo el bit FT = 0:
1 = El pin FT/OUT se queda en 1 lógico constante
0 = El pin FT/OUT se queda en 0 lógico constante

Se sabe que el RTC basa su operación en la señal de 1 Hz generada a partir del circuito
del XTAL de 32 kHz. Es decir, hay todo un circuito divisor que convierte esos 32 kHz en
una señal de 1 Hz, la cual debiera ser lo más perfecta posible; cosa que en la práctica
nunca se dará. Por más pequeño que sea el error, producirá una desviación de varios
segundos por año.
Pues bien, el DS1340 provee un mecanismo para paliar el error del oscilador restando
o sumando algunos pulsos de reloj en el circuito divisor. Esta calibración manual es
una labor muy engorrosa por depender de varios factores y situaciones; de modo que
no la describiremos aquí. Posteriormente se verá el DS3232, un RTC de precisión que
tiene la capacidad de auto-calibrarse.

El Cargador Trickle y el Registro TRICKLE
El registro Trickle Charger tiene la exclusiva función de configurar el Cargador Trickle.
El cargador Trickle es un circuito que puede recargar la batería conectada al
pinVBACKUP. Tiene un diodo y tres resistencias como sus elementos representativos.
La corriente puede fluir por ellos dado que el nivel de Vcc debe ser mayor que el
deVBACKUP (que no debe pasar de 3.7V).

Esquema básico del Trickle Charger del DS1340.
Al inicio el cargador Trickle está desactivado, o sea no hay conexión entre los
terminalesVcc y VBACKUP , como se muestra arriba.
Cerrando los switches adecuadamente podemos obtener las distintas configuraciones
que se muestran en la siguiente tabla. Es más fácil ver primero el circuito resultante y
fijarse luego en el código que le da lugar. Por ejemplo, si quieres activar el Cargador
Trickle y que esté formado por el diodo y la resistencia de 4 K, el valor que debes
cargar en el registro Trickle es 10101011. Si no deseas activar el cargador Trickle,
puedes tomar el código 00000000 (así inicia el DS1340).

Tabla Registro Trickle del DS1340

Registro Trickle
FUNCIÓN
TCS3 TCS2 TCS1 TCS0 DS1 DS0 ROUT1 ROUT0
1

0

1

0

0

1

0

1

Sin diodo, resistor 250

1

0

1

0

1

0

0

1

Un diodo, resistor 250

1

0

1

0

0

1

1

0

Sin diodo, resistor 2k

1

0

1

0

1

0

1

0

Un diodo, resistor 2k

1

0

1

0

0

1

1

1

Sin diodo, resistor 4k

1

0

1

0

1

0

1

1

Un diodo, resistor 4k

x

x

x

x

0

0

x

x

Sin Trickle charger
Tabla Registro Trickle del DS1340

Registro Trickle
FUNCIÓN
TCS3 TCS2 TCS1 TCS0 DS1 DS0 ROUT1 ROUT0
x

x

x

x

1

1

x

x

Sin Trickle charger

x

x

x

x

x

x

0

0

Sin Trickle charger

La lógica nos recomienda que, si habilitamos el cargador, deberíamos usar el diodo (no
vaya a ser que la batería se descargue en la fuente de Vcc si ésta baja su nivel :).

Acceso a los Registros del DS1340
A estas alturas ya debes haber captado bien los procedimientos seguidos para acceder
a los registros internos de cualquier dispositivo I2C, que no son establecidos por cada
uno de ellos, sino que son fijados por el protocolo I2C. Así que no se presentan los
diagramas de acceso para no redundar. Si deseas, puedes revisar Acceso a los
Registros del DS1307, que es igual, excepto porque:
En el DS1340 el puntero de registros tiene alcance limitado: solo permite realizar
accesos secuenciales hasta el registro de dirección 0x07. Los registros subsiguientes
(de direcciones 0x08 y 0x09), por tanto, deberían ser accedidos individualmente.
También habíamos dicho que todos los RTC de esta familia tienen la misma dirección
de esclavo 1101000.

Práctica: Uso del RTC DS1340
En el programa se aprecia el uso de los recursos del DS1340, excepto la calibración del
oscilador.
Hasta donde yo sé, ningún DS1340 viene en empaque PDIP, así que dudo que lo
puedas armar en un breadboard :(
Además el modelo DS1340C tiene un empaque de 16 pines. Si tienes uno de esos,
será mejor que revises la disposición de pines en el datasheet. Verás que la mayoría
de los pines son NC (No connection), aunque en el circuito todos ellos se deben
conectar a GND. Fuera de eso, la práctica debería funcionar igual.
Circuito para el RTC DS1340 y el microcontrolador AVR.
Gran parte de este programa es muy parecido al de la anterior práctica. Veamos
algunas novedades.
La frecuencia de onda del pin FT/OUT no podía ser otra que 512 Hz. Se pudo conectar
el pin FT/OUT a cualquiera de los tres pines INT del AVR y programar su interrupción,
como en la práctica anterior. Y la ISR habría cambiado para visualizar los datos luego
de contar 256 interrupciones.
Pero, ¿alguien dijo contar? Si de contar pulsos externos se trata, quién mejor para
hacerlo que un Timer trabajando en modo contador.

/* Configurar el Timer0 para operar en modo contador (sin prescaler).
* El Timer0 incrementará con cada flanco de bajada del pin T0.
*/
TCCR0A=0X00;
TCCR0B=0X06;
TCNT0=0x00;// Iniciar la cuenta en 0
De ese modo el Timer0 se desbordará con cada 256 pulsos, desbordamiento que
disparará su interrupción porque así se programado y en la ISR habrá que contar dos
de estas interrupciones para recién mostrar la hora.
/* Habilitar la interrupción de desbordamiento del Timer0 */
TIMSK0|=(1<<TOIE0);
sei();

/******************************************************************************
* FileName: main.c
* Purpose: Uso del RTC I2C con Trickle Charger DS1340
* Processor: ATmel AVR con módulo TWI
* Compiler: AVR IAR C y AVR GCC (WinAVR)
* Author:

Shawn Johnson. http://www.cursomicros.com.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License: Se permiten el uso y la redistribución de este código con
*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y las notas de autor y copyright de arriba.

*
* Disclaimer: El uso de este software queda bajo su completa responsabilidad.
*****************************************************************************/

#include "avr_compiler.h"
#include "usart.h"
#include "i2c.h"

/*** Prototipos de funciones ***/
voidds1340_setup(void);
voidds1340_SetTimeDate(char,char,char,char,char,char,char);
voidds1340_GetTimeDate(char*,char*,char*,char*,char*,char*,char*);
voidds1340_write(charaddress,chardata);
chards1340_read(charaddress);
voidDisplayTimeDate(void);
chargetbcd(void);

//****************************************************************************
// ISR o manejador de interrupción (del Timer0).
// La interrupción del Timer0 debería dispararse cada 500 ms.
//****************************************************************************
ISR(TIMER0_OVF_vect)
{
staticunsignedcharints=0;

/* Este bloque se debe ejecutar cada dos interrupciones, cada 1 segundo */
if(++ints>=2)
{
DisplayTimeDate();
ints=0;
}
}

/*** Función principal ***/
intmain(void)
{
i2c_init();// 100 kHz
usart_init();// 9600 - 8N1

printf("nr DS1340: I2C RTC with Trickle Charger");
printf("nr ====================================nr");

ds1340_setup();

/* Configurar el Timer0 para operar en modo contador (sin prescaler).
* El Timer0 incrementará con cada flanco de bajada del pin T0.
*/
TCCR0A=0X00;
TCCR0B=0X06;
TCNT0=0x00;// Iniciar la cuenta en 0

/* Habilitar la interrupción de desbordamiento del Timer0 */
TIMSK0|=(1<<TOIE0);
sei();

while(1)
{
/* Entrar en modo sleep (Idle mode). */
SMCR=0x01;
sleep_enter();
}
}
//****************************************************************************
// Pone valores iniciales de fecha y hora en el DS1340.
// Se configura el DS1340 para sacar por el pin FT/OUT una señal de 512 Hz
// El oscilador corre sin alteración de sus ciclos (sin calibración).
// Se habilita el Trickle Charger con un diodo y una resistencia de 2 K.
// Se limpia el flag OSF, que de seguro se activó.
//****************************************************************************
voidds1340_setup(void)
{
charsegs,mins,hors,diasem,dia,mes,anio;

// Configurar el pin FT/OUT para generar una señal que bascula a 512 Hz
// y dejar sin modificación los ciclos del oscilador (sin calibración).

ds1340_write(0x07,0x40);// Escribir en Control Register 01000000

// Activar el Trickle Charger con un diodo y una resistencia de 2 Kohm.

ds1340_write(0x08,0xaa);// Escribir en Trickle-Charger Register 10101010

// Limpiar el bit OSF (Oscilator Stop Flag). Se activa en el encendido

ds1340_write(0x09,0x00);// Escribir en Flag Register 00000000
/*** Poner valores iniciales de fecha y hora del DS1340 ***/
printf("nr Pon año [0..99]: ");anio=getbcd();
printf("nr Pon mes [1..12]: ");mes=getbcd();
printf("nr Pon día [1..31]: ");dia=getbcd();
printf("nr Pon día [1.. 7]: ");diasem=getbcd();
printf("nr Pon hora [0..23]: ");hors=getbcd();
printf("nr Pon minutos [0..59]: ");mins=getbcd();
printf("nr Pon segundos [0..59]: ");segs=getbcd();
printf("nr");

// Escribir valores iniciales de fecha y hora en el DS1340
ds1340_SetTimeDate(segs,mins,hors,diasem,dia,mes,anio);
}

//****************************************************************************
// Escribe los registros de Hora y Fecha del DS1340.
// Los parámetros deben estar en formato BCD.
//****************************************************************************
voidds1340_SetTimeDate(charseg,charmin,charhor,chardia,chardds,charmes,charanio)
{
i2c_start();
i2c_write(0xD0);// Slave address + Write
i2c_write(0x00);// Seconds register address
i2c_write(seg);// Data to register
i2c_write(min);// " " "
i2c_write(hor);// " " "
i2c_write(dds);// " " "
i2c_write(dia);// " " "
i2c_write(mes);// " " "
i2c_write(anio);// " " "
i2c_stop();
}

//****************************************************************************
// Lee los registros de Hora y Fecha del DS1340.
// Los parámetros salen en formato BCD.
//****************************************************************************
voidds1340_GetTimeDate(char*seg,char*min,char*hor,char*dds,char*dia,char*mes,char*ani)
{
i2c_start();
i2c_write(0xD0);// Slave address + Write
i2c_write(0x00);// Dirección de primer registro
i2c_restart();
i2c_write(0xD0|0x01);// Slave address + Read
*seg=i2c_read(0);// Leer registro y enviar ACK
*min=i2c_read(0);// " " "
*hor=i2c_read(0);// " " "
*dds=i2c_read(0);// " " "
*dia=i2c_read(0);// " " "
*mes=i2c_read(0);// " " "
*ani=i2c_read(1);// Leer último registro y enviar NACK
i2c_stop();
}

//****************************************************************************
// Escribe 'data' en el registro de dirección 'address' del DS1340.
//****************************************************************************
voidds1340_write(charaddress,chardata)
{
i2c_start();
i2c_write(0xD0);// Slave address + Write
i2c_write(address);// Register address
i2c_write(data);// Data to register
i2c_stop();
}

//****************************************************************************
// Lee el registro de dirección 'address' del DS1340.
//****************************************************************************
chards1340_read(charaddress)
{
charreg;
i2c_start();
i2c_write(0xD0);// Slave address + Write
i2c_write(address);// Register address
i2c_restart();
i2c_write(0xD0|0x01);// Slave address + Read
reg=i2c_read(1);// Leer registro y enviar NACK
i2c_stop();
returnreg;
}

//****************************************************************************
// Lee los registros de fecha y hora del DS1340 y los visualiza en el
// terminal serial.
// Formato de salida de visualización: 14/03/2009 05:32:45
//****************************************************************************
voidDisplayTimeDate(void)
{
charsegs,mins,hors,diasem,dia,mes,anio;
charflags;

flags=ds1340_read(0x09);// Leer registro 'Flag Register'

if(flags&0x80)// Si el RTC se detuvo (OSF = 1?)
{
printf("nr El RTC se detuvo");
}
else
{
/* Leer registros de fecha y hora del DS1340 */
ds1340_GetTimeDate(&segs,&mins,&hors,&diasem,&dia,&mes,&anio);
/* Mostrar los datos leídos */
printf("rn %2x/%02x/20%02x %02x:%02x:%02x",dia,mes,anio,
(hors&0x3F),// Ignorar bits CEB y CB (por si acaso :)
mins,
(segs&0x7F));// Ignorar bit EOSC (por si acaso :)
}
}

//****************************************************************************
// Lee un número BCD de dos dígitos del terminal serial.
//****************************************************************************
chargetbcd(void)
{
unsignedcharc,buff[3],i=0;
while(1){
c=getchar();
if(('0'<=c)&&(c<='9')&&(i<2)){// Si c es dígito 0..9 y si i>0
buff[i++]=c;// Guardar en buffer
putchar(c);// Eco
}
elseif((c=='b')&&(i)){// Si c es BACKSPACE y si i>0
i--;//
putchar(c);// Eco
}
elseif((c=='r')&&(i))// Si c es ENTER y si i>0
break;// Salir del bucle
}
c=buff[0]-'0';
if(i>1){// (i==2)
c<<=4;
c|=(buff[1]-'0');
}
returnc;
}

El RTC de Gran Precisión DS3232
Cuando buscamos información sobre RTCs un tema que se nos presenta con
recurrencia cuasi obsesiva es el de la precisión. Al inicio solo buscamos información
práctica que nos sirva para el momento. Y la verdad es que en ese momento el hecho
de que un RTC pueda tener una imprecisión de 0.03 segundos por hora honestamente
nos interesa un bledo.
Pero cuando vamos a emprender algún proyecto duradero nos empieza a preocupar
que un error casi imperceptible como el citado pueda provocar una desviación de
varios minutos por año, comúnmente cuatro o cinco en un RTC estándar operando a
temperatura ambiente.
Como sabemos, los osciladores usados por los dispositivos digitales están basados en
un cristal de cuarzo. El cristal funciona como un circuito RLC-C, muy conocido con el
nombre de oscilador Pearson (busca en Google si quieres saber más). Lo puedes ver
en la siguiente figura.

Circuito del oscilador del DS3232.
Los capacitores de los costados ayudan a estabilizar el oscilador para conseguir un
mejor factor de calidad Q (mejor precisión, para no hablar raro).
Por más que los RTCs incluyan los capacitores más adecuados internamente y en
algunos casos también un cristal de muy buen corte, el oscilador sufrirá ligeras
desviaciones debidas a factores externos como el ruido o la humedad ambientales pero
principalmente la temperatura.
El punto es que el DS3232 está diseñado para poder regular el valor de sus
capacitores, ya que son ellos los mayores afectados por la temperatura. Los
capacitores serán auto-calibrados de acuerdo con la temperatura que toma gracias al
sensor que lleva incorporado. Este sistema es muy sofisticado en los RTCs y se conoce
como TCXO(Temperature-Compensated Crystal Oscillator).
Otras características del DS3232 adicionales a las ya esperadas son:
Precisión de hasta ±2ppm. Esto da una desviación menor de 2 minutos por año.
236 registros SRAM de propósito general.
Dos alarmas programables.
Sensor de temperatura de ±3°C de precisión. La idea no es brindar una oferta 2 por 1,
“combo RTC + sensor”, no. Si bien puede usarse este sensor para medir temperaturas,
debe evitarse someter el dispositivo a condiciones extremas a propósito.
Salida de señal de interrupción para las alarmas.
Operación en Fast mode (velocidad de transferencia de datos de hasta 400 kbits/s).
Uso de batería de respaldo.

Descripción Funcional de Pines del DS3232

Diagrama de pines del RTC DS3232.
Los pines novedosos que merecen explicación son:
RST. Es un pin de E/S donde se puede conectar un pulsador para resetear el RTC. A la
vez, si el DS3232 detecta que el nivel de Vcc cae por debajo de 2.5V (valor típico),
pondrá RST en 0. Es de drenador abierto pero ya incorpora una pull-up de 50 k.
32kHz. Por aquí se puede sacar una onda cuadrada de 32 kHz (difícil de olvidar).
INT/SQW. Es parecido al pin SQW/OUT del DS1307. Es de drenador abierto sin pull-up
interna y puede quedarse en un estado fijo o sacar una onda de cuatro frecuencias (1
Hz, 1.024 kHz, 4.096 kHz u 8.192 kHz). Adicionalmente, puede activarse por acción de
las alarmas.

Los Registros del DS3232
Impresionante la cantidad de registros que tiene este RTC. Pero enseguida te los
presento para que te sientas en confianza.
Tabla de Registros del DS3232

Contenido
Dirección

Nombre
Bit 7

Bit 6

Bit 5

Bit 4

Bit 3

Bit 2 Bit 1 Bit 0

0x00

10 segundos

segundos

Seconds

0x01

10 minutos

minutos

Minutes

0x02

12/24

horas

Hours

0x03

0000

día (de semana)

Day

0x04

10 día

día (de mes)

Date

0x05

Century 10 mes

mes / siglo

Month/Century

0x06

10 año

año

Year

0x07

A1M1 10 segundos

segundos

Alarm 1 seconds

0x08

A1M2 10 minutos

minutos

Alarm 1 minutes

0x09

A1M3 12/24

AMPM
10 horas
10HR

AMPM 10 horas horas

Alarm 1 hours
Tabla de Registros del DS3232

Contenido
Dirección

Nombre
Bit 7

Bit 6

Bit 5

Bit 4

Bit 3

Bit 2 Bit 1 Bit 0

10HR
día (de semana)
día (de mes)

0x0A

A1M4 DY/DT

Alarm 1 day
Alarm 1 date

10 minutos

Alarm 2 minutes

10 día

0x0B

A1M2 10 minutos

0x0C

A1M3 12/24

AMPM
10 horas horas
10HR

Alarm 2 hours

día (de semana)
día (de mes)

0x0D

A1M4 DY/DT

Alarm 2 day
Alarm 2 date

10 día

0x0E

EOSC

BBSQW CONV RS2

RS1

INTCN A2IE A1IE Control

0x0F

OSF

BB32kHz CRATE1 CRATE0 EN32kHz BSY

0x10

SIGN

DATA

DATA DATA

DATA

DATA DATA DATA Aging Offset

0x11

SIGN

DATA

DATA DATA

DATA

DATA DATA DATA MSB of Temp

0x12

DATA

DATA

0

0

0

0

0

0

LSB of Temp

0x13

0

0

0

0

0

0

0

0

No usado

0x14

x

X

x

x

x

x

x

x

SRAM

...

...

...

...

...

...

...

...

...

SRAM

0xFF

x

X

x

x

x

x

x

x

SRAM

A2F A1F Control/Status

En el primer grupo, correspondiente a los registros de fecha y hora. Destaca el
bit Centurydel registro Month/Century. Él se activa a 1 al pasar del año 99 al año 00.
La Alarma y los Registros de Alarma
El segundo grupo lo forman los registros de las alarmas. Cuando las alarmas están
activas, el DS3232 comparará periódicamente sus registros con los registros de fecha
y hora. Cuando encuentre coincidencias activará la alarma. Tan simple como eso. Se
deduce por tanto que ambos tipos de registros deberían tener el mismo formato, sin
considerar los bits AxMy ni los bits DY/DT.
Los bits AxMy y DY/DT sirven para programar la frecuencia de activación de las
alarmas. Todas las combinaciones válidas se muestran en las siguientes tablas. Los
valores no presentes no deberían ser considerados.

Registros de Alarma del DS3232

Máscaras en registros de alarma 1
DY/DT

Razón de alarma
A1M4

A1M3 A1M2 A1M1

×

1

1

1

1

Una vez por segundo

×

1

1

1

0

Cuando coincidan segundos

×

1

1

0

0

Cuando coincidan segundos y
minutos

×

1

0

0

0

Cuando coincidan segundos,
minutos y hora

0

0

0

0

0

Cuando coincidan segundos,
minutos, hora y date

1

0

0

0

0

Cuando coincidan segundos,
minutos, hora y día

Tabla Registros de Alarma del DS3232

Máscaras en registros de alarma 2
DY/DT

Razón de alarma
A2M4

×

A2M3 A2M2

1

1

1

Una vez por minuto (00 segundos de
Registros de Alarma del DS3232

Máscaras en registros de alarma 1
DY/DT

Razón de alarma
A1M4

A1M3 A1M2 A1M1
cada minuto)

×

1

1

0

Cuando coincidan minutos

×

1

0

0

Cuando coincidan minutos y hora

0

0

0

0

Cuando coincidan minutos, hora y
date

1

0

0

0

Cuando coincidan minutos, hora y
día

Hasta ahora solo hemos descrito parte de la programación de las alarmas. Aún queda
por establecer si sus coincidencias activarán (a 0) o no el pin INT/SQW para
comunicarle el evento al microcontrolador.
En los registros Control y Control/Status, están los bits de habilitación y los bits de flag
de cada alarma. Si una alarma está habilitada, la coincidencia activará el flag
respectivo y luego el pin INT/SQW se pondrá a 0, siempre que el bit INTCN sea 1.

Los Registros CONTROL y CONTROL/STATUS
El registro Control programa la señal del pin INT/SQW, las interrupciones de las
alarmas, la frecuencia de medida de la temperatura del chip, y el estado del oscilador.
Registro Registros de control del DS3232

EOSC

BBSQW CONV

RS2

RS1

Registro de Microcontrolador

EOSC

Enable Oscillator
1 = Detiene el oscilador del DS3232
0 = Arranca el oscilador del DS3232

BBSQW

Battery-Backed Square-Wave Enable

INTCN

A2IE

A1IE
1 = La onda cuadrada del pin INT/SQW sigue activa si el DS3232 se alimenta de
la batería
0 = Aunque se haya programado la onda cuadrada del pin INT/SQW, ésta se
apaga cuando el DS3232 pase alimentarse de la batería
CONV

Convert Temperature
Normalmente el DS3232 utiliza su sensor para tomar la temperatura del chip
automática y periódicamente (según la frecuencia programada). Pero si se
quiere forzar una conversión, se debe usar este bit.
1 = Inicia una nueva conversión de temperatura
0 = No inicia una conversión

INTCN

Interrupt Control
1 = El pin INT/SQW se usa como señal de interrupción de las alarmas, siempre
que hayan sido habilitadas (leer más abajo). La coincidencia de cualquier
alarma activa este pin a 0.
0 = Por el pin INT/SQW sale un onda cuadrada cuya frecuencia depende de los
bits RS2 y RS1.

RS2:RS1:

Rate Select
Siendo el bit INTCN = 0:
Tabla RS1 RS0

RS1 RS0 Frecuencia de onda del pin INT/SQW
00
01

1.024 kHz

10

4.096 kHz

11
A1IE

1 Hz

8.193 kHz

Alarm1 Interrupt Enable
1 = Habilita interrupción de alarma 1
0 = Inhabilita interrupción de alarma 1
A2IE

Alarm2 Interrupt Enable
1 = Habilita interrupción de alarma 2
0 = Inhabilita interrupción de alarma 2

El registro Control/Status tiene bits para establecer la frecuencia de conversión
delsensor de temperatura, bits para configurar la señal del pin 32kHz y cuatro bits de
flag.
Registro de control y estado del DS3232

OSF

BB32kHz CRATE1

CRATE0

EN32kHz

BSY

A2F

A1F

Registro de Microcontrolador

OSF

Oscillator Stop Flag
1 = Oscilador detenido (por deficiencias en Vcc y Vbat, primera
alimentación,...)
0 = El oscilador sigue en marcha

BB32kHz

Battery-Backed 32kHz Output
1 = La onda cuadrada del pin 32kHz sigue activa si el DS3232 se alimenta de
la batería
0 = Aunque se haya programado la onda cuadrada del pin 32kHz, ésta se
apagará cuando el DS3232 pase alimentarse de la batería

CRATE1:

Conversion Rate

CRATE0

El DS3232 toma y convierte la temperatura del chip periódicamente. Según el
valor obtenido calibrará sus capacitores variables.
Tabla CRATE1 : CRATE0

CRATE1 : CRATE0 Frecuencia de conversión de temperatura
00

Cada 64 segundos

01

Cada 128 segundos
10
11
BSY

Cada 256 segundos
Cada 512 segundos

Enable Oscillator
1 = El sensor de temperatura está realizando un conversión. Debe evitarse
forzar una nueva conversión con el bit CONV del registroControl.
0 = No hay una conversión en curso

EN32kHz

Enable 32kHz Output
1 = Habilita la onda cuadrada del pin 32kHz
0 = Inhabilita la onda cuadrada del pin 32kHz

A1F

Alarm1 Flag
1 = Se cumplió la coincidencia de la alarma 1. Se debe limpiar por software
0 = No se cumplió la coincidencia

A2F

Alarm2 Flag
1 = Se cumplió la coincidencia de la alarma 2. Se debe limpiar por software
0 = No se cumplió la coincidencia

El Registro AGING OFFSET
Como tantas veces se dijo, el DS3232 usa su sensor para tomar la temperatura del
chip y con el valor obtenido calibra sus capacitores variables automáticamente. Pues
bien, si tú eres todo un experto y crees que puedes ayudarle con la calibración, puedes
usar este registro. El DS3232 hace su trabajo basándose solo en la temperatura y tú
sabes que eso no siempre basta, que hay otros factores que desvían el oscilador.
Dejando todo el registro a 0 te ahorras la “molestia”.

Los Registros de Temperatura
El DS3232 guarda los valores de la temperatura que toma en estos dos registros.
Juntos forman un dato de 10 bits con signo (SIGN) y de complemento a 2. El primer
registro contiene la parte entera y el segundo registro contiene los dos bits de la parte
fraccionaria, lo cual le da una resolución de 0.25°C.
Primer Registro de temperatura del DS3232
SIGN

DATA

DATA

DATA

DATA

DATA

DATA

DATA

0

0

0

0

0

Segundo Registro de temperatura del DS3232

SIGN

SIGN

0

Ejemplos:
Tabla Dato

Dato

Valor de la temperatura

00000000 00 0.0°C
00000101 01 5.25°C
11110110 00 -10.0°C
11110101 01 -10.75°C
Si eres novicio con estos códigos, puedes echarle un vistazo al apartado dedicado al
primer sensor de temperatura.

Acceso a los Registros del DS3232
La dirección de esclavo sigue siendo 0xD0 (incluyendo bit R/W = 0), el puntero de
registros puede barrer toda la SRAM para accesos secuenciales totales. Qué más puedo
decir... Si tienes dudas, puedes releer Acceso a los Registros del DS1307, que es igual.

Práctica: Uso del RTC de Precisión DS3232
En esta práctica con el DS3232 solo se verá el uso de los registros de fecha y hora, y
algunas configuraciones de los registros Control, Control/Status y Aging Offset. Tal vez
ponga ejemplos más completos en adelante.

El Circuito
Hasta donde yo vi, el DS3232 tampoco viene en empaque PDIP, así que dudo que lo
puedas armar en un breadboard :( Fuera de eso, la práctica debería funcionar igual.
Circuito para el RTC DS3232 y el microcontrolador AVR.

El Código Fuente
/******************************************************************************
* FileName: main.c
* Purpose: Uso del RTC de gran precisión y con XTAL incorporado DS3232
* Processor: ATmel AVR con módulo TWI
* Compiler: AVR IAR C y AVR GCC (WinAVR)
* Author:

Shawn Johnson. http://www.cursomicros.com.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License: Se permiten el uso y la redistribución de este código con
*

modificaciones o sin ellas, siempre que se mantengan esta
*

licencia y las notas de autor y copyright de arriba.

*
* Disclaimer: El uso de este software queda bajo su completa responsabilidad.
*****************************************************************************/

#include "avr_compiler.h"
#include "usart.h"
#include "i2c.h"

/*** Prototipos de funciones ***/
voidds3232_setup(void);
voidds3232_SetTimeDate(char,char,char,char,char,char,char);
voidds3232_GetTimeDate(char*,char*,char*,char*,char*,char*,char*);
voidds3232_write(charaddress,chardata);
chards3232_read(charaddress);
voidDisplayTimeDate(void);
chargetbcd(void);

//****************************************************************************
// ISR o manejador de interrupción INT2.
// Esta interrupción INT2 se dispara con el flanco de bajada.
// Posible bug. Al inicio se dispara doble interrupción?
//****************************************************************************
ISR(INT2_vect)
{
DisplayTimeDate();
}

/*** Función principal ***/
intmain(void)
{
i2c_init();// 100 kHz
usart_init();// 9600 - 8N1

printf("nr DS3232: RTC I2C de gran precisión con cristal integrado y SRAM");
printf("nr ==============================================================nr");

ds3232_setup();

/* Configurar y habilitar la Interrupción Externa INT2
* para que se dispare en cada flanco de bajada del pin INT2
*/
EICRA=0x20;
EIMSK=0x04;
sei();

while(1)
{
/* Entrar en modo sleep (Idle Mode). */
SMCR=0x01;
sleep_enter();
}
}

//****************************************************************************
// Pone valores iniciales de fecha y hora en el DS3232.
// Se configura la generación por el pin INT/SQW de una onda cuadrada de 1 Hz.
// Se desactiva la señal de 32 kHz del pin 32kHz.
// No se añade calibración extra a los capacitores del oscilador.
// Se deja la frecuencia de muestreo de temperatura a 64/segundo.
// No se habilitan las interrupciones de las alarmas.
//****************************************************************************
voidds3232_setup(void)
{
charsegs,mins,hors,diasem,dia,mes,anio;

// Configurar el pin INT/SQW para que saque una onda cuadrada de 1 Hz,
// incluso si el DS3232 se alimenta de la batería. Dejar sin habilitar
// las interrupciones de las alarmas.

ds3232_write(0x0E,0x40);// Escribir en registro 'Control' 01000000

// Desactivar la onda cuadrada de 32 kHz del pin 32kHz, mantener la
// razón de 64 muestros de temperatura por segundo y limpiar los flags
// de las alarmas y del oscilador.

ds3232_write(0x0F,0x00);// Escribir en registro 'Control/Status' 00000000
// No añadir ni sustraer nada a los valores que el DS3232 autocalcula
// para calibrar los capacitores de su oscilador.

ds3232_write(0x10,0x00);// Escribir en registro 'Aging Offset' 00000000

/*** Poner valores iniciales de fecha y hora del DS3232 ***/
printf("nr Pon año [0..99]: ");anio=getbcd();
printf("nr Pon mes [1..12]: ");mes=getbcd();
printf("nr Pon día [1..31]: ");dia=getbcd();
printf("nr Pon día [1.. 7]: ");diasem=getbcd();
printf("nr Pon hora [0..23]: ");hors=getbcd();
printf("nr Pon minutos [0..59]: ");mins=getbcd();
printf("nr Pon segundos [0..59]: ");segs=getbcd();
printf("nr");

// Escribir valores iniciales de fecha y hora en el DS3232

ds3232_SetTimeDate(segs,mins,hors,diasem,dia,mes,anio);
}

//****************************************************************************
// Escribe los registros de Hora y Fecha del DS3232.
// Los parámetros deben estar en formato BCD.
//****************************************************************************
voidds3232_SetTimeDate(charseg,charmin,charhor,chardia,chardds,charmes,charanio)
{
i2c_start();
i2c_write(0xD0);// Slave address + Write
i2c_write(0x00);// Register address
i2c_write(seg);// Data to register
i2c_write(min);// " "
i2c_write(hor);// " "
i2c_write(dds);// " "
i2c_write(dia);// " "
i2c_write(mes);// " "
i2c_write(anio);// " "
i2c_stop();
}

//****************************************************************************
// Lee los registros de Hora y Fecha del DS3232.
// Los parámetros salen en formato BCD.
//****************************************************************************
voidds3232_GetTimeDate(char*seg,char*min,char*hor,char*dds,char*dia,char*mes,char*ani)
{
i2c_start();
i2c_write(0xD0);// Slave address + Write
i2c_write(0x00);// Register address
i2c_restart();
i2c_write(0xD0|0x01);// Slave address + Read
*seg=i2c_read(0);// Read register and send ACK
*min=i2c_read(0);// " " "
*hor=i2c_read(0);// " " "
*dds=i2c_read(0);// " " "
*dia=i2c_read(0);// " " "
*mes=i2c_read(0);// " " "
*ani=i2c_read(1);// Read last register and send NACK
i2c_stop();
}

//****************************************************************************
// Escribe 'data' en el registro de dirección 'address' del DS3232.
//****************************************************************************
voidds3232_write(charaddress,chardata)
{
i2c_start();
i2c_write(0xD0);// Slave address + Write
i2c_write(address);// Register address
i2c_write(data);// Data to register
i2c_stop();
}

//****************************************************************************
// Lee el registro de dirección 'address' del DS3232.
//****************************************************************************
chards3232_read(charaddress)
{
charreg;
i2c_start();
i2c_write(0xD0);// Slave address + Write
i2c_write(address);// Register address
i2c_restart();
i2c_write(0xD0|0x01);// Slave address + read
reg=i2c_read(1);// Read register and send NACK
i2c_stop();
returnreg;
}

//****************************************************************************
// Lee los registros de fecha y hora del DS3232 y los visualiza en el
// terminal serial.
// Formato de salida de visualización: 14/03/2009 05:32:45
//****************************************************************************
voidDisplayTimeDate(void)
{
charsegs,mins,hors,diasem,dia,mes,anio;
charstat;

stat=ds3232_read(0x0F);// Leer registro 'Control/Status'

if(stat&0x80)// Si el RTC se detuvo (OSF = 1?)
{
printf("nr El RTC se detuvo");
}
else
{
/* Leer registros de fecha y hora del DS3232 */
ds3232_GetTimeDate(&segs,&mins,&hors,&diasem,&dia,&mes,&anio);

/* Mostrar los datos leídos */
printf("rn %2x/%02x/20%02x %02x:%02x:%02x",dia,
(mes&0x7F),// Ignorar bit Century (por si acaso :)
anio,hors,mins,segs);
}
}

//****************************************************************************
// Lee un número BCD de dos dígitos del terminal serial.
//****************************************************************************
chargetbcd(void)
{
unsignedcharc,buff[3],i=0;
while(1){
c=getchar();
if(('0'<=c)&&(c<='9')&&(i<2)){// Si c es dígito 0..9 y si i>0
buff[i++]=c;// Guardar en buffer
putchar(c);// Eco
}
elseif((c=='b')&&(i)){// Si c es BACKSPACE y si i>0
i--;//
putchar(c);// Eco
}
elseif((c=='r')&&(i))// Si c es ENTER y si i>0
break;// Salir del bucle
}
c=buff[0]-'0';
if(i>1){// (i==2)
c<<=4;
c|=(buff[1]-'0');
}
returnc;
}

El Reloj de Tiempo Real PCF8563
Philips, con su subsidiara NXP Semiconductors, fabrica otra de las familias de RTCs
muy populares. En esta ocasión estudiaremos el Reloj de Tiempo Real
/Calendario PCF8563, parte de la subfamilia conformada por los
dispositivos PCF8563, PCF8573, PCF8583,PCF8593.

Características del PCF8563
Computa segundos, minutos, horas, días de semana, días de mes, meses y años
(hasta el 2099).
Tiene una alarma programable.
Tiene un Timer para programar temporizaciones.
Tiene detección de baja tensión pero no ofrece alimentación alterna de batería.
Provee por el pin CLKOUT una señal de hasta 4 frecuencias configurables.
Tiene salida de interrupciones, pin INT, tanto de la alarma como del Timer.
Los pormenores del dispositivo, como límites de tensión de alimentación, consumo de
energía, etc., los puedes ver en el datasheet.

Descripción Funcional de Pines del PCF8563
Tabla Pines del PCF8563

Diagrama de pines del RTC PCF8563 en PDIP.
OSCI y OSCO. Son los pines para conectar un XTAL externo de 32.768 kHz. Los
capacitores de estabilización, como siempre, los lleva dentro.
Vdd y Vss. Pines de alimentación.
SDA y SCL. Pines de interface I2C.
CLKOUT. Puede sacar una onda cuadrada de 4 frecuencias (1 Hz, 32 Hz, 1024 Hz o
32768 Hz). Es un pin de drenador abierto que necesitará de una pull up externa.
INT. Saca la señal de activación de interrupciones de la Alarma o del Timer.

Los Registros del PCF8563
Tabla de Registros del PCF8563

Contenido
Dirección Nombre

Rango
Bit 7 Bit 6 Bit 5 Bit 4 Bit 3

0x00

control_status_1 TEST1 0

STOP 0

0x01

control_status_2 0

0

0

0x02

VL_seconds

10 segundos

VL

Bit 2 Bit 1 Bit 0

TESTC 0

TI_TP AF

TF

segundos

0

0

AIE TIE

----00 – 59
Tabla de Registros del PCF8563

Contenido
Dirección Nombre

Rango
Bit 7 Bit 6 Bit 5 Bit 4 Bit 3

Bit 2 Bit 1 Bit 0

0x03

Minutes

10 minutos

minutos

00 – 59

0x04

Hours

10 hora

horas

00 – 23

0x05

Days

10 día

día (de mes)

0 – 31

0x06

Weekdays

0000

día (de semana)

0–6

0x07

century_months C

mes

1 – 12

0x08

Years

10 año

año

00 – 99

0x09

minute_alarm

AE

10 minutos alarma minutos alarma

---

0x0A

hour_alarm

AE

10 horas alarma

horas alarma

---

0x0B

day_alarm

AE

10 días alarma

días alarma (de mes)

---

0x0C

weekday_alarm AE

000

días alarma (de semana) ---

0x0D

CLKOUT_control FE

0

0

0

0

0

FD1 FD0 ---

0x0E

timer_control

TE

0

0

0

0

0

TD1 TD0 ---

0x0F

Timer

×

×

×

×

×

×

×

10 mes

×

---

En esta sección explicamos los registros de fecha y hora y el primer registro de control.
Los demás serán descritos junto con el módulo al que corresponden.
Sobre los registros de fecha y hora no hay mucho que aclarar. Los días de semana van
de 0 a 6 (no de 1 a 7), solo hay formato de 24 horas, y los bits VL y C son de
funciones muy conocidas por nosotros:
El bit VL (Voltage Low) se activa a 1 cuando la tensión de alimentación falla y cae (por
debajo de 1V). En esta situación la información de los registros no es confiable. Se
limpia por software. El bit C (Century) se activa al pasar del año 99 al año 00. En
nuestro siglo estará en 0.
Sobre el registro control_status_1, el bitSTOP es el más relevante. Poniéndolo a 1 se
detiene el reloj del RTC, aunque la señal del pin CLKOUT todavía puede estar
disponible. Poniéndolo a 0 el RTC opera normalmente.
Si el bit TEST1 está en 0 el RTC funciona convencionalmente. Si lo seteamos, el RTC
opera en “modo de prueba”, esto es, no en tiempo real con su oscilador de XTAL, sino
en cámara lenta y guiado por la señal de reloj externa aplicada al pin CLKOUT, el cual
se adapta para tal función automáticamente. Es algo que nos hace recordar al modo
OCD de los microcontroladores.
La activación del bit TESTC es condición necesaria pero no suficiente para suprimir el
delay del reset POR del dispositivo. Salvo que realmente te interese eso, no debería
preocuparte si lo dejas en 0 ó 1. A mí me da igual.
El registro control_status_2 tiene dos propósitos: uno, controlar las interrupciones
delTimer y de la alarma y dos, configurar la acción del pin INT.

Control del Pin CLKOUT
Todo queda a cargo del registro CLKOUT control, mostrado a continuación.
Registro de control del rtc PCF8563

FE

0

0

0

0

0

FD1

FD0

Si el bit FE vale 0, el pin CLKOUT queda en alta impedancia, sin señal. Si FE vale
1,CLKOUT saca una onda cuadrada con frecuencia determinada por los bits FD1 y FD0.
Tabla FD1 FD0

FD1 FD0 Frecuencia de onda del pin CLKOUT
00

32 768 Hz

01

1024 Hz

10

32 Hz

11

1 Hz

La Alarma del PCF8563
La alarma se activa cuando se produce una coincidencia entre sus registros que tengan
a 0 el bit AE (Alarm Enable) y sus correspondientes registros de tiempo. Así,
podríamos programar la alarma para una coincidencia solo entre los registros de hora,
por ejemplo.
La coincidencia setea el bit AF (Alarm Flag) del registro control_status_2 y generará
una interrupción activando el pin INT a 0, si está habilitada. Para esta habilitación se
setea el bit AIE (Alarm Interrupt Enable) del mismo registro control_status_2.

El Timer y el Registro TIMER
El Timer es un registro de 8 bits que cuenta en modo descendente desde el valor que
se le cargue hasta 0. Cuando llegue al final del conteo se activa el bit TF del
registrocontrol_status_2. Esta condición puede generar una interrupción activando el
pin INT a 0 si se setea el bit TIE (Timer Interrupt Enable), también
de control_status_2.
La activación y frecuencia de conteo del Timer queda a cargo del
registro timer_control.
Registro Registro TIMER del PCF8563

TE

0

0

0

0

0

TD1

TD0

Si TE (Timer Enable) vale 0, el Timer se detiene. El Timer corre con el bit TE puesto a
1. Los bits TD1 y TD0 establecen la frecuencia de su reloj.
Tabla de frecuencias del PCF8563

TD1 TD0 Frecuencia de reloj del Timer
00

4 096 Hz

01

64 Hz

10

1 Hz

11

1/60 Hz

Programar este Timer para generar temporizaciones es tan fácil como hacerlo con
algún Timer de un microcontrolador. Sin embargo, las temporizaciones así obtenidas
serán difícilmente aceptables, no solo por las pequeñas frecuencias de reloj
disponibles, sino por la casi irremediable imprecisión acarreada por los tiempos de
acceso al dispositivo. Me refiero al tiempo que demoran la condición START, etc.

Acceso a los Registros del PCF8563
El puntero de registros tiene autoincremento y puede abarcar a todos los registros del
RTC. La dirección de esclavo es 0xA2 (incluyendo bit RW=0). ¿Qué más hace falta?
Práctica: Uso del PCF8563
En esta práctica se aprecia el uso básico del PCF8563. No se programan las alarmas ni
el Timer.

El circuito

Circuito para el RTC PCF8563 y el microcontrolador AVR.

El Código Fuente
Si encuentras cosas que crees que merecen aclaración, puedes revisar las
explicaciones de las prácticas con los RTCs DSxxx. Son todos los programas muy
parecidos. De hecho, los creé empezando con el “Ctrl + C / Ctrl + V”, y luego hice las
adaptaciones. ;)

/******************************************************************************
* FileName: main.c
* Purpose: Uso del Reloj de tiempo real / Calendario PCF8563
* Processor: ATmel AVR con módulo TWI
* Compiler: AVR IAR C y AVR GCC (WinAVR)
* Author:

Shawn Johnson. http://www.cursomicros.com.
*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License: Se permiten el uso y la redistribución de este código con
*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y las notas de autor y copyright de arriba.

*
* Disclaimer: El uso de este software queda bajo su completa responsabilidad.
*****************************************************************************/

#include "avr_compiler.h"
#include "usart.h"
#include "i2c.h"

/*** Prototipos de funciones ***/
voidpcf8563_setup(void);
voidpcf8563_SetTimeDate(char,char,char,char,char,char,char);
voidpcf8563_GetTimeDate(char*,char*,char*,char*,char*,char*,char*);
voidpcf8563_write(charaddress,chardata);
charpcf8563_read(charaddress);
voidDisplayTimeDate(void);
chargetbcd(void);

//****************************************************************************
// ISR o manejador de interrupción INT2.
// La interrupción INT2 se dispara con el flanco de bajada.
// Posible bug. Al inicio se dispara doble interrupción?
//****************************************************************************
ISR(INT2_vect)
{
DisplayTimeDate();
}

/*** Función principal ***/
intmain(void)
{
i2c_init();// 100 kHz
usart_init();// 9600 - 8N1

printf("nr PCF8563: Reloj de tiempo real / Calendario");
printf("nr ==========================================nr");

delay_us(60000);// pcf8563 Start up delay
delay_us(60000);//

pcf8563_setup();

/* Configurar y habilitar la Interrupción Externa INT2
* para que se dispare en cada flanco de bajada del pin INT2
*/
EICRA=0x20;
EIMSK=0x04;
sei();

while(1)
{
/* Entrar en modo sleep (Idle Mode). */
SMCR=0x01;
sleep_enter();
}
}

//****************************************************************************
// Pone valores iniciales de fecha y hora en el PCF8563.
// Se configura la generación por el pin CLKOUT de una onda cuadrada de 1 Hz.
// No se configura la funcionalidad del pin INT porque no se usa la alarma ni
// el timer, no por ahora.
//****************************************************************************
voidpcf8563_setup(void)
{
charsegs,mins,hors,diasem,dia,mes,anio;

// Configurar el registro 'CLKOUT_control' para sacar por el pin CLKOUT
// una onda cuadrada de 1 Hz.

pcf8563_write(0x0D,0x83);// 10000011

// Configurar el registro 'Control_status_1' para operación normal del
// RTC(no EXT_CLK test mode, reloj del RTC en marcha, soporte 'Power-on
// reset override'). No es necesario porque es su estado por defecto.

pcf8563_write(0x00,0x08);// 00001000

/*** Poner valores iniciales de fecha y hora del PCF8563 ***/
printf("nr Pon año [0..99]: ");anio=getbcd();
printf("nr Pon mes [1..12]: ");mes=getbcd();
printf("nr Pon día [1..31]: ");dia=getbcd();
printf("nr Pon día [1.. 7]: ");diasem=getbcd();
printf("nr Pon hora [0..23]: ");hors=getbcd();
printf("nr Pon minutos [0..59]: ");mins=getbcd();
printf("nr Pon segundos [0..59]: ");segs=getbcd();
printf("nr");

// Escribir valores iniciales de fecha y hora en el PCF8563

pcf8563_SetTimeDate(segs,mins,hors,diasem,dia,mes,anio);
}

//****************************************************************************
// Escribe los registros de Hora y Fecha del PCF8563.
// Los parámetros deben estar en formato BCD.
//****************************************************************************
voidpcf8563_SetTimeDate(charseg,charmin,charhor,chardia,chardds,charmes,charanio)
{
i2c_start();// START
i2c_write(0xA2);// Slave address + Write
i2c_write(0x02);// Register address
i2c_write(seg);// Data to register
i2c_write(min);// " "
i2c_write(hor);// " "
i2c_write(dia);// " "
i2c_write(dds);// " "
i2c_write(mes);// " "
i2c_write(anio);// " "
i2c_stop();// STOP
}

//****************************************************************************
// Lee los registros de Hora y Fecha del PCF8563.
// Los parámetros salen en formato BCD.
//****************************************************************************
voidpcf8563_GetTimeDate(char*seg,char*min,char*hor,char*dds,char*dia,char*mes,char*ani)
{
i2c_start();// START
i2c_write(0xA2);// Slave address + Write
i2c_write(0x02);// Register address
i2c_restart();// Repeated START
i2c_write(0xA2|0x01);// Slave address + Read
*seg=i2c_read(0);// Data from register
*min=i2c_read(0);// " " "
*hor=i2c_read(0);// " " "
*dia=i2c_read(0);// " " "
*dds=i2c_read(0);// " " "
*mes=i2c_read(0);// " " "
*ani=i2c_read(1);// " " "
i2c_stop();// STOP
}

//****************************************************************************
// Escribe 'data' en el registro de dirección 'address' del PCF8563.
//****************************************************************************
voidpcf8563_write(charaddress,chardata)
{
i2c_start();// START
i2c_write(0xA2);// Slave address + Write
i2c_write(address);// Register address
i2c_write(data);// Data to register
i2c_stop();// STOP
}

//****************************************************************************
// Lee el registro de dirección 'address' del PCF8563.
//****************************************************************************
charpcf8563_read(charaddress)
{
charreg;
i2c_start();// START
i2c_write(0xA2);// Slave address + Write
i2c_write(address);// Register address
i2c_restart();// Repeated START
i2c_write(0xA2|0x01);// Slave address + Read
reg=i2c_read(1);// Data from register
i2c_stop();// STOP
returnreg;
}

//****************************************************************************
// Lee los registros de fecha y hora del PCF8563 y los visualiza en el
// terminal serial.
// Formato de salida de visualización: 14/03/2009 05:32:45
//****************************************************************************
voidDisplayTimeDate(void)
{
charsegs,mins,hors,diasem,dia,mes,anio;

/* Leer registros de fecha y hora del PCF8563 */
pcf8563_GetTimeDate(&segs,&mins,&hors,&diasem,&dia,&mes,&anio);

if(segs&0x80)// Si el voltaje cayó (VL = 1?)
{
printf("nr Bajo voltaje detectado.");
printf("nr Los datos no son fiables.");
}

/* Mostrar los datos leídos */

printf("rn %2x/%02x/20%02x %02x:%02x:%02x",dia,
(mes&0x7F),// Ignorar bit Century (por si acaso :)
anio,hors,mins,
(segs&0x7F));// Filtrar bit VL
}

//****************************************************************************
// Lee un número BCD de dos dígitos del terminal serial.
//****************************************************************************
chargetbcd(void)
{
unsignedcharc,buff[3],i=0;
while(1){
c=getchar();
if(('0'<=c)&&(c<='9')&&(i<2)){// Si c es dígito 0..9 y si i>0
buff[i++]=c;// Guardar en buffer
putchar(c);// Eco
}
elseif((c=='b')&&(i)){// Si c es BACKSPACE y si i>0
i--;//
putchar(c);// Eco
}
elseif((c=='r')&&(i))// Si c es ENTER y si i>0
break;// Salir del bucle
}
c=buff[0]-'0';
if(i>1){// (i==2)
c<<=4;
c|=(buff[1]-'0');
}
returnc;
}

El sensor de temperatura DS1621
Con este dispositivo empezamos otra larga travesía, esta vez por los territorios de los
sensores de temperatura I2C. Yo prefiero decir sensor porque la palabra termómetro
me suena a eso que el médico te pone en la boca cuando te enfermas ;)
Por ser el primer modelo tratado, esta exposición será más extensa. Luego
comprobarás que, salvo raras excepciones, manejar los demás sensores de
temperatura I2C es más que similar sin importar el modelo y fabricante.
En la práctica verás que adaptar el programa de un termómetro a otro requiere
conocer al menos ciertas características fundamentales. Para el DS1621 éstas son:
Tiene una resolución de 9 bits. Permite medir temperaturas desde –55°C hasta
+125°C con incrementos de 0.5°C.
Tiene un tiempo de conversión máximo de 750 ms.
Puede operar en modo continuo, donde realiza conversiones sin cesar, y en modo One
shot, donde realiza una conversión cada vez que se le pida.
Puede trabajar como termostato con histéresis programable.
Soporta el protocolo I2C en Fast Mode (máxima frecuencia de reloj de 400 kHz).

Descripción de Pines del DS1621
Tabla Pines del DS1621
Diagrama de pines del termómetro DS1621 en PDIP.
SDA y SCL. Pines de interface I2C.
Vcc y GND. Pines de alimentación. Vcc es típicamente de 5 V.
A0, A1 y A2. Pines para configurar parte de la dirección de esclavo.
TOUT. Pin de salida del termostato. Puede indicar si la temperatura ha superado
ciertos niveles, los cuales son programables por el usuario. No se usa en aplicaciones
simples ni complejas. Si se usa, debe llevar una pull up externa por ser de drenador
abierto.

Set de Instrucciones del DS1621
La mayoría de las instrucciones permiten acceder a los registros internos del DS1621.
Sus funciones serán ampliadas en adelante.
Tabla Códigos de instruccion del DS1621

Código Instrucción

Función

0xAA

Read Temperature

Lee la última temperatura convertida

0xEE

Start Convert T

Inicia la conversión de temperatura

0x22

Stop Convert T

Detiene la conversión de temperatura

0xAC

Access Config

Lectura o escritura en el registro de Configuración

0xA1

Access TH

Lectura o escritura en el registro TH

0xA2

Access TL

Lectura o escritura en el registro TL

0xA8

Read Counter

Lee el valor de Count_Remain
Tabla Códigos de instruccion del DS1621

Código Instrucción

Función

0xA9

Lee el valor de Count_Per_C

Read Slope

Los Registros del DS1621
En los modelos de Dallas lo distintivo es que el acceso a los registros se realiza
mediante las instrucciones o comandos, en vez de por sus direcciones, aunque desde
el punto de vista del software se pueden ver e interpretar igual.
Como si se trataran de sus direcciones, los códigos de instrucciones se envían después
del byte de control (Slave address + Write). Si la instrucción implica una escritura de
datos, estos se envían después. Si la instrucción indica una lectura de datos, se envía
otro byte de control (Slave address + Read) y se procede a la lectura, todo según
elprotocolo I2C.
En general, los registros de los sensores de temperatura se dividen en tres categorías:
El registro de temperatura. Allí se guarda la temperatura convertida. Es un registro de
solo lectura de 2 bytes, de los cuales solo 9 bits son significativos. Se le accede con la
instrucción Read Temperature. La lectura está disponible en todo momento.

El registro de configuración. Controla y configura la operación del dispositivo. Suele ser
de 1 byte. Con eso basta y sobra. Se le accede con la instrucción Access Config.
Algunos de sus bits son EEPROM por lo que su escritura requerirá a lo sumo 10 ms.
Los registros del termostato. Son dos: uno para establecer el límite superior TH y otro
para establecer el límite inferior TL. Cada uno tiene el mismo tamaño y formato que el
registro de temperatura. Se les accede con las instrucciones Access TH yAccess TL. La
función del termostato se explica después.

El Registro de Configuración del DS1621
De todos solo el bit 1SHOT tiene uso imprescindible; los bits DONE y NVB tienen uso
alternativo; y los bits del termostato (THF, TLF y POL)... bueno, el termostato es otro
tema.
Registro Registro de Configuración del DS1621

DONE THF TLF
Registro de Microcontrolador

NVB

---

---

POL

1SHOT
DONE

Conversion Done bit
1 = Conversión completada
0 = Conversión en progreso
En el DS1621 una conversión de temperatura dura como mucho 750 ms. En la
práctica no se suele tener tanta prisa por leer la temperatura, por lo que es más
frecuente hacer una espera de más de 1 segundo que sondear este bit.

THF

Temperature High Flag (Termostato)
1 = La temperatura a igualado o superado el valor de TH. Se limpia por software
0 = La temperatura aún no llega a TH.

TLF

Temperature Low Flag (Termostato)
1 = La temperatura a igualado o ni bajado del valor de TL. Se limpia por software
0 = La temperatura aún no llega a TL.

NVB

Non Volatile Memory Busy Flag
Los registros del termostato TH y TL, y algunos bits del registro de Configuración son
EEPROM. La escritura en dichas locaciones tiene un tiempo típico de 4 ms y máximo
de 10 ms. En ese lapso no se deben escribir más datos. En las prácticas resulta más
cómodo poner un delay que sondear este bit.
1 = Hay una escritura de EEPROM en progreso
0 = No hay escrituras de EEPROM en progreso

POL

Output Polarity Bit (Termostato)
1 = Activo en alto
0 = Activo en bajo

1SHOT

One Shot Mode
1 = Establece operación del DS1621 en modo One Shot
0 = Establece operación del DS1621 en modo de Conversiones Continuas

Modo One shot y de Conversiones Continuas
Hay dos formas conocidas en las que los sensores de temperatura pueden trabajar. En
los DS162x se llaman modo One Shot y modo Continuo.
En el modo One Shot el sensor toma la temperatura solo cuando se le ordena. El resto
del tiempo el dispositivo permanece en modo Standby para ahorrar energía. Una vez
configurado este modo, se debe iniciar una conversión con la instrucción Start Convert.
Cuando el DS1621 acabe la conversión (luego de 750 ms a lo sumo) se actualiza el
registro de temperatura, se activa el flagDONE (del registro de Configuración) y luego
el dispositivo regresa a su estado Standby.
En el modo de Conversiones Continuas el sensor toma y convierte la temperatura sin
cesar. Una vez establecido este modo, el DS1621 queda en Standby y se debe usar la
instrucción Start Convert para arrancar las conversiones continuas. Con eso las
conversiones se realizan una a continuación de otra. El proceso se puede detener con
la instrucción Stop Convert, con lo cual el DS1621 terminará la conversión actual para
luego regresar a su Standby. No es tan diferente del modo One shot después de todo.

Formato del Dato de Temperatura
El valor de la temperatura convertida se almacena en un registro de uno o dos bytes,
según el dispositivo. La siguiente exposición de su formato es aplicable a todos los
termómetros I2C, o por lo menos a los considerados en este sitio. Es una
interpretación parcialmente alternativa que yo suelo usar y es lo que verás en los mis
programas.

Disposición del registro de temperatura.
El primer byte contiene la parte entera del dato. Se trata de un número con signo de
complemento a dos. Asumo que entiendes lo que significa eso. Es un concepto básico
en sistemas digitales. Los termómetros simples como el TC74 (visto más adelante)
solo tienen este byte.
El segundo byte (si lo hubiera) lleva la parte fraccionaria, justificada a la izquierda.
Sus nbits significativos representan un múltiplo de
. Para efectos de cálculo n es la
cantidad de bits que hay desde el primer 1 que aparece empezando por la derecha.
Los 0‟s a la derecha no valen nada, ¿recuerdas?
Finalmente el valor de temperatura se halla sumando la parte entera (positiva o
negativa) y la parte fraccionaria (siempre positiva). Por ejemplo.
Los sensores como el DS1621 o LM75 solo tienen un bit fraccional significativo. Por eso
es más fácil decir que cuando dicho bit es 1 vale 1×(1/21) = 0.5 °C.

Función de Termostato
Un termostato es un dispositivo capaz de mantener constante la temperatura de un
medio. La forma en que el DS1621 pretende cumplir esta función es mediante la
acción del pinTOUT. Este pin se activa cuando la temperatura llega o supera el valor
del registro TH y se desactiva cuando la temperatura iguala o cae por debajo del valor
del registro TL. A eso se resume el tan mentado termostato.
Es el diseñador quien debe establecer los límites de TH y TL y aprovechar la señal
deTOUT para lo que considere apropiado; por ejemplo, para encender un ventilador o
un calefactor cuando la temperatura alcance los límites programados.
Esa aplicación puede parecer interesante, pero como proyecto de escuela de un
estudiante de 10 años. Nosotros sabemos que el mundo no es en blanco y negro, que
no basta con encender o apagar unos artefactos, sino que es necesario regular su
funcionamiento. Pero ése es un tema que cae mejor en el “control de procesos”, una
materia que desarrolla potentes herramientas para implementar verdaderos sistemas
controlados y donde -creo- los pobres termostatos hardware de estos termómetros
tienen muy poco que hacer.
Así es como trato de justificar el hecho de ignorar todo lo relacionado a los
termostatos, pese a ser una funcionalidad muy presente de estos dispositivos ;)

Dirección de Esclavo del DS1621
Los valores de los bits A0, A1 y A2 corresponden a los pines homólogos del dispositivo.
En adelante veremos que esta misma dirección también es usada por otros
termómetros I2C, incluso de otros fabricantes.
El byte de control (dirección de esclavo + bit R/W) para el DS1621.

Práctica 1: Uso del DS1621: Modo One Shot
En esta práctica el DS1621 operará en modo One Shot. La temperatura se lee y
convierte cuando se pulsa la tecla ENTER. Se leen los dos bytes del registro de
temperatura pero solo se considera la parte entera. En las siguientes prácticas se
verán las demás funciones de este sensor.

Circuito para el sensor de temperatura DS1621 y el microcontrolador AVR.

El código fuente
En la función DisplayTemp después de iniciar la conversión se espera a que ella
termine comprobando que el bit DONE se active a 1. Como será una espera larga
(hasta de 750 ms), lo combiné con un delay de 10 ms. A veces será más cómodo
quitar toda esta rutina y cambiarla por un delay de 750 ms o más.

voidDisplayTemp(void)
{
//...
ds1621_StartConvert();// Iniciar conversión
do{
delay_ms(10);
done=ds1621_ReadConfig()&0x80;
}while(done==0);// Esperar conversión completada

temp=ds1621_ReadTemperature();// Leer registro de Temperatura
//...
}

/******************************************************************************
* FileName: main.c
* Purpose: Uso del termómetro digital DS1621: One Shot mode
* Processor: ATmel AVR con módulo TWI
* Compiler: AVR IAR C y AVR GCC (WinAVR)
* Author:

Shawn Johnson. http://www.cursomicros.com.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License: Se permiten el uso y la redistribución de este código con
*

modificaciones o sin ellas, siempre que se mantengan esta
*

licencia y las notas de autor y copyright de arriba.

*****************************************************************************/

#include "avr_compiler.h"
#include "usart.h"
#include "i2c.h"

/*** Prototipos de funciones ***/
signedchards1621_ReadTemperature(void);
voidDisplayTemp(void);
voidds1621_WriteConfig(char);
chards1621_ReadConfig(void);
voidds1621_StartConvert(void);

/*** Función principal ***/
intmain(void)
{
i2c_init();// 100 kHz
usart_init();// 9600 - 8N1

printf("nr DS1621: Sensor de temperatura");
printf("nr =============================nr");

ds1621_WriteConfig(0x01);// Establecer modo One Shot

printf("nr Pulse ENTER para leer temperatura");
while(1)
{
if(kbhit())// Si llegó algún dato
{
if(getchar()=='r')// Y si es ENTER
DisplayTemp();
}
}
}

//****************************************************************************
// Lee la temperatura del DS1621 y la muestra en el terminal serial.
//****************************************************************************
voidDisplayTemp(void)
{
inttemp;
chardone;

ds1621_StartConvert();// Iniciar conversión
do{
delay_us(10000);
done=ds1621_ReadConfig()&0x80;
}while(done==0);// Esperar conversión completada

temp=ds1621_ReadTemperature();// Leer registro de Temperatura
printf("rn Temp = %d °C",temp);// Mostrar temperatura leída
}

//****************************************************************************
// Escribe 'data' en el registro de Configuración del DS1621
//****************************************************************************
voidds1621_WriteConfig(chardata)
{
i2c_start();
i2c_write(0x90);// Slave address + Write
i2c_write(0xAC);// Access Config instruction
i2c_write(data);// Data to Config register
i2c_stop();
delay_us(12000);// EEPROM write cycle period > 10ms
}

//****************************************************************************
// Escribe el registro de Configuración del DS1621
//****************************************************************************
chards1621_ReadConfig(void)
{
chardata;
i2c_start();
i2c_write(0x90);// Slave address + Write
i2c_write(0xAC);// Access Config instruction
i2c_restart();
i2c_write(0x90|0x01);// Slave address + Read
data=i2c_read(1);// Data from register
i2c_stop();
returndata;
}

//****************************************************************************
// Inicia una/la conversión de temperatura.
//****************************************************************************
voidds1621_StartConvert(void)
{
i2c_start();
i2c_write(0x90);// Slave address + Write
i2c_write(0xEE);// Start Convert T instruction
i2c_stop();
}

//****************************************************************************
// Lee la temperatura del DS1621.
// Solo se toma la parte entera con signo, no la parte fraccionaria.
//****************************************************************************
signedchards1621_ReadTemperature(void)
{
signedchardata;
i2c_start();
i2c_write(0x90);// Slave address + Write
i2c_write(0xAA);// Read Temperature instruction
i2c_restart();
i2c_write(0x90|0x01);// Slave address + Read
data=i2c_read(0);// Read integer term
i2c_read(1);// Read fractional term (dummy read)
i2c_stop();
returndata;
}

Práctica 2: Uso del DS1621: Modo Continuo
Ahora el DS1621 operará en modo de Conversiones Continuas. El sufijo full en el
nombre del programa significa que esta vez trabajaremos con todo el registro de
temperatura, tanto con la parte entera como la fraccionaria.
Utilizaremos el mismo circuito de la práctica previa.
El código fuente
Cuando el programa va a trabajar con la parte entera y fraccionaria de la temperatura
se prefiere multiplicar el dato por 10 (u otro valor según convenga) para convertirlo en
un número entero. El dato es procesado así y vuelto a separar en sus partes entera y
fraccionaria solo cuando sea necesario; por ejemplo para visualizar su valor.
Esa operación era conveniente porque los datos que entregan los sensores no son
números de punto flotante que los compiladores puedan entender. Es decir, no
podríamos meter esos datos directamente en variables de tipo float del lenguaje
C/C++. Los verdaderos números de punto flotante son de 32 bits (simple precisión) o
de 64 bits (doble precisión) y deben cumplir el formato establecido en el estándar IEEE
754.

/******************************************************************************
* FileName: main.c
* Purpose: Uso del termómetro digital DS1621: Continuous conversion mode
* Processor: ATmel AVR con módulo TWI
* Compiler: AVR IAR C y AVR GCC (WinAVR)
* Author:

Shawn Johnson. http://www.cursomicros.com.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License: Se permiten el uso y la redistribución de este código con
*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y las notas de autor y copyright de arriba.

*****************************************************************************/

#include "avr_compiler.h"
#include "usart.h"
#include "i2c.h"
/*** Prototipos de funciones ***/
intds1621_ReadTemperature(void);
voidDisplayTemp(void);
voidds1621_WriteConfig(char);
chards1621_ReadConfig(void);
voidds1621_StartConvert(void);
voidds1621_StopConvert(void);
voiddelay_ms(unsignedintt);

/*** Función principal ***/
intmain(void)
{
unsignedchari;

i2c_init();// 100 kHz
usart_init();// 9600 - 8N1

printf("nr DS1621: Sensor de temperatura");
printf("nr =============================");

ds1621_WriteConfig(0x00);// Establecer modo Continuo

while(1)
{
ds1621_StartConvert();// Iniciar conversiones continuas
printf("nrr DS1621 Convirtiendo...");
for(i=0;i<10;i++)
{
delay_ms(800);// > 750 ms
DisplayTemp();// Visualizar temperatura
}

ds1621_StopConvert();// Detener conversiones continuas

printf("nrr DS1621 en Standby por 10s");
delay_ms(10000);
}
}

//****************************************************************************
// Lee la temperatura del DS1621 y la muestra en el terminal serial.
//****************************************************************************
voidDisplayTemp(void)
{
inttemp;

temp=ds1621_ReadTemperature();// Obtener temperatura

printf("rn Temp = %d.%d °C",temp/10,abs(temp%10));
}
//****************************************************************************
// Escribe 'data' en el registro de Configuración del DS1621
//****************************************************************************
voidds1621_WriteConfig(chardata)
{
i2c_start();
i2c_write(0x90);// Slave address + Write
i2c_write(0xAC);// Access Config instruction
i2c_write(data);// Data to Config register
i2c_stop();
delay_ms(12);// EEPROM write cycle period > 10ms
}

//****************************************************************************
// Inicia una/la conversión de temperatura.
//****************************************************************************
voidds1621_StartConvert(void)
{
i2c_start();
i2c_write(0x90);// Slave address + Write
i2c_write(0xEE);// Start Convert T instruction
i2c_stop();
}
//****************************************************************************
// Detiene una/la conversión de temperatura.
//****************************************************************************
voidds1621_StopConvert(void)
{
i2c_start();
i2c_write(0x90);// Slave address + Write
i2c_write(0x22);// Stop Convert T instruction
i2c_stop();
}

//****************************************************************************
// Lee la temperatura del DS1621.
// Devuelve el valor de la temperatura multiplicado por 10.
//****************************************************************************
intds1621_ReadTemperature(void)
{
intdata;
signedcharhigh,low;
i2c_start();
i2c_write(0x90);// Slave address + Write
i2c_write(0xAA);// Read Temperature instruction
i2c_restart();
i2c_write(0x90|0x01);// Slave address + Read
high=i2c_read(0);// Read integer term
low=i2c_read(1);// Read fractional term
i2c_stop();

data=high;
data*=10;// Multiplicar por 10
if(low&0x80)// ¿ Sumar 5°C ?
data+=5;
returndata;
}
//****************************************************************************
// delay_ms()
//****************************************************************************
voiddelay_ms(unsignedintt){
while(t--)
delay_us(1000);
}

Práctica 3 Uso del DS1621: Alta Resolución
El DS1621 realiza la conversión de temperatura “jugando” con algunos contadores, dos
de los cuales generarán en última instancia el resultado final. Ellos se
llaman Counter y Slope Accumulator. A grandes rasgos, digamos que el primero
produce el grueso de la temperatura y el segundo ayuda a afinar la conversión.
El hardware del DS1621 utiliza un circuito digital algo pobre para calcular la conversión
(basado en contadores y comparadores binarios básicamente). Pero nos ofrece la
posibilidad de acceder a sus contadores principales para calibrar la conversión “a
mano” y así poder conseguir una mejor resolución. Debemos utilizar la siguiente
fórmula:

Donde:
Temp_read es la parte entera del registro de temperatura. La parte fraccionaria será
ignorada en pos de la calibración manual.
Count_remain es el valor restante del Counter. Se le accede enviando la
instrucciónRead Counter.
Count_per_c es valor que quedó en el Slope Accumulator. Se le accede mediante la
instrucción Read Slope.
Debo hacer la salvedad de que dado el margen de error intrínseco del dispositivo de
hasta ±1/2 °C en el rango 0–70 °C (aumenta en otros rangos), creo que no tiene
mucho sentido obsesionarse con el hecho de lograr una alta precisión.
Pasando a la práctica, superficialmente el programa funciona como la primera práctica:
el DS1621 opera en modo One Shot y realiza una conversión cada vez que se presione
la tecla ENTER. Se aplicará la fórmula presentada para obtener una mayor resolución
de conversión y se mostrará la temperatura con dos dígitos decimales.
Utilizaremos el mismo de la práctica anterior.

El código fuente
/******************************************************************************
* FileName: main.c
* Purpose: Uso del termómetro digital DS1621: high resolution
* Processor: ATmel AVR con módulo TWI
* Compiler: AVR IAR C y AVR GCC (WinAVR)
* Author:

Shawn Johnson. http://www.cursomicros.com.
*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License: Se permiten el uso y la redistribución de este código con
*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y las notas de autor y copyright de arriba.

*****************************************************************************/

#include "avr_compiler.h"
#include "usart.h"
#include "i2c.h"

/*** Prototipos de funciones ***/
intds1621_ReadTemperature(void);
voidDisplayTemp(void);
voidds1621_WriteConfig(char);
unsignedchards1621_ReadByte(char);
voidds1621_StartConvert(void);
voiddelay_ms(unsignedintt);

/*** Función principal ***/
intmain(void)
{
i2c_init();// 100 kHz
usart_init();// 9600 - 8N1
printf("nr DS1621: Sensor de temperatura");
printf("nr =============================nr");

ds1621_WriteConfig(0x01);// Establecer modo One Shot

printf("nr Pulse ENTER para leer temperatura");

while(1)
{
if(kbhit())// Si llegó algún dato
{
if(getchar()=='r')// Y si es ENTER
DisplayTemp();
}
}
}

//****************************************************************************
// Lee la temperatura del DS1621 y la muestra en el terminal serial.
//****************************************************************************
voidDisplayTemp(void)
{
inttemp;
chardone;

ds1621_StartConvert();// Iniciar conversión
do{
delay_ms(10);
done=ds1621_ReadByte(0xAC)&0x80;// Leer Registro de Configuración
}while(done==0);// Esperar conversión completada

temp=ds1621_ReadTemperature();// Leer temperatura

printf("rn Temp = %d.%d °C",temp/100,abs(temp%100));
}

//****************************************************************************
// Escribe 'data' en el registro de Configuración del DS1621
//****************************************************************************
voidds1621_WriteConfig(chardata)
{
i2c_start();
i2c_write(0x90);// Slave address + Write
i2c_write(0xAC);// Access Config instruction
i2c_write(data);// Data to Config register
i2c_stop();
delay_ms(12);// EEPROM write cycle period > 10ms
}
//****************************************************************************
// Lee el registro de Configuración del DS1621
//****************************************************************************
unsignedchards1621_ReadByte(charcmd)
{
chardata;
i2c_start();
i2c_write(0x90);// Slave address + Write
i2c_write(cmd);// Instruction code
i2c_restart();
i2c_write(0x90|0x01);// Slave address + Read
data=i2c_read(1);// Read register and send NACK
i2c_stop();
returndata;
}

//****************************************************************************
// Inicia una/la conversión de temperatura.
//****************************************************************************
voidds1621_StartConvert(void)
{
i2c_start();
i2c_write(0x90);// Slave address + Write
i2c_write(0xEE);// Start Convert T instruction
i2c_stop();
}

//****************************************************************************
// Lee la temperatura del DS1621.
// Devuelve el valor de la temperatura multiplicado por 100.
//****************************************************************************
intds1621_ReadTemperature(void)
{
inttemp_read,count_per_c,count_remain;

i2c_start();
i2c_write(0x90);// Slave address + Write
i2c_write(0xAA);// Read Temperature instruction
i2c_restart();
i2c_write(0x90|0x01);// Slave address + Read
temp_read=i2c_read(0);// Read integer term
i2c_read(1);// Read fractional term (dummy read)
i2c_stop();

count_remain=ds1621_ReadByte(0xA8);// Leer valor restante de Counter
count_per_c=ds1621_ReadByte(0xA9);// Leer valor de Slope Accumulator

return(100*temp_read-25+(100*(count_per_c-count_remain))/count_per_c);
}
//****************************************************************************
// delay_ms()
//****************************************************************************
voiddelay_ms(unsignedintt){
while(t--)
delay_us(1000);
}
El sensor de temperatura DS1624
Como hermano mayor del DS1621, podríamos esperar que el DS1624 será más difícil
de manejar. Por fortuna, las pocas diferencias existentes vendrán a nuestro favor. En
primer lugar, el DS1624 no tiene función de termostato. Así que te puedes olvidar de
todo lo referente a él (los registros de histéresis y los bits del registro de configuración
relacionados). ¡Qué alivio! Había explicado antes mis razones para ignorar este
termostato.
Tabla Pines del DS1624

Diagrama de pines del termómetro DS1624 en PDIP.
En segundo lugar, el DS1624 no ofrece acceso a sus registros internos con los que
realiza la conversión de temperatura. Significa que no podremos ajustar la precisión
como en la tercera práctica del DS1621. Yo sí los extraño.
En tercer lugar diré que el DS1624 cuenta con una EEPROM de 256 bytes de propósito
general. El acceso a esta memoria es muy similar a hacerlo con una EEPROM 24xxx,
desde los accesos individuales o escrituras por páginas (de 8 bytes en el DS1624)
hasta la forma de usar el bucle Acknowledge Polling. Personalmente considero estos
aditamentos como prescindibles. Si quiero más EEPROM, pues busco un
microcontrolador que la tenga en mayor cantidad. De todos modos, pondré algunas
funciones de ejemplo al final.
En cuarto lugar tenemos la diferencia más sobresaliente por la que muchos pueden
elegir este termómetro. El DS1624 tiene una resolución de 13 bits. Como la mayoría,
sigue midiendo temperaturas en el rango de –55°C a +125°C, pero ahora lo hace con
incrementos de 0.03125 °C. Este incremento se debe a que la parte fraccionaria es de
5 bits y ya sabemos que eso da 1/32 = 0.03125. Abajo se muestra la disposición del
registro de temperatura.
Disposición del registro de temperatura del DS1624.
Por lo demás, diré que el DS1624 funciona igual que el DS1621. Tiene dos modos de
operación: de Conversiones Continuas y One Shot. En cualquier modo, permanecerá
en Standby mientras no se esté convirtiendo una temperatura, esto es, luego de
terminada una conversión en One Shot o después de detener las conversiones
(con Stop Convert T) en modo de Conversiones Continuas.
Salvando las diferencias citadas, el software debería ser compatible, ya que el DS1624
también usa las mismas instrucciones (ver siguiente sección), la misma dirección de
esclavo, etc.

El Registro de Configuración del DS1624
Esto es lo que queda al quitar los bits relacionados al termostato del DS1621. Solo el
bit1SHOT tiene uso imprescindible.
El registro de Configuración está implementado en EEPROM. Una escritura en él
tomará un tiempo típico de 10 ms y máximo de 50 ms. En ese lapso el DS1624
responderá con un NACK a los siguientes datos que reciba. Se puede usar eso como
flag, o poner un delay apropiado antes de realizar más lecturas o escrituras.
Registro de Configuración del DS1624

DONE

1

0

0

1

0

1

1SHOT

Registro de Microcontrolador

DONE

Conversion Done bit
1 = Conversión completada
0 = Conversión en progreso
En el DS1624 una conversión de temperatura suele durar 400 ms y 1000 ms como
mucho. Muchas veces será preferible esperar más de 1 segundo antes que
sondear este bit.

1SHOT

One Shot Mode
1 = Establece operación del DS1624 en modo One Shot
0 = Establece operación del DS1624 en modo de Conversiones Continuas

Instrucciones del DS1624
La única novedad es la presencia de Access Memory.
Tabla Código

Código Instrucción

Función

0xAA

Read Temperature

Lee la última temperatura convertida

0xEE

Start Convert T

Inicia la conversión de temperatura

0x22

Stop Convert T

Detiene la conversión de temperatura

0xAC

Access Config

Lectura o escritura en el registro de Configuración

0x17

Access Memory

Lee o escribe en la EEPROM

Práctica: Uso del DS1624: Modo One shot
El DS1624 es controlado en modo One Shot. Si se desea ver una operación en modo
de conversiones continuas, se puede revisar la segunda práctica del DS1621. Los
cambios en el código serían mínimos, en las
funciones ds1621_ReadTemperature y DisplayTempbásicamente.
Circuito para el sensor de temperatura DS1624 y el microcontrolador AVR.

El código fuente
/******************************************************************************
* FileName: main.c
* Purpose: Uso del termómetro digital y memoria DS1624: One Shot mode
* Processor: ATmel AVR con módulo TWI
* Compiler: AVR IAR C y AVR GCC (WinAVR)
* Author:

Shawn Johnson. http://www.cursomicros.com.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License: Se permiten el uso y la redistribución de este código con
*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y las notas de autor y copyright de arriba.
*****************************************************************************/

#include "avr_compiler.h"
#include "usart.h"
#include "i2c.h"

/*** Prototipos de funciones ***/
intds1624_ReadTemperature(void);
voidDisplayTemp(void);
voidds1624_WriteConfig(char);
chards1624_ReadConfig(void);
voidds1624_StartConvert(void);
voidds1624_WriteEEPROM(charaddress,chardata);
chards1624_ReadEEPROM(charaddress);
voidds1624_ready(void);
voiddelay_ms(unsignedintt);

/*** Función principal ***/
intmain(void)
{
i2c_init();// 100 kHz
usart_init();// 9600 - 8N1

printf("nr DS1624: Digital Thermometer and Memory");

ds1624_WriteConfig(0x01);// Establecer modo One Shot
printf("nrr Pulse ENTER para leer temperatura");

while(1)
{
if(kbhit())// Si llegó algún dato
{
if(getchar()=='r')// Y si es ENTER
DisplayTemp();
}
}
}

//****************************************************************************
// Lee la temperatura del DS1624 y la muestra en el terminal serial.
//****************************************************************************
voidDisplayTemp(void)
{
inttemp;
chardone;

ds1624_StartConvert();// Iniciar conversión

/* El siguiente bucle de espera puede sustituirse por un delay > 1000ms */
do{
delay_ms(10);
done=ds1624_ReadConfig()&0x80;
}while(done==0);// Esperar conversión completada

temp=ds1624_ReadTemperature();// Leer temperatura

printf("rn Temp = %d.%d °C",temp/100,abs(temp%100));
}

//****************************************************************************
// Escribe 'data' en el registro de Configuración del DS1624
//****************************************************************************
voidds1624_WriteConfig(chardata)
{
i2c_start();
i2c_write(0x90);// Slave address + Write
i2c_write(0xAC);// Access Config instruction
i2c_write(data);// Data to Config register
i2c_stop();
delay_ms(15);// EEPROM write cycle period > 10ms
}

//****************************************************************************
// Lee el registro de Configuración del DS1624
//****************************************************************************
chards1624_ReadConfig(void)
{
chardata;
i2c_start();
i2c_write(0x90);// Slave address + Write
i2c_write(0xAC);// Access Config instruction
i2c_restart();
i2c_write(0x90|0x01);// Slave address + Read
data=i2c_read(1);// Get register data
i2c_stop();
returndata;
}

//****************************************************************************
// Inicia una/la conversión de temperatura.
//****************************************************************************
voidds1624_StartConvert(void)
{
i2c_start();
i2c_write(0x90);// Slave address + Write
i2c_write(0xEE);// Start Convert T instruction
i2c_stop();
}

//****************************************************************************
// Lee la temperatura del DS1624.
// Devuelve el valor de la temperatura multiplicado por 100.
//****************************************************************************
intds1624_ReadTemperature(void)
{
constunsignedcharfrac[]={0,3,6,9,13,16,19,22,25,28,31,34,38,
41,44,47,50,53,56,59,63,66,69,72,75,78,81,84,88,91,94,97};
intdata;
signedcharhigh;
unsignedcharlow;
i2c_start();
i2c_write(0x90);// Slave address + Write
i2c_write(0xAA);// Read Temperature instruction
i2c_restart();
i2c_write(0x90|0x01);// Slave address + Read
high=i2c_read(0);// Read integer term
low=i2c_read(1);// Read fractional term
i2c_stop();

data=high;
data*=100;// Multiplicar por 100
low>>=3;// Justificar a la derecha
data+=frac[low];// Sumar parte fraccionaria redondeada
returndata;
}

//****************************************************************************
// FUNCIONES PARA ACCEDER A LA EEPROM DEL DS1624
//****************************************************************************
//****************************************************************************
// Escribe 'data' en la dirección 'address' de la EEPROM del DS1624
//****************************************************************************
voidds1624_WriteEEPROM(charaddress,chardata)
{
i2c_start();
i2c_write(0x90);// Slave address + Write
i2c_write(0x17);// Access Memory instruction
i2c_write(address);// EEPROM address
i2c_write(data);// Data to EEPROM
i2c_stop();
delay_ms(15);// EEPROM write cycle period > 10ms
}

//****************************************************************************
// Lee de la dirección 'address' de la EEPROM del DS1624
//****************************************************************************
chards1624_ReadEEPROM(charaddress)
{
chardata;
i2c_start();
i2c_write(0x90);// Slave address + Write
i2c_write(0x17);// Access Memory instruction
i2c_write(address);// EEPROM address
i2c_restart();
i2c_write(0x90|0x01);// Slave address + Read
data=i2c_read(1);// Data from EEPROM
i2c_stop();
returndata;
}

//****************************************************************************
// Espera hasta que el DS1624 esté listo para recibir nuevas lecturas o
// escrituras de datos.
//****************************************************************************
voidds1624_ready(void)
{
charack;
do{
i2c_start();// Enviar START +
ack=i2c_write(0x09);// Slave address + Write
i2c_stop();//
}while(ack!=0);// Hasta que se reciba un ACK (0)
}
//****************************************************************************
// delay_ms()
//****************************************************************************
voiddelay_ms(unsignedintt){
while(t--)
delay_us(1000);
}
Descripción del programa
Empiezo por comentar la función ds1624_ReadTemperature.
Sabemos que de los 13 bits de resolución de este sensor 5 bits le corresponden a la
parte fraccionaria. Eso significa que la parte fraccionaria puede tener uno de los
siguientes 32 posibles valores (columna central).

Nota que 5 dígitos decimales dan la falsa impresión de una precisión extrema, cuando
en realidad solo se pueden tener 32 valores discretos. Por eso pienso que no tendría
sentido trabajar con todos ellos. Lo que hice en el programa fue redondear estos
valores a dos dígitos decimales, multiplicarlos por 100 y colocarlos en la matriz
constante frac.
El título del código dice termómetro digital y memoria, aunque no se vea ninguna
rutina con dicha EEPROM. En caso de ser necesario, se pueden usar los siguientes
procedimientos. de acceso. La escritura en la EEPROM dura entre 10 ms y 50 ms. En
lugar del delay puesto también se podría usar la funciónds1624_ready. Lo mismo es
válido en la escritura del registro de configuración, dado que también es EEPROM.
La EEPROM del DS1624 también soporta acceso secuencial, como las 24xxx. En la
lectura se pueden obtener los 256 bytes si se quisiera, pero en las escrituras se graban
un máximo de 8 bytes por transferencia. Solo los tres primeros bits del puntero de
memoria se incrementan automáticamente. No pongo más códigos porque creo que ya
estoy redundando demasiado.

//****************************************************************************
// FUNCIONES PARA ACCEDER A LA EEPROM DEL DS1624
//****************************************************************************

//****************************************************************************
// Escribe 'data' en la dirección 'address' de la EEPROM del DS1624
//****************************************************************************
voidds1624_WriteEEPROM(charaddress,chardata)
{
i2c_start();
i2c_write(0x90);// Slave address + Write
i2c_write(0x17);// Access Memory instruction
i2c_write(address);// EEPROM address
i2c_write(data);// Data to EEPROM
i2c_stop();
delay_ms(15);// EEPROM write cycle period > 10ms
}

//****************************************************************************
// Lee de la dirección 'address' de la EEPROM del DS1624
//****************************************************************************
chards1624_ReadEEPROM(charaddress)
{
chardata;
i2c_start();
i2c_write(0x90);// Slave address + Write
i2c_write(0x17);// Access Memory instruction
i2c_write(address);// EEPROM address
i2c_restart();
i2c_write(0x90|0x01);// Slave address + Read
data=i2c_read(1);// Data from EEPROM
i2c_stop();
returndata;
}

//****************************************************************************
// Espera hasta que el DS1624 esté listo para recibir nuevas lecturas o
// escrituras de datos.
//****************************************************************************
voidds1624_ready(void)
{
charack;
do{
i2c_start();// Enviar START +
ack=i2c_write(0x09);// Slave address + Write
i2c_stop();//
}while(ack!=0);// Hasta que se reciba un ACK (0)
}
//****************************************************************************
// delay_ms()
//****************************************************************************
voiddelay_ms(unsignedintt){
while(t--)
delay_us(1000);
}

El Sensor de Temperatura LM75
No encuentro grandes razones para elegir entre un termómetro y otro. Puedo ver en
cada uno ciertas ventajas o limitaciones pero son pequeñas. Aquí tenemos algunas
características que nos pintan las potencialidades del LM75.
Tiene una resolución de 9 bits, 8 de parte entera y 1 de parte fraccionaria, pudiendo
medir temperaturas desde –55°C hasta +125°C, con incrementos de 0.5°C.
Tiempo de conversión típico de 100 ms y máximo de 300 ms.
Tiene una imprecisión de ±2°C entre –25°C y +100°C.
Velocidad de transferencia máxima 400 kbits/s (Fast mode).
Una vez puesto en marcha, el LM75 convierte la temperatura constante y
sucesivamente. Los registros de temperatura se actualizan cuando termina una
conversión y están disponibles para lectura en todo momento. Sin embargo se deberá
tener en cuenta que cada acceso al LM75 reiniciará la conversión actual. Como no hay
forma de conocer el momento exacto en que termina una conversión, será necesario
poner pausas lo suficientemente grandes para permitir que los registros de
temperatura se actualicen normalmente.
El LM75 no tiene un modo de conversión tipo One Shot pero sí se puede poner en
modoStandby o Shut down para ahorrar energía, de modo que sin mucha imaginación
podremos armar rutinas que lo emulen.
Diagrama de pines del termómetro LM75 en SOP-8.
El pin O.S. (Overtemperature Shutdown) es la salida del termostato. Sobra decir que
con los pines A0, A1 y A2 permiten conectar hasta 8 de estos dispositivos en una
misma red I2C.

El Termostato del LM75
Si hay alguna parte del LM75 que destaca con más claridad respecto de otros
termómetros es que tiene un termostato ligeramente más sofisticado.
El mecanismo del termostato puede cambiar el nivel del pin O.S. cuando la
temperatura llegue o pase los valores de los registros THYST Set Point y TOS Set
Point. La forma en que conmuta dicho pin dependerá del modo en que opere el
termostato, el cual puede sermodo de Interrupciones o modo Comparador. Este último
sería el equivalente a la función única del termostato del DS1621 (o similar). También
tiene la capacidad de reconfirmar las temperaturas extremas para asegurarse de que
se han alcanzado los límites de histéresis programados.
A pesar de todo, yo sigo pensando que no es tan atractivo a menos que opere en
modoStand alone, o sea, sin conexión a un microcontrolador. Así que no profundizaré
en este tema.

Los Registros del LM75
Aquí tenemos nuevamente los tres tipos de registros de un termómetro típico. El
registro de temperatura, el registro de configuración y los registros del termostato.
Tabla de Registro del LM75

Registro

Dirección Tamaño

Función

Temperature

0x00

2 bytes

Contienen la última temperatura convertida

Configuration

0x01

1 bytes

Configura y controla la operación del LM75

T HYST Set Point

0x10

2 bytes

Establece el límite superior del termostato

T OS Set Point

0x11

2 bytes

Establece el límite inferior del termostato

El registro de temperatura tiene el siguiente formato, ya familiar. Obviamente los
registros del termostato también tienen la misma estructura. Para ver ejemplos sobre
la interpretación de este formato ir a la sección correspondiente del DS1621.
Disposición del registro de temperatura del LM75.
En el registro de configuración los 3 bits superiores no están implementados, los 4 bits
siguientes pertenecen al termostato y no serán detallados aquí. Al final nos quedamos
solos con el bit Shut Down.
Registro de configuración del LM75

0

0 0 Fault Queue 1 Fault Queue 0 OS Polarity

Com/Int

ShutDown

Registro de Microcontrolador

Fault Queue 1: Fault Queue bits
Fault Queue 0:
Establecen la cantidad de veces que el dispositivo tendrá que detectar los
valores de umbral para la función del termostato. Así funciona como filtro.
OS Polarity

Overtemperature Shutdown Polarity bit
Establece si el pin de salida del termostato O.S. se activará en 1 ó en 0

Comp/Int

Comparator Interrupt mode select bit
Establece si el termostato operará en modo Comparador o deInterrupción

ShutDown

Shut Down bit
1 = Pone al LM75 en modo Shut Down o Standby
0 = El LM75 opera normalmente convirtiendo temperaturas sin cesar

Acceso a los Registros del LM75
Todo lo que necesitamos saber es que el LM75 tiene la misma dirección de
esclavo 0x90(incluyendo bit R/W = 0) y que los procedimientos de acceso... Bueno,
mejor velos en el código de la siguiente práctica.

Práctica: Uso del LM75
El hecho de que el LM75 sea similar al DS1621 se reflejará en que este programa es
una adaptación del segundo programa del DS1621. Siempre se puede ignorar la parte
fraccionaria pero nosotros seguiremos considerándola. Sería un desperdicio no hacerlo.
Para tal caso mejor sería trabajar con un dispositivo ad hoc como el TC74, estudiado
después de terminar esta práctica.
El LM75 es quizá el sensor bandera deNational Semiconductors. Pero hay otros
mejores de la misma familia LM7X. En adelante se pueden encontrar ejemplos con el
LM76 o LM73.

Circuito para el sensor de temperatura LM75 y el microcontrolador AVR.

El código fuente
/******************************************************************************
* FileName: main.c
* Purpose: Uso del sensor de temperatura digital LM75
* Processor: ATmel AVR con módulo TWI
* Compiler: AVR IAR C y AVR GCC (WinAVR)
* Author:

Shawn Johnson. http://www.cursomicros.com.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License: Se permiten el uso y la redistribución de este código con
*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y las notas de autor y copyright de arriba.

*****************************************************************************/

#include "avr_compiler.h"
#include "usart.h"
#include "i2c.h"

/*** Prototipos de funciones ***/
voidlm75_config(char);
intlm75_GetTemperature(void);
voidDisplayTemp(void);
voiddelay_ms(unsignedintt);

/*** Función principal ***/
intmain(void)
{
unsignedchari;

i2c_init();// 100 kHz
usart_init();// 9600 - 8N1

printf("nr LM75: Sensor de temperatura");

while(1)
{
lm75_config(0x00);// Arrancar las conversiones continuas

printf("nrr LM75 Convirtiendo...");
for(i=0;i<10;i++)
{
delay_ms(800);// Debe ser mayor que 300 ms
DisplayTemp();
}

lm75_config(0x01);// Entrar en modo Shutdown o Standby

printf("nrr LM75 en Standby por 10s");
delay_ms(10000);
}
}

//****************************************************************************
// Lee la temperatura del LM75 y la muestra en el terminal serial.
//****************************************************************************
voidDisplayTemp(void)
{
inttemp;

temp=lm75_GetTemperature();// Leer temperatura
printf("rn Temp = %d.%d °C",temp/10,abs(temp%10));
}

//****************************************************************************
// Escribe 'data' en el registro de Configuración del LM75
//****************************************************************************
voidlm75_config(chardata)
{
i2c_start();
i2c_write(0x90);// Slave address + Write
i2c_write(0x01);// Configuration register address
i2c_write(data);// Data to register
i2c_stop();
}

//****************************************************************************
// Lee la temperatura del LM75.
// El valor devuelto está multiplicado por 10.
//****************************************************************************
intlm75_GetTemperature(void)
{
intdata;
signedcharhigh,low;
i2c_start();
i2c_write(0x90);// Slave address + Write
i2c_write(0x00);// Temperature register address
i2c_restart();
i2c_write(0x90|0x01);// Slave address + Read
high=i2c_read(0);// Read integer term
low=i2c_read(1);// Read fractional term
i2c_stop();

data=high;
data*=10;
if(low&0x80)// ¿ Sumar 5°C ?
data+=5;
returndata;
}

//****************************************************************************
// delay_ms()
//****************************************************************************
voiddelay_ms(unsignedintt){
while(t--)
delay_us(1000);
}

El Sensor de Temperatura LM76
Para ser honesto, ya me cansé de repetir casi las mismas cosas, así que dejaremos la
teoría pertinente de lado aunque sea por esta ocasión. La única gran diferencia con
que nos toparemos en estas prácticas con el LM76 y LM73 estará en el formato de los
registros de temperatura.

Práctica: Uso del LM76
Circuito para el sensor de temperatura LM76 y el microcontrolador AVR.

El código fuente
/******************************************************************************
* FileName: main.c
* Purpose: Uso del termómetro digital LM76
* Processor: ATmel AVR con módulo TWI
* Compiler: AVR IAR C y AVR GCC (WinAVR)
* Author:

Shawn Johnson. http://www.cursomicros.com.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License: Se permiten el uso y la redistribución de este código con
*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y las notas de autor y copyright de arriba.
*****************************************************************************/

#include "avr_compiler.h"
#include "usart.h"
#include "i2c.h"

/*** Prototipos de funciones ***/
intlm76_ReadTemperature(void);
voidDisplayTemp(void);
voidlm76_config(char);
voiddelay_ms(unsignedintt);

/*** Función principal ***/
intmain(void)
{
i2c_init();// 100 kHz
usart_init();// 9600 - 8N1

printf("nr LM76: Digital Temperature Sensor");

while(1)
{
lm76_config(0x00);// Arrancar las conversiones continuas

printf("nrr LM76 Convirtiendo...");
for(inti=0;i<10;i++)
{
delay_ms(1100);// Debe ser mayor que 1000 ms
DisplayTemp();
}

lm76_config(0x01);// Entrar en modo Shutdown o Standby

printf("nrr LM76 en Standby por 10s");
delay_ms(10000);
}
}

//****************************************************************************
// Lee la temperatura del LM76 y la muestra en el terminal serial.
//****************************************************************************
voidDisplayTemp(void)
{
inttemp;

temp=lm76_ReadTemperature();// Leer temperatura

printf("rn Temp = %d.%d °C",temp/10,abs(temp%10));
}

//****************************************************************************
// Escribe 'data' en el registro de Configuración del LM76
//****************************************************************************
voidlm76_config(chardata)
{
i2c_start();
i2c_write(0x90);// Slave address + Write
i2c_write(0x01);// Configuration register address
i2c_write(data);// Data to Configuration register
i2c_stop();
}

//****************************************************************************
// Lee la temperatura del LM76.
// Devuelve el valor de la temperatura multiplicado por 10.
//****************************************************************************
intlm76_ReadTemperature(void)
{
constunsignedcharfrac[]={0,1,1,2,3,3,4,4,5,6,6,7,8,8,9,9};
signedintdata;
signedcharhigh;
unsignedcharlow;
i2c_start();
i2c_write(0x90);// Slave address + Write
i2c_write(0x00);// Temperature register address
i2c_restart();
i2c_write(0x90|0x01);// Slave address + Read
high=i2c_read(0);// Read first byte
low=i2c_read(1);// Read second byte
i2c_stop();

data=high;
data*=20;// Multiplicar por 20
if(low&0x80)
data+=10;// Sumar LSbit
low&=0x7F;// Limpiar MSbit
low>>=3;// Justificar a la derecha
data+=frac[low];// Sumar parte fraccionaria redondeada
returndata;
}

//****************************************************************************
// delay_ms()
//****************************************************************************
voiddelay_ms(unsignedintt){
while(t--)
delay_us(1000);
}

Práctica: Uso del LM73
Circuito para el sensor de temperatura LM73 y el microcontrolador AVR.

El código fuente
/******************************************************************************
* FileName: main.c
* Purpose: Uso del termómetro digital LM73: modo One shot y de 14 bits
* Processor: ATmel AVR con módulo TWI
* Compiler: AVR IAR C y AVR GCC (WinAVR)
* Author:

Shawn Johnson. http://www.cursomicros.com.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License: Se permiten el uso y la redistribución de este código con
*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y las notas de autor y copyright de arriba.
*****************************************************************************/

#include "avr_compiler.h"
#include "usart.h"
#include "i2c.h"

/*** Prototipos de funciones ***/
voidDisplayTemp(void);
voidlm73_write(charaddress,chardata);
charlm73_ReadCtrlStat(void);
intlm73_ReadTemperature(void);
unsignedintlm73_ReadID(void);
voiddelay_ms(unsignedintt);

/* Posibles direcciones de esclavo (con bitRW = 0) del LM73 según su
versión (LM73-0/1) y la conexión del pin ADDR */

//#define SLAVE 0x90 // LM73-0, ADDR pin -> float
#define SLAVE 0x92

// LM73-0, ADDR pin -> GND

//#define SLAVE 0x94 // LM73-0, ADDR pin -> VDD
//#define SLAVE 0x98 // LM73-1, ADDR pin -> float
//#define SLAVE 0x9A // LM73-1, ADDR pin -> GND
//#define SLAVE 0x9C // LM73-1, ADDR pin -> VDD

#define CONFIG

0X00 // CONFIGURATION register address

#define CTRL_STAT 0X04 // CONTROL/STATUS register address
/*** Función principal ***/
intmain(void)
{
i2c_init();// 100 kHz
usart_init();// 9600 - 8N1

printf("nr LM73: Digital Temperature Sensor");

if((lm73_ReadID()&0xFFF0)==0x0190)
printf("nrr A National LM73 Thermometer has been detected.");

// Establecer resolución de 14 bits e inhabilitar los temporizadores de
// bus-idle de los pines SCL y SDA para ahorrar más enegía.

lm73_write(CTRL_STAT,0xE0);// Write to CONTROL/STATUS register

// El LM73 inicia en modo de Conversiones Continuas. En seguida lo
// pondremos en modo Standby para establecer una operación One-Shot.
// Va seguido de un delay > 112 ms (máximo tiempo de conversión para
// una resolución de 14 bits) para que termine la conversion actual
// y asegurar un buen ingreso en modo Standby :)

lm73_write(CONFIG,0x80);// Write to CONFIGURATION register
delay_ms(120);
printf("nrr Press ENTER to get temperature");

while(1)
{
if(kbhit())// Si llegó algún dato
{
if(getchar()=='r')// Y si es ENTER
DisplayTemp();
}
}
}

//****************************************************************************
// Lee la temperatura del LM73 y la muestra en el terminal serial.
//****************************************************************************
voidDisplayTemp(void)
{
inttemp;
chardav;

// Inicia una conversión de temperatura seteando el bit ONE SHOT y
// manteniendo a 1 el bit PD (Power Down) del registro CONFIGURATION

lm73_write(CONFIG,0x84);// Write to CONFIGURATION register

// El siguiente bucle espera a que termine la conversión. Como el LM73
// opera con resolución de 14 bits, puede sustituirse por un delay > 112ms
do{
delay_ms(5);
dav=lm73_ReadCtrlStat();// Read CONTROL/STATUS register
}while((dav&0x01)==0);// If DAV bit = 1 => Conversion is finished

temp=lm73_ReadTemperature();// Leer temperatura

printf("rn Temp = %d.%d °C",temp/100,abs(temp%100));
}

//****************************************************************************
// Escribe el byte 'data' en el registro de dirección 'address' del LM73
//****************************************************************************
voidlm73_write(charaddress,chardata)
{
i2c_start();
i2c_write(SLAVE);// Slave address + Write
i2c_write(address);// Register address
i2c_write(data);// Data to Config register
i2c_stop();
}

//****************************************************************************
// Lee el registro de CONTROL/STATUS del LM73
//****************************************************************************
charlm73_ReadCtrlStat(void)
{
chardata;
i2c_start();
i2c_write(SLAVE);// Slave address + Write
i2c_write(CTRL_STAT);// CONTROL/STATUS register address
i2c_restart();
i2c_write(SLAVE|0x01);// Slave address + Read
data=i2c_read(1);// Get register data
i2c_stop();
returndata;
}

//****************************************************************************
// Lee la temperatura del LM73.
// Devuelve el valor de la temperatura multiplicado por 100.
//****************************************************************************
intlm73_ReadTemperature(void)
{
constunsignedcharfrac[]={0,3,6,9,13,16,19,22,25,28,31,34,38,41,44,
47,50,53,56,59,63,66,69,72,75,78,81,84,88,91,94,97};
intdata;
signedcharhigh;
unsignedcharlow;
i2c_start();
i2c_write(SLAVE);// Slave address + Write
i2c_write(0x00);// TEMPERATURE register address
i2c_restart();
i2c_write(SLAVE|0x01);// Slave address + Read
high=i2c_read(0);// Read MSbyte
low=i2c_read(1);// Read LSbyte
i2c_stop();

data=high;
data*=200;// Multiplicar por 200
if(low&0x80)
data+=100;// Sumar LSbit
low&=0x7F;// Limpiar MSbit
low>>=2;// Justificar a la derecha
data+=frac[low];// Sumar parte fraccionaria redondeada
returndata;
}

//****************************************************************************
// Lee el registro IDENTIFICATION del LM73
//****************************************************************************
unsignedintlm73_ReadID(void)
{
unsignedintid;
i2c_start();
i2c_write(SLAVE);// Slave address + Write
i2c_write(0x07);// IDENTIFICATION register address
i2c_restart();
i2c_write(SLAVE|0x01);// Slave address + Read
id=i2c_read(0);// MSbyte
id<<=8;
id|=i2c_read(1);// LSbyte
i2c_stop();
returnid;
}

//****************************************************************************
// delay_ms()
//****************************************************************************
voiddelay_ms(unsignedintt){
while(t--)
delay_us(1000);
}

El Mini Sensor de Temperatura TC74
Éste es un pequeño termómetro fabricado por Microchip. Es ideal para proyectos
sencillos por su bajo precio y porque cuenta con los recursos mínimamente necesarios.
No tiene función de termostato ni memorias RAM o EEPROM adicionales. Viene en
empaques pequeños y con una forma (T0-220) que permite captar rápidamente la
temperatura del medio o material al que se le aplica.
El mini sensor de temperatura TC74 en empaque TO-220.
Las características generales del sensorTC74 para comparar su performance con la de
otros termómetros son:
Tiene una resolución de 8 bits y permite medir temperaturas desde –40°C hasta
+125°C con incrementos de 1°C.
Su tiempo de conversión típico es de 125 ms, salvo la primera conversión (después del
POR), la cual puede durar hasta 250 ms.
Máxima velocidad de transferencia de datos de 100 kbit/s (Standard Mode).
Se pueden encontrar con 8 direcciones de esclavo diferentes para conectar hasta 8
termómetros TC74 en un mismo bus I2C.

Operación del TC74
En cuanto a la conversión de temperatura el TC74 solo tiene un modo de operación, el
cual sería comparable con el modo de conversiones continuas del DS1621 (o similar).
Mientras esté activado, el TC74 convertirá la temperatura constantemente y depositará
el resultado en el registro de temperatura, el cual se puede leer en todo momento.
El TC74 también puede ser puesto en modo Standby para reducir el consumo de
energía. Al salir del Standby el TC74 reanuda la conversión de inmediato.
Sabemos que en la práctica podemos combinar las características descritas para
manejar el termómetro de forma que parezca una operación tipo One Shot.

Los Registros del TC74
La sencillez de este dispositivo se refleja en el hecho de que solo tiene dos registros y
de 8 bits cada uno. Como siempre TEMP es de solo lectura y CONFIG es de
lectura/escritura. Ambos se inician con 0x00 tras el reset POR.
Tabla Registro
Registro Dirección Función
TEMP

0x00

CONFIG 0x01

Contiene la última temperatura convertida
Registro de configuración

A continuación, el mapa de bits del registro CONFIG. Los 6 primeros bits no están
implementados y siempre se leerán como 0s.
Registro CONFIG del TC74

SHDN

DATA_RDY 0

0

0

0

0

0

Registro de Microcontrolador

SHDN

Shut Down bit (read-write bit)
0 = El TC74 opera normalmente convirtiendo las temperaturas
sucesivamente
1 = El TC74 permanece en estado Standby

DATA_RD
Y

Data Ready bit (read only bit)
0 = La conversión está en progreso
1 = La conversión de temperatura ha terminado
En el TC74 una conversión de temperatura dura como mucho 150 ms,
excepto la primera, la cual puede llegar a 250 ms. Se puede evitar el
sondeo de este bit poniendo un delay apropiado. Este bit es de solo lectura.
Se limpia automáticamente por hardware al entrar en modo Standby o
luego de un reset.

El registro de temperatura TEMP almacena la temperatura convertida más
recientemente. Su valor no cambiará si el dispositivo entra en modo Standby. El
formato del dato sigue siendo de complemento a 2 y con el primer bit como signo. Si
tuvieras dudas sobre esto, podrías revisar la teoría relacionada del DS1621.

El acceso a ambos registros es de modo individual y siguiendo el procedimiento
esperado, los cuales se exponen en el código de la siguiente práctica.

Dirección de Esclavo del TC74
Es posible poner en paralelo hasta 8 sensores TC74 en un bus I2C. La pregunta es
¿cómo se hace esto si este sensor no tiene pines de dirección como otros dispositivos
I2C? Sucede que en realidad el TC74 puede venir con diferentes direcciones de
esclavo. Cada uno se identifica con un código diferente inscrito en su empaque. De
este código lo que ahora nos interesa es el AX que sigue al TC74. Allí la X vale por los
3 bits inferiores de la dirección de esclavo. Los 4 bits superiores son siempre iguales
a 1001.
Por ejemplo, el termómetro que Microchip distribuye como dispositivo genérico tiene el
código TC74A5-xxx (el valor de las xxx no viene al caso), es decir, su dirección de
esclavo es 1001 101.

Un TC74 estándar en empaque TO-220 con código completo.
Los otros códigos pertenecen a los TC74 con las siguientes direcciones (de todos
modos, estos modelos no son fáciles de hallar):
El TC74A0-XXX tiene dirección de esclavo 1001 000
El TC74A1-XXX tiene dirección de esclavo 1001 001
El TC74A2-XXX tiene dirección de esclavo 1001 010
El TC74A3-XXX tiene dirección de esclavo 1001 011
El TC74A4-XXX tiene dirección de esclavo 1001 100
El TC74A5-XXX tiene dirección de esclavo 1001 101
El TC74A6-XXX tiene dirección de esclavo 1001 110
El TC74A7-XXX tiene dirección de esclavo 1001 111

Práctica: Uso del TC74
El programa es una adaptación más de los tantos programas vistos anteriormente. El
TC74 permanece en modo Standby y se reactiva cuando el microcontrolador recibe un
ENTER por el puerto serie. Después de leer la temperatura se vuelve a enviar el
termómetro a modo Standby.

Circuito para el sensor de temperatura TC74 y el microcontrolador AVR.

El código fuente
/******************************************************************************
* FileName: main.c
* Purpose: Uso del mini sensor digital de temperatura TC74
* Processor: ATmel AVR con módulo TWI
* Compiler: AVR IAR C y AVR GCC (WinAVR)
* Author:

Shawn Johnson. http://www.cursomicros.com.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License: Se permiten el uso y la redistribución de este código con
*

modificaciones o sin ellas, siempre que se mantengan esta
*

licencia y las notas de autor y copyright de arriba.

*****************************************************************************/

#include "avr_compiler.h"
#include "usart.h"
#include "i2c.h"

/*** Prototipos de funciones ***/
voidDisplayTemp(void);
voidtc74_config(chardata);
chartc74_read(charaddress);

/*** Función principal ***/
intmain(void)
{
i2c_init();// 91 kHz
usart_init();// 9600 - 8N1

printf("nr TC74: Tiny Serial Digital Thermal Sensor");

tc74_config(0x80);// Poner TC74 en Standby

printf("nrr Pulse ENTER para obtener la temperatura ");

while(1)
{
if(kbhit())// Si llegó algún dato
{
if(getchar()=='r')// Y si es ENTER
DisplayTemp();
}
}
}

//****************************************************************************
// Obtiene la temperatura del TC74 y la muestra en el terminal serial.
//****************************************************************************
voidDisplayTemp(void)
{
signedchartemp;
charconf;

tc74_config(0x00);// Arrancar el conversor

/* El siguiente bucle espera a que termine la conversión. Eso pasará
* cuando el bit DATA_RDY (bit 6) del registro CONFIG se active a 1.
* Esta rutina se puede sustituir por un delay > 125ms, ó > 250ms si es
* la primera conversión.
*/
do{//
delay_us(10000);// Para no sondear demasiado seguido
conf=tc74_read(0x01);// Leer registro CONFIG
}while((conf&0x40)==0);// Hasta que conf.6 = 1

temp=tc74_read(0x00);// Leer temperatura

tc74_config(0x80);// Poner TC74 en Standby

printf("rn Temp = %d °C",temp);
}

//****************************************************************************
// Escribe 'data' en el registro de configuración (CONFIG) del TC74.
//****************************************************************************
voidtc74_config(chardata)
{
i2c_start();
i2c_write(0x9A);// Slave address + Write
i2c_write(0x01);// CONFIG register address
i2c_write(data);// Data to register
i2c_stop();
}

//****************************************************************************
// Lee el registro de dirección 'address' del TC74.
// - address = 0x00 => Registro de temperatura TEMP
// - address = 0x01 => Registro de configuración CONFIG
//****************************************************************************
chartc74_read(charaddress)
{
signedchardata;
i2c_start();
i2c_write(0x9A);// Slave address + Write
i2c_write(address);// Register address
i2c_restart();
i2c_write(0x9A|0x01);// Slave address + Read
data=i2c_read(1);// Read register and send NACK
i2c_stop();
returndata;
}

Práctica: Uso del TMP100 TMP101
Circuito para el sensor de temperatura TMP100 y el microcontrolador AVR.

El código fuente
/******************************************************************************
* FileName: main.c
* Purpose: Uso del termómetro digital TMP100/TMP101
* Processor: ATmel AVR con módulo TWI
* Compiler: AVR IAR C y AVR GCC (WinAVR)
* Author:

Shawn Johnson. http://www.cursomicros.com.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License: Se permiten el uso y la redistribución de este código con
*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y las notas de autor y copyright de arriba.

*****************************************************************************/

#include "avr_compiler.h"
#include "usart.h"
#include "i2c.h"

/* Posibles direcciones de esclavo (con bitRW = 0) del TMP100/101 según su
versión (TMP100 - TMP101) y la conexión de los pines ADD0 - ADD1 */

// #define SLAVE 0x90 // TMP101, ADD0 -> GND
// #define SLAVE 0x92 // TMP101, ADD0 -> Float
// #define SLAVE 0x94 // TMP101, ADD0 -> VDD
#define SLAVE 0x90 // TMP100, ADD0 -> GND, ADD1 -> GND
// #define SLAVE 0x92 // TMP100, ADD0 -> Float, ADD1 -> GND
// #define SLAVE 0x94 // TMP100, ADD0 -> VDD, ADD1 -> GND
// #define SLAVE 0x98 // TMP100, ADD0 -> GND, ADD1 -> VDD
// #define SLAVE 0x9A // TMP100, ADD0 -> Float, ADD1 -> VDD
// #define SLAVE 0x9C // TMP100, ADD0 -> VDD, ADD1 -> VDD
// #define SLAVE 0x96 // TMP100, ADD0 -> GND, ADD1 -> Float
// #define SLAVE 0x9E // TMP100, ADD0 -> VDD, ADD1 -> Float

/*** Prototipos de funciones ***/
inttmp100_ReadTemperature(void);
voidDisplayTemp(void);
voidtmp100_config(char);
voiddelay_ms(unsignedintt);

/*** Función principal ***/
intmain(void)
{
i2c_init();// 100 kHz
usart_init();// 9600 - 8N1

printf("nr TMP100: Digital Temperature Sensor");

// Configurar el TMP100/101 con una resolución de 12 bits y ponerlo
// en modo Standby para realizar conversiones One shot.
// Se entrará realmente en Standby cuando se TMP100/101 termine la
// conversión actual.

tmp100_config(0x61);

printf("nrr Press ENTER to get Temperature");

while(1)
{
if(kbhit())// Si llegó algún dato
{
if(getchar()=='r')// Y si es ENTER
DisplayTemp();
}
}
}

//****************************************************************************
// Lee la temperatura del TMP100/101 y la muestra en el terminal serial.
//****************************************************************************
voidDisplayTemp(void)
{
inttemp;

tmp100_config(0xE1);// Iniciar conversión

// No hay forma de saber el momento preciso en que termina la conversión,
// así que habrá que esperar el tiempo adecuado de acuerdo con la
// resolución establecida para el TMP100/101, más de 320 ms para 12 bits

delay_ms(350);// > 320 ms for 12 bits resolution

temp=tmp100_ReadTemperature();// Leer temperatura

tmp100_config(0x61);// Clear OS/ALERT bit ???

printf("rn Temp = %d.%d °C",temp/10,abs(temp%10));
}

//****************************************************************************
// Escribe 'data' en el registro de Configuración del TMP100/101
//****************************************************************************
voidtmp100_config(chardata)
{
i2c_start();
i2c_write(SLAVE);// Slave address + Write
i2c_write(0x01);// Configuration register address
i2c_write(data);// Data to Configuration register
i2c_stop();
}

//****************************************************************************
// Lee la temperatura del TMP100/101.
// Devuelve el valor de la temperatura multiplicado por 10.
//****************************************************************************
inttmp100_ReadTemperature(void)
{
constunsignedcharfrac[]={0,1,1,2,3,3,4,4,5,6,6,7,8,8,9,9};
signedintdata;
signedcharhigh;
unsignedcharlow;
i2c_start();
i2c_write(SLAVE);// Slave address + Write
i2c_write(0x00);// Temperature register address
i2c_restart();
i2c_write(SLAVE|0x01);// Slave address + Read
high=i2c_read(0);// Read first byte
low=i2c_read(1);// Read second byte
i2c_stop();

data=high;
data*=10;// Multiplicar por 10
low>>=4;// Justificar a la derecha
data+=frac[low];// Sumar parte fraccionaria redondeada
returndata;
}

//****************************************************************************
// delay_ms()
//****************************************************************************
voiddelay_ms(unsignedintt){
while(t--)
delay_us(1000);
}

El Expansor de E/S PCA9554/A
Cuando yo veo que me faltan pines en un microcontrolador para alguna aplicación,
pues busco otro microcontrolador. Además también está la opción de usar registros de
desplazamiento (como los 74HC597 y74HC595.) para ampliar las señales de entrada o
salida del sistema si fuera necesario. Pero supongo que si estos expansores existen, es
porque deben servir para algo, o para alguien. En fin...
Al ser de poco uso, no hay muchos dispositivos expansores. Quizá el PCF8574 sea el
más conocido. El PCF8574 tiene un diseño pedestre y poco robusto a mi criterio. Por
otro lado están los expansores como elMCP23008 de Microchip, que, aunque de gran
velocidad, son muy pesados de manejar.
El PCA9554 es de NXP Semiconductors y es la versión mejorada del PCF8574. Aun así,
te puedo garantizar que es más fácil de usar: es casi tan simple como manejar un
puerto cualquiera de un AVR. Tan solo reconoce las siguientes características del
PCA9554.
Provee 8 pines de E/S bidireccionales configurables pin por pin (¿familiar?).
Todos los pines configurados como entradas tienen resistencias de pull-up.
Puede provocar una interrupción si alguno de los pines de entrada cambia su nivel.
Velocidad de operación en modo Fast Mode (hasta de 400 kbits/s).
El PCA9554A es el hermano del PCA9554. Solo los diferencia la dirección de
dispositivo.

Descripción de Pines del PCA9554 / A
El PCA9554 viene en diversos empaques. El siguiente diagrama corresponde al PDIP.
Diagrama de pines del PCA9554 / PCA9554A.
IO0...IO7. Pines de entrada/salida. Los pines configurados como entradas se ponen en
alta impedancia gracias a sus resistencias internas de pull-up de 100 k. Como fuente
pueden entregar hasta 85 mA en total y como sumidero pueden recibir hasta 100 mA
en total.
INT. Este pin se activa a 0 cuando se detecta un cambio de tensión en cualquier pin de
E/S configurado como entrada. Esa condición permanecerá hasta que se lea el puerto o
hasta que el pin de E/S retorne a su estado previo. Es de drenador abierto y necesita
de una pull-up externa.
VDD y VSS. Pines de alimentación
A2, A1 y A0. Pines para configurar parte de la dirección de dispositivo.
SDA y SCL. Pines de interface I2C.

Registros del PCA9554 / A
Tabla Dirección

Dirección Registro

Función

0x00

Input Port

Leer pines del puerto

0x01

Output Port

Escribir en pines del puerto

0x10

Polarity Inversion

Invertir lectura de pines del puerto

0x11

Configuration

Configurar dirección de pines del puerto
Input Port. Leyendo este registro se obtienen los valores de los pines del puerto, tanto
si están configurados como entradas o como salidas. Es de solo lectura y las escrituras
no tienen sentido.
Output Port. Escribiendo en este registro se establecen los niveles de los pines
configurados como salidas. No surte efecto en los pines de entrada. La lectura de este
registro no es de utilidad.
Configuration. Escribiendo en este registro se configura la dirección de los pines del
puerto. Un 1 es para entrada y un 0 es para salida. Funciona parecido a los registros
DDR de los AVR, pero al revés. Tras encender la alimentación todos los pines son
entradas.
Polarity Inversion. Si está todo a 0, no pasa nada. Si escribimos 1s en los bits de este
registro, haremos que la lectura de los pines correspondientes se invierta. Por ejemplo,
si en el puerto hay 0xF0 y Polarity Inversion vale 0xFF, el valor leído deInput Port será
0x0F. ¡Tanto para eso! Si quisiéramos invertir algún dato, sabemos que bien lo
podemos hacer vía software. Francamente, no le encuentro sentido a este registro.

Dirección de Esclavo del PCA9554 / A
He aquí la única diferencia entre el PCA9554 y el PCA9554A: la dirección de esclavo.
Verás a su vez que son las mismas direcciones que las del PCF8574 y del PCF8574A.
Al igual que en las EEPROM 24xxxx, los bits A2, A1 y A0 corresponden a la conexión de
los pines del mismo nombre.
Tabla Dirección de Esclavo del PCA9554

Byte de control (Slave address + bit R/W) del PCA9554 y PCA9554A, respectivamente.

Acceso a los Registros del PCA9554 / A
Conociendo la dirección de esclavo, los registros, sus direcciones y para qué sirven, y
las transferencias de datos en el protocolo I2C, el resto es como armar un
rompecabezas de piezas conocidas, salvo una:
En el PCA9554 el puntero de registros no se incrementa automáticamente luego de
cada acceso. Eso significa que si se ejecutará por ejemplo una lectura secuencial
(procedimiento válido), se leería el mismo registro una y otra vez.
Acceso a los registros del PCA9554 para escritura.

Acceso a los registros del PCA9554 para lectura.

Práctica 1 Uso del PCA9554 / A
Se controla un keypad o teclado de 4×4 con el PCA9554.
Circuito para el expansor PCA9554A y el microcontrolador AVR.

El código fuente
/******************************************************************************
* FileName: main.c
* Purpose: Expansor de E/S para bus I2C PCA9554. Control de teclado.
* Processor: ATmel AVR con módulo TWI
* Compiler: AVR IAR C y AVR GCC (WinAVR)
* Author:

Shawn Johnson. http://www.cursomicros.com.

*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License: Se permiten el uso y la redistribución de este código con
*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y las notas de autor y copyright de arriba.

*****************************************************************************/

#include "avr_compiler.h"
#include "usart.h"
#include "i2c.h"

// Direcciones de los registros del PCA9554/PCA9554A
#define PortIn

0x00 // Input Port register address

#define PortOut 0x01 // Output Port register address
#define PolarInv 0x02 // Polarity Inversion register address
#define Config

0x03 // Configuration register address

/*** Prototipos de funciones ***/
voidpca9554_write(charaddress,chardata);
charpca9554_read(charaddress);
charkeypad_scan(void);

/*** Función principal ***/
intmain(void)
{
charkey;

i2c_init();// 100 kHz
usart_init();// 9600 - 8N1

puts("nr PCA9554: Expansor de E/S para bus I2C");
puts("nr =====================================nr");

// Establecer polaridad normal del puerto, no invertida
pca9554_write(PolarInv,0x00);

// Configurar dirección de puerto: nibble alto entrada y nibble bajo salida
pca9554_write(Config,0xF0);

// Escribir 0xF0 en puerto del PCA9554
pca9554_write(PortOut,0xF0);

while(1)
{
if((PINC&0x08)==0)// ¿PC3 = 0? ¿INT = 0?
{
if(key=keypad_scan())
{
putchar(key);
delay_us(30000);// Debounce
while(keypad_scan());// Esperar teclado libre
}
}
}
}

//****************************************************************************
// Escribe 'data' en el registro de dirección 'address' del PCA9554.
// Se asume que los pines A2, A1 y A0 están conectados a GND.
//****************************************************************************
voidpca9554_write(charaddress,chardata)
{
i2c_start();
i2c_write(0x40);// Slave address + Write
i2c_write(address);// Register address
i2c_write(data);// Data to register
i2c_stop();
}

//****************************************************************************
// Lee el registro de dirección 'address' del PCA9554.
// Se asume que los pines A2, A1 y A0 están conectados a GND.
//****************************************************************************
charpca9554_read(charaddress)
{
charreg;
i2c_start();
i2c_write(0x40);// Slave address + Write
i2c_write(address);// Register address
i2c_start();// CCS C forces a Restart here
i2c_write(0x40|0x01);// Slave address + Read
reg=i2c_read(1);// Data from register and send NACK
i2c_stop();
returnreg;
}

//****************************************************************************
// Escanea el teclado y retorna el valor ASCII de la primera tecla que
// encuentre pulsada. De otro modo retorna 0x00.
//****************************************************************************
charkeypad_scan(void)
{
unsignedcharCol,Row;
charRowMask,ColMask,val,key=0x00;

//

Col0 Col1 Col2 Col3

constcharkeys[4][4]={{'7','8','9','A'},// Row 0
{'4','5','6','B'},// Row 1
{'1','2','3','C'},// Row 2
{'.','0','#','D'}};// Row 3

RowMask=0xFE;// Inicializar RowMask 11111110;

for(Row=0;Row<4;Row++)
{
pca9554_write(PortOut,RowMask);
ColMask=0x10;// Inicializar ColMask 00010000

for(Col=0;Col<4;Col++)
{
val=pca9554_read(PortIn);

if((val&ColMask)==0)// ¿Tecla pulsada ?
{
key=keys[Row][Col];// Guardar código de tecla pulsada
goto_exit;
}

ColMask<<=1;// Desplazar ColMask para escanear
}// siguiente columna

RowMask<<=1;// Desplazar RowMask para escanear
RowMask|=0x01;// siguiente fila
}
_exit:
// Escribir 0xF0 en puerto del PCA9554
pca9554_write(PortOut,0xF0);
returnkey;// Retornar código hallado
}
El Expansor de E/S PCF8574/A
El PCF8574 es un expansor con puerto de 8 pines paralelos, parecido al PCA9554, pero
más rústico. El PCF8574 se diferencia del PCF8574A por la dirección de esclavo.
Sus principales características son:
Provee 8 pines cuasi-bidireccionales. Siempre puede actuar como entrada pero como
salida tiene limitaciones. Como entrada cada pin activa su resistencia de pull-up.
El acceso al puerto es directo. No tiene registros de lectura o escritura ni registro de
configuración de dirección de los pines.
Puede soportar grandes corrientes en su puerto de I/O como sumidero (hasta 100 mA
sumando todos los pines), pero no como fuente.
En un bus se pueden colgar hasta 8 PCF8574 y 8 PCF8574A más configurando parte de
sus direcciones de esclavo con los pines A2, A1 y A0.
Velocidad de bus I2C de hasta 100 kbits/s (Standard Mode).

Diagrama de pines del PCF8574 / PCF8574A en empaque PDIP.
El pin INT es de drenador abierto y necesitará de una resistencia de pull-up externa
para ponerse en alto. Estará en ese estado hasta que cualquiera de los pines de I/O
que actúan como entradas detecte un cambio de nivel, momento en que INT se activa
a 0. INTvolverá a alto cuando el pin de I/O que cambió retorne a su estado o cuando
sea leído o cuando sea escrito.

Operación del PCF8574 / A
¿Qué significa que el PCF8574/ PCF8574A tenga un puerto cuasi-bidireccional y que no
tenga registros para configurar la dirección de sus pines? Lo explico rápido.
Al escribir 0 en un pin, éste se convierte automáticamente en salida y su estado será,
por supuesto, bajo.
Al escribir 1 en un pin, éste se convierte automáticamente en entrada. Pero incluso así,
a su salida se verá un nivel alto gracias a su resistencia de pull-up. Este estado alto
puede ser suficiente para excitar las entradas de otros dispositivos como ICs o
transistores, aunque será incapaz de sostenerse como para encender un LED por la
poquísima corriente que deja pasar la pull-up.
La lectura de los pines del puerto devuelve sus valores actuales, ya sea que se estén
comportando como entradas o como salidas.
Tras encender la alimentación todos los pines se comportan como entradas.

Acceso al Puerto del PCF8574 / A
Como el PCF8574 no tiene registros de puerto ni de configuración, el acceso a su
puerto es directo: se envía el byte de control (dirección de esclavo + orden de
lectura/escritura) y se efectúa la operación respectiva.
Como tampoco tiene un puntero de registros, las operaciones secuenciales acceden
siempre al mismo puerto. Por lo menos evita enviar START y STOP a cada rato.
Las direcciones de esclavo del PCF8574 y PCF8574A son las mismas que para el
PCA9554 y PCA9554A, respectivamente. Puedes verlas en Dirección de Esclavo del
PCA9554 / PCF8574A.

Acceso al puerto del PCF8574 para escritura.
Acceso al puerto del PCF8574 para lectura.

Práctica 2 Uso del PCF8574 / A
Esta vez usamos el expansor para controlar un display LCD. El modo de acceso es
mediante un bus de 4 bits y sin usar el bit busy flag.

Circuito para el expansor PCF8574 y el microcontrolador AVR.

El código fuente
/******************************************************************************
* FileName: main.c
* Purpose: Expansor de E/S para bus I2C PCF8574. Control de LCD.
* Processor: ATmel AVR con módulo TWI
* Compiler: AVR IAR C y AVR GCC (WinAVR)
* Author:

Shawn Johnson. http://www.cursomicros.com.
*
* Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
*
* License: Se permiten el uso y la redistribución de este código con
*

modificaciones o sin ellas, siempre que se mantengan esta

*

licencia y las notas de autor y copyright de arriba.

*****************************************************************************/

#include "avr_compiler.h"
#include "i2c.h"

voidlcd_init(void);
voidlcd_write(chardat);
voidlcd_nibble(charnibble);
voidlcd_puts(char*);
voidlcd_gotorc(charr,charc);
voidlcd_clear(void);
voidlcd_clear(void);
voiddelay_ms(unsignedintt);

staticcharlcd_DBUS;
#define lcd_E 1 // Pin 1 de PCF8574 -> Enable
#define lcd_RS 0 // Pin 0 de PCF8574 -> Resietr Select

intmain(void)
{
i2c_init();// 100 kHz
lcd_init();

while(1){
lcd_puts(" CURSOMICROS ");
lcd_gotorc(2,1);
lcd_puts(" LCD on PCF8574 ");
delay_ms(700);
lcd_clear();
delay_ms(700);
}
}

//****************************************************************************
// Ejecuta la inicialización software completa del LCD. La configuración es:
// Interface de 4 bits, despliegue de 2 líneas y caracteres de 5x7 puntos.
//****************************************************************************
voidlcd_init(void)
{
lcd_DBUS=0x00;
i2c_start();
i2c_write(0x40);// PCF8574 Slave address + Write
i2c_write(lcd_DBUS);
i2c_stop();
delay_ms(45);// > 40 ms
lcd_nibble(0x30);// Function Set: 8-bit
delay_ms(5);// > 4.1 ms
lcd_nibble(0x30);// Function Set: 8-bit
delay_ms(1);// > 100 µs
lcd_nibble(0x30);// Function Set: 8-bit
delay_ms(1);// > 40 µs
lcd_nibble(0x20);// Function Set: 4-bit
delay_ms(1);// > 40 µs
lcd_nibble(0x20);// Function Set: 4-bit, 2lines, 4×7font
lcd_nibble(0x80);//
lcd_write(0x0C);// Display ON/OFF Control
lcd_write(0x01);// Clear Display
delay_ms(2);
lcd_write(0x06);// Entry Mode Set
}

//****************************************************************************
// Envía una instrucción al LCD.
// Pre-condición: Estado de línea lcd_RS
// - Si lcd_RS = 0, 'dat' va al Registro de Comandos
// - Si lcd_RS = 1, 'dat' va a la DDRAM o CGRAM
//****************************************************************************
voidlcd_write(chardat)
{
lcd_nibble(dat);// Nibble alto
lcd_nibble(dat<<4);// Nibble bajo
}
//****************************************************************************
// Envía el nibble alto de 'nibble' al LCD.
//****************************************************************************
voidlcd_nibble(charnibble)
{
i2c_start();
i2c_write(0x40);// PCF8574 Slave address + Write
lcd_DBUS=(nibble&0xF0)|(lcd_DBUS&0x0F);
i2c_write(lcd_DBUS);
lcd_DBUS|=(1<<lcd_E);// E high
i2c_write(lcd_DBUS);
lcd_DBUS&=~(1<<lcd_E);// E low
i2c_write(lcd_DBUS);
i2c_stop();
}

//****************************************************************************
// Envía una instrucción de dato al LCD.
//****************************************************************************
voidlcd_puts(char*s)
{
unsignedcharc,i=0;
lcd_DBUS|=(1<<lcd_RS);
while(c=s[i++])
lcd_write(c);
}

//****************************************************************************
// Ubica el cursor del LCD en la columna c de la línea r.
//****************************************************************************
voidlcd_gotorc(charr,charc)
{
if(r==1)r=0x80;// Línea 1
elser=0xC0;// Línea 2
lcd_DBUS&=~(1<<lcd_RS);
lcd_write(r+c-1);
}

//****************************************************************************
// Limpia la pantalla del LCD y regresa el cursor al inicio.
//****************************************************************************
voidlcd_clear(void)
{
lcd_DBUS&=~(1<<lcd_RS);
lcd_write(0x01);
delay_ms(2);// > 1.5 ms
}

//****************************************************************************
// delay_ms()
//****************************************************************************
voiddelay_ms(unsignedintt){
while(t--)
delay_us(1000);
}

Un curso de microcontroladores como éste implica abarcar tres áreas:
Conocer el microcontrolador. Un microcontrolador es un circuito integrado genérico cuyas

partes debemos adaptar para que funcionen según los requerimientos de nuestro diseño.
Obviamente no podríamos programar lo que no conocemos.
Conocer los periféricos externos. Un microCONTROLADOR no sería muy útil si no tiene qué

controlar. Muchos dispositivos a controlar o mediante los cuales se va a controlar son comunes de
la electrónica analógica, como transistores, relés, diodos LED, registros de desplazamiento e
incluso los motores, y se da por hecho que el lector ya conoce lo suficiente de ellos. También están
los periféricos que difícilmente pudo el alumno haber operado antes sin ayuda de un
microcontrolador o una computadora, como por ejemplo, LCDs, los motores de pasos, los
sensores de temperatura digitales, etc. Es todo este segundo grupo de periféricos externos el que
se cubre en un curso de microcontrolador como éste.
Conocer un lenguaje de programación. Conocer un lenguaje de programación es un mundo

aparte y es raro que una persona trate de conocer un microcontrolador al mismo tiempo que va
aprendiendo el lenguaje. El lenguaje C en particular es un tema que normalmente se aprende por
separado.
Los lenguajes de alto nivel son mucho más potentes que el ensamblador aunque su aprendizaje
demanda un mayor esfuerzo. Para empezar a programar en ensamblador nos puede bastar con
aprender unas 50 palabras (las instrucciones básicas). En cambio dominar un lenguaje de alto nivel
como el C es como aprender a hablar en un nuevo idioma. No basta con memorizar palabras
nuevas, sino que debemos aprender a manejar una nueva estructura gramatical. Además, los
procesadores no son como las personas: si en un código de 100 líneas te olvidaste de una sola
coma, los compiladores no te lo pasarán por alto.
Por qué C y no Basic
Ciertamente, el Basic es el lenguaje más fácil de aprender (no es exactamente la razón de su
nombre). Y aunque los programadores en C de computadoras miren con desdén a los que usan el
Basic, en el mundo de los microcontroladores los compiladores Basic no tienen motivo para
sentirse menos. De hecho, algunos pueden ser casi tan eficientes como los mejores compiladores
C.
Las características (muchas veces complejas) del C fueron ideadas para el trabajo con sofisticados
proyectos, propios de las computadoras. Muchas de esas características ya no resultan tan
ventajosas en el limitado hardware de los microcontroladores y se convierten en prescindibles.
La simplicidad de los compiladores Basic para microcontroladores también permite que varios de
ellos mantengan una compatibilidad entre sus códigos que no se encuentra entre los compiladores
C.
Ésas podrían ser razones más que convincentes para empezar por el Basic y, de hecho, es la opción
que muchos han elegido en especial con los microcontroladores PIC. ¿Por qué nosotros no?
Porque es verdad comprobable que los mejores programadores trabajan en C (no siempre
exclusivamente, pero lo manejan). Por consiguiente, los proyectos más fantásticos que se pueden
encontrar están en C. Es más, la mayoría de, por no decir todos, los programadores de Basic tarde
o temprano se ven obligados a aprender el C. No sé tú, pero yo opino que esa razón pesa más.
Además, dada la robustez y la aceptación del lenguaje C, se lo ha tomado como referencia para
lenguajes de otros propósitos como Java, JavaScript, php o de Matlab, entre otros. Así que, el C
podrá servirte para trabajar en otros campos. El programador de C podría, inclusive, aprender
luego el Basic sin el menor esfuerzo; lo contrario no es cierto.
Pienso que, comparado con el Basic para microcontroladores, el C es infinitamente más difícil de
aprender. Quienes lo usan, en gran parte, son personas que han tenido experiencia programando
computadoras, personas que han estudiado más de un libro para dominarlo. Es literalmente como
aprender un nuevo idioma, y eso no es algo que se hace de la noche a la mañana. ¿Eso no suena
muy alentador?
Para simplificar las cosas, en este capítulo no voy a exponer todas las reglas del lenguaje C, aunque
sí la mayoría; digamos el 95 % de lo necesario. El resto: o es solo aplicable a las computadoras, o
son temas raros o que difieren demasiado entre de compilador a otro y conviene más revisarlos en
sus respectivos manuales.
También, y para ahorrar los ejemplos prácticos, asumo que no eres un novato cualquiera, asumo
que conoces algo de programación (aunque sea en ensamblador), que sabes cómo usar las
subrutinas, que sabes cómo emplear los bucles, que sabes lo que significa redirigir el flujo de un
programa, que sabes para qué sirven las variables, etc.

Estructura de un programa en C
Tomaremos en cuenta este sencillísimo ejemplo, escrito para los compiladores AVR IAR
C y AVR GCC.

/******************************************************************************
* FileName: main.c
* Purpose: LED parpadeantwe
* Processor: ATmel AVR
* Compiler: AVR IAR C & AVR GCC (WinAVR)
* Author:

Shawn Johnson. http://www.cursomicros.com.

*****************************************************************************/

#include "avr_compiler.h"

//****************************************************************************
// delay_ms
//****************************************************************************
voiddelay_ms(unsignedintt)
{
while(t--)
delay_us(1000);
}

//****************************************************************************
// Función principal
//****************************************************************************
intmain(void)
{
DDRB=0x01;// Configurar pin PB0 como salida

for(;;)
{
PORTB|=0x01;// Poner 1 en pin PB0
delay_ms(400);//
PORTB&=0xFE;// Poner 0 en pin PB0
delay_ms(300);
}
}

No hay que ser muy perspicaz para descubrir lo que hace este programa: configura el
pin PB0 como salida y luego lo setea y lo limpia tras pausas. Es como hacer parpadear
un LED conectado al pin PB0. Parpadea porque el bloque de while se ejecuta
cíclicamente.
Los elementos más notables de un programa en C son las sentencias, las funciones, las
directivas, los comentarios y los bloques. A continuación, una breve descripción de
ellos.

Los comentarios
Los comentarios tienen el mismo propósito que en ensamblador: documentar y
“adornar” el código. Es todo es texto que sigue a las barritas // y todo lo que está
entre los signos /* y */. Se identifican fácilmente porque suelen aparecer en color
verde.
Ejemplos.

// Éste es un comentario simple

/*
Ésta es una forma de comentar varias líneas a la vez.
Sirve mucho para enmascarar bloques de código.
*/
Las sentencias
Un programa en C, en lugar de instrucciones, se ejecuta por sentencias.
Una sentencia es algo así como una mega instrucción, que hace lo que varias
instrucciones del ensamblador.
Salvo casos particulares, donde su uso es opcional, una sentencia debe finalizar con un
punto y coma (;). Así que también podemos entender que los ; sirven para separar las
sentencias. Alguna vez leí que el compilador C lee el código como si lo absorbiera con
una cañita, línea por línea, una a continuación de otra (evadiendo los comentarios por
supuesto). Por ejemplo, la función main del programa de arriba bien puede escribirse
del siguiente modo.

//****************************************************************************
// Función principal
//****************************************************************************
intmain(void){DDRB=0x01;for(;;){PORTB|=0x01;delay_ms(400);
PORTB&=0xFE;delay_ms(300);}}
¿Sorprendido? Podrás deducir que los espacios y las tabulaciones solo sirven para darle
un aspecto ordenado al código. Es una buena práctica de programación aprender a
acomodarlas.
Las sentencias se pueden clasificar en sentencias de asignación, sentencias selectivas,
sentencias iterativas, de llamadas de función, etc. Las describiremos más adelante.

Los bloques
Un bloque establece y delimita el cuerpo de las funciones y algunas sentencias
mediante llaves ({}).
Como ves en el ejemplo de arriba, las funciones main y pausa tienen sus bloques, así
como los bucles while y for. Creo que exageré con los comentarios, pero sirven para
mostrarnos dónde empieza y termina cada bloque. Podrás ver cómo las tabulaciones
ayudan a distinguir unos bloques de otros. Afortunadamente, los editores de los
buenos compiladores C pueden resaltar cuáles son las llaves de inicio y de cierre de
cada bloque. Te será fácil acostumbrarte a usarlas.

Las directivas
Son conocidas en el lenguaje C como directivas de preprocesador, de preprocesador
porque son evaluadas antes de compilar el programa. Como pasaba en el
ensamblador, las directivas por sí mismas no son código ejecutable. Suelen ser
indicaciones sobre cómo se compilará el código.
Entre las pocas directivas del C estándar que también son soportadas por los
compiladores C para AVR están #include (para incluir archivos, parecido al
Assembler), #define (mejor que el #define del ensamblador) y las #if, #elif, #endif y
similares. Fuera de ellas, cada compilador maneja sus propias directivas y serán
tratadas por separado.

Las funciones
Si un programa en ensamblador se puede dividir en varias subrutinas para su mejor
estructuración, un programa en C se puede componer de funciones. Por supuesto que
las funciones son muchísimo más potentes y, por cierto, algo más complejas de
aprender. Por eso ni siquiera el gran espacio que se les dedica más adelante puede ser
suficiente para entenderlas plenamente. Pero, no te preocupes, aprenderemos de a
poco.
En un programa en C puede haber las funciones que sean posibles, pero la nunca debe
faltar la función principal, llamada main. Donde quiera que se encuentre, la
función main siempre será la primera en ser ejecutada. De hecho, allí empieza y no
debería salir de ella.

Variables y Tipos de Datos
En ensamblador todas las variables de programa suelen ser registros de la RAM
crudos, es decir, datos de 8 bits sin formato. En los lenguajes de alto nivel estos
registros son tratados de acuerdo con formatos que les permiten representar números
de 8, 16 ó 32 bits (a veces más grandes), con signo o sin él, números enteros o
decimales. Esos son los tipos de datos básicos.
Las variables de los compiladores pueden incluso almacenar matrices de datos del
mismo tipo (llamadas arrays) o de tipos diferentes (llamadasestructuras). Estos son
los tipos de datos complejos.
Los siguientes son los principales tipos de datos básicos del lenguaje C. Observa que la
tabla los separa en dos grupos, los tipos enteros y los tipos de punto flotante.
Tabla de variables y tipos de datos del lenguaje C

Tipo de dato

Tamaño en bits Rango de valores que puede adoptar

char

8

0 a 255 ó -128 a 127

signed char

8

-128 a 127

unsigned char

8

0 a 255
Tabla de variables y tipos de datos del lenguaje C

Tipo de dato

Tamaño en bits Rango de valores que puede adoptar

(signed) int

16

-32,768 a 32,767

unsigned int

16

0 a 65,536

(signed) short

16

-32,768 a 32,767

unsigned short

16

0 a 65,536

(signed) long

32

-2,147,483,648 a 2,147,483,647

unsigned long

32

0 a 4,294,967,295

(signed) long long (int)

64

-263 a 263 - 1

unsigned long long(int)

64

0 a 264 - 1

float

32

±1.18E-38 a ±3.39E+38

double

32

±1.18E-38 a ±3.39E+38

double

64

±2.23E-308 a ±1.79E+308

Afortunadamente, a diferencia de los compiladores para PIC, los compiladores para
AVR suelen respetar bastante los tipos establecidos por el ANSI C. Algunos
compiladores también manejan tipos de un bit como bool (o boolean) o bit, pero con
pequeñas divergencias que pueden afectar la portabilidad de los códigos además de
confundir a los programadores. Esos tipos son raramente usados.
Por defecto el tipo double es de 32 bits en los microcontroladores. En ese caso es
equivalente al tipo float. Los compiladores más potentes como AVR IAR C y AVR
GCC sin embargo ofrecen la posibilidad de configurarlo para que sea de 64 bits y poder
trabajar con datos más grandes y de mayor precisión.
Los especificadores signed (con signo) mostrados entre paréntesis son opcionales. Es
decir, da lo mismo poner int que signed int, por ejemplo. Es una redundancia que se
suele usar para “reforzar” su condición o para que se vea más ilustrativo.
El tipo char está pensado para almacenar caracteres ASCII como las letras. Puesto que
estos datos son a fin de cuentas números también, es común usar este tipo para
almacenar números de 8 bits. Es decir es equivalente a signed char o unsigned char,
dependiendo de la configuración establecida por el entorno compilador. Y como es
preferible dejar de lado estas cuestiones, si vamos a trabajar con números lo mejor es
poner el especificador signed ounsigned en el código.
Quizá te preguntes cuál es la diferencia entre los tipos de datos int y short si
aparentemente tienen el mismo tamaño y aceptan el mismo rango de valores. Esa
apariencia es real en el entorno de los microcontroladores AVR. Es decir, al compilador
le da lo mismo si ponemos int oshort. Sucede que el tipo short fue y siempre debería
ser de 16 bits, en tanto que int fue concebido para adaptarse al bus de datos del
procesador. Esto todavía se cumple en la programación de las computadoras, por
ejemplo, un dato int es de 32 bits en un Pentium IV y es de 64 bits en un procesador
Core i7. De acuerdo con este diseño un tipo int debería ser de 8 bits en un megaAVR y
de 32 bits en un AVR32. Sin embargo, la costumbre de relacionar el tipo intcon los 16
bits de las primeras computadoras como las legendarias 286 se ha convertido en
tradición y en regla de facto para los microcontroladores. Actualmente solo en CCS C el
tipo intes de 8 bits. Es irónico para ser el compilador que menos respeta los tipos de
datos del ANSI C.
A pesar de todo, se nota que todavía pueden aparecer ciertas imprecisiones en los
tipos de datos que pueden perturbar la portabilidad de los programas entre los
diferentes compiladores. Es por esto que el lenguaje C/C++ provee la
librería stdint.h para definir tipos enteros que serán de un tamaño específico
independientemente de los procesadores y de la plataforma software en que se
trabaje.
Tabla de variables y tipos de datos del lenguaje C

Tipo de dato Tamaño en bits Rango de valores que puede adoptar
int8_t

8

-128 a 127

uint8_t

8

0 a 255

int16_t

16

-32,768 a 32,767

uint16_t

16

0 a 65,536

int32_t

32

-2,147,483,648 a 2,147,483,647

uint32_t

32

0 a 4,294,967,295

int64_t

64

-263 a 263 - 1

uint64_t

64

0 a 264 - 1
Es fácil descubrir la estructura de estos tipos para familiarizarse con su uso. Para ello
debemos en primer lugar incluir en nuestro programa el archivo stdint.h con la
siguiente directiva.

#include <stdint.h>
Esta inclusión ya está hecha en el archivo avr_compiler.h que se usa en todos los
programas de cursomicros, así que no es necesario volverlo a hacer. Aunque el
objetivo de este archivo es permitir la compatibilidad de códigos entre los
compiladores AVR IAR C y AVR GCC, debemos saber que en AVR IAR C el
archivo avr_compiler.h solo está disponible al usar la librería DLIB. Como las prácticas
de cursomicros trabajan sobre la librería CLIB, he evitado recurrir a los tipos
extendidos de stdint.h.
Finalmente, existen además de los vistos arriba otros tipos y especificadores de datos
que no son parte del lenguaje C pero que fueron introducidos por los compiladores
pensando en las características especiales de los microcontroladores. Muchos de ellos
son redundantes o simples alias y algunos que sí son de utilidad como el
tipo PGM_P los veremos en su momento.

Declaración de variables
Esta parte es comparable, aunque lejanamente a cuando se identifican las variables
del ensamblador con la directiva .def. No se puede usar una variable si antes no se ha
declarado. La forma general más simple de hacerlo es la siguiente:

data_typemyvar;
donde data_type es un tipo de dato básico o complejo, del compilador o definido por el
usuario ymyvar es un identificador cualquiera, siempre que no sea palabra reservada.
Ejemplos.

unsignedchard;// Variable para enteros de 8 bits sin signo
charb;// Variable de 8 bits (para almacenar
// caracteres ascii)
signedcharc;// Variable para enteros de 8 bits con signo
inti;// i es una variable int, con signo
signedintj;// j también es una variable int con signo
unsignedintk;// k es una variable int sin signo
También es posible declarar varias variables del mismo tipo, separándolas con comas.
Así nos ahorramos algo de tipeo. Por ejemplo:
floatarea,side;// Declarar variables area y side de tipo float
unsignedchara,b,c;// Declarar variables a, b y c como unsigned char
Especificadores de tipo de datos
A la declaración de una variable se le puede añadir un especificador de tipo
como const, static,volatile, extern, register, etc. Dichos especificadores tienen diversas
funciones y, salvo const, se suelen usar en programas más elaborados. Como no
queremos enredarnos tan pronto, lo dejaremos para otro momento.
Una variable const debe ser inicializada en su declaración. Después de eso el
compilador solo permitirá su lectura mas no su escritura. Ejemplos:

constinta=100;// Declarar constante a
intb;// Declarar variable b

//...
b=a;// Válido
b=150;// Válido

a=60;// Error! a es constante
a=b;// Error! a es constante
Por más que las variables constantes sean de solo lectura, ocuparán posiciones en la
RAM del microcontrolador. En CodeVisionAVR es posible configurar para que sí residan
en FLASH pero por compatibilidad se usa muy poco. Por eso muchas veces es
preferible definir las constantes del programa con las clásicas directivas #define (como
se hace en el ensamblador).

#define a 100

// Definir constante a

Sentencias selectivas
Llamadas también sentencias de bifurcación, sirven para redirigir el flujo de un
programa según la evaluación de alguna condición lógica.
Las sentencias if e if–else son casi estándar en todos los lenguajes de programación.
Además de ellas están las sentencias if–else escalonadas y switch–case.
La sentencia if
La sentencia if (si condicional, en inglés) hace que un programa ejecute una sentencia
o un grupo de ellas si una expresión es cierta. Esta lógica se describe en el siguiente
esquema.

Diagrama de flujo de la sentencia if.
La forma codificada sería así:

sentenciaA;
if(expression)// Si expression es verdadera,
// ejecutar el siguiente bloque
{// apertura de bloque
sentenciaB;
sentenciaC;
// algunas otras sentencias
}// cierre de bloque
sentenciaX;
Después de ejecutar sentenciaA el programa evalúa expression. Si resulta ser
verdadera, se ejecutan todas las sentencias de su bloque y luego se ejecutará
la sentenciaX.
En cambio, si expression es falsa, el programa se salteará el bloque de if y
ejecutará sentenciaX.

La sentencia if – else
La sentencia if brinda una rama que se ejecuta cuando una condición lógica es
verdadera. Cuando el programa requiera dos ramas, una que se ejecute si cierta
expression es cierta y otra si es falsa, entonces se debe utilizar la sentecia if – else.
Tiene el siguiente esquema.

Diagrama de flujo de la sentencia if – else.
Expresando lo descrito en código C, tenemos: (Se lee como indican los comentarios.)

SentenciaA;
if(expression)// Si expression es verdadera, ejecutar
{// este bloque
sentenciaB;
sentenciaC;
// ...
}
else// En caso contrario, ejecutar este bloque
{
sentenciaM;
sentenciaN;
// ...
}
sentenciaX;
// ...
Como ves, es bastante fácil, dependiendo del resultado se ejecutará uno de los dos
bloques de la sentencia if – else, pero nunca los dos a la vez.

La sentencia if – else – if escalonada
Es la versión ampliada de la sentencia if – else.
En el siguiente boceto se comprueban tres condiciones lógicas, aunque podría haber
más. Del mismo modo, se han puesto dos sentencias por bloque solo para simplificar
el esquema.

if(expression_1)// Si expression_1 es verdadera ejecutar
{// este bloque
sentencia1;
sentencia2;
}
elseif(expression_2)// En caso contrario y si expression_2 es
{// verdadera, ejecutar este bloque
sentencia3;
sentencia4;
}
elseif(expression_3)// En caso contrario y si expression_3 es
{// verdadera, ejecutar este bloque
sentencia5;
sentencia6;
}
else// En caso contrario, ejecutar este bloque
{
sentencia7;
sentencia8;
};// ; opcional
// todo...
Las “expresiones” se evalúan de arriba abajo. Cuando alguna de ellas sea verdadera,
se ejecutará su bloque correspondiente y los demás bloques serán salteados. El bloque
final (deelse) se ejecuta si ninguna de las expresiones es verdadera. Además, si dicho
bloque está vacío, puede ser omitido junto con su else.

La sentencia switch
La sentencia switch brinda una forma más elegante de bifurcación múltiple. Podemos
considerarla como una forma más estructurada de la sentencia if – else –
if escalonada, aunque tiene algunas restricciones en las condiciones lógicas a evaluar,
las cuales son comparaciones de valores enteros.
Para elaborar el código en C se usan las palabras
reservadas switch, case, break y default.
El siguiente esquema presenta tres case‟s pero podría haber más, así como cada
bloque también podría tener más sentencias.

switch(expression)
{
caseconstante1:// Si expression = constante1, ejecutar este bloque
sentencia1;
sentencia2;
break;
caseconstante2:// Si expression = constante2, ejecutar este bloque
sentencia3;
sentencia4;
break;
caseconstante3:// Si expression = constante3, ejecutar este bloque
sentencia5;
sentencia6;
break;
default:// Si expression no fue igual a ninguna de las
// constantes anteriores, ejecutar este bloque
sentencia7;
sentencia8;
break;
}
sentenciaX;
// todo...
donde constante1, constante2 y constante3 deben ser constantes enteras, por
ejemplo, 2, 0x45, „a‟, etc. („a‟ tiene código ASCII 165, que es, a fin de cuentas, un
entero.)
expresion puede ser una variable compatible con entero. No es una expresión que
conduce a una condición lógica como en los casos anteriores.
El programa solo ejecutará uno de los bloques dependiendo de qué constante coincida
conexpression. Usualmente los bloques van limitados por llaves, pero en este caso son
opcionales, dado que se pueden distinguir fácilmente. Los bloques incluyen la
sentencia break. ¿Qué es eso?
La sentencia break hace que el programa salga del bloque de switch y ejecute la
sentencia que sigue (en el boceto, sentenciaX). ¡Atento!: de no poner break, también
se ejecutará el bloque del siguiente case, sin importar si su constante coincida
con expression o no.
No sería necesario poner el default si su bloque estuviera vacío.

Sentencias iterativas
Las sentencias de control iterativas sirven para que el programa ejecute una sentencia
o un grupo de ellas un número determinado o indeterminado de veces. Así es, esta
sección no habla de otra cosa que de los bucles en C.
El lenguaje C soporta tres tipos de bucles, las cuales se construyen con las
sentencias while, do– while y for. El segundo es una variante del primero y el tercero
es una versión más compacta e intuitiva del bucle while.

La sentencia while
El cuerpo o bloque de este bucle se ejecutará una y otra vez mientras (while, en
inglés) una expresión sea verdadera.

Diagrama de flujo de las sentencia while.
El bucle while en C tiene la siguiente sintaxis y se lee así: mientras
(while) expression sea verdadera, ejecutar el siguiente bloque.

sentenciaA;
while(expression)// Mientras expression sea verdadera, ejecutar el
// siguiente bloque
{
sentenciaB;
sentenciaC;
// ...
};// Este ; es opcional
sentenciaX;
// ...
Nota que en este caso primero se evalúa expression. Por lo tanto, si desde el
principioexpression es falsa, el bloque de while no se ejecutará nunca. Por otro lado,
si expression no deja de ser verdadera, el programa se quedará dando vueltas “para
siempre”.

La sentencia do - while
Como dije antes, es una variación de la sentencia while simple. La principal diferencia
es que la condición lógica (expression) de este bucle se presenta al final. Como se ve
en la siguiente figura, esto implica que el cuerpo o bloque de este bucle se ejecutará al
menos una vez.

Diagrama de flujo de las sentencia do – while.
La sintaxis para la sentencia do – while es la siguiente y se lee: Ejecutar (do) el
siguiente bloque, mientras (while) expression sea verdadera.

sentenciaA;
do
{
sentenciaB;
sentenciaC;
// ...
}while(expression);// Este ; es mandatorio
sentenciaX;
// ...
La sentencia for
Las dos sentencias anteriores, while y do – while, se suelen emplear cuando no se
sabe de antemano la cantidad de veces que se va a ejecutar el bucle. En los casos
donde el bucle involucra alguna forma de conteo finito es preferible emplear la
sentencia for. (Inversamente, al ver un for en un programa, debemos suponer que
estamos frente a algún bucle de ese tipo.)
Ésta es la sintaxis general de la sentencia for en C:

for(expression_1;expression_2;expression_3)
{
sentencia1;
sentencia2;
// ...
};// Este ; es opcional
Ahora veamos por partes cómo funciona:
expression_1 suele ser una sentencia de inicialización.
expression_2 se evalúa como condición lógica para que se ejecute el bloque.
expression_3 es una sentencia que debería poner coto a expression_2.
Por la forma y orden en que se ejecutan estas expresiones, el bucle for es equivalente
a la siguiente construcción, utilizando la sentencia while. Primero se
ejecuta expression_1 y luego se ejecuta el bloque indicado tantas veces
mientras expression_2 sea verdadera.

expression_1;
while(expression_2)
{
sentencia1;
sentencia2;
// ...

expression_3;
}
No obstante, de esa forma se ve más rara aún; así que, mejor, veamos estos
ejemplos, que son sus presentaciones más clásicas. (i es una variable y a y b son
constantes o variables):

for(i=0;i<10;i++)
{
sentencias;
}
Se lee: para (for) i igual a 0 hasta que sea menor que 10 ejecutar sentencias. La
sentencia i++indica que i se incrementa tras cada ciclo. Así, el bloque de for se
ejecutará 10 veces, desde quei valga 0 hasta que valga 9.
En este otro ejemplo las sentencias se ejecutan desde que i valga 10 hasta que
valga 20. Es decir, el bucle dará 11 vueltas en total.

for(i=10;i<=20;i++)
{
sentencias;
}
El siguiente bucle for empieza con i inicializado a 100 y su bloque se ejecutará
mientras i sea mayor o igual a 0. Por supuesto, en este caso i se decrementa tras cada
ciclo.

for(i=100;i>=0;i--)
{
sentencias;
}
Se pueden hacer muchas más construcciones, todas coincidentes con la primera
plantilla, pero también son menos frecuentes.

Sentencias con bloques simples
Cuando las sentencias selectivas (como if) o de bucles (como while o for) tienen
cuerpos o bloques que constan de solo una sentencia, se pueden omitir las llaves. Aun
así, es aconsejable seguir manteniendo las tabulaciones para evitarnos confusiones.
Por ejemplo, las siguientes sentencias:

if(a>b)
{
a=0;
}

if(a==b)
{
a++;
}
else
{
b--;
}

while(a>=b)
{
a=a+b;
}

for(i=0;i<=10;i++)
{
a=a*2;
}
bien se pueden escribir de la siguiente forma:

if(a>b)
a=0;

if(a==b)
a++;
else
b--;

while(a>=b)
a=a+b;

for(i=0;i<=10;i++)
a=a*2;

Los operadores
Sirven para realizar operaciones aritméticas, lógicas, comparativas, etc. Según esa
función se clasifican en los siguientes grupos.

Operadores aritméticos
Además de los típicos operadores de suma, resta, multiplicación y división, están los
operadores de módulo, incremento y decremento.
Tabla de Operadores aritméticos

Operador Acción
+

Suma

-

Resta

*

Multiplicación

/

División

%

Módulo. Retorna el residuo de una división entera. Solo se debe usar con números
enteros.

++

Incrementar en uno

--

Decrementar en uno
Ejemplos:

inta,b,c;// Declarar variables a, b y c

a=b+c;// Sumar a y b. Almacenar resultado en c
b=b*c;// Multiplicar b por c. Resultado en b
b=a/c;// Dividir a entre c. Colocar resultado en b
a=a+c–b;// Sumar a y c y restarle b. Resultado en a
c=(a+b)/c;// Dividir a+b entre c. Resultado en c
b=a+b/c+b*b;// Sumar a más b/c más b×b. Resultado en b
c=a%b;// Residuo de dividir a÷b a c
a++;// Incrementar a en 1
b--;// Decrementar b en 1
++c;// Incrementar c en 1
--b;// Decrementar b en 1
¿Te recordaron a tus clases de álgebra del colegio? A diferencia de esas matemáticas,
estas expresiones no son ecuaciones; significan las operaciones que indican sus
comentarios.
Por lo visto, los operadores ++ y -- funcionan igual si están antes o después de una
variable en una expresión simple. Sin embargo, hay una forma (tal vez innecesaria y
confusa para un novato, pero muy atractiva para los que ya estamos acostumbrados a
su uso) que permite escribir código más compacto, es decir, escribir dos sentencias en
una.
Si ++ o -- están antes del operando, primero se suma o resta 1 al operando y luego
se evalúa la expresión.
Si ++ o -- están después del operando, primero se evalúa la expresión y luego se
suma o resta 1 al operando.

inta,b;// Declarar variables enteras a y b

a=b++;// Lo mismo que a = b; y luego b = b + 1;
a=++b;// Lo mismo que b = b + 1; y luego a = b;

if(a++<10)// Primero comprueba si a < 10 y luego
{// incrementa a en 1
// algún código
}

if(++a<10)// Primero incrementa a en 1 y luego
{// comprueba si a < 10
// algún código
}
Operadores de bits
Se aplican a operaciones lógicas con variables a nivel binario. Aquí tenemos las
clásicas operaciones AND, OR inclusiva, OR exclusiva y la NEGACIÓN. Adicionalmente,
he incluido en esta categoría los operaciones de desplazamiento a la derecha y la
izquierda.
Si bien son operaciones que producen resultados análogos a los de las instrucciones de
ensamblador los operadores lógicos del C pueden operar sobre variables de distintos
tamaños, ya sean de 1, 8, 16 ó 32 bits.
Tabla de operadores de bits

Operador Acción
&

AND a nivel de bits

|

OR inclusiva a nivel de bits

^

OR exclusiva a nivel de bits

~

Complemento a uno a nivel de bits

<<

Desplazamiento a la izquierda
Tabla de operadores de bits

Operador Acción
>>

Desplazamiento a la derecha

Ejemplos:

charm;// variable de 8 bits
intn;// variable de 16 bits

m=0x48;// m será 0x48
m=m&0x0F;// Después de esto m será 0x08
m=m|0x24;// Después de esto m será 0x2F
m=m&0b11110000;// Después de esto m será 0x20
n=0xFF00;// n será 0xFF00
n=~n;// n será 0x00FF
m=m|0b10000001;// Setear bits 0 y 7 de variable m
m=m&0xF0;// Limpiar nibble bajo de variable m
m=m^0b00110000;// Invertir bits 4 y 5 de variable m

m=0b00011000;// Cargar m con 0b00011000
m=m>>2;// Desplazar m 2 posiciones a la derecha
// Ahora m será 0b00000110
n=0xFF1F;
n=n<<12;// Desplazar n 12 posiciones a la izquierda
// Ahora n será 0xF000;
m=m<<8;// Después de esto m será 0x00
Fíjate en la semejanza entre las operaciones de desplazamiento con >> y << y las
operaciones del rotación del ensamblador. Cuando una variable se desplaza hacia un
lado, los bits que salen por allí se pierden y los bits que entran por el otro lado son
siempre ceros. Es por esto que en la última sentencia, m = m << 8, el resultado es
0x00. Por cierto, en el lenguaje C no existen operadores de rotación. Hay formas
alternativas de realizarlas.

Desplazamientos producidos por los operadores << y >>.

Operadores relacionales
Se emplean para construir las condiciones lógicas de las sentencias de control
selectivas e iterativas, como ya hemos podido apreciar en las secciones anteriores. La
siguiente tabla muestra los operadores relacionales disponibles.
Tabla de Operadores relacionales

Operador Acción
==

Igual

!=

No igual

>

Mayor que

<

Menor que

>=

Mayor o igual que

<=

Menor o igual que

Operadores lógicos
Generalmente se utilizan para enlazar dos o más condiciones lógicas simples. Por
suerte, estos operadores solo son tres y serán explicados en las prácticas del curso.
Tabla de Operadores lógicos

Operador Acción
&&

AND lógica
Tabla de Operadores lógicos

Operador Acción
||

OR lógica

!

Negación lógica

Ejemplos:

if(!(a==0))// Si a igual 0 sea falso
{
// sentencias
}

if((a<b)&&(a>c))// Si a<b y a>c son verdaderas
{
// sentencias
}

while((a==0)||(b==0))// Mientras a sea 0 ó b sea 0
{
// sentencias
}
Composición de operadores
Se utiliza en las operaciones de asignación y nos permite escribir código más
abreviado. La forma general de escribir una sentencia de asignación mediante los
operadores compuestos es:

obtectop=expression;
que es equivalente a la sentencia

object=objectopexpression;
op puede ser cualquiera de los operadores aritméticos o de bit estudiados arriba. O
sea, oppuede ser +, - , *, /, %, &, | , ^, ~, << ó >>. Nota: no debe haber ningún
espacio entre el operador y el signo igual.
Ejemplos:

inta;// Declarar a
a+=50;// Es lo mismo que a = a + 50;
a+=20;// También significa sumarle 20 a a
a*=2;// Es lo mismo que a = a * 2;
a&=0xF0;// Es lo mismo que a = a & 0xF0;
a<<=1;// Es lo mismo que a = a << 1;
Precedencia de operadores
Una expresión puede contener varios operadores, de esta forma:

b=a*b+c/b;// a, b y c son variables
A diferencia del lenguaje Basic, donde la expresión se evalúa de izquierda a derecha,
en esta sentencia no queda claro en qué orden se ejecutarán las operaciones
indicadas. Hay ciertas reglas que establecen dichas prioridades; por ejemplo, las

multiplicaciones y divisiones siempre se ejecutan antes que las sumas y restas.
Pero es más práctico emplear los paréntesis, los cuales ordenan que primero se
ejecuten las operaciones de los paréntesis más internos. Eso es como en el álgebra
elemental de la escuela, así que no profundizaré.
Por ejemplo, las tres siguientes sentencias son diferentes.

b=(a*b)+(c/b);
b=a*(b+(c/b));
b=((a*b)+c)/b);
También se pueden construir expresiones condicionales, así:

if((a>b)&&(b<c))// Si a>b y b<c, ...
{
// ...
}
Las funciones
Una función es un bloque de sentencias identificado por un nombre y puede recibir y
devolver datos. En bajo nivel, en general, las funciones operan como las subrutinas de
Assembler, es decir, al ser llamadas, se guarda en la Pila el valor actual
del PC (Program Counter), después se ejecuta todo el código de la función y finalmente
se recobra el PC para regresar de la función.
Dada su relativa complejidad, no es tan simple armar una plantilla general que
represente a todas las funciones. El siguiente esquema es una buena aproximación.

data_type1function_name(data_type2arg1,data_type3arg2,...)
{
// Cuerpo de la función
// ...
returnSomeData;// Necesario solo si la función retorna algún valor
}
Donde:
function_name es el nombre de la función. Puede ser un identificador cualquiera.
data_type1 es un tipo de dato que identifica el parámetro de salida. Si no lo hubiera,
se debe poner la palabra reservada void (vacío, en inglés).
arg1 y arg2 (y puede haber más) son las variables de tipos data_type1, data_type2...,
respectivamente, que recibirán los datos que se le pasen a la función. Si no hay ningún
parámetro de entrada, se pueden dejar los paréntesis vacíos o escribir un void entre
ellos.

Funciones sin parámetros
Para una función que no recibe ni devuelve ningún valor, la plantilla de arriba se
reduce al siguiente esquema:

voidfunction_name(void)
{
// Cuerpo de la función
}
Y se llama escribiendo su nombre seguido de paréntesis vacíos, así:

function_name();
La función principal main es otro ejemplo de función sin parámetros. Dondequiera que
se ubique, siempre debería ser la primera en ejecutarse; de hecho, no debería
terminar.

voidmain(void)
{
// Cuerpo de la función
}
Funciones con parámetros (por valor)
Por el momento, solo estudiaremos las funciones que pueden tener varios parámetros
de entrada pero solo uno de salida.
Si la función no tiene parámetros de entrada o de salida, debe escribirse un void en su
lugar. El valor devuelto por una función se indica con la palabra reservada return.
Según el comportamiento de los parámetros de entrada de la función, estos se dividen
enparámetros por valor y parámetros por referencia. Lo expuesto en este apartado
corresponde al primer grupo porque es el caso más ampliamente usado. Con esto en
mente podemos seguir.
Para llamar a una función con parámetros es importante respetar el orden y el tipo de
los parámetros que ella recibe. El primer valor pasado corresponde al primer
parámetro de entrada; el segundo valor, al segundo parámetro; y así sucesivamente si
hubiera más.
Cuando una variable es entregada a una función, en realidad se le entrega una copia
suya. De este modo, el valor de la variable original no será alterado. Mejor, plasmemos
todo esto en el siguiente ejemplo.

intminor(intarg1,intarg2,intarg3)
{
intmin;// Declarar variable min
min=arg1;// Asumir que el menor es arg1
if(arg2<min)// Si arg2 es menor que min
min=arg2;// Cambiar a arg2

if(arg3<min)// Si arg3 es menor que min
min=arg3;// Cambiar a arg3

returnmin;// Retornar valor de min
}
voidmain(void)
{
inta,b,c,d;// Declarar variables a, b, c y d

/* Aquí asignamos algunos valores iniciales a 'a', 'b' y 'c' */
/* ... */
d=minor(a,b,c);// Llamar a minor
// En este punto 'd' debería ser el menor entre 'a', 'b' y 'c'
while(1);// Bucle infinito
}
En el programa mostrado la función minor recibe tres parámetros de tipo int y
devuelve uno, también de tipo int, que será el menor de los números recibidos.
El mecanismo funciona así: siempre respetando el orden, al llamar a minor el valor
de a se copiará a la variable arg1; el valor de b, a arg2 y el valor de c, a arg3.
Después de ejecutarse el código de la función el valor de retorno (min en este caso)
será copiado a una variable temporal y de allí pasará a d.
Aunque el C no es tan implacable con la comprobación de tipos de datos como Pascal,
siempre deberíamos revisar que los datos pasados sean compatibles con los que la
función espera, así como los datos recibidos, con los que la función devuelve. Por
ejemplo, estaría mal llamar a la función minor del siguiente modo:

d=minor(-15,100,5.124);// Llamar a minor
Aquí los dos primeros parámetros están bien, pero el tercero es un número decimal
(de 32 bits), no compatible con el tercer parámetro que la función espera (entero de
16 bits). En estos casos el compilador nos mostrará mensajes de error, o cuando
menos de advertencia.

Parámetros por referencia
La función que recibe un parámetro por referencia puede cambiar el valor de la
variable pasada. La forma clásica de estos parámetros se puede identificar por el uso
del símbolo &, tal como se ve en el siguiente boceto de función.

intminor(int&arg1,int&arg2,int&arg3)
{
// Cuerpo de la función.
// arg1, arg2 y arg3 son parámetros por referencia.
// Cualquier cambio hecho a ellos desde aquí afectará a las variables
// que fueron entregadas a esta función al ser llamada.
}
No voy profundizar al respecto porque he visto que muchos compiladores C no
soportan esta forma. Otra forma de pasar un parámetro por referencia es mediante los
punteros, pero eso lo dejamos para el final porque no es nada nada fácil para un
novato.

Prototipos de funciones
El prototipo de una función le informa al compilador las características que tiene, como
su tipo de retorno, el número de parámetros que espera recibir, el tipo y orden de
dichos parámetros. Por eso se deben declarar al inicio del programa.
El prototipo de una función es muy parecido a su encabezado, se pueden diferenciar
tan solo por terminar en un punto y coma (;). Los nombres de las variables de entrada
son opcionales.
Por ejemplo, en el siguiente boceto de programa los prototipos de las
funciones main, func1 yfunc2 declaradas al inicio del archivo permitirán que dichas
funciones sean accedidas desde cualquier parte del programa. Además, sin importar
dónde se ubique la función main, ella siempre será la primera en ejecutarse. Por eso
su prototipo de función es opcional.

#include <avr.h>
voidfunc1(charm,longp);// Prototipo de función "func1"
charfunc2(inta);// Prototipo de función "func2"
voidmain(void);// Prototipo de función "main". Es opcional

voidmain(void)
{
// Cuerpo de la función
// Desde aquí se puede acceder a func1 y func2
}
voidfunc1(charm,longp)
{
// Cuerpo de la función
// Desde aquí se puede acceder a func2 y main
}
charfunc2(inta)
{
// Cuerpo de la función
// Desde aquí se puede acceder a func1 y main
}
La llamada a main, por supuesto, no tiene sentido; solo lo pongo para ilustrar.
Si las funciones no tienen prototipos, el acceso a ellas será restringido. El compilador
solo verá las funciones que están implementadas encima de la función llamadora o, de
lo contrario, mostrará errores de “función no definida”. El siguiente boceto ilustra este
hecho. (Atiende a los comentarios.)

#include <avr.h>
voidmain(void)
{
// Cuerpo de la función
// Desde aquí no se puede acceder a func1 ni func2 porque están abajo
}
voidfunc1(charm,longp)
{
// Cuerpo de la función
// Desde aquí se puede acceder a main pero no a func2
}
charfunc2(inta)
{
// Cuerpo de la función
// Desde aquí se puede acceder a func1 y main
}
Para terminar, dado que los nombres de las variables en los parámetros de entrada
son opcionales, los prototipos de func1 y func2 también se pueden escribir asi

voidfunc1(char,long);
charfunc2(int);

Variables locales y variables globales
Los lenguajes de alto nivel como el C fueron diseñados para desarrollar los programas
más grandes y complejos que se puedan imaginar, programas donde puede haber
cientos de variables, entre otras cosas. ¿Imaginas lo que significaría buscar nombres
para cada variable si todos tuvieran que ser diferentes? Pues bien, para simplificar las
cosas, el C permite tener varias variables con el mismo nombre.
Así es. Esto es posible gracias a que cada variable tiene un ámbito, un área desde
donde será accesible. Hay diversos tipos de ámbito, pero empezaremos por
familiarizarnos con los dos más usados, que corresponden a lasvariables
globales y variables locales.
Las variables declaradas fuera de todas las funciones y antes de sus implementaciones
tienen carácter global y podrán ser accedidas desde todas las funciones.
Las variables declaradas dentro de una función, incluyendo las variables del
encabezado, tienen ámbito local. Ellas solo podrán ser accedidas desde el cuerpo de
dicha función.
De este modo, puede haber dos o más variables con el mismo nombre, siempre y
cuando estén en diferentes funciones. Cada variable pertenece a su función y no tiene
nada que ver con las variables de otra función, por más que tengan el mismo nombre.
En la mayoría de los compiladores C para microcontroladores las variables locales
deben declararse al principio de la función.
Por ejemplo, en el siguiente boceto de programa hay dos variables globales
(speed y limit) y cuatro variables locales, tres de las cuales se llaman count. Atiende a
los comentarios.

charfoo(long);// Prototipo de función

intspeed;// Variable global
constlonglimit=100;// Variable global constante

voidinter(void)
{
intcount;// Variable local
/* Este count no tiene nada que ver con el count
de las funciones main o foo */

speed++;// Acceso a variable global speed

vari=0;// Esto dará ERROR porque vari solo pertenece
// a la función foo. No compilará.
}
voidmain(void)
{
intcount;// Variable local count
/* Este count no tiene nada que ver con el count
de las funciones inter o foo */
count=0;// Acceso a count local
speed=0;// Acceso a variable global speed
}
charfoo(longcount)// Variable local count
{
intvari;// Variable local vari
}
Algo muy importante: a diferencia de las variables globales, las variables locales tienen
almacenamiento temporal, es decir, se crean al ejecutarse la función y se destruyen al
salir de ella. ¿Qué significa eso? Lo explico en el siguiente apartado.
Si dentro de una función hay una variable local con el mismo nombre que una variable
global, la precedencia en dicha función la tiene la variable local. Si te confunde, no
uses variables globales y locales con el mismo nombre.

Variables static
Antes de nada debemos aclarar que una variable static local tiene diferente significado
que unavariable static global. Ahora vamos a enfocarnos al primer caso por ser el más
común.
Cuando se llama a una función sus variables locales se crearán en ese momento y
cuando se salga de la función se destruirán. Se entiende por destruir al hecho de que
la locación de memoria que tenía una variable será luego utilizada por el compilador
para otra variable local (así se economiza la memoria). Como consecuencia, el valor de
las variables locales no será el mismo entre llamadas de función.
Por ejemplo, revisa la siguiente función, donde a es una variable local ordinaria.

voidincrem()
{
inta;// Declarar variable a
a++;// Incrementar a
}
Cualquiera que haya sido su valor inicial, ¿crees que después de llamar a esta función
10 veces, el valor de a se habrá incrementado en 10?... Pues, no necesariamente.
Cada vez que se llame a increm se crea a, luego se incrementa y, al terminar de
ejecutarse la función, se destruye.
Para que una variable tenga una locación de memoria independiente y su valor no
cambie entre llamadas de función tenemos dos caminos: o la declaramos como global,
o la declaramos comolocal estática. Los buenos programadores siempre eligen el
segundo.
Una variable se hace estática anteponiendo a su declaración el especificador static. Por
defecto las variables estáticas se auto inicializan a 0, pero se le puede dar otro valor
en la misma declaración (dicha inicialización solo se ejecuta la primera vez que se
llama a la función), así:

staticintvar1;// Variable static (inicializada a 0 por defecto)
staticintvar2=50;// Variable static inicializada a 50
Ejemplos.

voidincrem()
{
staticinta=5;// Variable local estática inicializada a 5
a++;// Incrementar a
}
voidmain()
{
inti;// Declarar variable i

// El siguiente código llama 10 veces a increm
for(i=0;i<10;i++)
increm();

// Ahora la variable a sí debería valer 15
while(1);// Bucle infinito
}
Variables volatile
A diferencia de los ensambladores, los compiladores tienen cierta “inteligencia”. Es
decir, piensan un poco antes de traducir el código fuente en código ejecutable. Por
ejemplo, veamos el siguiente pedazo de código para saber lo que suele pasar con una
variable ordinaria:

intvar;// Declarar variable var
//...
var=var;// Asignar var a var
El compilador creerá (probablemente como nosotros) que la sentencia var = var no
tiene sentido (y quizá tenga razón) y no la tendrá en cuenta, la ignorará. Ésta es solo
una muestra de lo que significa optimización del código. Luego descubrirás más formas
de ese trabajo.
El ejemplo anterior fue algo burdo, pero habrá códigos con redundancias aparentes y
más difíciles de localizar, cuya optimización puede ser contraproducente. El caso más
notable que destacan los manuales de los compiladores C para microcontroladores es
el de las variables globales que son accedidas por la función de interrupción y por
cualquier otra función.
Para que un compilador no intente “pasarse de listo” con una variable debemos
declararla comovolatile, anteponiéndole dicho calificador a su declaración habitual.
Por ejemplo, en el siguiente boceto de programa la variable count debe ser accedida
desde la función interrupt como desde la función main; por eso se le declara
como volatile. Nota: el esquema de las funciones de interrupción suele variar de un
compilador a otro. Éste es solo un ejemplo.

volatileintcount;// count es variable global volátil

voidinterrupt(void)// Función de interrupción
{
// Código que accede a count
}

voidmain(void)// Función principal
{
// Código que accede a count
}

Arrays y Punteros
Probablemente éste sea el tema que a todos nos ha dado más de un dolor de cabeza y
que más hemos releído para captarlo a cabalidad. Hablo más bien de los punteros. Si
ellos el C no sería nada, perdería la potencia por la que las mejores empresas lo eligen
para crear sus softwares de computadoras.
Pero bueno, regresando a lo nuestro, estos temas se pueden complicar muchísimo más
de lo que veremos aquí. Solo veremos los arrays unidimensionales y los punteros (que
en principio pueden apuntar a todo tipo de cosas) los abocaremos a los datos básicos,
incluyendo los mismos arrays. Aun así, te sugiero que tengas un par de aspirinas al
lado.

Los arrays o matrices
Un array es una mega variable compuesto de un conjunto de variables simples del
mismo tipo y ubicadas en posiciones contiguas de la memoria. Con los arrays podemos
hacer todos lo que hacíamos con las tablas (de búsqueda) del ensamblador y
muchísimo más.
Un array completo tiene un nombre y para acceder a cada uno de sus elementos se
utilizan índices entre corchetes ([ ]). Los índices pueden estar indicados por variables o
constantes. En el siguiente esquema se ve que el primer elemento de un array tiene
índice 0 y el último, N-1, siendo N la cantidad de elementos del array.

Estructura de un array unidimensional de N elementos.

Declaración de arrays
Para declarar un array unidimensional se utiliza la siguiente sintaxis:

data_typeidentifier[NumElementos];
Donde data_type es un tipo de dato cualquiera, identifier es el nombre del array
yNumElementos es la cantidad de elementos que tendrá (debe ser un valor constante).
De este modo, el índice del primer elemento es 0 y el del último es NumElements - 1.
Por ejemplo, las siguientes líneas declaran tres arrays.

charletters10];// letters es un array de 10 elementos de tipo char
longHexTable[16];// HexTable es un array de 16 elementos de tipo long
intaddress[100];// address es un array de 100 elementos de tipo int
Para el array letters el primer elemento es letters[0] y el último, letters[9]. Así,
tenemos 10 elementos en total. Si quisiéramos asignar a cada uno de los elementos
de letters los caracteres desde la „a‟ hasta la „j‟, lo podríamos hacer individualmente
así:

letters[0]='a';// Aquí el índice es 0
letters[1]='b';// Aquí el índice es 1
letters[2]='c';// ...
letters[3]='d';//
letters[4]='e';
letters[5]='f';
letters[6]='g';
letters[7]='h';
letters[8]='i';
letters[9]='j';// Aquí el índice es 9
Pero así no tiene gracia utilizar arrays. En este caso lo mejor es utilizar un bucle, así:
(Nota: los caracteres son, al fin y al cabo, números en códigos ASCII y se les puede
comparar.)

charc;
for(c='a';c<='j';c++)
letters[i]=c;
Inicialización de arrays
Los elementos de un array se pueden inicializar junto con su declaración. Para ello se
le asigna una lista ordenada de valores encerrados por llaves y separados por comas.
Por supuesto, los valores deben ser compatibles con el tipo de dato del array. Este tipo
de inicialización solo está permitido en la declaración del array.
Ejemplos:

unsignedcharmask[3]={0xF0,0x0F,0x3C};// Ok
inta[5]={20,56,87,-58,5000};// Ok
charvocals[5]={'a','e','i','o','u'};// Ok
intc[4]={5,6,0,-5,0,4};// Error, demasiados inicializadores
También es posible inicializar un array sin especificar en su declaración el tamaño que
tendrá, dejando los corchetes vacíos. El tamaño será pre calculado y puesto por el
compilador. Ésta es una forma bastante usada en los arrays de texto, donde puede
resultar muy incómodo estar contando las letras de una cadena. Por ejemplo:

inta[]={70,1,51};// Un array de 3 elementos
charvocals[]={'a','e','i','o','u'};// Un array de 5 elementos
charmsg[]="Este es un array de caracteres";// Un array of 31 elementos
¿Por qué el último array tiene 31 elementos si solo se ven 30 letras? Lo sabremos
luego.

Cadenas de texto terminadas en nulo
Son arrays de tipo de dato char. Hay dos características que distinguen a estas
cadenas de los demás arrays. Primero: su inicialización se hace empleando comillas
dobles y segundo, el último término del array es un carácter NULL (simplemente
un 0x00). De ahí su nombre.
Ejemplos:

charGreet[10]="Hello";// Un array de 10 elementos
charmsg[]="Hello";// Un array de 6 elementos
El array Greet tiene espacio para 10 elementos, de los cuales solo los 5 primeros han
sido llenados con las letras de Hello, el resto se rellena con ceros.
El array msg tiene 6 elementos porque además de las 5 letras de “Hello” se le ha
añadido unNull (0x00) al final (claro que no se nota). Es decir, la inicialización
de msg es equivalente a:
charmsg[]={'H','e','l','l','o',0x00};// Un array de 6 elementos
Visto gráficamente, msg tendría la siguiente representación:

Estructura de una cadena de texto.

Los punteros
Los punteros suelen ser el tema que más cuesta entender en programación. Pero si ya
llegaste aquí, es el momento menos indicado para detenerte.
Los punteros son un tipo de variables muy especial. Son variables que almacenan las
direcciones físicas de otras variables. Si tenemos la dirección de una variable, tenemos
acceso a esa variable de manera indirecta y podemos hacer con ellas todo lo que
queramos.

Declaración de punteros
Los punteros pueden apuntar a todo tipo de variables, pero no a todas al mismo
tiempo. La declaración de un puntero es un tanto peculiar. En realidad, se parece a la
declaración de una variable ordinaria solo que se pone un asterisco de por medio. En
este punto debes recordar las declaraciones de todo tipo de variables que hemos visto,
incluyendo las influenciadas por los calificadores const, static, etc. Todas excepto los
arrays; ¿por qué?
La forma general de declarar un puntero es la siguiente:

data_type*PointerName;
Los siguientes ejemplos muestran lo fácil que es familiarizarse con la declaración de los
punteros:

int*ip;// ip es un puntero a variable de tipo int
char*ucp;// cp es un puntero a variable de tipo char
unsignedchar*ucp;// Puntero a variable de tipo unsigned char
constlong*clp;// Puntero a constante de tipo long
float*p1,*p2;// Declara dos punteros a variable de tipo float
Apuntando a variables
Decimos que una variable puntero “apunta” a una variable x si contiene la dirección de
dicha variable. Para ello se utiliza el operador &, el cual extrae la dirección de la
variable a la que acompaña. Un puntero siempre debería apuntar a una variable cuyo
tipo coincida con el tipo del puntero.
En los siguientes ejemplos vemos cómo apuntar a variables de tipo básico,
como int, char o float. Más adelant
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros
Curso micros

Más contenido relacionado

DOCX
Panel posterior
PPTX
Microprocessor 8085 Chapter 3
PPSX
La Fuente de Alimentación del PC
PPTX
PIC Microcontroller | ADC Interfacing
PPTX
Introduction to seven segment display new
PPT
microcontroller basics
PPTX
Introduction to Microcontroller
PDF
Using PCB wizard for PCB implementation
Panel posterior
Microprocessor 8085 Chapter 3
La Fuente de Alimentación del PC
PIC Microcontroller | ADC Interfacing
Introduction to seven segment display new
microcontroller basics
Introduction to Microcontroller
Using PCB wizard for PCB implementation

La actualidad más candente (20)

PPT
intel 8086 introduction
PDF
LCD interfacing with arduino
PDF
Microcontroller pic 16f877 architecture and basics
PPTX
Introduction to PCB Design
DOCX
seven segment display using 74LS78 IC decoder
PPTX
LCD Interacing with 8051
PPTX
8086 microprocessor
PDF
Minimum and Maximum Modes of microprocessor 8086
PDF
2 introduction to arm architecture
PDF
Msp430 assembly language instructions &amp;addressing modes
PPTX
Introduction to FPGAs
PPTX
8051 microcontroller
PDF
Unit II Arm 7 Introduction
PPS
Placa base
 
PPTX
ATMEGA 328
PDF
Tp bus i2_c
PPT
La tarjeta madre y sus diferentes dispositivos
PDF
Report on remote control home appliances.
PPTX
GSM Based Fault Monitoring System (Project)
intel 8086 introduction
LCD interfacing with arduino
Microcontroller pic 16f877 architecture and basics
Introduction to PCB Design
seven segment display using 74LS78 IC decoder
LCD Interacing with 8051
8086 microprocessor
Minimum and Maximum Modes of microprocessor 8086
2 introduction to arm architecture
Msp430 assembly language instructions &amp;addressing modes
Introduction to FPGAs
8051 microcontroller
Unit II Arm 7 Introduction
Placa base
 
ATMEGA 328
Tp bus i2_c
La tarjeta madre y sus diferentes dispositivos
Report on remote control home appliances.
GSM Based Fault Monitoring System (Project)
Publicidad

Destacado (17)

PPT
Demo procesadores
PDF
Netduino
PPT
Noticias sobre microcontroladores
PDF
Hoja datos AVR-Board
PPTX
ATMEL-Presentación
PPTX
Programacion de microcontroladores
PPT
Introduccion a los Sistemas Embebidos
PDF
Microcontroladores 4 – comunicación (uart)
PPTX
Técnicas para la localización de averías en los sistemas digitales
PDF
Microcontroladores ver2.0
PDF
Fallas en circuitos
PPTX
Proyectos electrónica digital
PDF
Coleccion de-circuitos II
PDF
Cuaderno del-tecnico-reparador-de-equipos-electronicos
PDF
500 proyectos de electronica
PDF
2 manual de experimentos electronicos
PDF
Proyectos electronicos
Demo procesadores
Netduino
Noticias sobre microcontroladores
Hoja datos AVR-Board
ATMEL-Presentación
Programacion de microcontroladores
Introduccion a los Sistemas Embebidos
Microcontroladores 4 – comunicación (uart)
Técnicas para la localización de averías en los sistemas digitales
Microcontroladores ver2.0
Fallas en circuitos
Proyectos electrónica digital
Coleccion de-circuitos II
Cuaderno del-tecnico-reparador-de-equipos-electronicos
500 proyectos de electronica
2 manual de experimentos electronicos
Proyectos electronicos
Publicidad

Similar a Curso micros (20)

PDF
Microcontroladores: Entendiendo los AVR de ATMEL
PDF
Sesion 1
PPTX
Microcontroladores AVR
PDF
Microcontroladores: Tutorial de microcontrolador AVR desde 0
PPT
Curso plataforma arduino......
PDF
3 microcontroladores
PDF
3 microcontroladores
PDF
ARQ_Microcontroladores clase de micro .pdf
TXT
Microcontrolador wiki
PPT
Microcontroladores PIC USS
PDF
Diapositiva de Estudio: ppt-introduccion-a-los-microcontroladores_compress.pdf
PDF
Datasheet
PDF
RetroEuskal 2019:Taller «Hasta en la sopa: el desconocido mundo de los microc...
PDF
Introduccion a los MCUs AVR.pdf
DOCX
Qué es un microcontrolador
PPT
semtec micro controladores para pac laptos.ppt
PDF
Microcontroladores Ciscx
PPT
Introduccion micro´s
PDF
INTRODUCCION A MICROCONTROLADORES_34244a60bdd31b87ca74f0dadac0c0e6.pdf
PDF
Microcontroladores
Microcontroladores: Entendiendo los AVR de ATMEL
Sesion 1
Microcontroladores AVR
Microcontroladores: Tutorial de microcontrolador AVR desde 0
Curso plataforma arduino......
3 microcontroladores
3 microcontroladores
ARQ_Microcontroladores clase de micro .pdf
Microcontrolador wiki
Microcontroladores PIC USS
Diapositiva de Estudio: ppt-introduccion-a-los-microcontroladores_compress.pdf
Datasheet
RetroEuskal 2019:Taller «Hasta en la sopa: el desconocido mundo de los microc...
Introduccion a los MCUs AVR.pdf
Qué es un microcontrolador
semtec micro controladores para pac laptos.ppt
Microcontroladores Ciscx
Introduccion micro´s
INTRODUCCION A MICROCONTROLADORES_34244a60bdd31b87ca74f0dadac0c0e6.pdf
Microcontroladores

Último (20)

PPT
El-Gobierno-Electrónico-En-El-Estado-Bolivia
PPTX
REDES INFORMATICAS REDES INFORMATICAS.pptx
PDF
clase auditoria informatica 2025.........
PDF
taller de informática - LEY DE OHM
PDF
CyberOps Associate - Cisco Networking Academy
PDF
Calidad desde el Docente y la mejora continua .pdf
PDF
Influencia-del-uso-de-redes-sociales.pdf
PDF
MANUAL TECNOLOGÍA SER MINISTERIO EDUCACIÓN
DOCX
Zarate Quispe Alex aldayir aplicaciones de internet .docx
PDF
Liceo departamental MICRO BIT (1) 2.pdfbbbnn
PPTX
IA de Cine - Como MuleSoft y los Agentes estan redefiniendo la realidad
PDF
Plantilla para Diseño de Narrativas Transmedia.pdf
PPTX
COMO AYUDAN LAS TIC EN LA EDUCACION SUPERIOR.pptx
PDF
5.1 Pinch y Bijker en libro Actos, actores y artefactos de Bunch Thomas (coor...
PPT
Que son las redes de computadores y sus partes
PPTX
Power Point Nicolás Carrasco (disertación Roblox).pptx
PPTX
Acronis Cyber Protect Cloud para Ciber Proteccion y Ciber Seguridad LATAM - A...
PPTX
RAP02 - TECNICO SISTEMAS TELEINFORMATICOS.pptx
PPT
introduccion a las_web en el 2025_mejoras.ppt
PPTX
Propuesta BKP servidores con Acronis1.pptx
El-Gobierno-Electrónico-En-El-Estado-Bolivia
REDES INFORMATICAS REDES INFORMATICAS.pptx
clase auditoria informatica 2025.........
taller de informática - LEY DE OHM
CyberOps Associate - Cisco Networking Academy
Calidad desde el Docente y la mejora continua .pdf
Influencia-del-uso-de-redes-sociales.pdf
MANUAL TECNOLOGÍA SER MINISTERIO EDUCACIÓN
Zarate Quispe Alex aldayir aplicaciones de internet .docx
Liceo departamental MICRO BIT (1) 2.pdfbbbnn
IA de Cine - Como MuleSoft y los Agentes estan redefiniendo la realidad
Plantilla para Diseño de Narrativas Transmedia.pdf
COMO AYUDAN LAS TIC EN LA EDUCACION SUPERIOR.pptx
5.1 Pinch y Bijker en libro Actos, actores y artefactos de Bunch Thomas (coor...
Que son las redes de computadores y sus partes
Power Point Nicolás Carrasco (disertación Roblox).pptx
Acronis Cyber Protect Cloud para Ciber Proteccion y Ciber Seguridad LATAM - A...
RAP02 - TECNICO SISTEMAS TELEINFORMATICOS.pptx
introduccion a las_web en el 2025_mejoras.ppt
Propuesta BKP servidores con Acronis1.pptx

Curso micros

  • 1. Qué son los microcontroladores y para qué sirven Respondiendo a la primera parte, un microcontrolador (µC o MCU para abreviar) es un circuito integrado programable capaz de llevar a cabo una determinada tarea. El tipo de tarea vendría a ser la segunda parte. Si alguien nos preguntara qué es lo que hace una computadora personal, le responderíamos de todo, según el programa que le instalemos. De igual modo, un microcontrolador, como un “micro computador" que es, puede hacer casi de todo (dentro de sus posibilidades, claro está), según el programa grabado en su memoria. La analogía de un microcontrolador con una computadora va más allá de su programación. Los microcontroladores son circuitos integrados que encierran en un solo chip un CPU (unidad central de procesamiento), las memorias ram y rom, los diversos periféricos especiales y los puertos de entrada/salida. Diagrama de bloques de un microcontrolador Si echamos un vistazo a nuestro alrededor, podremos notar que estamos cada vez más rodeados de los microcontroladores: los periféricos de nuestras computadoras, como las impresoras, teclados, ratones, monitores y demás, tienen incorporados uno o más microcontroladores. Los electrodomésticos, cada vez más modernos, como equipos de sonido, televisores LCD, reproductores de DVD, etc., sin duda tienen microcontroladores que guían sus funciones. Los aparatos de entretenimiento como los reproductores de MP3 y MP4 portátiles también tienen microcontroladores en su parte cerebral. Los teléfonos celulares, las cámaras digitales, etc., etc. De hecho, los microcontroladores se han infiltrado en todos los campos de la vida moderna, desde los pasatiempos hasta la
  • 2. industria robótica, de telecomunicaciones, automovilística... En fin, ¿todavía quieres saber para qué sirven? Características de los AVR Los productos estrella de Atmel son sus microcontroladores AVR. Comparado con otros microcontroladores, en distintos modelos por supuesto, pueden tener memoria de programa flash reprogramable, capacidad ISP (In System Programming), puertos configurables como E/S pin a pin, interfaces de comunicación serial RS232 e I2C, módulos generadores de onda PWM, etc. Yo pienso que una de las razones por las que la gente novel no empieza por los AVR es su set de 130 instrucciones; una cantidad que los haría desistir. Este hándicap inicial se invierte cuando se utiliza un compilador de alto nivel, ya que los AVR fueron diseñados para un óptimo trabajo con el lenguaje C. Por si fuera poco, la gente del software libre ha desarrollado el poderoso compilador AVR GCC, el cual está disponible en sus versiones para Windows y Linux. Así que, si de herramientas para desarrollar proyectos se trata, los AVR toman la delantera y se convierten en serios competidores de los actuales monarcas de Microchip. Las características de cada AVR son descritas con todo detalle en el capítulo Arquitectura interna de los AVR. Microcontroladores con Arquitectura Harvard Como todos los microcontroladores modernos, los AVR fueron diseñados con arquitectura Harvard. Con esta estructura los microcontroladores AVR disponen de dos memorias, una que contiene el programa y otra para almacenar los datos. De este modo el CPU puede tener acceso simultáneo a ambas memorias utilizando buses diferentes. Más específicamente, el CPU puede leer la siguiente instrucción de programa mientras está procesando los datos de la instrucción actual. Arquitectura Harvard de un microcontrolador Antiguamente los microcontroladores tenían una arquitectura Von Neumann. Como se ve en el diagrama de abajo, estos microcontroladores usaban una memoria única que constituía tanto el segmento de memoria de programa como el de datos. Con un solo bus
  • 3. de comunicación entre dicha memoria y el procesador no es posible realizar diversos accesos a la vez. Arquitectura Von Neumann de un microcontrolador Microcontroladores con Instrucciones RISC RISC es sigla de Reduced Instruction Set Computer. También es una característica propia de los microcontroladores actuales como los AVR. Estos microcontroladores cuentan con instrucciones sencillas y en un número mínimo. En muchos casos ello permite que la programación en ensamblador sea una labor cómoda y esté al alcance de todos. Sin embargo, cuando se desarrollan proyectos mucho más complejos, el uso del lenguaje ensamblador se torna cada vez más engorroso. Entonces se prefiere optar por los compiladores de alto nivel, para los cuales un set CISC no es obstáculo. CISC significa Complex Instruction Set Computer y era un distintivo de los primeros microcontroladores que aparecieron en el mundo, los cuales estaban inspirados en los procesadores de los grandes computadores de la época. Es complejo porque consta de muchas instrucciones, complicadas y difíciles de recordar a la hora de programar en lenguaje ensamblador. Además, al crecer el número de instrucciones también crecerán los códigos de las instrucciones, lo cual deriva en una mella en la eficiencia del microcontrolador. Características de los AVR Algunas de las características y recursos generales y comunes a casi todos los AVR son: Están fabricados con tecnología CMOS. Aunque los dispositivos CMOS son más lentos que los TTL, son ideales para los microcontroladores porque requieren de menor consumo de energía. Es posible implementar sistemas que solo se alimenten de baterías corrientes. La tecnología CMOS, como sabemos, también significa que los transistores, al ser mucho menos, ocupan mucho menor espacio en el chip. Memorias de programa (FLASH o ROM), memoria de datos estática (SRAM) y memoria EEPROM internas. Puertos de E/S bidireccionales configurables independientemente pin por pin. Suministro de alta corriente en los puertos de E/S.
  • 4. Timer‟s. Temporizadores de alta precisión o contadores de pulsos externos. También funcionan como generadores de ondas PWM (Pulse Width Modulation), particularmente útiles para controlar la velocidad de los motores DC. WatchDog. Monitoriza que el AVR funcione adecuadamente a lo que se esperaba y no se cuelgue. ISP (In System Programming). Permite realizar la programación del AVR utilizando una interface serial con muy pocos pines. Fuses y Lock bits, permiten establecer un determinado modo de funcionamiento del AVR, como el tipo de oscilador que utilizará o si el código grabado podrá o no ser leído después de la programación. Otros recursos, más avanzados, son específicos a cada familia de AVR y pueden ser: Conversores Analógico-Digital, ADC. Para recibir señales del mundo analógico. Módulos SPI. Para la comunicación con dispositivos que utilizan el bus SPI. Módulos TWI. Para la comunicación con dispositivos que utilizan el bus I2C. USART, Transmisor Receptor Síncrono Asíncrono Universal. Para comunicarse mediante los protocolos RS232 con cualquier dispositivo que también lo soporte. Por ejemplo, podemos conectar nuestro AVR al puerto serie del PC o a cualquier otro microcontrolador con USART. Módulo Comparador Analógico. Nos puede ahorrar un OP-AMP y algo más. Módulo CAN. Para facilitarle al AVR su conexión con otros microcontroladores en una pequeña red LAN con un protocolo robusto para trabajar en condiciones extremas. Módulo USB. Casi todos los dispositivos digitales modernos presentan interface USB. Con esto podemos diseñar sistemas que no tengan nada que envidiarles. Etc., etc. Clasificación de los AVR. ¿Con qué AVR empezar? Cuando la gente va a trabajar con una nueva familia de microcontroladores como los AVR revisa primero su clasificación para saber con cuál puede empezar. Normalmente esta gente ha tenido alguna experiencia previa con otros microcontroladores como los PIC y suele pensar que para programar los AVR primero hay que empezar por lo más básico así como el PIC16F84 en el mundo de los PICs. y seguir avanzando con el PIC16F877 y luego con el PIC18F4550 y así... Bueno, no los puedo culpar, pues yo mismo pensaba así :) Aquí descubriremos por qué esa idea es errónea. Pero volviendo al tema, en el mundo de los AVR hay 4 familias de ellos: los tinyAVR, los megaAVR, losXMEGA y los AVR32. A la vez, puede haber varias decenas de AVR dentro de cada familia, pero las diferencias entre ellos son cada vez menores, como tener algunos pines de E/S más o menos, tener
  • 5. algo de memoria más o menos, tener un Timer más o menos, emplear otro tipo de memoria, y demás detalles de ese tipo. Clasificación de los AVR (fuente: atmel.com). Los tinyAVR Son los "tiny toons" de los AVR, son los microcontroladores de Atmel con menos recursos de memoria y periféricos posibles. Sin embargo, son muy veloces, alcanzando a operar a 20 MIPS (millones de instrucciones por segundo). Así que tampoco los deberíamos subestimar y no se te vaya ocurrir equipararlos con un PIC16F84 o similar. Es tranquilamente factible usar un tinyAVR para conectarse al puerto USB de una computadora con todo el firmware implementado a nivel software. Por ejemplo,Limor Fried uso un ATtiny2313 para su programador USBtinyISP. ¿Crees que un PIC16F84A podría hacer eso? Muy difícil, ¿verdad? Yo no conozco aplicaciones similares ni siquiera con un PIC16F877A (Debe ser mi ignorancia). Inicialmente todos eran muy pequeños en tamaño, por lo general de 8 pines, pero últimamente están apareciendo modelos que llegan a los 20 pines. Lo que varía muy poco es su set de cerca de 130 instrucciones, que es el mismo set base de los megaAVR y XMEGA. Es decir, que sean los más limitados no significa que sean los más fáciles de programar. Los megaAVR. Popular e inapropiadamente conocidos como ATmegas, son los microcontroladores de 8 bits más sobresalientes de Atmel. Detesto hacer estas comparaciones pero creo que son un buen referente para conocer de forma rápida los AVR. Pero bueno, yo diría que en general los megaAVR son como losPIC18 de Microchip. Claro que habrá diferencias a favor y en contra: los PIC18 tienen mayor variedad de ejemplares (personalmente me confunde más) y los megaAVR trabajan un poco más veloz, y etc., etc... No voy a abrir un debate aquí. Todos los megaAVR tienen más de 130 instrucciones, la mayoría de 16 bits, y pueden llegar a velocidades de 20 MIPS. Sus memorias flash pueden alcanzar 256 kB para almacenas
  • 6. hasta 128k instrucciones y sus memorias RAM pueden alojar hasta 4 kB de datos temporales. En general, en ellos se pueden encontrar casi todos los recursos hardware buscados en un microcontrolador de 8 bits, por eso se suele tomar de aquí algunos modelos como punto de partida de aprendizaje. Ahora bien, dentro de la familia de los megaAVR todavía debemos distinguir dos grupos: Los clásicos megaAVR, que ahora Atmel denomina los viejos AVR, como los ejemplares cuyos nombres empiezan con AT90S (AT90S8535, AT90S2313, etc.) e incluso los no tan antiguos ATmega8, ATmega16, ATmega32, ... y demás. Los nuevos megaAVR. Bueno, eso de "nuevos" lo puse yo, por si acaso, solo para darles cierta distinción. No es que estos AVR hagan la gran revolución por el hecho de que puedan trabajar hasta a 20 MIPS en lugar de los 16 MIPS de sus antecesores o porque sus Timers puedan generar 2 canales de ondas PWM cada uno. En cursomicros.com trabajamos con los nuevos megaAVR, en especial con los de la serie ATmegaXX8 yATmegaXX4, que son los megaAVR más potentes que se pueden conseguir en empaques DIP de 28 y 40 pines respectivamente. Los otros megaAVR son más grandes en cuanto a numero de pines y por tanto solo vienen en empaques TQFP, QFN, etc. Sus periféricos son básicamente los mismos, solo suelen diferenciarse por tener más puertos de E/S. No digo que lo viejos AVR no sirvan para nada. Si bien es cierto que pueden tener pequeños bugs (como cualquier microcontrolador de cualquier otra marca), los miles de proyectos disponibles enAVRfreaks.net e incluso los primeros Robots de Dale Heatherington demuestran que se pueden hacer maravillas con ellos. Yo solo digo que si podemos elegir entre un buen producto y otro un poco mejor, ¿por qué no tomar el mejor? Los XMEGA Son microcontroladores de 8 bits pero con injertos de un típico microcontrolador de 16 bits que elevan su performance hasta equipararse con los verdaderos microcontroladores de 16 bits como algunos dsPIC de Microchip. Solo que a diferencia de ellos, ningún Xmega viene en empaque DIP, lo cual dificulta su empleo para fines de experimentación de un principiante. Los AVR XMEGA alcanzan velocidades de hasta 33 MHz pero solo operan a tensiones de hasta 3.3V. Su memoria de programa flash llega a 384 kB. Debido a su renovada arquitectura presentan muchas instrucciones nuevas pero su set básico sigue siendo compatible con las 130 instrucciones de losmegaAVR y tinyAVR. Los AVR32 Son los microcontroladores AVR de 32 bits. Aquí hay dos grupos: los de la serie UC3 y los de la serieAP7. Son AVRs que para los noveles diseñadores deberían ser descartados como punto de partida. Otros Microcontroladores
  • 7. Hay muchas marcas de microcontroladores en el mercado. De ellas solo mencionaré las que creo más populares. A veces un mismo tipo de microcontrolador lo suelen proveer diversos fabricantes, por lo que ésta no es una clasificación estrictamente metódica En esta presentación las descripciones se hacen teniendo en cuenta solo a los microcontroladores de 8 bits. En este sentido, salvo el caso peculiar de los Basic Stamp, personalmente no encuentro diferencias notables en el hardware interno de cada microcontrolador que me hagan optar por uno u otro para un proyecto en específico. No puedo decir lo mismo sobre la disponibilidad de herramientas de desarrollo software y hardware. Los Microcontroladores PICmicro o PIC de Microchip Sin lugar a dudas, son los microcontroladores que han fascinado al mundo en los últimos años. Su facilidad de uso, comodidad y rapidez en el desarrollo de aplicaciones, abundante información y libre disposición de herramientas software proporcionada por Microchip le han permitido ganar terreno rápidamente en el mercado de los microcontroladores a nivel mundial, hasta convertirse en los microcontroladores más vendidos en la actualidad. Los buenos resultados que le dieron a Microchip la estrategia de proveer libremente a los usuarios de muchas herramientas software para el desarrollo de proyectos con sus productos hicieron que los otros fabricantes de microcontroladores también la adoptaran, aunque parece que la ventaja de Microchip en el mercado está ya marcada y tal vez se acentúe más en el futuro. Por qué empezar con los PICS Por su fácil adquisición. Se pueden conseguir en casi cualquier tienda de electrónica. Por su pequeño set de instrucciones, que no logra ser igualado por ningún otro microcontrolador. Es casi mágica la forma cómo se pueden implementar fácilmente casi cualquier algoritmo de programa con solo sus 35 instrucciones básicas. Por su bajo costo. Los PICs son tal vez los microcontroladores más baratos con las características que poseen. Por su fácil aprendizaje. Los PICs cuentan con el menor conjunto de instrucciones, y no por ello menos eficientes, que los convierten de lejos en los de mejor aprendizaje. Por la disponibilidad de herramientas. Las herramientas de hardware y software son de amplio alcance. Eso nos permitirá empezar muy pronto con la experimentación sin la preocupación por mayores recursos. Los Microcontroladores de Freescale Hasta no hace muchos años Motorola era uno de los fabricantes de microcontroladores con mayores ventas en el mundo. En esos tiempos el trabajo con microcontroladores era una actividad casi exclusiva de los considerados gurúes de la microelectrónica y que contaban con suficientes medios para acceder a las herramientas necesarias. Lo cierto es que con el tiempo Motorola empezó a perder su liderazgo y ha preferido ceder la franquicia a Freescale.
  • 8. Freescale continúa con la producción de microcontroladores basados en la arquitectura los viejos productos de Motorola y dotándoles de todo el arsenal tecnológico de la actualidad. Salvo el prestigio legado no tienen nada nuevo en su hardware que no se pueda hallar en otros microcontroladores. Los Microcontroladores 8051 de Intel Intel era otro de los gigantes de los microcontroladores y µPs. Sus productos más conocidos eran los famosos 8051, 80151 y 80251, pero actualmente ya no tiene interés en fabricarlos. En su lugar, fueron otras compañías, como Atmel, Philips, Infineon, Dallas, entre otros, las que tomaron la posta y fabrican algunas partes compatibles. Cabe mencionar que, salvo raras excepciones (como los PICs), el resto de los microcontroladores fueron inspirados en la arquitectura de estos procesadores de Intel. Por lo demás, no tiene caso especificar sus características porque no hay diferencias grandes respecto de los otros productos. En este sentido, no se puede afirmar qué marca de microcontrolador es mejor o peor. Es decir, si tomamos un microcontrolador cualquiera, siempre podremos encontrar un modelo de otro fabricante que pueda sustituirlo en una determinada aplicación. Los Módulos Basic Stamp de Parallax Los Basic Stamp nos son una nueva familia de microcontroladores; son módulos montados sobre otros microcontroladores. Cuentan con un microcontrolador, un circuito oscilador, el circuito de interface con el puerto serie de la computadora, una memoria externa para almacenar el programa y un regulador de tensión; todo en una pequeña tarjeta directa y/o fácilmente conectable a las computadoras. Una vez cargado el programa, el módulo está listo para ser insertado en el circuito de aplicación, incluso si está armado en un simple breadboard. Los programas se desarrollan íntegramente en un lenguaje Basic adaptado. El programa se carga en la EEPROM serial y el microcontrolador del Basic Stamp tiene que interpretarlo. Constitución de un módulo Basic stamp
  • 9. Constitución de un módulo Basic Stamp Por ejemplo, el BS2sx mostrado arriba cuenta con un microcontrolador que está pre programado específicamente para trabajar como intérprete, esto es, para leer las sentencias de comando de la EEPROM serial, decodificarlas y ejecutar las instrucciones que representan. El microcontrolador no se puede reprogramar, viene así de fábrica. Aunque el intérprete opera a toda su potencia, la mayor parte del tiempo la "desperdicia" leyendo la EEPROM serial y decodificando sus comandos. Por tanto, el campo de aplicación de los Basic Stamp es más bien de carácter didáctico y de entrenamiento; no son para grandes proyectos. Actualmente solo hay tres familias de Basic Stamp, cada una con muy pocas variantes, referidas básicamente a la velocidad de operación, capacidad de memoria y cantidad de pines de I/O. En realidad, el tercer grupo está formado por los Javelin Stamp, que interpretan código Java en vez de Basic. Si después de todo aún tuvieras interés por estos módulos, los circuitos de hardware y las herramientas software están a libre disposición en la web de la firma http://www.parallax.com/. Programadores de microcontroladores AVR Conseguir las herramientas software siempre es la tarea más fácil comparada con la parte hardware. Podemos incluso trabajar con las versiones demo de los programas y ver por nosotros mismos cuál nos resultará más conveniente. Pero con el hardware no podemos darnos el lujo de ir probando los que queramos. Así que debemos informarnos bien antes de gastar dinero en una compra inadecuada y/o de perder tiempo construyendo un programador que más tarde nos pueda decepcionar. Pero como en ninguna otra marca de microcontrolador que haya conocido, los programadores de AVR son tan diversos que mucha gente novata se estanca buscando un
  • 10. buen programador por no saber exactamente lo que quiere, gente que mientras más busca en Internet, más opciones y términos nuevos encuentra que al finalmente queda más confundida. Así que si eres nuevo en esto, no pienses que la lectura de este extenso capítulo puede frenar tu progreso. Todo lo contrario, despacio se llega lejos. Esquema de la programación de un AVR. Para evaluar a priori la utilidad y calidad de un producto tenemos que comparar sus características. En el caso de un programador de AVR nosotros vamos a considerar su interface con la PC, su interface con el microcontrolador y el software de computadora con el que lo utilizaremos. La siguiente tabla nos muestra los programadores más reconocidos. Por supuesto que existen muchos otros, pero como veremos luego, la mayoría de ellos son variaciones o derivados de estos. Tabla Programadores de AVR Programador Interface con Interface con el AVR la PC Software Boot Loader USB, COM USART, USB,… AVRDUDE, AVROSP, FLIP,... USBasp USB SPI y TPI AVRDUDE USBtinyISP USB SPI AVRDUDE, Studio 6 AVR Doper USB SPI y HVSP AVRDUDE, Studio 6 HVProg COM SPI, HVSP y HVPP AVRDUDE, Studio 6
  • 11. Tabla Programadores de AVR Programador Interface con Interface con el AVR la PC Software AVRminiProg USB SPI, JTAG, HVSP y HVPP AVRDUDE, Studio 6 ArduinoISP USB SPI AVRDUDE FTDI USB SPI AVRDUDE SI-Prog COM SPI AVRDUDE DASA COM SPI AVRDUDE BSD LPT SPI AVRDUDE AVR910 COM SPI AVRDUDE, AVROSP AVRISP COM SPI AVRDUDE, Studio 6 STK500 COM SPI, HVSP, y HVPP AVRDUDE, Studio 6 AVRISP MkII USB SPI, PDI y TPI AVRDUDE, Studio 6 STK600 USB SPI, PDI, TPI, TPI-HV, JTAG, aWire, HVSP y HVPP AVRDUDE, Studio 6 AVR Dragon USB SPI, PDI, JTAG, aWire, HVSP y HVPP AVRDUDE, Studio 6 JTAGICE MkII USB SPI, PDI, JTAG y aWire AVRDUDE, Studio 6 AVR ONE! USB SPI, PDI, JTAG y aWire AVRDUDE, Studio 6 Si te preguntas por qué en nuestra tabla de comparación no hemos considerado la cantidad o el tipo de microcontroladores que cada programador soporta, es porque está indicado de forma implícita. Es decir, eso depende de la interface de programación del AVR. Por ejemplo, si vamos a trabajar con un AVR XMEGA, debemos saber que ellos tienen interface de programación PDI y por tanto en ese caso necesitaremos un programador como el AVRISP MkII. Simple, ¿verdad? En principio sí, pero los AVR pueden tener más de una interface de programación, unas con más alcance que otras. Enseguida las explicamos.
  • 12. Programación Serial y Paralela La interface entre el programador y el microcontrolador puede ser Serial y Paralela. En la tabla de arriba la Interface paralela figura como HVPP. Todas las otras interfaces desde SPI hasta aWire son seriales. Las líneas de las interfaces seriales varían según el protocolo. En cambio la interface paralela es única y consta de 16 líneas sin contar los pines de alimentación del AVR. Se deduce fácilmente que este modo solo está disponible en los AVR de bastantes pines, más de 14 pines, si queremos establecer una regla. Programación de Alto Voltaje HVPP y HVSP Antes hablamos de la dicotomía serie-paralela. Ahora describiremos los modos de programación en alto y bajo voltaje así como de la relación entre todos ellos. El alto voltaje es un modo de programación adicional que solo está disponible en los AVR de las familias tinyAVR y megaAVR. Es un modo cuya interface de programación involucra el pin de RESET, pues allí donde se aplica el voltaje de 12V. Es el único pin diseñado para soportar ese nivel de tensión. La programación de alto voltaje ofrece algunas opciones que no se pueden encontrar en la programación de bajo voltaje como programar los fuses SPIEN (para activar o desactivar la programación serial) y RSTDISBL (para rehabilitar el pin RESET). En la programación de bajo voltaje el AVR trabaja con su alimentación habitual de 5V mientras que el pin de RESET permanece en 0V. Este modo tiene algunas limitaciones como impedirnos el acceso al fuse SPIEN, para bien o para mal. Advertencia: en los algunos AVR (los ATmegaXX8 de nuestro curso) el pin de RESET puede ser programado por el fuse RSTDISBL para que trabaje como un pin de puerto más, o sea, para que lo usemos para sacar o leer datos por él, aunque el precio a pagar puede ser muy alto. A eso se llamadeshabilitar el pin RESET. Podemos hacerlo desde la programación de alto o bajo voltaje, pero para volver a habilitarlo el único camino será la programación de alto voltaje. Me pregunto si puedes descubrir el porqué. La programación serial puede ser de alto o bajo voltaje. En cambio, la programación paralela (PP) es siempre de alto voltaje. Por eso es casi redundante su denominación de HVPP (High Voltage Parallel Programming). La sigla HVPP aparece en el manual de Atmel Studio 6 solo una vez. Antes me desagradaba pero ahora me empieza a gustar. Las líneas de la interface paralela son 16 sin contar los pines de alimentación del AVR. De aquí concluimos que este modo solo está disponible en los AVR de bastantes pines, más de 14 pines, si queremos establecer una regla.
  • 13. Líneas de interface de la programación paralela, HVPP. La programación serial de alto voltaje está presente en los tinyAVR que cuentan con 14 pines o menos. Si uno de estos AVR tiene interface de programación SPI entones su programación serial de alto voltaje se denomina HVSP (High Voltage Serial Programming). Igualmente parece poco apropiado limitar el calificativo porque los tinyAVR cuya interface de programación es TPI también soportan el alto voltaje. Debe ser porque son muy poquitos :-(. Lo anterior no significa que la programación HVSP vaya por el puerto SPI. Los pines de interface en este caso son otros, tal como se ve en la siguiente imagen. Líneas de interface de la programación serial de alto voltaje, HVSP. Serial, paralelo, alto, bajo, más o menos de 14 pines. Sé que todo esto suena algo enredado sobre todo si los modos e interfaces también se cruzan, pero se puede resumir y entender fácilmente así: “En principio todo AVR debe tener al menos una programación serial de bajo voltaje. Adicionalmente puede tener una programación de alto voltaje, que debe ser paralela si el AVR tiene más de 14 pines o serial si el AVR tiene 14 pines o menos”. El pin RESET es determinante en el modo de operación del AVR. Si aplicamos 12 V (vale entre 11.5V y 12.5V) al pin RESET, el AVR entra en modo de programación de alto voltaje (serial o paralela, según el AVR).
  • 14. Si aplicamos 5 V al pin RESET, el AVR trabaja en operación normal, o sea, empieza a ejecutar su programa. Si mantenemos el pin RESET en 0V, el AVR permanece en estado de RESET, es decir, no hace nada y se queda esperando a que el pin RESET valga 5V para iniciar su operación normal o esperando a que lleguen las instrucciones para entrar en modo de programación de bajo voltaje. Si ponemos el pin RESET en 0 V y luego iniciamos la secuencia de programación, uno de cuyos paso incluye aplicar algunos pulsos al mismo pin RESET, entonces el AVR entra en modo de programación de bajo voltaje (serial, obviamente). Aquí hay una lección importante que debemos aprender. Algunos AVR tienen la capacidad de multiplexar la función del pin de RESET mediante el fuse RSTDISBL. Si dejamos este fuse con su valor de fábrica (sin programar), el pin RESET servirá para resetear el AVR, o sea, con su función habitual. Pero si lo programamos, el pin RESET trabajará como un pin de puerto convencional. Es útil si queremos ampliar nuestras líneas de E/S, pero observa que ello impedirá que el AVR pueda volver a entrar en modo de programación de bajo voltaje. La única señal que le hará recordar al pin RESET su función primigenia es la tensión de 12 V, es decir, necesitaremos de un programador de alto voltaje. Interfaces de programación Aquí estudiaremos las interfaces SPI, TPI, PDI, JTAG y aWire, que podemos juntarlas a las de alto voltaje HVSP y HVPP examinadas previamente. A todas se les suele también denominar modos de programación. Interface de programación SPI Al ser esta la principal interface de programación de los AVR tinyAVR y megaAVR, vamos a explicarlo largo y tendido, así que hecha esa salvedad espero que no te me desanimes. Bueno, empezaremos por distinguir entre los términos ISP y SPI. ISP es la sigla de In System Programming y hace referencia al tipo de programación realizada estando el AVR en su circuito de aplicación, es decir, no es necesario quitarlo de allí y colocarlo en el hardware del programador. Alguna gente le suele llamar también ICSP, por In Circuit Serial Programming, que viene a significar lo mismo, pero se usa más con otros microcontroladores como los PICs. Además ese término es marca registrada de Microchip. SPI es la sigla de Serial Port Interface, que es un bus de comunicación serial, así como lo son los buses RS232, I2C o USB. Los microcontroladores suelen tener un módulo hardware también llamado SPI que facilita las comunicaciones con otros dispositivos usando este protocolo. En los AVR ese mismo módulo SPI sirve también como su interface de programación. Técnicamente hablando todas las interfaces de programación permiten programar el AVR estando en su circuito de aplicación (In System), así que todos los programadores podrían merecer el calificativo de ISP. Debemos tener eso en cuenta porque hasta en la documentación de Atmel es frecuente hablar de programación ISP como sinónimo de la interface SPI.
  • 15. Las comunicaciones por el puerto SPI se llevan a cabo en una relación maestro-esclavo. En este caso el maestro es el programador y el esclavo es el dispositivo programado (AVR). Las transferencias de datos requieren de 4 líneas de interface: MISO, MOSI, SCK y SS. Para la programación del AVR sin embargo se omite la línea SS. En su lugar se usa el pin de RESET y en algunos casos hasta una señal de reloj para AVR puede ser necesaria. En este momento no vamos a profundizar más sobre el control del bus SPI, pero sí vamos a explicar la función de sus pines para que al menos sepamos cómo conectarlos cualquiera que sea el AVR o el programador que usemos. Líneas de interface de la programación SPI. MISO. Master Input Slave Output. Es la línea por donde el maestro recibe datos del esclavo, es decir, en el maestro el pin MISO es entrada y en el esclavo es salida. MOSI. Master Output Slave Input. Es la línea por donde al maestro envía datos al esclavo, es decir, en el maestro el pin MOSI es salida y en el esclavo es entrada. SCK. Serial Clock. Es la señal de reloj. En la comunicación SPI el reloj está siempre a cargo del maestro así que el pin SCK es salida en el maestro y entrada en el esclavo. Con esta señal se establece la velocidad de transferencia de datos. Según el diseño del puerto SPI de los AVR, esta velocidad debe ser como mucho igual a la cuarta parte de la frecuencia del procesador. Por ejemplo, si un AVR trabaja con un XTAL de 8MHz, la frecuencia de SCK no debe superar los 2MHz. RESET. Esta señal no forma parte del bus SPI pero siempre debe haber una línea con el mismo nombre que sale del programador y va al pin RESET del AVR a programar para iniciar el modo de programación. Encuentras más información sobre la función de este pin en la secciónProgramación de alto voltaje HVSP y HVPP. XTAL1. Esta señal es necesaria solo si el AVR está configurado para trabajar con reloj externo. De fábrica no lo está. En un AVR nuevo los fuses de reloj seleccionan el oscilador RC interno, así que la primera programación no necesita esta señal. Pero desde el principio lo común es reprogramar los fuses de reloj para seleccionar una fuente de reloj externa, como un XTAL o resonador cerámico. Solo en esos casos debemos usar la señal XTAL1. SS significa Slave Select. En principio esta señal sirve para que el maestro seleccione el esclavo con el que entablará la comunicación, pues en una red SPI puede haber varios esclavos. Sin embargo, no participa en la programación porque el AVR target da por hecho que es el esclavo elegido desde el momento en que entra en modo de programación.
  • 16. VCC y GND. Naturalmente son los pines de alimentación del AVR. La señal VCC suele a veces aparecer con el nombre de VTG, por Voltage of Target. Hay dos tipos de conectores para la interface de programación SPI. El grande es de 10 pines llamadoISP10PIN. Es un conector antiguo que todavía se encuentra en la placa STK500 e incluso en la STK600pero solo por compatibilidad. Aunque parece mejor para el diseño de PCB, Atmel nos recomienda dejarlo de lado y que procuremos usar el conector de 6 pines ISP6PIN, que debiera estar presente en todo hardware para AVR moderno. Conectores ISP6PIN e ISP10PIN de la interface de programación SPI. Conectores ISP6PIN e ISP10PIN de la interface de programación SPI. Interface de programación TPI TPI quiere decir Tiny Programming Interface es decir es una interface de programación de los "tiny" AVR. Con eso podríamos pensar que está presente todos los tinyAVR, pero no. La mayoría de lostinyAVR utilizan las dos interfaces de programación SPI y HVSP. La interface TPI solo está disponible en los ATtiny4, ATtiny5, ATtiny9, ATtiny10, ATtiny20, ATtiny28 y ATtiny40. Por otro lado, la documentación de Atmel y algunos autores dicen que esta interface queda destinada a los tinyAVR de 6 pines pero los ATtiny20, ATtiny28 y ATtiny40 echan por tierra esa teoría. Ellos tienen como 20 pines y sin embargo también se programan vía TPI. Como sea, la interface TPI utiliza dos líneas de comunicación llamadas TPIDATA y TPICLK, para datos y reloj, evidentemente. Para la programación también es necesaria la intervención del pin de RESET y según el nivel de tensión que se le aplique el tinyAVR puede entrar en dos modos de programación: Si se aplica 12V al pin RESET, el tinyAVR entra en modo de programación de alto voltaje. No tiene una sigla o abreviatura propia, así que en la primera tabla presentada le llamé simplemente TPI-HV. En este modo podemos habilitar y deshabilitar el pin de RESET para que trabaje como el pin GPIO PB3. Si el programador no puede entregar 12V, debe mantener el pin de RESET en 0V para iniciar el modo de programación convencional (de bajo voltaje). También en este caso tendremos acceso al fuseRSTDISBL para configurar el pin RESET como pin PB3, pero ya no
  • 17. podremos devolverle su función de pin de RESET a menos recurramos a la programación de alto voltaje. Líneas de interface de la programación TPI. El conector que usa la interface TPI es el mismo conector ISP6PIN visto arriba, solo que con diferentes nombres de señal. Conector de la interface de programación TPI. Interface de programación PDI Como su nombre indica PDI (Program and Debug Interface) es la interface de dos líneas diseñada por Atmel con fines de programación y depuración exclusivamente para sus AVR de la familia XMEGA. Adicionalmente los XMEGA tienen la interface JTAG, también con propósitos de programación y depuración. Eso es todo; ellos no tienen interface aWire ni mucho menos SPI ni TPI. Los XMEGA también poseen fuses y lock bits para establecer diferentes formas de operación y niveles de seguridad, incluso tienen la capacidad de poder configurar su pin de RESET mediante el fuseRSTDISBL, pero ellos lo hacen todo mediante su interface PDI y no necesitan programación de alto voltaje. Las transferencias de datos en una comunicación PDI se rigen por un protocolo muy similar al delUSART trabajando en modo síncrono. La señal de datos, llamada DATA o PDI_DATA, es bidireccional y utiliza un pin dedicado del XMEGA; en tanto que la señal de reloj, CLOCK o PDI_CLK, es siempre controlada por el programador y está multiplexado con el pin RESET.
  • 18. Líneas de la interface de programación y depuración PDI. El conector que usa la interface PDI también es el mismo conector ISP6PIN, pero con diferentes nombres de señal. Por eso el programador AVRISP MkII utiliza el mismo conector para las interfaces SPI, PDI y TPI. Interface de programación JTAG JTAG también es una interface de programación y depuración presente en los AVR32, XMEGA y en losmegaAVR de 40 pines o más. A diferencia de las interfaces PDI y aWire, su principal modo de operación siempre ha sido la depuración, quedando su función de programador como recurso auxiliar. El conjunto de los pines de la interface JTAG se conoce como TAP, por Test Access Port. De ellos los 4 pines que participan en la programación del AVR son: TMS. Test Mode Select. Indica el estado el controlador Test. TCK. Test Clock. Es la señal de reloj. TDI. Test Data Input. Es la línea de entrada para las instrucciones de datos o de comando. TDO. Test Data Output. La línea por donde el AVR envía los datos seriales. Líneas de la interface de programación y depuración JTAG. El conector de la interface JTAG según el estándar IEEE incluye también dos pines de RESET: nSRSTque serviría para producir un reset software y nTRST para un reset del controlador TAP. Ninguno se usa como tal en el AVR. En su lugar el módulo TAP puede regresar a su estado inicial manteniendo la líneaTMS durante 5 periodos de reloj. A veces
  • 19. se usa la señal nTRST pero aplicada al pin RESET del AVR para un RESET convencional. Eso es útil no directamente para llevar al AVR al modo de programación sino como una forma alterna de limpiar el bit JTD del registro MCUCR. Eso junto con el fuse JTAGENprogramado son condiciones para que se abra el puerto JTAG, ya sea para programación o depuración. Conector de la interface de programación y depuración JTAG. Interface de programación aWire Es la interface de programación y depuración constituida por un solo cable que tienen los AVR32 de pocos pines, aunque eso parezca exagerado pues como mínimo los AVR32 tienen 48 pines. Lo que sí es seguro es que no está disponible en ningún AVR de 8 bits. A pesar de su requerimiento mínimo, como interface de depuración ofrece el mismo alcance que JTAG. La única señal, DATA, es bidireccional y en el AVR32 corresponde al pin de RESET. Líneas de la interface de programación y depuración aWire.
  • 20. Conector de la interface de programación y depuración aWire. Boot Loader Pongo el Boot Loader en el primer lugar de la lista porque quiero que le demos especial consideración. Quienes empezamos programando los PIC adquirimos la costumbre de trabajar con un programador. Utilizar un Boot Loader en ese mundo era una alternativa superflua o un camino para los aventureros. Es comprensible entonces que al migrar a los AVR nos preguntemos ¿y ahora qué programador vamos a utilizar? Tenemos muchas páginas después de ésta donde hallar una respuesta. Pero antes de pensar en un programador deberíamos saber que en el mundo de los AVR el Boot Loader es la mejor opción. Al menos es mi conclusión personal. Pero, ¿qué es un Boot Loader? Es un programa de microcontrolador que le permite auto-programarse. Mediante el Boot Loader el AVR recibe su nuevo programa directamente de la computadora sin intermediación de ningún dispositivo programador y lo graba en su memoriaFLASH. La transferencia se realiza por cualquier interface disponible, siendo las más comunes los puertos serial y USB. La siguiente figura nos ayudará a entender su mecanismo de funcionamiento. Esquema de la auto-programación de un AVR usando un Boot Loader. La memoria FLASH, mostrada en color naranja, es donde se almacena el programa del AVR, o sea, el firmware. En tiempo de ejecución el CPU lee cada una de las instrucciones de esta memoria, las decodifica y las lleva a cabo. Aunque aparece dividida en dos secciones, físicamente la memoria FLASH es un bloque compacto y el firmware puede ocupar parte o todo el espacio disponible. Anteriormente lo habitual era tener un solo firmware en el microcontrolador, que realizaba la tarea de usuario como controlar dispositivos externos, comunicarse con la computadora, etc. Ahora lo llamaríamos firmware de aplicación.
  • 21. Siendo el Boot Loader un firmware también, significa que podemos tener dos firmwares en el AVR. El firmware de aplicación va en la Sección de Aplicación y el firmware de Boot Loader, en laSección de Boot Loader. ¿Cómo sabrá el AVR qué código ejecutar? Cada vez que se inicia el programa de un AVR, esto es tras un RESET, el CPU empieza por ejecutar el Boot Loader –si existe– para ver si la computadora le está enviando datos que representan un nuevo firmware de aplicación. De ser así, recibe los datos y los graba en la sección de Aplicación, operando en lo que podemos llamar modo de auto-programación. Pero si no encuentra nada, el CPU pasa a ejecutar el firmware de Aplicación; operación en modo normal, por así decirlo. Así como la memoria FLASH el Boot Loader también permite programar la memoria EEPROM y el Byte de Lock bits. Además puede tener acceso de lectura a los fuses aunque no los puede modificar. Cuando trabajamos con la auto-programación dejamos de lado ese término para introducir otros dos. El proceso de cargar un firmware de aplicación en el AVR mediante el Boot Loader se denomina upload o subir. Y como el Boot Loader también permite leer el firmware, a dicho proceso se denomina download o bajar. Ventajas y desventajas El uso de un Boot Loader tiene sus pros y contras respecto de un programador convencional. El espacio que el código del Boot Loader ocupa en la memoria FLASH lo descarta como opción de la mayoría de los microcontroladores pequeños. Generalmente un Boot Loader puede abarcar entre 1 kB y 2 kB de memoria. Para los tinyAVR cuya FLASH anda por estos rangos no tendría sentido. En cambio para los megaAVR cuyas memorias llegan hasta los 256 kB, el Boot Loader apenas si se deja notar. El código del Boot Loader debería ser compilado o ensamblado para cada tipo de microcontrolador. Es raramente compatible por ejemplo un Boot Loader para un ATmega16 que para un ATmega32, a pesar de pertenecer a la misma serie. El primer requisito de compatibilidad es que los AVR tengan el mismo tamaño de memoria FLASH puesto que el Boot Loader se aloja en su parte final. El Boot Loader no puede programar los fuses. A veces esto puede ser una pena pero en general será una bendición. Existen algunos microcontroladores que ya traen un Boot Loader de fábrica, pero en su gran mayoría vienen vacíos. El Boot Loader lo tenemos que grabar como cualquier otro programa, con un dispositivo programador convencional. Si nos vamos a dedicar a la programación masiva de microcontroladores, nos puede resultar poco práctico grabar el Boot Loader primero y subir elfirmware de aplicación después. Sin duda en esos casos sería mejor usar un programador para grabar el firmware de aplicación directamente.
  • 22. Por otro lado, en los procesos de aprendizaje y de desarrollo de proyectos, donde modificamos constantemente el firmware de aplicación, el uso del Boot Loader es la mejor opción. Aquí nos basta con un microcontrolador para experimentar con diferentes programas y en diferentes circuitos. Para no exagerar y asumiendo la realidad de los microcontroladores que quemaremos en camino podemos preparar el Boot Loader en varios de ellos. Éste será el paso más difícil pero bien habrá valido la pena porque después solo necesitaremos de un clic para subir un nuevo firmware sin ni siquiera tener que desconectar nuestro circuito de aplicación. Boot Loader AVR109 y Butterfly La variedad de Boot Loaders es tan amplia como la de los programadores mismos. Los hay con diferente interface, con diferente protocolo, propietarios o de código abierto, y para diferentes series de AVR. Aquí nos ocuparemos brevemente de los más reconocidos. AVR109 y Butterfly son términos sinónimos para identificar el mismo tipo de Boot Loader que se presenta en la nota de aplicación AVR109 de Atmel. Este Boot Loader está escrito en C para los compiladores AVR IAR C y AVR GCC. Su código es de libre disposición así que podemos compilarlo para cualquiera de nuestros AVR con sección de Boot Loader. Utiliza el puerto serie para conectarse con la computadora. En una laptop un conversor USB-UART funciona bastante bien. La computadora por su parte deberá correr un software que entienda el protocolo AVR109. El más sobresaliente es AVR OSP, descrito en la nota de aplicación AVR911 de Atmel. Su nombre significa AVR Open Source Programmer, así que también es libre. Pero lo mejor de todo es que trabaja con los archivos XML de dispositivo originales y actuales de Atmel, es decir, soporta todos los AVR con Boot Loader. Abajo tenemos una versión para Windows de AVR OSP editada por Mike Henning.
  • 23. Entono del programa AVR-OSP II. Butterfly para ser más precisos es una adaptación del Boot Loader AVR109 hecha por Atmel para su mini tarjeta de desarrollo del mismo nombre. La tarjeta AVR Butterfly está basada en un ATmega169, el cual además del Boot Loader se puede programar mediante las interfaces SPI yJTAG. Esta tarjeta ofrecía características muy útiles a un bajo precio, por eso Joe Pardue apostó por ella al escribir su eBook C Programming for Microcontrollers, pero actualmente su preferencia ha sido desplazada por el Arduino.
  • 24. Anverso y reverso de la tarjeta de desarrollo AVR Butterfly (fuente: atmel.com). Boot Loader de Arduino El Arduino es, sin duda, el sistema de desarrollo del momento. Su tarjeta se puede reducir a un megaAVR y un conversor USB-UART. Siendo así de simple es sorprendente el éxito que ha alcanzado. Mucho se debe al framework de su lenguaje de programación pero en cuanto al hardware contribuyó el hecho de que el conversor USB-UART se conecte a la computadora para el data logging así como para recibir el nuevo firmware de aplicación del megaAVR. Tarjeta del Arduino. Al igual que la tarjeta AVR Butterfly, el Arduino también posee un conector ISP6PIN para la programación SPI de su megaAVR. Es solo una alternativa, pues el principal medio de su [auto] programación sigue siendo el Boot Loader. El Boot Loader del Arduino está basado en el protocolo de programación STK500 1.x.
  • 25. El IDE del Arduino permite subir al megaAVR el código del firmware automáticamente después de compilar el programa tan solo con presionar el botón Upload (ver la figura de abajo). El usuario no tiene que saber dónde se encuentra el archivo hex generado ni mucho menos conocer de los protocolos que operan detrás del escenario. ¡Genial! Entorno de Desarrollo Integrado del Arduino. Empero a nosotros nos interesará saber que detrás de esta ventana corre AVRDUDE luego de presionar Upload. AVRDUDE es un programa open source que soporta casi todos los programadores de AVR. Como no tiene un entorno propio se han hecho muchos intentos de darle uno; aunque todos quedaron incompletos. Geir Lunde escribió una aplicación Windows para AVRDUDE que permite usarlo para subir o cargar el archivo hex al AVR del Arduino. Por lo visto arriba esto parecería innecesario, pero resulta sumamente útil cuando trabajamos fuera del entorno del Arduino. El
  • 26. programa se llama Xloader y lo puedes encontrar en versión más reciente en su web russemotto.com. Entorno del programa Xloader. El Boot Loader del Arduino está hecho para los AVR de sus tarjetas: ATmega328, ATmega2560 o ATmega32U4, por ejemplo. La buena noticia es que es fácilmente recompilable para cualquiermegaAVR con soporte de Boot Loader, en especial de las series ATmegaXX4 y ATmegaXX8, que son los usados en cursomicros.com. También es posible compilarlo para los XMEGA e incluso para los tinyAVR pero eso requeriría de edición extra. Para los AVR con periférico USB se usa el Boot Loader llamado Caterina escrito por Dean Camera usando su librería LUFA. Hay una característica en el Boot Loader del Arduino que lo convierte en mi favorito y es que su ejecución es íntegramente controlada por el software de la computadora. Con otros Boot Loaders como el del AVR109 suele ser necesario presionar algún botón en el circuito del AVR aparte del botón RESET para entrar en modo de autoprogramación. En el Arduino el reset se genera mediante el conversor USB-UART. A decir verdad en algunas variaciones del Arduino sí se requiere presionar algún botón pero como todo el código es abierto lo podemos modificar a placer. Los códigos de Boot Loader de Arduino están en la subcarpeta hardwarearduinobootloaders. Programador FTDI Los módulos FTDI que estudiaremos son los mismos puentes USB-UART que usaremos intensivamente para las comunicaciones de nuestro AVR con la computadora. Son los comercialmente conocidos como conversores USB a Puerto Serie, solo que ahora los veremos en su desempeño como programadores de AVR. Bueno, en realidad no hay ningún programador llamado como tal. Denominamos así a los módulos de comunicación basados en el transceiver FT232R o similar. Este chip es un conversor USB-UART fabricado por FTDI chip. De ahí su apelativo, aunque ello no quita la posibilidad de usar este método con un transceiver de otra marca, como por
  • 27. ejemplo el CP2102 de Silicon Labso el MCP2200 de Microchip, o un conversor personalizado basado en un microcontrolador, como el USB Serial Light apoyado por el Arduino. De hecho dicen que el MCP2200 es en realidad un PIC pre programado. Los módulos FTDI están difundidos en dos formas: Como un conversor USB - Puerto Serie y como un conversor USB-UART. Dos ejemplos del primer grupo son los módulos EVAL232R y USB-to-RS232, de la misma FTDI Chips, los cuales ofrecen a la salida un conector DB9 con los 9 pines completos, con los voltajes y niveles lógicos del Estándar RS232. Se comportan como auténticos puertos serie. Muy buenos. Además los circuitos de estos y otros productos similares están disponibles en su web. Conversores USB-UART Los conversores USB-Puerto Serie EVAL232R y USB-to-RS232, de FTDI Chips. Por otro lado, quienes trabajamos con microcontroladores utilizamos el puerto serie para intercambiar con la computadora datos que no tienen que viajar en los niveles y voltajes RS232. Tampoco solemos emplear las 9 señales disponibles. Muchas de ellas solo son un legado de los antiguos módems. Es por eso que varias empresas como Adafruit o Sparkfun comercializan conversores USB-UART. Estos son como la tarjeta EVAL232R pero sin el transceiver MAX232, encargado de la conversión de los niveles de voltaje. En lugar del DB9 poseen un conector lineal que solo brinda las señales útiles para nosotros. Para decirlo de otra forma, son pequeñas tarjetas que básicamente unen las líneas del FT232RL a un conector USB por un lado y a un conector plano por el otro. En las imágenes mostradas abajo puedes ver que casi no tienen más elementos que esos. Por eso a veces se les llama también cables FTDI. En inglés un término muy extendido es Breakout. Así aparece identificado en los circuitos de las prácticas de cursomicross.com. conversores USB-UART
  • 28. Los conversores USB-UART FTDI Friend, FTDI Basic y USB Serial Light El conversor USB Serial Light es una tarjeta basada en un microcontrolador ATmega8U2 programado para emular un puerto serie. El AVR funciona también como el FT232RL aunque a nivel software los drivers con que trabaja no tienen el prestigio de FTDI chips. Los tres adaptadores son muy parecidos. Lo que nos interesa a fin de cuentas son las 6 señales de salida que proveen. Las 5 primeras son las mismas: GND, CTS, VDD, TXD y RXD (los nombres cambian un poco pero son las mismas). La sexta señal es DTR en el FDTI Basic y USB Serial Lightpero RTS en el FTDI Friend. Esa es la diferencia crucial. Como puente de comunicación para transferencias de mensajes con la computadora funcionan igual pues allí solo intervienen TXD y RXD además de VDD y GND claro está. Sin embargo, como circuito para programar un AVR ya sea mediante el Boot Loader del Arduino o por la interface SPI, sí debemos considerar esa diferencia. Módulo FTDI como programador de interface SPI Mediante el chip FT232RL podemos emular cualquier programador de puerto serie de los llamados bit bang como el programador SI-Prog o los programadores DASA. Puesto que elFT232RL puede manejar niveles de tensión TTL, la conexión con el microcontrolador sería directa, sin resistencias, ni diodos zéner ni transistores. Pero si vamos a trabajar con un adaptador como alguno de los tres mencionados arriba, debemos considerar dos puntos. Primero, que el software de grabación AVRDUDE asume que a la salida del puerto serie las señales tienen polaridad según el estándar RS232, esto es, invertidas respecto de las señales TTL. Y segundo, que los programadores seriales como SI-Progo DASA usan las señales DTR y RTS al mismo tiempo, lo cual deriva en un escollo puesto que los adaptadores FTDI Basic y USB
  • 29. Serial Light solo usan DTR, en tanto que FTDI Friend o usa DTR oRTS pero muy difícilmente las dos a la vez. Una alternativa para resolver estas dos contrariedades sería usar un conversor que nos brinde todos los pines del puerto serie necesarios, como por ejemplo el UM232R o el UM232H, mostrados abajo. conversores USB-UART Los conversores USB-UART UM232R y UM232H, de FTDI Chips. Afortunadamente también existe una solución software. Para ello debemos abrir en un editor de texto el archivo avrdude.conf que usa AVRDUDE. Se halla en el mismo directorio donde resideavrdude.exe, por ejemplo en C:WinAVR-20100110bin, suponiendo que lo instalamos con las opciones por defecto de WinAVR. Entre otras cosas el archivo avrdude.conf contiene las descripciones e interfaces de todos los programadores que soporta. Lo que haremos será añadir las características correspondientes de nuestro nuevo programador que solo usa las señales de los adaptadores FTDI Basic o FTDI Friend y con las polaridades debidas. Podemos incluirlo en cualquier parte pero para mantener las categorías lo pondremos entre los programadores seriales bit bang. Como ves en el extracto mostrado abajo, yo lo puse en el segundo lugar del grupo, entre ponyser y siprog. Es todo el contenido con fondo gris. # # some ultra cheap programmers use bitbanging on the # serialport. # # PC - DB9 - Pins for RS232: # # GND 5 -- |O
  • 30. # | O| <- 9 RI # DTR 4 <- |O | # | O| <- 8 CTS # TXD 3 <- |O | # | O| -> 7 RTS # RXD 2 -> |O | # | O| <- 6 DSR # DCD 1 -> |O # # Using RXD is currently not supported. # Using RI is not supported under Win32 but is supported under Posix. # serial ponyprog design (dasa2 in uisp) # reset=!txd sck=rts mosi=dtr miso=cts programmer id="ponyser"; desc="design ponyprog serial, reset=!txd sck=rts mosi=dtr miso=cts"; type=serbb; reset=~3; sck=7; mosi=4; miso=8; ; # programador basado en un adaptador FTDI
  • 31. # reset=dtr sck=cts mosi=txd miso=rxd programmer id="ftdi"; desc="programador ftdi, reset=DTR sck=CTS mosi=TXD miso=RXD"; type=ft245r; reset=4;# DTR sck=3;# CTS mosi=0;# TXD miso=1;# RXD ; # Same as above, different name # reset=!txd sck=rts mosi=dtr miso=cts programmer id="siprog"; desc="Lancos SI-Prog <http://www.lancos.com/siprogsch.html>"; type=serbb; Extracto del archivo avrdude.conf modificado. En principio, a cada señal de la interface SPI, mosi, miso, sck y reset, le deberíamos asignar un número entre 1 y 9 que son los que tendría el conector DB9. Pero como dice el comentario (todo lo mostrado en azul oscuro es comentario), AVRDUDE no puede controlar la señal RXD de forma normal. Al no poder utilizarla, cualquiera de nosotros se habría dado por vencido, pero Doshwalo hizo. No sé cómo lo descubrió pero lo hizo. En su esquema no emplea la numeración del conector DB9 ni la polaridad invertida que se esperaría. No voy a quitarle más el crédito así que si deseas ver cómo funciona de fondo te recomiendo que lo leas en su sitio web. Los que llevan prisa solo
  • 32. copien y peguen lo indicado. Guarden los cambios hechos en el archivo avrdude.conf y cierren el editor. Ahora ya podemos usar nuestro conversor FTDI Basic u otro similar para programar cualquier AVR. Un ejemplo sería así. >avrdude -c ftdi -p atmega324p -P ft0 –U flash:w:main.hex:i-B 1 Por si no quedó clara la interface entre el módulo FTDI y el AVR, abajo tenemos el circuito a utilizar. Como se ve, la alimentación del AVR proviene del mismo módulo FTDI. No es recomendable ponerle una fuente propia. Si el AVR no tiene el pin AVCC pues no pasa nada, y si tiene varias señales VCC o GND, se conectan todas. El circuito del XTAL no será necesario si el AVR está configurado para operar con su reloj interno. De fábrica viene así. Este módulo FTDI es perfectamente compatible con el FTDI Basic y USB Serial Light. Para el FTDI Friend debemos aclarar que el pin RTS se debe re direccionar para unirse con la señal DTR. Incluye un pequeñísimo jumper en la parte posterior para realizar esta maniobra. Otra opción sería modificar de nuevo el archivo avrdude.conf como lo hicimos arriba. Esquema para programar un AVR con un Módulo FTDI. Módulo FTDI como programador Boot Loader de Arduino Una tarjeta Arduino es en última instancia un módulo FTDI unido a un AVR. Pero no están unidos como aparece justo en la figura de arriba. En el Arduino el módulo FTDI le sirve al AVR en su función original de comunicarse con la computadora como puerto serie virtual, ya sea para intercambiar simples mensajes de texto o para cargar el nuevo firmware de aplicación mediante el Boot Loader del AVR. En el primer caso las líneas TXD y RXD se conectan con sus homólogos en el AVR de forma cruzada. No hay novedad en ese aspecto, así que ni ponemos un circuito. Por otro lado, cuando se usa el módulo FTDI para cargar el nuevo programa del AVR interviene además de TXD y RXD la señal DTR, cuya función es resetear el AVR para
  • 33. que reinicie desde elBoot Loader. Para conocer los detalles de este proceso puedes leer la sección programador Boot Loader. El circuito en este caso queda como se ve abajo. Valen las mismas sugerencias indicadas para el circuito anterior sobre la fuente de alimentación y la conexión de los pines relacionados a ella. Aquí el AVR siempre trabaja con un XTAL de 16MHz. Esquema para programar un AVR con un Módulo FTDI usando el Boot Loader del Arduino. El Boot Loader le permite al AVR auto grabar en su memoria FLASH el programa que recibe por elmódulo FTDI (auto-programación). Pero para que esto sea posible primero el AVR debe tener grabado el programa del Boot Loader. Esto será sencillo si usamos el módulo FTDI como programador de interface SPI, estudiado antes. Luego el trabajo será más que sencillo. El circuito mostrado arriba es un "Arduino desnudo". Parecerá precario pero con el Boot Loader debidamente cargado será completamente funcional. Podremos usarlo desde su entorno natural (mostrado abajo) o desde un programa alternativo como XLoader.
  • 34. Entorno de Desarrollo Integrado del Arduino. El programa XLoader es particularmente útil para quienes programamos el AVR desde otras plataformas como de los compiladores IAR AVR C, CodeVisionAVR o AVR GCC alojado en Atmel Studio 6. De esa forma trabajaremos en todas las prácticas de cursomicros.com.
  • 35. Entorno del programa Xloader. Programador SI-Prog o PonySer Los programadores de puerto serie o paralelo se están convirtiendo en herramientas obsoletas por la casi extinción de esos puertos en las computadoras. Si necesitamos armar un programador similar aunque sea provisionalmente (en un breadboard) será para grabar el AVR que servirá de cerebro de un programador USB. Estamos hablando de programadores que no llevan un AVR en su circuito. De esos tenemos varios otros y los veremos luego. Ahora examinaremos los programadores conocidos como de bit bang por la manipulación bit a bit de los datos desde la computadora. No serán los más veloces pero sí los más fáciles de armar. Para muchos que reconocen, a veces hasta con cierto bochorno, haber empezado con los PIC antes que otro microcontrolador, es casi inevitable preguntar si habrá algún programador de AVR parecido al entrañable JDM. Un sencillo pero fiable y cómodamente portable programador de puerto serie que no necesitaba de alimentación externa. El programador más cercano es el también conocido en la comunidad PIC. Estamos hablando del viejo superviviente SI-Prog, de Claudio Lanconelli, quizá más conocido por su softwarePonyProg. Este programador usa la interface SPI para grabar todos los megaAVR y tinyAVR que la soportan. El circuito del SI-Prog lo podemos dividir en dos etapas: la que controla las líneas de programación (parte inferior) y la que conforma la fuente de alimentación (parte superior). He puesto un conector ISP de 6 pines estándar en vez del conector SIL que figura en el circuito original y he ignorado la etapa de los zócalos para los AVR que allí se incluían. Puedes encontrar los circuitos originales y el software PonyProg que los controla en la web del autor lancos.com.
  • 36. Circuito del programador SI-Prog. La etapa de alimentación del SI-Prog parte de los 3 diodos 1N4148 que toman corriente de las líneas del puerto serie y recargan el capacitor C3. Si el jumper está cerrado como se ve en la figura también participa el capacitor C4 para acumular en la entrada del LM2936Z-5 hasta 12V tensión. El chip LM2936Z-5 es un regulador parecido al 7805 que a su salida entrega una tensión de 5V, aunque no deberíamos reemplazarlo pues el 7805 no es LDO. De este modo el circuito obtiene su alimentación del puerto serie. Pero si vamos a usar una fuente de alimentación externa debemos mover el jumper a la otra posición. En este caso sí podemos pensar en un 7805 como alternativa. Si quitamos toda la etapa de alimentación y nos quedamos con las líneas básicas de programación, el circuito se reduce a su forma más difundida. Ahora el programador SI-Prog es netamente ISP porque su fuente de alimentación VCC la obtendrá del circuito del AVR programado.
  • 37. Circuito reducido del programador SI-Prog. Programador SI-Prog reducido comercializado por deccanrobots. El circuito resultante sigue siendo igual de fiable que el original. Es bastante bueno aunque no podemos decir lo mismo de PonyProg. Como la mayoría de los programas con entorno gráfico para Windows, este software ha quedado desactualizado. Soporta pocos dispositivos, la mayoría antiguos modelos, y difícilmente funcionará en los sistemas operativos actuales.
  • 38. Entorno de trabajo del programador PonyProg. Dada la popularidad del programador SI-Prog, se pueden encontrar en Internet algunos otros softwares con entorno gráfico que lo soportan. Podría citar una lista de ellos, con características tan diversas como sus limitaciones, pero sería en vano, un desperdicio de tiempo tratando de explicar a quiénes les puede convenir tal o cual programa. Mejor es usar sugerir un programa que a todos nos servirá sin importar el hardware ni sistema operativo de nuestra computadora. Este programa universal es AVRDUDE: el mejor software programador que existe, a pesar de no tener entorno gráfico GUI completo. En la siguiente captura, obtenida al escribir avrdude –c x, AVRDUDE nos muestra la lista de todos los programadores que soporta. Los 5 primeros son programadores bit bang de puerto serie. ElSI-Prog aparece identificado como siprog, pero también es reconocido por su alias ponyser. Por eso también se le conoce así.
  • 39. Programadores seriales bit bang soportados por AVRDUDE. De hecho, y como podremos comprobar después, todos los programadores bit bang de puerto serie tienen la misma estructura de circuito. Esto a veces puede hacer confusa su identificación. Por ejemplo, es frecuente encontrar pequeñas variaciones en un programador SI-Prog que hacen que se vea como un programador DASA3, y viceversa. Sucede mucho en las versiones comerciales porque no suelen identificarse con los nombres aquí presentados. Pero si vamos a trabajar con AVRDUDE, poco interesa la procedencia de nuestro programador serial y tampoco es imprescindible saber cómo se llama. Todo lo que necesitamos es conocer la relación de las señales MISO, MOSI, SCK y RST con los pines del conector DB9. Con estas correspondencias podemos identificarlo fácilmente. Por ejemplo, la imagen que tenemos abajo confirma que en el programador SIProg o PonySer la interface es MISO-CTS, MOSI-DTR, SCK-RTSy RST-TXD.
  • 40. Pines de interface del programador SI-Prog o PonySer. A modo de ejemplo, si queremos grabar un archivo main.hex en la memoria FLASH de un ATmega128P con AVRDUDE estando nuestro programador conectado al puerto serie COM1, podríamos escribir: >avrdude –c siprog –P com1 –p atmega128p –U flash:w:main.hex:i O de la siguiente forma, puesto que ponyser es alias de siprog. >avrdude –c ponyser –P com1 –p atmega128p –U flash:w:main.hex:i A falta de un puerto serie en las computadoras actuales se han difundido bastante losconversores USB a Puerto Serie, casi todos basados en la familia del transceiver FT232RL, pero sobre su uso el manual del AVRDUDE no garantiza nada y hasta recomienda no usarlo como puerto serie virtual. Si tienes un módulo de este tipo es recomendable que leas la página delprogramador FTDI. Programadores DASA Existen 3 programadores de esta serie: DASA,DASA2 y DASA3. Son programadores que no tienen alimentación propia ni derivada del puerto serie y tampoco una señal de reloj para el AVR en caso de que éste trabajara con oscilador externo. Empezaremos por describir el último. No, no ha habido un error al poner la imagen. El circuito que ves abajo corresponde al programador DASA3. Es muy parecido al circuito reducido del programador SI-Prog.
  • 41. Serían idénticos de no ser por el diodo 1N4148, que en muchas ocasiones también suele aparecer en elSI-Prog, y porque la conexión de los pines TXD yDTR en el conector DB9 está permutada. Circuito del programador DASA3. El circuito del programador DASA2 es más parecido aun. De hecho podríamos decir que es el mismo SI-Prog reducido. Supongo que por eso no es considerado por el programa AVRDUDE. Poner su circuito por tanto sería desperdiciar espacio. La primera versión de esta familia era el programador DASA. Es el programador serial más simple de todos. Yo creo en el adagio kiss pero este circuito no me da mucha confianza. Me atrevería a armarlo solo como programador provisional. Observa que nuevamente ha cambiado la conexión de las líneas en el conector DB9. Circuito del programador DASA.
  • 42. El programador DASA (fuente: make.larsi.org). Podríamos seguir mostrando otros programadores de puerto serie pero solo descubriríamos que son derivados o variaciones de los programadores SIProg y DASA3. Podemos incluso hacer nuestras propias adaptaciones que puedan ser controladas por AVRDUDE. Tanto su nombre como la conexión de señales en el conector DB9 deben ser añadidas en el archivo avrdude.conf, que utiliza AVRDUDE. Más adelante veremos cómo hacer esto pero para un programador que utiliza un conversor USB-UART. Debido a su parecido, son muy recurrentes las metidas de pata al confundir los programadoresDASA entre ellos y con el SI-Prog. Antes de usar un programador de estos debemos cerciorarnos en su identificación viendo los pines de interface que usa. La siguiente imagen por ejemplo nos resalta las interfaces de los programadores DASA y DASA3. Observa que no aparece DASA2. Si tuviéramos uno, podemos invocarlo como siprog o ponyser.
  • 43. Programadores DASA soportados por AVRDUDE. A modo de ejemplo, si queremos grabar con un DASA3 un archivo main.hex en la memoria FLASH de un ATmega128P con AVRDUDE estando nuestro programador conectado al puerto serie COM1, podríamos escribir: >avrdude –c dasa3 –P com1 –p atmega128p –U flash:w:main.hex:i O, si usamos un DASA. >avrdude –c dasa –P com1 –p atmega128p –U flash:w:main.hex:i O, si usamos un DASA2. >avrdude –c siprog –P com1 –p atmega128p –U flash:w:main.hex:i A falta de un puerto serie en las computadoras actuales se han difundido bastante losconversores USB a Puerto Serie, casi todos basados en la familia del transceiver FT232RL, pero sobre su uso el manual del AVRDUDE no garantiza nada y hasta recomienda no usarlo como puerto serie virtual. Si tienes un módulo de este tipo es recomendable que leas la página delprogramador FTDI. Programador USBasp USBasp deber por mucho el programador USB para AVR más vendido y también el más construido en casa debido a la libre disponibilidad de su hardware, firmware y la amplia gama de software de computadora que lo soporta. Su popularidad se ha
  • 44. acrecentado además por la flexibilidad de su circuito que permite usarlo con el firmware de otros programadores USB comoAVR Doper, AVRminiProg o USBtinyISP. Todo esto lo explicaremos de a poco. Así que empecemos por mostrar sus principales características entres pros y contras. Programador usbasp Programadores USBasp. Soporta las interfaces de programación SPI y TPI. Con SPI podemos programar todos losmegaAVR y la gran mayoría de los tinyAVR. Con TPI cubrimos el pequeño restante de los tinyAVR. Recordemos que la programación TPI también puede realizarse en alto voltaje, pero ese modo no es manejado por USBasp. Fue diseñado por Thomas Fischl utilizando la librería V-USB de Christian Starkjohann que administra todo el protocolo de comunicación USB a nivel software, de modo que no es preciso disponer de un microcontrolador con USB incorporado. Inicialmente el firmware está escrito y compilado para el ATmega8 y ATmega88 pero puede adaptarse para funcionar en cualquier AVR siempre que tenga la memoria y los pines suficientes. Puesto que la librería V-USB trabaja en bit banging (bit a bit), las transferencias de datos se dan a la mínima velocidad del protocolo USB, esto es, 1.5 Mb/s (modo Low Speed). A pesar de ello la velocidad de programación alcanza los 5 kB/s. Es bastante rápido. Usa un jumper para activar la alimentación Vcc al AVR target y otro para “frenar” cuando sea necesario la velocidad de programación. Mover jumpers cada vez que vamos a programar un AVR podría resultar hasta frustrante en especial si tenemos planeado usar el programador activamente. No tiene un software de computadora con entorno a lo Windows que lo maneje a plenitud. AVRDUDE lo controla estupendamente pero su uso desde la línea de comandos con frecuencia termina por ahuyentar a los principiantes. Muchos han intentado darle a AVRDUDE un entorno grafico que permita programar el AVR con un
  • 45. par de clics: avrdude-gui, SinaProg, AVR Burn-O-Mat, son algunos ejemplos. Para mí todos están incompletos en especial por las ventanas de configuración de los fuses. Inicialmente eso me disgustaba pero luego vi que los fuses no los programaba más que una vez. El resto del tiempo solo programaba la memoria flash, para lo cual efectivamente bastan con un par de clics. AVRDUDE corre sin ningún inconveniente en Mac OS X y Linux. En Windows requiere la instalación del driver open source para USB libusb. No tendría que resaltar esto de no ser porque libusb se lleva mal con los drivers USB de Jungo que emplea Atmel Studio 6 para sus programadores y depuradores hardware, como AVRISP mkII, AVR Dragon, etc. En estos casos se recomienda emplear la librería libusb en modo filtro, el cual lamentablemente no está del todo acabado. Parece que al final la balanza no favorece mucho que digamos el lado de los pros. Siendo el programador USBasp de hardware y software open source, tal vez pienses que en vez de criticar debería hacer mi aporte para remediar sus supuestas deficiencias. De hecho lo pensé al principio pero luego comprendí por qué otros no lo habían hecho antes. ¿Por qué? Si echamos un vistazo al sitio web oficial de USBasp, quizá nos sorprenda encontrar además del diseño original las distintas adaptaciones que hicieron muchos usuarios. ¿Por qué tantas versiones?, ¿Serán igualmente fiables?, ¿Cuál me conviene construir? No es fácil responder a estas preguntas para alguien que pretende ser imparcial. Así que en vez de dar un sí, no, éste o aquél, vamos a hacer un pequeño viaje para conocer las características del circuito de este programador y comprender por qué le hicieron algunas variaciones. Empecemos por el circuito original del USBasp. Programador USBasp original.
  • 46. Circuito del programador USBasp original. La interface USB El AVR es un dispositivo esclavo en la red USB que debe estar atento a todos los datos que le envía la computadora. Esto se consigue usando la interrupción INT0 para detectar los cambios de nivel en la línea D+. Se puede editar fácilmente el firmware del AVR para conectar las líneasD+ y D- a otros pines del AVR (siempre que
  • 47. pertenezcan al mismo puerto), pero la conexión deD+ al pin INT0 debe permanecer. En realidad, según la librería de USB Virtual V-USB que usa el firmware y las interrupciones de cambio de pin del ATmega88, es posible incluso pasar por alto esta conexión pero con una edición un poco más avanzada. La resistencia de 2.2k presenta el programador a la computadora como dispositivo Low Speed(1.5MHz). Si estuviera atada al pin D+ la computadora trataría de reconocerlo como dispositivoFull Speed (12 MHz). Es regla general que las transferencias de datos a altas velocidades no se pueden realizar a niveles de tensión muy altos. Por eso el USB fue diseñado para que sus líneas de datos D+ y D-manejen señales diferenciales de 0 y 3.3V, no los niveles TTL de 0 y 5V. Si en D+ hay 3.3V en D- debe haber 0V y viceversa. Los microcontroladores con USB hardware tienen pines preparados para entender estos niveles incluso si están alimentados por Vcc = 5V. Pero como el USBasp no lleva uno de esos, tiene que usar diodos zéner. Nota que si fueran de 3.3V, estaríamos en el borde del rango que puede entender el AVR. 3.6 V está bien. Eso junto con las resistencias de 68 Ω dan los niveles de tensión e impedancias que el estándar USB puede aceptar. Interface USB del programador USBasp. Uno de los diseños que me llamó la atención rápidamente por su PCB compacto (solo tiene 3 discretos puentes) era el de J.A. de Groot. Pero me desilusioné al percatarme de que le había quitado los diodos zéner. Yo no habría hecho eso, pues la especificación del USB es bastante estricta. Si bien es cierto que era así en las primeras del programador, no he visto otros proyectos con USB emulado que omitan los diodos zéner.
  • 48. Programador USBasp modificado por J.A. de Groot. La interface SPI En el circuito del USBasp original las líneas de la interface SPI van directamente al conector. Puesto que el AVR programador siempre trabaja a 5 V, es necesario que el AVR target (a programar) también lo haga o, de lo contrario, no se podrán entender. Hasta allí no parece haber nada novedoso, ¿verdad? Interface SPI del programador USBasp.
  • 49. Sin embargo, varias personas decidieron interponer elementos en las líneas del bus SPI pensando en los AVR alimentados por 3.3 V o, en general, por voltajes inferiores a 5 V. No estamos hablando de los XMEGA ni de los AVR32 que son los que trabajan a 3.3 V de forma exclusiva. Además ellos no se programan por la interface SPI. Nos referimos a los tinyAVR ymegaAVR que como sabemos pueden trabajar entre 1.8V y 5.5 (esos son valores críticos, no significa que tengamos que llegar a esos extremos). En algunos rediseños como el de Fisch und Fischl GmbH y Thomas Pfeifer se han puesto resistencias con el propósito adicional de prevenir cortocircuitos. En otros diseños como Pawel Szramowski o yuki-lab.jp han ido más lejos y le han colocado un buffer como el 74HC125 o el 74HC541. En cualquiera de esos casos el jumperSupply Target debe permanecer abierto durante la programación dejando que AVR target trabaje con su propia alimentación (inferior a 5V). Programador usbasp Interface SPI del programador USBasp. Los jumpers Hay tres jumpers en el programador USBasp:
  • 50. Jumpers del programador USBasp. Slow SCK. Sirve para reducir velocidad de programación del USBasp. Por defecto, cuando este jumper está abierto, la velocidad del bus SPI se establece a Fsck = 375kHz (frecuencia de la señal SCK). En la gran mayoría de los casos se cumplirá con el requisito de ser menor a la cuarta parte de la frecuencia del procesador, es decir, Fsck < F_CPU/4, y no habrá problemas pues en general nuestro F_CPU vale la frecuencia del XTAL usado. Una de las excepciones se da cuando el AVR es nuevo, donde los fuses de reloj configuran la frecuencia F_CPU = 1MHz, esto es, la octava parte de la frecuencia del oscilador RC interno del AVR. En ese caso la condición 375kHz < 1MHz/4 no es cierta y por tanto será necesario disminuir el valor de Fsck. El programador USBasp ofrece dos formas de reducir el valor de Fsck: por la vía hardware, cerrando el jumper Slow SCK, y por la vía software, desde el programa como AVRDUDE. Si el jumper está cerrado, el bus SPI se configura con Fsck = 8kHz. Esta frecuencia no solo cumplirá la exigencia de Fsck < F_CPU/4, sino que puede reducir demasiado la velocidad de programación del AVR. La buena noticia es que si programamos los fuses de reloj para que la frecuencia F_CPU sea superior a 1.5MHz, en las siguientes ocasiones ya no habrá necesidad de reducir el valor de Fsck. Self Programming es el jumper de auto-programación. Los programadores USB suelen tener un jumper similar para permitir la actualización automática de su firmware, pero este no es el caso. En el USBasp la actualización o la primera grabación del firmware
  • 51. es manual. Si queremos hacerlo, debemos grabar el AVR del USBasp con otro programador. Solo en esa ocasión se debe cerrar el jumper Self Programming. El resto del tiempo debe estar abierto. Supply Target. Este jumper sí lo usaremos con frecuencia. Si está cerrado se conectan las líneas Vcc del programador USBasp y del circuito de aplicación (target). Aunque eso te parezca obvio, debes notar que si el circuito target tiene su propia alimentación activada, el jumper cerrado podría dejar el puerto USB dañado. El USB en este caso está configurado para brindar alimentación [al USBasp y al circuito target inclusive]; no para recibirla. Así que antes de cerrar el jumper, es recomendable apagar la alimentación del circuito target. En ese caso podemos incluso mantener el jumper cerrado todo el tiempo; así funcionaba el USBasp en sus primeras versiones. Dejar el jumper abierto y permitir que el circuito target trabaje con su propia alimentación es útil cuando ésta tiene un valor inferior a 5 V. Recordemos que esto requiere la adaptación de las líneas del bus SPI con buffers o al menos con resistencias en serie. Para más información ver la interface SPI. Los diodos LED Son elementos básicamente decorativos. El LED verde se enciende cuando apenas se conecta el programador al puerto USB de la computadora. El LED rojo se enciende durante el proceso de grabación del AVR. Desde el firmware es posible reubicarlos así como editar su función. Yo por ejemplo modifiqué el código para que el LED verde se encienda tras la correcta enumeración del programador, o sea cuando la computadora lo haya reconocido correctamente. También Limor Fried hizo lo mismo con su programador USBtinyISP. En el circuito original las resistencias de cada LED son de 1k. Eso por supuesto depende del tipo de LED. Para mis diodos LED yo tomé resistencias de 330 Ω.
  • 52. Diodos LED del programador USBasp. Los conectores Si por su tamaño el programador necesitará un cable para conectarse a la computadora, se usará un conector de recepción de tipo B estándar (ese que tiene forma de cajita y es el más usado) o micro (demasiado pequeño). Aunque los conectores mini ya no están contemplados en especificación 3.0 del USB, todavía se encuentran en el mercado y son muy populares en dispositivos portátiles. Pero si el programador es muy pequeño se puede conectar directamente a la computadora usando un conector de tipo A macho. Es el conector que usaron por ejemplo Fabio Baltieri y Sven Hedin en sus adaptaciones. Programador usbasp Adaptaciones del USBasp de Fabio Baltieri (izquierda) y Sven Hedin (derecha). Los dos diseños se ven muy parecidos, uno más presentable que el otro, pero la principal diferencia está en el conector ISP. ¿Por qué un conector tiene 6 pines y el otro 10?
  • 53. En el USBasp original el conector es un ISP10PIN, de 10 pines. Además de las líneas de programación propias de la interface SPI (MISO, MOSI, SCK, RST) y de alimentación (VCC y GND), están conectadas las líneas de transmisión TXD y recepción RXD del USART. El USART sirve para comunicarse con la computadora por el puerto serie. El autor, Thomas Fischl, la puso para la depuración del programador, es decir, para quienes deseen contribuir al desarrollo y mejora de su firmware. Para los usuarios finales esta interface está por defecto desactivada y TXD y RXDson “líneas muertas”, de modo que pueden prescindir de ellas. Sin las líneas del USART basta con un conector de 6 pines para el programador. Líneas del USART en el conector ISP10PIN pines del USBasp. El conector de 10 pines del USBasp original es compatible con el hardware antiguo de los AVR. Atmel todavía lo incluye en algunos de sus productos como sus placas STK500 y STK600 pero solo con fines de compatibilidad. Al final siempre nos insta a usar el conector de 6 pines. Así lo han entendido los diseñadores de programadores similares como AVR Doper o USBtinyISP. Parece que somos muy pocos los que seguimos el mismo camino con el USBasp hecho íntegramente en versión through hole. Recuerdo haber visto también el programador de yuki-lab.jp con un conector ISP6PIN. Los zócalos Para ser sincero, yo era de los que venían del mundo de los PIC y tenía la casi viciosa costumbre de utilizar un programador con zócalo donde colocar el PIC para después
  • 54. retirarlo y llevarlo al circuito de aplicación. Con los AVR me deshice definitivamente de esa práctica. Luego entenderás por qué. Programador usbasp Programadores USBasp con zócalos. Un turco de nickname gevv armó un USBasp con zócalo ZIF para los AVR de 8, 20, 28 y 40 pines. Adicionalmente cuenta con un conector ISP de 10 pines para programar AVRs fuera del zócalo; es el de la imagen derecha. Es uno de mis anhelos llegar algún día a Turquía, pero no imaginaba que el idioma me resultara tan complicado. No entendí nada de su web. Ni siquiera sé si acepta los AVR de 20 pines antiguos o de los nuevos. Haré un mayor esfuerzo en otra oportunidad. Por ahora no tengo interés en su programador. El programador de la imagen izquierda es la adaptación de Matthias Görner utilizando zócalos ordinarios de 28 y 40 pines. No tiene conector ISP para programar los AVR que no quepan en sus zócalos. Si al igual que a mí, no te gusta ninguno de los dos programadores mostrados, puedes rehacer el tuyo propio según tus preferencias. Con el firmware y el circuito a tu disposición solo necesitas conocer dos cosas: Las señales del programador MOSI, MISO, SCK y RST deben ir a los pines del AVR con el mismo nombre. Se da por hecho la conexión de todos los pines de alimentación GND, VCC y, si existe, también AVCC. Puedes encontrar información adicional en la sección interface de programaciónSPI. El modo de programación es una de las formas de trabajo del AVR, y para que trabaje el AVR necesita de una señal de reloj que puede ser interno o venir del exterior. Cuando el AVR es nuevo su reloj es el circuito RC interno. Después lo programamos para que su reloj sea externo, normalmente de un XTAL. Los dos programadores descritos antes utilizan un XTAL para el AVR target. Otra forma sería generar una onda cuadrada periódica en el AVR del programador y aplicarla al
  • 55. pinXTAL1 del AVR target. Esta señal es compatible con cualquiera de las fuentes de reloj externas del AVR. Cuando programamos los fuses de reloj en realidad estamos optimizando el circuito de reloj interno del AVR para alguno de los elementos externos entre XTAL, resonador cerámico, circuito RC o simplemente una señal cuadrada en el pin XTAL1; no significa que una configuración de reloj impedirá el funcionamiento del AVR con la otra fuente de reloj externa, solo que quizá no será la mejor opción. Un programador con zócalos puede ser realmente útil cuando nos dedicamos a la programación en serie de microcontroladores. Las pocas veces que tuve que hacerlo fue utilizando microcontroladores de 28 o 40 pines (comprándolos en cantidades, los microcontroladores pequeños cuestan casi lo mismo que los medianos). Así que al empezar a trabajar con los AVR supuse que si quizá, tal vez, alguna vez,… necesitaba de un programador con zócalo, sería con un ZIF para los AVR de 28 y 40 pines. En el hardware puse un ATmega88 SMD para reducir el tamaño de la PCB y en el firmware configuré el Timer2 para generar una onda cuadrada de 2MHz como reloj del AVR target. Programador usbasp Programador USBasp con zócalo ZIF. El resultado es como lo que ves arriba. Para mí quedó muy bonito pero te confieso que de todos mis programadores es el que menos uso. Me costó casi lo mismo que un Arduino. Así que antes que cometas el posible mismo error, te recomiendo que te compres eso: un Arduino. Con ello tendrás un microcontrolador, un programador, un conversor USB-UART (puerto serie para tu laptop) y hasta una fuente de alimentación de 5V y 500mA. Programador USBtinyISP El programador USBtinyISP fue desarrollado porLimor Fried a partir del programador USBtiny deDick Streefland. Como su nombre deja suponer, está basado en un microcontrolador tinyAVR, el ATtiny2313, y solo ofrece la interface de programación SPI (ISP por antonomasia). Describamos algunas de sus características: Puede programar todos los megaAVR ytinyAVR con interface SPI, excepto los que cuentan con memoria superior a 64K, que son los familiares del ATmega128 y ATmega256. Utiliza buffers para grabar microcontroladores AVR que operan a Vcc < 5V.
  • 56. Su alimentación proviene del puerto USB. También puede suministrar alimentación de 5V al circuito target, siempre que no exceda de los 100mA. Este tope se puede extender editando el firmware. Tiene una aceptable velocidad de programación de 1kB/s. Para tener una referencia práctica, los programas de cursomicros son de 2kB en promedio, así que grabarlos en el AVR tomaría cerca de 2 segundos cada uno. Se puede usar desde AVRDUDE o desde Atmel Studio 6. En resumen es bastante parecido al USBasp, salvo por la primera limitación y por el costo. Este programador no está disponible comercialmente en versión ensamblada. Lo podemos comprar desde adafruit.com solo como un kit. La ventaja de este kit es que el microcontrolador ATtiny2313 ya viene pre-programado. Eso te puede ahorrar bastante tiempo y esfuerzo. Programador usbasp Vistas exterior e interior del programador USBtinyISP (fuente: ladyada.net). Las líneas de la interface de programación SPI del USBtinyISP se reúnen en los dos conectores estándar: el cada vez menos usado de 10 pines y el recomendado por Atmel de 6 pines. El jumper OUTPWR tiene las mismas funciones que en el programador USBasp: Brindar alimentación (con el jumper cerrado) de 5V al circuito target si éste no la tiene, o Permitir (con el jumper abierto) que el circuito target trabaje con su propia alimentación, lo cual es deseable cuando su nivel es diferente de 5 V. Actualmente el USBtinyISP se encuentra en su versión 2.0. La diferencia notoria respecto de la versión 1.0, aparte de algunos pequeños ajustes en el firmware, está en el buffer 74AHC125Ncolocado en las líneas de programación MISO, MOSI, SCK y RST, del hardware. Además de aislante, este CI sirve de puente entre las señales TTL del
  • 57. AVR programador y los niveles del AVR programado, que pueden ser de 0-5 V o bajar hasta 0-2 V. El buffer 74AHC125N se alimenta del pin Vcc del circuito target. En el circuito de la versión 1.0 en lugar de los buffers las líneas de programación se aislaban con resistencias de 1.5k. En teoría, también permiten la comunicación entre dos microcontroladores que operan a diferente nivel de Vcc, pero algunos dicen que en la práctica no es una interface muy fiable. Circuito del programador USBtinyISP, versión 2.0.
  • 58. Circuito del programador USBtinyISP, versión 1.0. Construcción de un USBtinyISP El programador USBtinyISP es open source. Todos los archivos están disponibles en la web de su autora, www.ladyada.net. Como no pasa por muchas actualizaciones aquí están algunas réplicas, actualizadas a noviembre de 2012. Circuito y PCB en Eagle. (Versión 2.0) Firmware del microcontrolador ATtiny2313. (Versión 2.0) Circuito y PCB en Eagle. (Versión 1.0) Firmware del microcontrolador ATtiny2313. (Versión 1.0) El firmware compilado es main.hex y se encuentra en la carpeta spi. Para construir cualquier programador de AVR con interface USB se sigue el mismo procedimiento, el cual se divide en dos etapas:
  • 59. Elaborar la placa de circuito impreso, PCB, y soldar todos los componentes. En lo posible usamos un zócalo en vez del AVR del programador. Aquí terminaríamos si el programador no se basara en un microcontrolador. De hecho, si compraste el kit de ensamblaje deadafruit, coloca el ATtiny2313 en el zócalo y ya puedes empezar a usar tu programadorUSBtinyISP. Quienes estén armando su programador con componentes propios, deberán enfrentarse a la siguiente parte. Programar el AVR del programador usando otro programador. Esta fase puede ser la más tediosa. Si al inicio nos cuesta conseguir ese otro programador, después nos quebrará la paciencia usarlo con AVRDUDE. Si ya estás acostumbrado a usar otros programadores como el USBasp, entonces esto será pan comido. Puedes seguir el procedimiento totalmente ilustrado por la misma Limor Fried en la páginawww.ladyada.net/make/usbtinyisp/make.html de su web. Desafortunadamente Limor solo guía durante la primera etapa. Y lo que para muchos puede caer como baldazo de agua fría, nos pide que no busquemos ayuda sobre la grabación del firmware en su foro. ¿ ? Debe ser porque la respuesta involucra múltiples formas o para animarnos a comprar el kit, donde el AVR ya viene pre-programado. Uso del USBtinyISP Al conectar el programador el LED verde se enciende si su enumeración se llevó a cabo con éxito. El programador USBtinyISP puede ser controlado por AVRDUDE o desde Atmel Studio 6. Su nombre para ser invocado desde AVRDUDE es usbtiny. Por ejemplo, para programar la memoria FLASH de un ATmega324P con el archivo main.hex podríamos ejecutar: >avrdude –c usbtiny –p atmega324p –U flash:w:main.hex:i El LED rojo se enciende durante el proceso de grabación. La instalación de WinAVR trae consigo los drivers de libusb que usa AVRDUDE. Sin embargo, Limor sugiere usar los drivers modificados [por ella]. Hay dos versiones: el driver para Windows de 32 bits y el driver para Windows de 64 bits. Los programadores que pueden ser controlados por Atmel Studio 6 son los que utilizan los protocolos de programación estándar de Atmel; el STK500, por ejemplo. Pero ese no es el caso del USBtinyISP. En vez de implementar el protocolo STK500 en el firmware del microcontrolador, cosa que no hubiera sido posible debido a su poca memoria, de Limor Fried decidió escribir una aplicación que hace la conversión del protocolo en la computadora. Para decirlo burdamente, se trata de un parche que engaña a Atmel Studio 6 haciéndole creer que el USBtinyISP es un programador de protocolo STK500v2. Funcionando como programador STK500 el USBtinyISP tiene dos restricciones destacables respecto del original:
  • 60. No siempre puede programar el byte de calibración. Este byte permite ajustar la frecuencia del oscilador RC interno del AVR cuando se usa como reloj del sistema. Creo que nos es relevante puesto que lo habitual es usar el AVR con XTAL. Además la calibración del circuito RC interno también se puede hacer en tiempo de ejecución, es decir, podríamos añadir un par de líneas en el programa del microcontrolador para solucionar el problema. Sin duda la principal diferencia entre un programador original de Atmel y cualquiera de sus clones que trabajan emulando un puerto USB es la velocidad de programación. El USBtinyISPacepta los comandos para cambiar la velocidad de programación pero no los obedece. ElUSBtinyISP sigue con su tope de 1kB/s. Para que nuestro USBtinyISP emule un STK500 debemos seguir dos pasos: Instalar la aplicación com0com para generar dos puertos series virtuales en nuestra computadora. Este software es open source y lo puedes descargarla desdehttp://com0com.sourceforge.net. Luego debemos instalar el software puente USBtiny500 de Limor Fried. Programador AVR Doper La inexistencia de un periférico USB en los microcontroladores AVR de empaque DIP junto con la arquitectura y compatibilidad en el set de instrucciones han alentado a algunas personas a desarrollar librerías de USB software. Son librerías optimizadas a nivel ensamblador que manejan las rutinas mínimamente necesarias del protocolo USB. Tenemos por ejemplo, la librería de Igor Cesko explicada en la nota de aplicaciónAVR309 de Atmel. Otra librería, con mejor soporte y actualización, es VUSB (acrónimo de Virtual USB), de Christian Starkjohann. Es la misma librería que utilizó Thomas Fischl para su programador USBasp. IMAGINO que cuando Christian vio el trabajo de Thomas pensó “yo puedo hacerlo mejor” y decidió crear el programador AVR Doper. Programador AVR Doper en su circuito original.
  • 61. AVR Doper es un proyecto Open Source tanto en hardware y software. Muchas de sus características son también apreciables en otros programadores: se alimenta desde el puerto USB, posee buffers en las líneas de programación para programar dispositivos que trabajan con voltajes inferiores a 5V y hasta cuenta con un jumper como forma alternativa de reducir la frecuencia del reloj SCK (velocidad de programación) similar al USBasp. El firmware del programador AVR Doper está basado en el protocolo de programación STK500v2de Atmel y como tal puede programar todos los megaAVR y la mayoría de los tinyAVR. El protocolo original maneja las tres interfaces de programación: SPI, HVSP y HVPP, de los cuales AVR Doper acepta los dos primeros. Utiliza un conector de 10 pines para la programación SPI que, con algunas variaciones, todavía es compatible con el estándar de Atmel. Para la programación de alto voltaje dispone de dos zócalos para los [tiny] AVR de 8 y 14 pines. Esta interface HVSP también sale al exterior mediante un conector libre de 20 pines. Pero lo más importante –creo yo– es que, como todo programador STK500, puede ser controlado desde Atmel Studio 6. Adiós insufrible línea de comandos de AVRDUDE y a sus incompletas máscaras como SinaProg. Esto nos facilitará el trabajo ahorrándonos tiempo. ¡Wow! Hubiera conocido este programador antes de construir mi USBasp dirán algunos. La buena noticia para ellos es que pueden convertir su USBasp en un AVR Doper tan solo cambiándole el firmware. Claro que el circuito del USBasp no da para la programación de alto voltaje. Así que está funcionalidad no estará disponible. Para mí es una limitación inapreciable. Observa que si renunciamos a la programación HVSP, podemos deshacernos del circuito elevador de tensión, de los dos zócalos in-situ y de ese enorme conector de 20 pines, que es el mismo que usó Tobias Hammer en su programador HVProg. Él lo hizo justificadamente, pues empleaba todos los pines. Pero no entiendo por qué Christian Starkjohann le siguió el paso. Ni siquiera es un conector estándar. En fin, el caso es que al final nos quedará un circuito muy similar al del USBasp o del mismo USBtinyISP. Incluso podemos notar los tres jumpers que, de hecho, Christian los puso ex profeso por compatibilidad con el USBasp.
  • 62. Circuito original del programador AVR Doper (fuente: obdev.at). AVR Doper no es el primer programador que presentamos. Su descripción la hacemos asumiendo que ya conocemos los programadores tratados previamente en especial el USBasp. Quienes lo estudiaron comprenderán fácilmente el funcionamiento del AVR Doper. Quizá les sorprenda un poco encontrar dos señales de reloj en el circuito, pero también este punto ya lo explicamos anteriormente. La señal SCK es el reloj del bus SPI; es la que marca la velocidad de transferencia de datos entre el AVR programador y el AVR programado. La señal TSCK es el reloj de sistema del AVR programado (target, de ahí la T); es la que establece su velocidad de trabajo. En otros programadores no hay esta señal porque se asume que AVR target trabaja con su propio reloj, generalmente basado en un XTAL. Al igual que pasó con el programador USBasp, tras el éxito del AVR Doper muchas personas hicieron sus propias adaptaciones. A continuación, algunas de ellas.
  • 63. Las siguientes imágenes son del rediseño de Robert. Casi no hay diferencia con el original pero sí se nota que tiene los componentes mejor ordenados. El circuito es el mismo. Programador usbasp Programador AVR Doper con PCB mejorado por Robert. También hay quienes prefieren tener su programador en una caja pequeña. AVR tiny Doper es un rediseño hardware que prescinde de los zócalos de 8 y 14 pines pero como el AVR es de empaque SMD incorpora un conector ISP6PIN para la actualización del firmware. Parece que al autor tampoco le gustaba el gigantesco conector de 20 pines. Programador AVR tiny Doper SE de www.s-engel.de. Por supuesto, no faltan quienes modifican el programador quitándole la funcionalidad que no usarán. Domen Ipavec se deshizo de todos los componentes que permiten la programaciónHVSP y en el espacio ganado le añadió diodos dobles en casi todas las señales como protección ESD. Similar característica posee el programador AVRISP mkII. Ni los empaques SMD le ayudaron a darle un mejor aspecto a su placa.
  • 64. Programador AVR Doper adaptado por Domen Ipavec. En realidad tenemos hasta tres formas de construir un AVR Doper: (1) construirlo con su circuito original, (2) montarlo en el circuito metaboard y (3) implementarlo en el hardware de un USBasp. Puesto que la disposición de las líneas que van o salen del ATmega8 es diferente en cada caso, el autor ha escrito el firmware usando macros que permiten compilarlo para uno u otro circuito. Pero no te preocupes, no es necesario que nosotros compilemos el programa. Christian también ha publicado todos dos archivos hex, listos para grabar en el ATmega8. El programador AVR Doper ha sido corregido y mejorado a lo largo de tres años. La última actualización es de noviembre de 2008 y la puedes descargar desde su web o desde aquí. Uso del AVR Doper El programador AVR Doper posee un jumper USB HID que establece dos modos de operación. Este jumper se debe mover antes de conectar el programador a la computadora. Si el jumper USB HID está cerrado, el AVR Doper inicializará como un dispositivo HID. En este caso solo podremos usarlo desde AVRDUDE. Podemos invocarlo con stk500v2 o sus alias (avrispmkII o avrispv2). También será necesario especificar avrdoper en parámetro –P. Por ejemplo, para programar la flash de un ATmega168p con el programa main.hex podemos escribir. >avrdude –c stk500v2 –P avrdoper –p atmega168p –U flash:w:main.hex:i Si dejamos el jumper USB HID abierto, el AVR Doper trabajará en modo CDC. Ahora el programador aparecerá como un dispositivo conectado a un puerto serie COM virtual. Debemos especificar ese puerto COM al configurar el software programador. En la siguiente línea por ejemplo repetimos el ejercicio anterior considerando que se ha creado el puerto COM1: >avrdude –c stk500v2 –P com1 –p atmega168p –U flash:w:main.hex:i El trabajo desde Atmel Studio 6 se reduce a algunos clics.
  • 65. Debemos aclarar que según el autor, Christian Starkjohann, el modo CDC presenta algunas incongruencias con la especificación 1.1 del USB que pueden afectar el desempeño del programador o simplemente dejarlo irreconocible. Aunque la mayoría de las computadoras de ahora soportan las especificaciones 2.0 y hasta 3.0 del USB, Christian nos recomienda usarlo solo en modo HID. Programador HVProg Este programador fue hecho por Tobias Hammertomando como referencia el circuito de la placaSTK500. Su característica más distintiva y sobresaliente está en su nombre. No estoy bromeando. Las letras HV vienen de alto voltaje. Éste junto con el AVRminiProg son los únicos programadores open source que conozco con soporte para las interfaces de programaciónHVSP y HVSP. Comercialmente tenemos al programador AVR Dragon y las tarjetas de desarrollo STK500 y STK600. Naturalmente, aparte del alto voltaje HVProg maneja el modoSPI. Todas las interfaces están basadas en el protocolo STK500, de modo que además de AVRDUDE lo podemos controlar con Atmel Studio 6. La programación de alto voltaje, como dice Dean Camera, sirve para resucitar el AVR cuando por algún accidente hemos mal configurado los fuses RSTDISBL o SPIEN, quitándole la forma de ser reprogramado por la interface serial SPI o TPI de bajo voltaje. Claro que puede haber ocasiones donde adrede se programen esos fuses. Afortunadamente, a diferencia de otros microcontroladores como los PIC, en los AVR la programación de los fuses va separada de la programación de la memoria flash. En la práctica, grabar los fuses es una tarea muy esporádica. Por ejemplo, para experimentar con todas lasprácticas de cursomicros, puede bastar con grabar los fuses una vez. Luego ni los tocamos y solo nos dedicamos a trabajar con la memoria flash. De modo que las probabilidades de meter la pata son realmente muy bajas. Si trabajamos con los cuidados debidos quizá nunca tengamos que recurrir a un programador de alto voltaje. Pero si por algún motivo necesitamos uno, el HVProg puede ser una buena opción.
  • 66. Programador HVProg (versión 0.5) Arriba se muestra la versión más reciente del programador HVProg. Es un rediseño hecho porKlaus Leidinger. Al describir su hardware vamos a encontrar muchos aspectos desalentadores: Se conecta a la computadora por el puerto serie. Usa un MAX232 para la conversión deniveles RS232. Utiliza una fuente de alimentación de 15 V. En otros programadores como el AVR Doper la alimentación es de 5 V y generan el alto voltaje en el circuito. Para la programación SPI usa un conector ISP de 10 pines estándar. Para las programaciones HVSP y HVPP usa un conector libre de 20 pines. Usa un jumper para habilitar la programación de alto voltaje. Circuito del programador HVProg (versión 0.5) La primera versión del HVProg utilizaba 5 conectores para las interfaces de programación: los 2 conectores estándar ISP6PIN e ISP10PIN para la programación SPI; un conector de 8 pines para la programación HVSP y otros 2
  • 67. conectores de 10 pines para la programación HVPP. No sé si te parezcan muchos pero a mí me gustaba. Al menos están el conector ISP de 6 pines que recomienda Atmel y los dos conectores de HVPP son compatibles con los que lleva la tarjeta de desarrollo STK500. Al parecer Crazy Horse (no sé cómo se llama en realidad) pensaba lo mismo que yo. Él tomó como referencia el primer diseño y lo modificó usando componentes SMD. Aparte de los 5 conectores descritos le añadió otro ISP6PIN para la actualización del firmware. Programador HVProg adaptado por Crazy Horse.
  • 68. Circuito del programador HVProg (versión de Crazy Horse) Programador AVRminiProg El programador AVRISP mkII es la siguiente generación de los programadores que usan el protocolo del de la tarjeta STK500. Se conecta al puerto USB de la computadora directamente (sin conversores USB-UART) y puede programar todos los AVR de 8 bits por las tres interfaces SPI, TPI yPDI. Como su protocolo también está disponible en la web de Atmel, no tardaron en aparecer sus denominados clones. Hay dos tipos de clones del programador AVRISP mkII: los que usan un AVR con USB hardware y los que usan un AVR con USB emulado a nivel software. Los AVR con USB incorporado siempre serán mejor, permitiendo igualar la performance del AVRISP mkII original, pero como aún no vienen en empaque DIP muchos prefieren el segundo grupo. Nosotros los veremos después. El programador AVRminiProg fue desarrollado por Simon Qian usando también la librería de USB virtual V-USB de Christian Starkjohann. Más que un clon del AVRISP mkII también puede emular a los programadores JTAGICE mkII y AVR Dragon. La compatibilidad de su firmware le permite comunicarse con Atmel Studio 6. A juzgar por todo eso es sencillamente fenomenal, el mejor programador open source que se puede encontrar. Siendo tan prometedor, sin embargo, no se puede entender por qué su autor olvidó su web http://www.simonqian.com. En realidad el programador de Simon Qian tiene más de una versión. AVRminiProg como su nombre revela es la versión más limitada. Solo soporta las interfaces de programación SPI yJTAG. Su circuito es una ampliación del programador USBasp de modo que su firmware se puede recompilar para él. Esto es podemos convertir un programador USBasp en un AVRminiProg tan solo con cambiar de firmware. Programador AVRminiProg. A estas alturas del capítulo, después de estudiar los programadores USBasp, USBtinyISP y AVR Doper, ya resultaría hasta tedioso estar analizando el circuito del programador AVRminiProg. Creo que lo único que cambia son los nombres y valores de algunos elementos.
  • 69. Circuito del programador AVRminiProg. La Tarjeta de Desarrollo STK500 y el Programador AVRISP Antes que un simple programador STK500 es la tarjeta de desarrollo de Atmel destinada a sustinyAVR y megaAVR de empaque DIP, o quizá relegada a ellos. Se conecta a la computadora por el puerto serie, tiene zócalos hasta para los AVR más antiguos, botones, diodos LED, conectores DIL para todos los puertos y, el punto que nos trae aquí, lleva incorporado un programador
  • 70. Tarjeta de desarrollo STK500 (fuente: atmel.com) En la siguiente imagen se distinguen las principales partes de la tarjeta STK500. Partes de la tarjeta de desarrollo STK500. No he etiquetado los zócalos porque su identificación es inmediata ¿o no? Obviamente allí se colocan los AVR de prueba. Parece que hubiera duplicados pero no. Sucede que no todos los AVR son compatibles en pines por más que sean del mismo tamaño. Por ejemplo, el patillaje de un ATmegaXX4 difiere de un AT90S8515, siendo ambos de 40 pines (este último es un modelo antiguo y debe ir en el zócalo inferior). También sus
  • 71. colores de fondo parecen resultado del mal gusto pero tienen un significado que veremos luego. Cada puerto de los AVR tiene como a lo sumo 8 pines. Todos ellos van unidos a los 5 conectores de puertos. Con los AVR de los zócalos pequeños, por supuesto, quedarán conectores libres. Los 8 botones o pulsadores alineados al costado izquierdo están enlazados al conector etiquetado como Botones. Mediante él podemos usar un conector para unir los 8 botones a cualquiera de los puertos del AVR. Del mismo modo, los 8 diodos LED ubicados al lado de cada botón están unidos al conector LEDs. Así que también podemos acoplarlos a cualquiera de los puertos del AVR. En la siguiente imagen por ejemplo, los 8 botones se unen al puerto A y los diodos LED, al puerto D. si queremos conexiones aisladas tendríamos que usar conectores de un cable. Conexión de los puertos a los botones y LEDs (fuente: atmel.com). El circuito de Alimentación se basa en un regulador que puede recibir entre 10V y 15V por su conector externo. A su salida puede entregar hasta 500mA. El bloque etiquetado con USART del AVR enlaza los pines TXD y RXD de los AVR que los tienen al conector DB9. El circuito, que ya incluye la conversión de niveles entre RS232 y TTL, permite el intercambio de datos entre el AVR y la computadora, para control o data logging. Y así llegamos al bloque de nuestro mayor interés: el Programador. Los principales elementos de su circuito son dos microcontroladores AVR. El primero se encarga de administrar las interfaces y el protocolo de programación mientras que el segundo le asiste en el Boot Loader. El segundo se conecta a la computadora y se encarga de
  • 72. revisar la versión del firmware en el primer AVR para actualizarlo si fuera necesario. La actualización es un proceso que se realiza automáticamente desde el entorno de Atmel Studio 6. De ese modo el programador estará al día para reconocer los AVR más recientes. El firmware actual es de versión 2.x, denominadoSTK500v2, que tanto se menciona en este capítulo. El programador de la tarjeta STK500 soporta tres interfaces de programación: SPI, HVSP y HVPP. Como sabemos, estas interfaces utilizan diferentes pines del AVR (puedes regresar a revisar si deseas). Por eso existen varios conectores del programador en el bloque enmarcado de azul. Las programaciones de alto voltaje son recursos auxiliares que raramente usaremos a diferencia de la interface SPI. En la tarjeta hay dos conectores para este medio: uno de 10 pines ISP10PINsolo para programar dispositivos fuera de la tarjeta y el otro de 6 pines llamado ISP6PIN para programar los AVR de los zócalos, además de los externos, obviamente. En la imagen de la izquierda el conector ISP6PIN está dirigido al conector de base roja para programar cualquier AVR que esté en un zócalo de base roja también. Si lo conectamos al conector de base azul programaremos el AVR que esté en algún zócalo azul, y lo mismo con el conector de base verde. En la imagen derecha se muestra cómo tomar el conector ISP6PIN para programar un AVR de un circuito externo. Programador usbasp Uso del conector ISP6PIN para programar AVRs en la tarjeta y fuera de ella. Pregunta: ¿qué sucede si a la tarjeta STK500 le quitamos todos los elementos dejando solamente el circuito del programador con los conectores ISP6PIN e ISP10PIN? Obtenemos algo muy cercano al programador AVRISP.
  • 73. Programador AVRISP Mature (fuente: atmel.com). Ya sé. Imagino que estarás pensando dónde está el otro conector. Pues está allí adentro. Debe ser que dos cables colgando no se ven bien. Por eso solo aparece uno a la vez. Si queremos cambiarlo debemos abrir la caja y poner otro cable al otro conector. La siguiente figura nos ilustra esta maniobra. Interior del programador AVRISP Mature (fuente: atmel.com). Programador AVRISP MkII Hay demasiada información en este capítulo que podría terminar consumiendo tiempo de oro de algunos lectores mientras se deciden cuál comprar o construir. Si aceptan un consejo, yo les sugiero comprar el programador AVRISP mkII, por solo $35. Es lo mejor que se puede conseguir a bajo precio. Permite programar todos los AVR de 8 bits, entre tinyAVR, megaAVR y XMEGA, por sus principales interfaces. Tendrá la limitación de no manejar la programación de alto voltaje, pero como ese recurso es prescindible en la gran mayoría de los casos, el AVRISP mkII sigue siendo la herramienta a elegir.
  • 74. Programador AVRISP mkII original. Éstas son las principales características con que Atmel nos lo presenta: Es compatible con Atmel Studio 6. Suporta las interfaces de programación SPI, PDI y TPI. Se alimenta del puerto USB, no necesita de alimentación externa. Ofrece actualización automática para reconocer los AVR más recientes. Puede programar AVRs que trabajan a tensiones entre 1.6V y 5.5V. Permite regular la velocidad de programación SPI desde 50Hz a 8MHz. Permite programar las memorias FLASH y EEPROM del AVR. Permite programar los fuses y locks bits del AVR. Es compatible con estándar USB 2.0 en modo full speed o 12 Mbps. Ofrece protección de corto circuito. Ahora bien, si antes que comprar prefieres construir tus propias herramientas para ahorrar algo de dinero o para aprender hasta de esa experiencia, puedes elegir entre uno de los denominados clones del AVRISP mkII. Hasta antes de la aparición de la librería LUFA, descrita después, todos los clones de los programadores con puerto USB de Atmel usaban AVRs que emulaban el puerto USB a nivel software. Tal es el caso por ejemplo del programador AVRminiProg o del clon hecho por Gerhard Schmidt en base a la herramienta USB AVR Lab de Christian Ulrich. Son programadores que usan componentes discretos de fácil adquisición para emprender su más pronta construcción, aunque presentan notables restricciones respecto del original.
  • 75. Clon del programador AVRISP mkII de Gerhard Schmidt. Circuito del clon del programador AVRISP mkII de Gerhard Schmidt. La historia empieza a cambiar desde que Dean Camera escribió su cada vez más famoso proyecto LUFA, consistente en una librería USB para todos los microcontroladores AVR (de 8 y de 32 bits) con módulo USB hardware incorporado. La
  • 76. librería abarca las operaciones del AVR en modo Host, Device así como Dual role. También soporta las clases CDC, HID, Mass Storage y Audio. El proyecto LUFA incluye varios ejemplos demo para probar la funcionalidad de la librería. Uno de esos demos es un clon del programador AVR ISP MkII de Atmel. Este clon no tiene un hardware especifico. Dean escribió el firmware de forma genérica de modo que cualquiera pudiera adaptarlo a su propio hardware basándose en la configuración de pines del programa. El proyecto LUFA es open source y está liberado bajo licencia MIT que permite su uso para fines no comerciales y también comerciales. Esto ha sido aprovechado por muchas empresas para vender sus propias versiones del programador AVR ISP MkII Clone. Por eso si buscamos en Internet vamos a encontrar muchísimas variantes. Las versiones comerciales (que cuestan unos $10 menos que el original) suelen ofrecer el archivo HEX como firmware de actualización aunque no el código fuente modificado y menos el circuito. Programador avr doper Clones del programador AVRISP mkII ofertadas por Brave kit y Olimex. Puesto que estos programadores se basan en un AVR con USB hardware, su performance puede llegar a igualar al original en sus principales aspectos. Así que no vamos a citar sus características para no repetir la lista presentada arriba. Más bien vamos a dedicarnos a describir las diferencias, que han ayudado a simplificar el diseño para reducir el costo y por ende su construcción. Usa tres conectores para cada una de las interfaces de programación SPI, PDI y TPI.I. El original usa un solo conector de 6 pines para todos. Dependiendo de la configuración del firmware y de los drivers de la computadora, algunos clones pueden tener dificultades en ser reconocidos por Atmel Studio 6.
  • 77. Para programar los AVR que trabajan a tensiones menores a 5V, el circuito target debe tener su propia alimentación y pasar algo de ella al regulador que usa el programador. En el AVRISP mkII original el circuito target también trabaja con su propia alimentación pero permanece siempre aislada del programador. No sé si eso sea ventaja o inconveniente. Lleva diferentes LEDs para indicar las operaciones y el estado del programador. El original lleva un LED multicolor para todo eso, aparte de un LED verde interno que refleja el tráfico USB. No cuenta con protección de corto circuito como el original. Las diferencias mostradas no son propias del clon del AVRISP mkII, pues como dijimos, Dean Camera solo escribió el firmware. Dependerá del hardware que le dotemos mejorar su funcionalidad. Sin embargo esa parece una tarea innecesaria. Con el circuito mínimo basta. Pero, ¿cuál es ése? Los clones mostrados arriba son comerciales así que difícilmente se develarán sus circuitos. A decir verdad, no es difícil descubrirlo, como tampoco es difícil diseñar un circuito personal. Pero como imagino que debes pensar que no es éste el momento de ponerse retos ni somos los primeros interesados, vamos a valernos de algunos diseños experimentales que se pueden encontrar en Internet. .De los circuitos que he visto, me quedo con el USBTiny-MkII SLIM, de P. Kisielewski. Él se basó en el diseño USBTiny-MkII de Tom Light quien es por así decirlo el “partner oficial” de Dean Camera encargado de poner a prueba su firmware. Así que andamos por buen camino. El diseño se ve compacto. Da ganas de construirlo ya. Desilusiónate si estás pensando en una versión de agujero pasante, pues el AVR que lleva no viene en empaque DIP. El programador USBTiny-MkII SLIM. P. Kisielewski trató inicialmente de usar el transceiver MAX3002 por su mayor disponibilidad similar al circuito del USBTiny MkII PL pero descubrió que provocaba algunos errores. Así que retomó el chip GTL2003 como puente de interface entre el programador y los AVR que operan en todo el rango 1.8V y 5.5V.
  • 78. Circuito del programador USBTiny-MkII SLIM. Programador AVR910 Éste no es el nombre del programador propiamente hablando. Se le llama así porque aparece en la nota de aplicación AVR910 de Atmel. En ese documento y en su firmware aparece simplemente como programador de AVR ISP. No lo denominamos así para no confundirlo con el programador AVRISP, que usa el protocolo STK500. El programador AVR910 usa un protocolo mucho más simple. Poco robusto dicen algunos. El programador es proveído por Atmel como herramienta de bajo costo. Abajo tenemos una imagen de su circuito original. Como se ve, utiliza la interface de programación SPI, se conecta a la computadora por el puerto serie y usa arreglos con transistores en vez de un MAX232 (o similar) para convertir los niveles RS232 a TTL. El firmware del AT90S1200 también es de disposición pública.
  • 79. Circuito original del programador AVR910 (fuente: atmel.com). Por lo visto parece ser un diseño antiguo, dirán algunos. Y es cierto. Es un programador algo desatendido por Atmel. Pese a que la nota de aplicación AVR910 fue actualizada a 2008, el firmware del AVR se quedó con la revisión del año 2000. Es muy difícil por tanto utilizarlo como está para que reconozca los AVR actuales como los ATmegaXX8 y ATmegaXX4. Sin embargo, sigue siendo muy útil estudiar el documento AVR910. Con lo aprendido mucha gente ha mejorado el firmware del AVR por su cuenta y también han adaptado el circuito para conectarlo al puerto USB de la computadora. Abajo tenemos una placa reeditada para el ATtiny2313 por Vassilis Serasidis que usa el firmware de Klaus Leidinger. Tampoco es que sea un ejemplo de diseño moderno. Se puede ver que el conversor USB-UART es el transceiver FT232BM, un chip viejo que usaba un XTAL y se valía de una memoria EEPROM externa para almacenar algunos datos del protocolo USB. Ahora se usa el FT232RL.
  • 80. Programador AVR910 con conversor USB-UART. Circuito del programador AVR910 con conversor USB-UART. El software recomendado para controlar este programador es AVR-OSP II.
  • 81. Entono del programa AVR-OSP II. Y, por supuesto, también podemos usar AVRDUDE. Por ejemplo. >avrdude –c svr910 –P com1 –p atmega644p –U flash:w:main.hex:i Programador ArduinoISP Con el Arduino se han hecho casi todos los proyectos imaginables: con displays touch screen, con comunicaciones inalámbricas, con motores BLDC, con sensores de todos los tipos, etc., etc., Es válido entonces que nos preguntemos ¿no habrá alguien hecho un programador de AVRs?
  • 82. Tarjeta del Arduino. Sí. Y más que eso. El proyecto se encuentra como parte de los ejemplos que el Arduino trae por defecto. Así que ni siquiera tendremos que buscarlo afuera. Simplemente vamos al menú Archivo Ejemplos ArduinoISP, luego presionamos el botón Upload y esperamos a que termine de cargar el firmware, tal como se indica abajo. Programador arduino isp Cargando el firmware de ArduinoISP en la tarjeta del Arduino. Y eso es todo. Ahora nuestra tarjeta de Arduino ya es un programador de AVR. Pero, ¿cómo lo usamos? Como dice el comentario del sketch, este proyecto emula el programador AVRISP con el protocoloSTK500 en su version1.x. Es una versión algo antigua que Atmel Studio 6 tendrá problemas en reconocer. Si vamos al menú Herramientas Programadores en el entorno de Arduino, encontraremos algunos programadores
  • 83. entre ellos nuestro recién creado ArduinoISP. Esta opción nos permite usarlo pero solo para grabar el archivo hex del Boot Loader. Podríamos engañar al Arduino reemplazando ese archivo por el archivo hex de nuestras prácticas, pero tampoco es la mejor forma. Es preferible hacerlo desde AVRDUDE directamente y no con el AVRDUDE que trae Arduino sino con el que viene con WinAVR. El sobrenombre de nuestro ArduinoISP en AVRDUDE es avrisp. En teoría stk500 también debería funcionar pero vaya uno a comprobarlo. Un ejemplo de uso sería así: >avrdude –c svrisp –P com1 –p atmega644p –U flash:w:main.hex:i Programador ArduinoISP Conector ISP de 6 pines del megaAVR del Arduino. Según el firmware del ArduinoISP, las líneas de programación SPI son los mismos pines MOSI,MISO, SCK y SS del bus SPI. El pin SS desempeña la función de la línea RESET. En el circuito parcial del Arduino mostrado arriba se ve que corresponden a los pines 10, 11, 12 y 13 de su tarjeta. Los AVR grandes tienen más pines de alimentación. En esos casos es recomendable conectarlos todos. Si hay un pin AVCC también se debe conectar a VCC. Por otro lado, el XTAL solo es necesario si el AVR trabaja con un reloj externo. Sobre esto puedes leer más información en la sección Interface de Programación SPI. Compiladores para microcontroladores AVR Un microcontrolador ejecuta el programa que lleva grabado en su memoria FLASH. Ese programa está guardado allí en formato de código máquina (a base de unos y ceros) y nosotros lo podemos cambiar por otro programa que queramos (a eso se llama grabar
  • 84. el microcontrolador). Pero antes tenemos que crear ese programa. Lo podemos hacer de tres formas. Escribiendo el código máquina directamente con unos y ceros o sus equivalentes en números hexadecimales. Es un método factible pero que muy pocos se atreverían a intentar. El programa escrito puede ser un simple archivo de texto (con extensión .hex) que luego será decodificado por el software grabador de microcontroladores. Un programa en código máquina luce más o menos así. :020000020000FC :1000000012E01EBF1FEF1DBF36D0F0E0E8E15BD06D :10001000F0E0E8E458D0FFCF0D0D0A207777772E77 :10002000637572736F6D6963726F732E636F6D208A :100030000D0A203D3D3D3D3D3D3D3D3D3D3D3D3D70 :100040003D3D3D3D3D3D20000D0D0A20546573743E :10005000696E67207468652054574920696E74651D :100060007266616365200D0A00000D48692074689E :10007000657265210000B0E0A0EC1C9112601C9339 :100080001127B0E0A5EC1C9318E6B0E0A4EC1C939B :1000900016E0B0E0A2EC1C9318E1B0E0A1EC1C93D8 :1000A0000895B0E0A0EC1C9115FFFBCFB0E0A6ECEA :1000B0000C930895B0E0A0EC1C9117FFFBCFB0E0CB :1000C000A6EC0C9108950591002311F0EADFFBCF17 :1000D0000895FF93EF93F0E0ECE03197F1F7EF91A3 :1000E000FF910895000000000000089508953AEF80 :1000F00011D03AEF0FD008953AEF0CC034E60AC0A1 :100100003EE108C030E106C035E004C032E002C084 :1001100031E000C0FF93EF93F1E0EAE43197F1F7AB :0A012000EF91FF913A95B1F70895B1 :00000001FF
  • 85. Escribiendo el programa en código ensamblador. El ensamblador es el lenguaje más elemental de los microcontroladores y muchos aún lo usamos como recurso auxiliar. Sin embargo, sigue siendo muy engorroso como método de desarrollo ya que si queremos, por ejemplo, que el microcontrolador ejecute una simple división de números puede tomar cerca de 100 líneas de código ensamblador. El programa escrito en este lenguaje sigue siendo un archivo de texto (esta vez con extensión .asm) que luego será convertido en código máquina por un software de computadora llamado también ensamblador (AVRASM2en el caso de los microcontroladores AVR). A continuación se lista un ejemplo de programa en ensamblador. ;////////////////////////////////////////////////////////////// ;/// FileName: main.asm ;/// Author: Shawn Jhonson ;/// Copyright: (C) 2008-2008 Shawn Jhonson ;/// Date: 04 December 2008 ;////////////////////////////////////////////////////////////// .nolist .include "m48Pdef.inc" .list .def arg = R16 ; argument for functions .def tmp = R17 ; for temporal operations .def status = R18 ; .def del = R19 ; .def AddressHigh = R27 ; X Pointer .def AddressLow = R26 ; ;////////////////////////////////////////////////////////////// ; Code program ;////////////////////////////////////////////////////////////// .cseg
  • 86. .org0x0000; Reset vector address Start: ; Setup Stack Pointer ldi tmp,high(RAMEND) out SPH, tmp ldi tmp,low(RAMEND) out SPL, tmp rcall usart_init ldi ZH,high(2*StrTest) ldi ZL,low(2*StrTest) rcall puts ldi ZH,high(2*StrType) ldi ZL,low(2*StrType) rcall puts ; Initialize the usart. Main: rjmp Main ;////////////////////////////////////////////////////////////////////// ; Strings. Null ended strings StrTest: .db0x0D,0x0D,0x0A, " www.cursomicros.com " .db0x0D,0x0A, " =================== ",0x00 StrType: .db0x0D,0x0D,0x0A, " Testing the TWI int",0x0D,0x0A,0x00 StrHi: .db0x0D, "Hi there!",0x00
  • 87. ;////////////////////////////////////////////////////////////////////// .include "usart.asm" .include "delays.asm" .exit Escribiendo el programa en un lenguaje de alto nivel. Los dos lenguajes más usados son el C y el Basic. Con menos frecuencia se aprecia el Pascal. En este caso el código a escribir es más comprensible para nosotros, los humanos. Por ejemplo, para una división es suficiente con escribir una sentencia como a = b/c. El programa escrito en este lenguaje también es un archivo de texto (ahora con extensión .c, .bas o .pas) que luego será convertido en código ensamblador por el compilador y luego convertido de código ensamblador en código maquina por el ensamblador del compilador. Abajo tenemos un ejemplo de un programa escrito en lenguaje C. /******************************************************************** * FileName: main.c * Purpose: Comunicación básica por USART0 * Processor: ATmel AVR con módulo TWI * Compiler: AVR IAR C y AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. *******************************************************************/ #include "avr_compiler.h" #include "usart.h" int main(void) { char name[20];
  • 88. usart_init();// Inicializar USART0 @ 9600-8N1 puts("nr www.cursomicros.com "); puts("nr =================== nr"); puts("nr - ¿Cuál es tu nombre?... nr - "); scanf("%s", name); printf(" - Gusto en conocerte, %s", name); while(1); } Los compiladores son las herramientas de programación mas potentes que existen. En resumen, el compilador traduce el programa escrito en su lenguaje (C, Basic, Pascal u otro) en código ensamblador y luego generará los archivos de depuración (*.dbg, *.cof, *.d31, etc.) necesarios y de ejecución (*.hex). Al trabajar con un compilador el programador ya no tiene que preocuparse (o lo hace muy poco) por los vericuetos del hardware ni por el ensamblador nativo de cada microcontrolador. Esto simplifica de manera asombrosa el desarrollo de proyectos. El compilador convierte el código fuente en el código máquina de cada microcontrolador sin importar mucho cuál sea. Por ejemplo, un código escrito para un ATmega128P podría ser fácilmente compilado para un ATxmega64A3U u otro y viceversa. Incluso es posible adaptar el código para un microcontrolador de otra marca, por ejemplo, de Freescale o Microchip. Eso se llama portabilidad. Conocer un lenguaje de programación como el C es un mundo aparte y es raro que una persona trate de aprenderlo al mismo tiempo que va conociendo el microcontrolador. En cursomicros se ofrece un capítulo entero sobre el lenguaje C aunque puede no ser suficiente dada su complejidad comparado con lenguajes como el Basic. A continuación se describen brevemente los principales compiladores para los microcontroladores AVR. Para conocer más de su uso se presentan posteriormente los tutoriales respectivos. El Compilador CodeVisionAVR El Compilador AVR IAR C
  • 89. El Compilador AVR GCC El Compilador Bascom AVR El Compilador ImageCraft C El Compilador mikroC for AVR El Compilador CodeVisionAVR CodeVisionAVR es un compilador desarrollado por Pavel Haiduc para los microcontroladores AVR de 8 bits, desde los tinyAVR hasta los XMEGA. Su principal ventaja es que provee librerías integradas para controlar sus periféricos internos y también dispositivos externos como LCDs, GLCDs, RTCs, sensores de temperatura, memorias SD, etc. En este sentido se le podría comparar con los compiladores C de CCS o Mikroe para los PICmicro. Como lo veremos luego,CodeVisionAVR es el compilador C para los AVR más fácil de usar, sin embargo, no llega a igualar la eficiencia de los compiladores AVR IAR C o AVR GCC. Luego podremos comprobar algo de esta diferencia. La versión de evaluación de CodeVisionAVR permite usarlo en casi toda su funcionalidad para fines no comerciales. Tiene ciertas limitaciones con algunas de sus librerías (casi ni se nota) y no compila programas que superen los 4 kbytes de código ejecutable. Sin embargo, es suficiente para la mayoría de los programas como las prácticas de cursomicros.com. Así que la puedes descargar desde su web www.hpinfotech.ro/html/download.htm o, más directo, haciendo clic aquí. Si al final te convences de su performance puedes descargar la versión comercial y tendrás que comprar la clave de activación. El compilador CodeVisionAVR no tiene un simulador del programa creado. En su lugar genera un buen archivo COFF que puede ser utilizado por otros programas como Atmel Studio 6 o Proteus. De hecho, si a veces uso este compilador es por la cómoda depuración del código fuente desde Proteus. Yo hubiera querido que todas las programas de cursomicros.com también pudieran ser compilados directamente con CodeVisionAVR como sucede con los compiladores AVR IAR C y AVR GCC pero lamentablemente ciertas divergencias de sintaxis de CodeVisionAVR lo hacían complicado y le quitaban la claridad y sencillez que he querido hacer prevalecer en todos los programas del curso.
  • 90. Entorno de trabajo del compilador CodeVisionAVR. El Compilador AVR IAR C AVR IAR C y AVR GCC son de lejos los mejores compiladores para los AVR. Entre ellos hay ligeras diferencias. Hay algunos aspectos en que un compilador saca cierta ventaja y otros aspectos en que el otro trabaja mejor. Si nos pusiéramos a compararlos punto a punto, estoy casi seguro de que el veredicto final sería… un empate. En cursomicros.com se da preferencia a AVR GCC pero todos los programas están escritos para ser compilados también por AVR IAR C. Esto se consigue gracias al archivo proporcionado por Atmel avr_compiler.h, al cual le añadí pequeñas macros. No todas las prácticas están compiladas en AVR IAR C pero los programas que tomé al
  • 91. azar compilaron perfectamente a la primera y sin errores. Bueno, a decir verdad tuve un traspié en un programa que utilizaba la función itoa, ya que dicha función no es parte del ANSI C y como estricto seguidor de este estándar, AVR IAR Cno la tiene implementada. Fuera de esa anécdota no tuve ningún inconveniente. El entorno de desarrollo de AVR IAR C se ve bastante sencillo para la potencia que ostenta el compilador. Se llama IAR Embedded Workbench y también sirve de plataforma para los otros compiladores de la empresa IAR Systems. Si bien es cierto que AVR GCC se integra a Atmel Studio 6 para aprovechar sus utilidades para las herramientas hardware de Atmel , el entorno de IAR cuenta con sus propias armas de programación, simulación y depuración, incluyendo los drivers para AVR ICE200, AVR JTAG ICE, AVR JTAGICE mkII, AVR Dragon y AVR ONE! Puedes ver una breve descripción de estas herramientas en la presentación de Atmel Studio 6.
  • 92. Entorno de desarrollo del compilador AVR IAR C. El Compilador AVR GCC Como su nombre lo revela, AVR GCC es un software derivado del compilador libre GCC (Gnu C Compiler). Tiene por tanto todo el respaldo de la comunidad Open Source. Mucha gente tiene la idea de que el software libre, por más bueno que sea, nunca llegará a superar a los productos comerciales, además de la falta de soporte técnico. Bueno, quizá eso sea cierto con algunos programas como el conocido compilador SDCC donde muchos programadores pasan más tiempo descubriendo que el bug es del compilador y no de su firmware. SDCC es un compilador de relativa
  • 93. juventud destinado a múltiples microcontroladores, incluyendo los AVR, y tal vez esos sean su principal punto débil. Ya sabes, "el que mucho abarca poco aprieta". Por fortuna, el compilador AVR GCC ya está lo suficientemente evolucionado y corregido. Es uno de los pocos programas que supera a sus contrapartes comerciales como CodeVisionAVR, ImageCraftC, MikroC para AVR, Bascom AVR y otros. El único compilador que le puede hacer frente es AVR IAR C. Éstas, por supuesto, son apreciaciones personales basadas también en mi experiencia personal. Me gusta su velocidad de compilación y su capacidad de generar el código más eficiente. No tiene bugs y todos mis programas compilan limpiamente. En el pasado el principal inconveniente del compilador AVR GCC era que no tenía un IDE propio. Los programadores debían valerse de otros entornos como CodeBlocks o Eclipse. Pero con el paso del tiempo el AVR Studio de Atmel lo fue incorporando cada vez más hasta convertirlo en la actualidad en su compilador casi oficial para sus microcontroladores AVR, todo gracias a su prestigio. Ahora ya no es necesario buscar el compilador AVR GCC por separado, pues AVR Studio 5 y Atmel Studio 6 lo incluyen en su paquete de instalación. De hecho, incluyen una versión del compilador que es mejor (?) que la disponible yendo a la web de AVR GCC para Windows (WinAVR) http://winavr.sourceforge.net. Se van entendiendo, por tanto, las razones por que AVR GCC sea el principal compilador utilizado en todas las prácticas de cursomicros.com. La plataforma elegida es desde luego Atmel Studio 6, así que en ese capítulo se explica todo sobre cómo trabajar con AVR GCC en versión Windows (WinAVR).
  • 94. El compilador AVR GCC (WinAVR) corriendo en Atmel Studio 6. El Compilador Bascom AVR Bascom AVR es el compilador Basic para los microcontroladores AVR que sobresale entre los de su clase, aunque, la verdad, no tiene mucha competencia. Soporta los microcontroladores AVR de 8 bits: los tinyAVR, los megaAVR y los XMEGA. La sintaxis
  • 95. de sus funciones tiene ciertas diferencias respecto de otros compiladores Basic como MBasic o Proton Plus, pero en general es fácil de asimilar. Bascom AVR ofrece aceptables librerías, incorpora un sencillo simulador, un terminal serial y un muy buen software programador de AVRs que soporta casi todos dispositivos conocidos como el USBasp, USB-ISP, PROGGY, FLIP, AVR ISP mkII (con la interface ISP), KamProg for AVR, STK600, ARDUINO, etc., etc. Su autor, Mark Alberts, desarrolló Bascom AVR como una adaptación de su compilador Basic para los microcontroladores 8051. Actualmente lo mantiene desde su empresa MCS electronics y ofrece un versión demo que la puedes descargar desde de aquí. Es completamente funcional y solo tiene el limitante de generar códigos ejecutables de hasta 4 kbytes.
  • 96. Uso del Compilador CodeVisionAVR CodeVisionAVR es un compilador desarrollado por Pavel Haiduc para los AVR de 8 bits, desde los tinyAVR hasta los XMEGA. Su principal ventaja es que provee librerías integradas para controlar sus recursos internos y también dispositivos externos como LCDs, GLCDs, RTCs, sensores de temperatura, etc. En este sentido se le podría comparar con los compiladores C de CCS o Mikroe para los PICmicro. Como lo comprobaremos enseguida, CodeVisionAVR es el compilador C para los AVR más fácil de usar, sin embargo, no llega a igualar la eficacia de los compiladores IAR AVR o AVR GCC. También podremos comprobar algo de esta diferencia en la siguiente práctica. La versión de evaluación de CodeVisionAVR permite usarlo en casi toda su funcionalidad para fines no comerciales. Tiene ciertas limitaciones con algunas de sus librerías (casi ni se nota) y no compila programas que superen los 4 kbytes de código ejecutable. Para la presente práctica nos bastará esta versión, así que la puedes descargar desde www.hpinfotech.ro/html/download.htmo haciendo clic aquí. Si al final te convences de su performance puedes descargar la versión comercial y tendrás que comprar la clave de activación. Instalación de CodeVisionAVR Su instalación no requiere ninguna salvedad. Es el clásico procedimiento de Next, Next,…
  • 97. Instalación de CodeVisionAVR. Creación de un Proyecto Al iniciar CodeVisionAVR por primera vez nos aparece un ide como el de la siguiente figura.
  • 98. Entorno de desarrollo IDE de CodeVisionAVR. Muchos de los paneles mostrados pueden ser útiles para los principiantes. Nosotros las cerramos luego. Por el momento las dejaremos como están y empezaremos sin más por crear nuestro proyecto. Vamos al menú File New y escogemos la opción Project.
  • 99. Luego nos surge una ventanita si queremos usar el asistente de creación de proyectos CodeWizardAVR. Personalmente me aburre usar este asistente, aunque al inicio a todos nos intriga saber qué cosas nos ofrece. Así que esta vez lo tomaremos haciendo clic en Yes. Luego podrás recorrer por tu cuenta el camino al que lleva la opción No. Naturalmente tomamos la opción de los megaAVR. A continuación nos aparece la ventana donde podemos configurar los recursos del microcontrolador. Empezamos por la pestaña Chip para seleccionar el AVR y establecer su frecuencia de operación en Clock, tal como se indica en la siguiente figura.
  • 100. La opción Crystal Oscillator Divider generará el código para que el prescaler del reloj del sistema lo divida entre alguno de los valores allí indicados (desde 1 hasta 256). Por ahora no nos interesa porque de todos modos borraremos dicho código, asumiendo que configuramos ese prescaler con el fuse CKDIV8.
  • 101. Si está activada la opción Check Reset source el asistente generará el código que comprueba el origen de cada reset del AVR. Por el momento tampoco nos interesa esto. La opción Program Type debe quedar en Application, a menos que estemos desarrollando un programa con Boot Loader. Ahora pasamos a la pestaña USART0 ya que vamos a utilizar el puerto serie. Allí activamos las casillas para habilitar los módulos del Transmisor y Receptor porque los mensajes serán de ida y vuelta. No utilizaremos las interrupciones del USART. Las demás opciones las dejamos con su valor por defecto, como ve en la siguiente figura. Si deseas saber lo que significan estos parámetros puedes revisar el estándar RS232.
  • 102. El USART es el único periférico de comunicación que utilizaremos en esta práctica, así que ya podemos terminar con esta etapa y presionamos el botón Generate program, save and exit, como se indica abajo.
  • 103. Enseguida se nos presentan tres ventanas para nombrar los archivos del proyecto. Al archivo de código fuente principal yo siempre le llamo main.c. Previamente debemos ubicar una carpeta para estos archivos. Los otros archivos corresponden al proyecto y al asistente que estamos usando. Este proyecto se llama ledflasher3 así que le puse ese nombre a ambos archivos (no tienen que ser iguales).
  • 105. Por fin llegamos a entorno para editar el código en el editor que apenas se vislumbra. Por eso vamos a cerrar los paneles indicados en la siguiente figura. Si luego te interesa utilizarlos, podrás encontrarlos en el menú View. Ahora tenemos al frente al editor mostrándonos el código que generó el asistente. La mayor parte es “código basura” con inicialización redundante de los periféricos del AVR. Lo único que vamos a rescatar de este código es el fragmento que configura el USART0, mostrado abajo, y para que el programa luzca lo más parecido posible al código original escrito para AVR IAR C oAVR GCC lo empaquetaremos en una función llamada usart_init.
  • 106. Después de realizar lo descrito y de borrar el resto del contenido debemos completar nuestro código que como dije anteriormente corresponde a la práctica ledflasher3 y su listado para CodeVisionAVR se muestra a continuación. /****************************************************************************** * FileName: main.c * Purpose: LED Flasher 3. Secuenciador de 3 efectos seleccionables vía USART * Processor: ATmega164P * Compiler: CodeVision AVR
  • 107. * Author: Shawn Johnson. http://www.cursomicros.com. * Basado en el led flasher 2 de Seiichi Inoue localizado en * http://hobby_elec.piclist.com/index.htm. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. * * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta * licencia y la nota de autor de arriba. *****************************************************************************/ #include <io.h>. #include <stdio.h> #include <delay.h> #define kbhit() (UCSR0A & (1<<RXC0)) voidusart_init(void); /****************************************************************************** Patrón que contiene el patrón de efecto del secuencial led1 111111111111111111111111111111... Prendido 6 de 6 partes led2 100100100100100100100100100100... Prendido 2 de 6 partes led3 100000100000100000100000100000... Prendido 1 de 6 partes - Cada bloque tiene 6 items - Cada bloque se repite 100 veces
  • 108. - Hay una pausa de 150 µs entre los ítems - Hay 12/11 bloques en total -> Cada barrido dura 6 * 100 * 150 * 12 = 1.08 segundos aprox. ******************************************************************************/ __flashcharPattern[]= { // Efecto 1. 12 bloques de 6 items 0x81,0x81,0x81,0x81,0x81,0x81,0xc3,0x42,0x42,0xc3,0x42,0x42, 0xe7,0x24,0x24,0x66,0x24,0x24,0x7e,0x18,0x18,0x3c,0x18,0x18, 0x3c,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, 0x3c,0x24,0x24,0x3c,0x24,0x24,0x7e,0x42,0x42,0x66,0x42,0x42, 0xe7,0x81,0x81,0xc3,0x81,0x81,0xc3,0x00,0x00,0x81,0x00,0x00, 0x81,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // Efecto 2. 11 bloques de 6 items 0x80,0x80,0x80,0x80,0x80,0x80,0xc0,0x40,0x40,0xc0,0x40,0x40, 0xe0,0x20,0x20,0x60,0x20,0x20,0x70,0x10,0x10,0x30,0x10,0x10, 0x38,0x08,0x08,0x18,0x08,0x08,0x1c,0x04,0x04,0x0c,0x04,0x04, 0x0e,0x02,0x02,0x06,0x02,0x02,0x07,0x01,0x01,0x03,0x01,0x01, 0x03,0x00,0x00,0x01,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00, // Efecto 3. 11 bloques de 6 items 0x01,0x01,0x01,0x01,0x01,0x01,0x03,0x02,0x02,0x03,0x02,0x02, 0x07,0x04,0x04,0x06,0x04,0x04,0x0e,0x08,0x08,0x0c,0x08,0x08, 0x1c,0x10,0x10,0x18,0x10,0x10,0x38,0x20,0x20,0x30,0x20,0x20, 0x70,0x40,0x40,0x30,0x40,0x40,0xe0,0x80,0x80,0xc0,0x80,0x80,
  • 109. 0xc0,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00 }; /***************************************************************************** * Main function ****************************************************************************/ voidmain(void) { unsignedinti,j,k,b,bi,ba; charc,Effect; DDRB=0xFF;// Setup PORTB for output usart_init();// Initialize USART @ 9600 N 1 puts("nr Escoja un efecto "); puts("nr (1) Barrido simétrico"); puts("nr (2) Barrido a la derecha"); puts("nr (3) Barrido a la izquierda"); Effect='1';// Por defecto iniciar con efecto 1 while(1) { start:
  • 110. /* Establecer parámetros de barrido del efecto actual */ switch(Effect) { case'1':ba=0;b=12;break; case'2':ba=6*12;b=11;break; case'3':ba=6*(12+11);b=11;break; } /* Iniciar barrido del efecto actual */ for(i=0;i<b;i++)// Para barrer b bloques { for(j=0;j<100;j++)// Cada bloque se repite 100 veces { bi=ba+6*i; for(k=0;k<6;k++)// Cada bloque tiene 6 items { PORTB=Pattern[bi+k]; //PORTB = pgm_read_byte(&(Pattern[bi+k])); /* Este bloque sondea el puerto serie esperando recibir una * opción válida para cambiar de efecto */ if(kbhit())// Si hay datos en el USART,.. { c=getchar();// Leer dato if((c<='3')&&(c>='1'))// Si es una opción válida,...
  • 111. { Effect=c;// Tomar opción gotostart;// y reiniciar } } delay_us(150); } } } } } voidusart_init(void) { // USART0 initialization // Communication Parameters: 8 Data, 1 Stop, No Parity // USART0 Receiver: On // USART0 Transmitter: On // USART0 Mode: Asynchronous // USART0 Baud Rate: 9600 (Double Speed Mode) UCSR0A=0x02; UCSR0B=0x18; UCSR0C=0x06; UBRR0H=0x00; UBRR0L=0x67; }
  • 112. Copia todo este código y pégalo en el editor de CodeVisionAVR. Construir el Proyecto de CodeVisionAVR Los botones de las barras de herramientas de CodeVisionAVR no me parecen nada intuitivos porque después de tantos años apenas si logro distinguirlos. Para construir el proyecto yo prefiero utilizar el menú Proyect Build All.
  • 113. Después de construir el proyecto (esto es compilar, enlazar, ensamblar y generar los archivos de salida), tendremos la ventana que reporta los resultados del proceso. Si la construcción fue exitosa esta ventana tendrá adicionalmente la pestaña Assembler con la información de memoria utilizada, como se aprecia en la siguiente figura.
  • 114. También el panel Code Navigator puede dar cuenta de la creación de los archivos de salida como resultado de una construcción exitosa. De todos modos podemos examinar la carpeta del proyecto para comprobar la existencia de los archivos de salida, como el archivo HEX para grabar el AVR o el archivo COFF para las simulaciones en programas como Proteus o Atmel Studio 6. El archivo HEX se encuentra dentro de la carpeta Exe.
  • 115. Si quieres que los archivos de salida no se “desparramen” entre tantas carpetas (Exe, Linker,List y Obj) y que más bien vayan todos a una carpeta Debug como en AVR GCC o AVR IAR C, que es como se acostumbra en los programas de cursomicros.com, puedes hacer la reconfiguración en el menú Project Configure File Output directories. Escribes Debug en los campos respectivos como se ve en la siguiente captura, aceptas con OK y luego podrás borrar las otras carpetas.
  • 116. Simulación del Programa CodeVisionAVR CodeVisionAVR no tiene un simulador integrado. Si queremos simular nuestro programa tendremos que recurrir a un simulador externo como Proteus VSM o Atmel Studio 6. Para simular en Proteus creamos el diseño respectivo y cargamos el archivo HEX, o archivo COFFsi queremos trabajar con una depuración paso a paso. Obviamente,
  • 117. ahora no voy a entrar en detalles. Para eso puedes revisar el tema dedicado a simulaciones con Proteus. Quienes no tengan el privilegio de contar con Proteus, puedes utilizar el simulador de Atmel Studio 6. Ahora el traspase del programa ya no es tan flexible como lo era en el Studio 4 pero todavía es posible mediante el archivo COFF. Así que abrimos Atmel Studio 6 y vamos al menú File Debuggingcomo se indica en la siguiente toma. Open Open Object File for
  • 118. En la siguiente ventana debemos ubicar el archivo COFF a simular. También dice que AVR Studio creará un proyecto para la simulación, así que debemos darle un nombre y especificar una carpeta donde se guardará el proyecto. El nombre no es relevante pero igual le puseledflasher3. Es preferible que la carpeta sea la misma que contiene el proyecto de CodeVisionAVR.
  • 119. Luego seleccionamos el microcontrolador del proyecto.
  • 120. Ahora podremos apreciar que el panel Solution Explorer de Atmel Studio 6 muestra los archivos de depuración COFF y de código fuente main.c. Sin más inicia la simulación presionando el botón indicado.
  • 121. Aquí seleccionamos AVR simulador. Se habla con más amplitud de esto en esta sección. Lo que sigue es historia conocida para quienes conocen las simulaciones con Atmel Studio 6. Si aún no eres de ellos, puedes revisar la sección simulación del programa con Atmel Studio 6. Observa que el archivo COFF puede mostrar las variables ubicadas en los registros de trabajo, a diferencia de la depuración con el archivo ELF.
  • 122. Uso del Compilador AVR IAR C IAR AVR C y AVR GCC son de lejos los mejores compiladores para los AVR. Entre ellos hay ligeras diferencias. Hay algunos aspectos en que un compilador saca cierta ventaja y otros aspectos en que el otro trabaja mejor. Si nos pusiéramos a compararlos punto a punto, estoy casi seguro de que el veredicto final sería… un empate. En cursomicros.com se da preferencia a AVR GCC(WinAVR) pero todos los programas están codificados para ser compilados también por AVR IAR C. Esto se consigue gracias
  • 123. al archivo proporcionado por Atmel avr_compiler.h, al cual le añadí pequeñas cosas. No todas las prácticas están compiladas en AVR IAR C pero los programas que tomé al azar compilaron perfectamente a la primera y sin errores. Bueno, a decir verdad tuve un traspié en un programa que utilizaba la función itoa, ya que dicha función no es parte del ANSI C y como estricto seguidor de este estándar, AVR IAR C no la tiene implementada. Fuera de esa anécdota no tuve ningún inconveniente. El entorno de desarrollo de AVR IAR C se ve bastante sencillo para la potencia que ostenta el compilador. Se llama IAR Embedded Workbench y también sirve de plataforma para los otros compiladores de la empresa IAR Systems. Si bien es cierto que AVR GCC se integra a Atmel Studio 6 para aprovechar sus utilidades para las herramientas hardware de Atmel, el entorno de IAR cuenta con sus propias armas de programación, simulación y depuración, incluyendo los drivers para AVR ICE200, AVR JTAG ICE, AVR JTAGICE mkII, AVR Dragon y AVR ONE! Puedes ver una breve descripción de estas herramientas en la presentación de Atmel Studio 6.
  • 124. Creando un Proyecto en IAR AVR El desarrollo de programas en el IDE de AVR IAR C es muy similar a hacerlo en Atmel Studio 6. Los programas deben integrar un proyecto, el proyecto debe pertenecer a un espacio de trabajo, y un espacio de trabajo puede agrupar varios proyectos. Un espacio de trabajo oWorkspace se podría equiparar con las Soluciones de Atmel Studio 6. Así que empezamos por crear nuestro Workspace o espacio de trabajo yendo al menú File New Workspace. Sin importar su contenido actual, el IDE de AVR IAR C se limpiará y quedará como se muestra abajo.
  • 125. Seguidamente pasamos a crear el proyecto seleccionando el menú Project Create New Project… Para los “pequeños” programas no hay mucha diferencia entre las diversas opciones de proyectos en C (Empty Project, C++ o C). Sin embargo tomaremos Empty Project. Si tienes varios compiladores instalados, debes seleccionar AVR en el deplegable Tool Chain. Con la configuración como se muestra abajo presionamos OK.
  • 126. Luego tendremos que buscar una carpeta donde guardar el archivo del proyecto creado y escribir su nombre. Como ves, yo le llamé ledflasher3.
  • 127. Recién ahora podemos guardar el espacio de trabajo que creamos al inicio. Así que vamos al menú File Save Workspace. El archivo de Workspace también puede tener cualquier nombre, pero yo le llamé otra vez ledflasher3. Si el espacio de trabajo va a contener varios proyectos será preferible que este archivo se ubique en un directorio superior. Como en este caso el espacio de trabajo tendrá un único proyecto, lo guardaré en la misma carpeta del proyecto. Luego podemos ver que el panel Workspace apenas si contiene el nombre del proyecto. Aún no vemos dónde escribir el código del programa, pero eso viene después.
  • 128. Asumiendo que estamos creando un proyecto desde cero, pasamos a crear un archivo en blanco que será donde se edite el código del programa. Para esto vamos al menú File New File o hacemos clic en ese típico botón blanco. El nuevo archivo aparece por ahora como Untitled1. Así que pasamos a guardarlo con ese botón de diskette. Cuando los proyectos tienen más de un archivo de código fuente al archivo principal se le suele llamar main.c. La siguiente imagen parece indicar que éste es uno de esos proyectos.
  • 129. Ahora por fin ya podemos editar el código del programa. En esta ocasión vamos a reproducir el programa de la práctica ledflasher3. Por eso estaba insistiendo con ese nombre ;) El código completo se muestra a continuación. /****************************************************************************** * FileName: main.c * Purpose: LED Flasher 3. Secuenciador de 3 efectos seleccionables vía USART * Processor: ATmega164P * Compiler: AVR IAR C y AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com. * Basado en el led flasher 2 de Seiichi Inoue localizado en * http://hobby_elec.piclist.com/index.htm. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. *
  • 130. * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta * licencia y la nota de autor de arriba. *****************************************************************************/ #include "avr_compiler.h" #include "usart.h" /****************************************************************************** Patrón que contiene el patrón de efecto del secuencial led1 111111111111111111111111111111... Prendido 6 de 6 partes led2 100100100100100100100100100100... Prendido 2 de 6 partes led3 100000100000100000100000100000... Prendido 1 de 6 partes - Cada bloque tiene 6 items - Cada bloque se repite 100 veces - Hay una pausa de 150 µs entre los ítems - Hay 12/11 bloques en total -> Cada barrido dura 6 * 100 * 150 * 12 = 1.08 segundos aprox. ******************************************************************************/ PROGMEM const charPattern[]= { // Efecto 1. 12 bloques de 6 items 0x81,0x81,0x81,0x81,0x81,0x81,0xc3,0x42,0x42,0xc3,0x42,0x42, 0xe7,0x24,0x24,0x66,0x24,0x24,0x7e,0x18,0x18,0x3c,0x18,0x18, 0x3c,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,
  • 131. 0x3c,0x24,0x24,0x3c,0x24,0x24,0x7e,0x42,0x42,0x66,0x42,0x42, 0xe7,0x81,0x81,0xc3,0x81,0x81,0xc3,0x00,0x00,0x81,0x00,0x00, 0x81,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // Efecto 2. 11 bloques de 6 items 0x80,0x80,0x80,0x80,0x80,0x80,0xc0,0x40,0x40,0xc0,0x40,0x40, 0xe0,0x20,0x20,0x60,0x20,0x20,0x70,0x10,0x10,0x30,0x10,0x10, 0x38,0x08,0x08,0x18,0x08,0x08,0x1c,0x04,0x04,0x0c,0x04,0x04, 0x0e,0x02,0x02,0x06,0x02,0x02,0x07,0x01,0x01,0x03,0x01,0x01, 0x03,0x00,0x00,0x01,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00, // Efecto 3. 11 bloques de 6 items 0x01,0x01,0x01,0x01,0x01,0x01,0x03,0x02,0x02,0x03,0x02,0x02, 0x07,0x04,0x04,0x06,0x04,0x04,0x0e,0x08,0x08,0x0c,0x08,0x08, 0x1c,0x10,0x10,0x18,0x10,0x10,0x38,0x20,0x20,0x30,0x20,0x20, 0x70,0x40,0x40,0x30,0x40,0x40,0xe0,0x80,0x80,0xc0,0x80,0x80, 0xc0,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00 }; /***************************************************************************** * Main function ****************************************************************************/ intmain(void) { unsignedinti,j,k,b,bi,ba; charc,Effect;
  • 132. DDRB=0xFF;// Setup PORTB for output usart_init();// Initialize USART @ 9600 N 1 puts("nr Escoja un efecto "); puts("nr (1) Barrido simétrico"); puts("nr (2) Barrido a la derecha"); puts("nr (3) Barrido a la izquierda"); Effect='1';// Por defecto iniciar con efecto 1 while(1) { start: /* Establecer parámetros de barrido del efecto actual */ switch(Effect) { case'1':ba=0;b=12;break; case'2':ba=6*12;b=11;break; case'3':ba=6*(12+11);b=11;break; } /* Iniciar barrido del efecto actual */ for(i=0;i<b;i++)// Para barrer b bloques {
  • 133. for(j=0;j<100;j++)// Cada bloque se repite 100 veces { bi=ba+6*i; for(k=0;k<6;k++)// Cada bloque tiene 6 items { // PORTB = Pattern[bi+k]; PORTB=pgm_read_byte(&(Pattern[bi+k])); /* Este bloque sondea el puerto serie esperando recibir una * opción válida para cambiar de efecto */ if(kbhit())// Si hay datos en el USART,.. { c=getchar();// Leer dato if((c<='3')&&(c>='1'))// Si es una opción válida,... { Effect=c;// Tomar opción gotostart;// y reiniciar } } delay_us(150); } } } } }
  • 134. Puedes copiar todo el listado y pegarlo en el editor de AVR IAR C. Deberá quedar algo así. Observa que el panel Workspace aún no despliega el nombre de main.c. Ese archivo todavía está flotando en el aire. Para que forme parte del proyecto tenemos que incluirlo siguiendo el procedimiento que indica la siguiente captura.
  • 135. Configurando el Proyecto AVR IAR C El entorno IAR Embedded Workbench está diseñado para trabajar con los múltiples compiladores de IAR Systems así que la configuración predeterminada del proyecto puede variar entre una y otra Toolsuit. A continuación veremos los cambios básicos que normalmente deberemos realizar a través de la ventana Opciones disponible en el menú Project o en el menú emergente del archivo del proyecto como se aprecia abajo.
  • 136. Aquí el primer paso es cambiar el microcontrolador.
  • 137. Pasamos a las opciones de C/C++ Compiler y fijamos un nivel de optimización que puede referirse al código más pequeño que deseamos obtener (size) o al más veloz en ejecución (speed).
  • 138. En algunos modos el proyecto genera por defecto el archivo de salida d90 para la depuración del programa. Si en la pestaña Output del enlazador vemos que esto no es así, deberemos modificar la configuración para que quede como se indica en la imagen. Obviamente no todos los archivos de salida de llamarán ledflasher3. Solo asegúrate de que tenga la extensión d90 (la escribes si fuera necesario) y de que su formato sea ubrof 8 (forced). Observa que el enlazador también puede generar otros archivos de depuración como COFF o ELF, pero no todos los formatos serán igualmente válidos para todos los microcontroladores. Los archivos ELF de IAR, por ejemplo, solo están disponibles para los 68HC11, 68HC12, 68HC16, ARM, M16C, MC80, M32C, R32C, SH, y V850.
  • 139. No sé por qué, pero normalmente el archivo HEX no se genera de forma automática. Para ello debemos ubicarnos en la pestaña Extra Output y dejamos la configuración mostrada en la siguiente imagen. Como en el caso anterior debes escribir la extensión HEX si fuera necesario. De hecho, puedes intercambiar el orden y configurar en la anterior ventana la generación del archivo HEX y en esta ventana la del archivo d90.
  • 140. Añadiendo Archivos o Librerías Bueno, si se tratara de un programa más sencillo que constara solo de un archivo de código fuente, ya podríamos construirlo. Pero aunque sea ésta una sencilla demostración, quise realizarla con un programa promedio de cursomicros.com. Aquí casi todas las prácticas utilizan el puerto serie para intercambiar datos con la computadora y para ello se requiere de las librerías del USART respectivas conformadas por los archivos usart.h y usart.c. Además será necesario incluir el archivo avr_compiler.h, que se encarga de mantener la compatibilidad del código para que funcione en los compiladores AVR IAR C y AVR GCC (WinAVR). Así que ahora procedemos a incluir los archivos descritos. Los puedes encontrar en casi cualquier práctica de cursomicros. Cuando los hayas localizado cópialos en la carpeta de este proyecto, junto al archivo main.c, como se ve abajo.
  • 141. Que los archivos señalados estén en la misma carpeta del proyecto no significa que ya formen parte de él. Para indexarlos al proyecto hacemos lo que indica la siguiente figura.
  • 142. En el cuadro de diálogo que surge seleccionamos los archivos a añadir. Podemos seleccionar varios archivos al mismo tiempo.
  • 143. Ahora por fin el panel Workspace nos muestra todos los archivos del proyecto, debidamente indexados. Construyendo el Proyecto AVR IAR C
  • 144. No queda más que construir el proyecto. Ya sabemos que en los compiladores C construir implica compilar, enlazar, ensamblar y generar los archivos de salida. Si la construcción resulta exitosa tendremos el siguiente reporte.
  • 145. Los archivos de salida incluyen el archivo HEX para grabar el microcontrolador y el archivo d90que se utiliza en programas depuradores como Proteus. Estos archivos se encuentran en la carpeta Exe que a su vez está dentro de la carpeta Debug.
  • 146. Simulando el Programa en AVR IAR C El entorno de AVR IAR C incluye un buen simulador y un potente depurador que trabaja incluso a nivel hardware llamado C-SPY. La siguiente captura muestra la simulación en Proteus. El procedimiento es sencillo: solo creas un diseño en ISIS y en el AVR cargas el archivo con extensión d90 generado por el compilador. Para una información pormenorizada puedes ir aquí.
  • 147. Para la simulación con C-SPY de IAR se debe primero reconfigurar la generación del archivo d90 de acuerdo con lo mostrado en la siguiente figura. Ésta era la opción por defecto que tuvimos que cambiar para que el archivo d90 sea aceptado por Proteus. La opción Allow C-SPY specific extra output file nos permitirá poder obtener nuestro archivo HEX de costumbre.
  • 148. Después de reconstruir el proyecto podemos ir al menú Project Download and Debug y estaremos frente a la siguiente interface. Aparecerán varias nuevas opciones en los menús y también nuevas barras de herramientas, entre ellas la que contiene los ya clásicos comandos de simulación como step over, step into, etc.
  • 149. Atmel Studio 6 Atmel Studio 6 es el Entorno de Desarrollo Integrado (IDE) de Atmel para el desarrollo de proyectos con sus productos relacionados con sus microcontroladores. Durante muchos años los IDEs de Atmel para sus microcontroladores se mantuvieron separados en dos versiones principalmente. Por un lado estaba el AVR Studio, que soportaba sus microcontroladores AVR de 8 bits, entre lostinyAVR, los megaAVR y los XMEGA. Eventualmente aparecían nuevas versiones AVR Studio 4.xx pero era esencialmente el mismo y con ese 4 tan inamovible como el nombre de una estación
  • 150. radial. En tanto que por otro lado teníamos al AVR32 Studio, que era desde luego la plataforma análoga pero para trabajar con los microcontroladores AVR de 32 bits. Recién en 2011 Atmel decide lanzar su totalmente renovado Studio 5, esta vez fusionando el soporte para sus microcontroladores emblema de 8 bits y de 32 bits, es decir, AVR Studio 5 = AVR Studio + AVR32 Studio Al igual que la version 4 yo creía que ese 5 iba a perdurar por muchos años pero en 2012 Atmel vuelve a repotenciar su Studio dándoles cabida esta vez a sus microcontroladores con procesador ARM de la serie Cortex-M, denominados SAM3 y SAM4. Como era de esperarse, el nombre ya no va precedido por la palabra AVR. Ahora se llama simplemente Atmel Studio 6. Sin emargo, al igual que las versiones anteriores, los usuarios de los AVR lo suelen llamar simplemente Studio 6, a secas. Entre las herramientas que incluye el nuevo Atmel Studio 6 nos deben interesar las siguientes: Un editor de códigos, para editar los programas. Como todo gran editor permite mostrar los códigos fuente a colores, con números de línea, etc. Un administrador de proyectos, que además de trabajar con programas en ensamblador, le da un completo soporte a los compiladores ARM GCC, AVR32 GCC y AVR GCC. A diferencia de versiones anteriores ahora es menos flexible la integración con los compiladores comerciales como CodeVisionAVR o ImageCraft AVR. El ensamblador AVRASM, para trabajar con programas en ensamblador. Los compiladores de software libre AVR32 GCC y AVR GCC en su versión para Windows (WinAVR), para desarrollar programas en C para los AVR de 8 y 32 bits, como los tinyAVR,megaAVR, XMEGA y AVR32. En versiones pasadas de Atmel Studio 6, este compilador se debía instalar por separado. El compilador ARM GCC, que es otro derivado de la colección GCC pero adaptado para los microcontroladores ARM. El simulador AVR Simulator, para simular los programas de los AVR tanto si están escritos en lenguaje C o ensamblador. Todavía no está disponible un simulador para los microcontroladores con procesador ARM. El paquete Atmel Software Framework o ASF, que es un conjunto de más de 1000 proyectos escritos en lenguaje C para los AVR de 8 bits y para los ARM y AVR de 32 bits. Incluye librerías y APIs que facilitan la interface con todos los periféricos de los microcontroladores, desde los puertos hasta los módulos USB o CAN. Un completo sistema de ayuda integrado con servidor local. Había algunos problemas con esta característica en el Studio 5 pero en Atmel Studio 6 las cosas andan muy bien.
  • 151. Atmel Studio 6 permite trabajar con más de 300 microcontroladores, entre AVR y ARM.Atmel Studio 6 ya no soporta los siguientes dispositivos, que aún están disponibles en el Studio 4, ATtiny11, ATtiny12, ATtiny15, ATtiny22, AT90S1200, AT90S2313, AT90S2323, AT90S2343, AT90S4433, AT90S8515, AT90S8535, ATmega323, ATmega161, ATmega163, ATmega103, ATmega165, ATmega169, ATmega406, ATmega16HVA, ATmega16HVA2, ATmega64HVE, ATmega32U6, AT90PWM2, AT90PWM3, AT90SCR100, AT86RF401. Atmel Studio 6 también incluye las siguientes herramientas, las cuales son de uso opcional porque requieren del hardware correspondiente. De todos modos, si tú puedes conseguirlos o deseas saber más de ellos, puedes visitar la web de Atmel. Los softwares de programación como AVR ISP MkII. Éste programa trabaja con el dispositivo programador comercial del mismo nombre. El programador AVR ISP mkII. Los programadores y depuradores como AVR Dragon, JTAGICE3, JTAGICE MkII o AVR ONE!La performance y potencia de estas herramientas siguen el orden de su presentación. ICE significa In Circuit Emulator, y hace referencia a un sistema de depuración en el mismo circuito y con el mismo microcontrolador. Obviamente deben trabajar con sus propios adaptadores hardware, que se conectan a la computadora por el puerto USB y al microcontrolador AVR vía una interface que puede ser JTAG, PDI, debugWire o aWire, dependiendo del depurador y del microcontrolador seleccionados. La interface más común para con el megaAVR es JTAG y está conformada por los pines TMS, TCK, TDI y TDO del microcontrolador AVR. Esta interface también permite la programación serial del AVR, es decir, el hardware es un depurador y programador. No todos los AVR tienen soporte JTAG ICE (ejemplo los tinyAVR).
  • 152. programadores y depuradores AVRICE MkII y AVR ONE!. Los programadores/depuradores AVRICE MkII (izquierda) y AVR ONE! (derecha). El software depurador (AVR Dragon, JTAGICE3, JTAGICE MkII o AVR ONE!) puede dirigir el programa del AVR en la misma aplicación paso a paso, como en cámara lenta o en tiempo real, e ir visualizando los resultados de las operaciones a medida que se van ejecutando. De ese modo se puede monitorizar y analizar «en vivo y en directo» cómo van cambiando los recursos hardware del AVR, como el contenido de sus memorias y de sus registros internos. Para quienes conozcan algo, es parecido a la simulación de Proteus o del mismoAtmel Studio 6, pero esto será real. Los elementos hardware de control y programación del AVR también suelen estar disponibles en las tarjetas de desarrollo que provee ATmel, como las tarjetas STK500,STK600 o EVKxxx. Las utilidades software correspondientes están incluidas en Atmel Studio 6.
  • 153. La tarjeta de desarrollo STK600. Descarga de Atmel Studio 6 y WinAVR Ahora que ya tienes una idea bien formada de lo que es y lo que puede hacer Atmel Studio 6, puedes descargarlo libremente desde www.atmel.com. Puede pesar más de 700 MB, así que tendrás que esperar un poco. Si ya tienes este programa, pero en su versión 5.x, será mejor que te actualices. Hay sustanciales diferencias entre la versión 5.x y las anteriores, en las cuales no perderé tiempo citándolas. Solo espero que luego no te quejes si encuentras cosas que no te salen igual ;).
  • 154. En la página de descarga podremos encontrar los siguientes paquetes. El instalador completo (Full) de Atmel Studio 6. Como allí se indica, aquí se incluyen los paquetes Microsoft Windows Framework .NET 4.0 y Visual Studio Shell (Isolated Mode) 2010. Ambos son prerrequisitos para la instalación de Atmel Studio 6 y probablemente ya los tengas instalados en tu PC. Si no estás seguro de ello o si prefieres perder un poco más de tiempo en la descarga antes que averiguarlo, puedes descargar este paquete completo. Recuerda que el instalador trae incluidos los compiladores AVR GCC, AVR32 GCC y ARM GCC. El instalador solo de Atmel Studio 6. Lo mismo que el anterior pero sin Microsoft Windows Framework .NET 4.0 ni Visual Studio Shell (Isolated Mode) 2010. La actualización de Atmel Software Framework (ASF). Contiene el paquete de los más de los más de 1000 proyectos, ejemplos, librerías, etc. El instalador de Atmel Studio 6 ya lo trae incluido solo que quizá no en su versión más actual. Descarga este archivo exe solo si quieres estar súper actualizado e instálalo luego de Atmel Studio 6. Instalación de Atmel Studio 6 y AVR GCC - WinAVR Requisitos de sistema Instalación de MVS 2010 Instalación de drivers USB Instalación de Atmel Studio Requisitos de sistema Windows XP (x86) con Service Pack 3 en todas las ediciones excepto Starter Edition. Windows Vista (x86) con Service Pack 1 en todas las ediciones except Starter Edition. Windows XP (x64) con Service Pack 2. Windows Vista (x64) con Service Pack 1. Windows 7 (x86 y x64). Windows Server 2003 R2 (x86 y x64). Computadora con procesador de 1.6GHz o superior. 1 GB de RAM para x86. 2 GB de RAM para x64. 512 MB de RAM adicionales si se trabaja en una PC virtual. 3GB de espacio disponible en el disco duro.
  • 155. Disco duro de 5400 RPM o superior. Tarjeta de vídeo con soporte DirectX 9 y resolución de 1024 x 768 o superiores. Antes de instalar Atmel Studio 6 debemos desinstalar la versión Beta del Studio 5, si es que lo teníamos. Y si tenemos instalados el AVR Studio 5 versión Release, AVR Studio 4 o AVR32 Studio, los podemos conservar porque Atmel Studio 6 puede trabajar en paralelo con ellos. Bueno ahora sí empezamos la instalación como cualquier otro programa de Windows, siguiendo las indicaciones presentadas. Aquí, algunos screenshots: Lista de los requisitos software. Antes de instalar Atmel Studio 6 propiamente se instalarán los paquetes listados. Para quienes ya tenían instalados Microsoft .NET Framework 4.0 (se supone que Windows 7 Ultimate ya lo incluye) o Microsoft Visual Studio 2010 Shell, dichas opciones ya no estarán disponibles y por tanto pasarás directamente a la Instalación de los Drivers USB.
  • 158. Instalación de Microsoft Visual Studio 2010 Shell Luego vendrá la instalación de Microsoft Visual Studio 2010 Shell. Si tienes Internet y deseas enviar a Microsoft información sobre tu experiencia con la instalación de Visual Studio, puedes activar la casilla indicada.
  • 162. Terminada esta parte nos dirán que Microsoft Visual Studio 2010 Shell se instaló con éxito, a pesar de que pueden aparecer esas X en rojo indicando que aún no se ha instalado la documentación respectiva (no es necesario para el desempeño de Atmel Studio 6 pero si deseas la puedes descargar desde la web de Microsoft). También me recomienda actualizar mi PC con los parches de seguridad más recientes de Windows.
  • 163. Instalación de los Drivers USB de Atmel Y ahora viene la instalación de los Drivers USB de Atmel. Atmel Studio 6 utiliza estos drivers para manejar sus tarjetas hardware con interface USB. Si ya los tenías instalados, digamos porque trabajaste con versiones anteriores del AVR Studio, entonces este procedimiento actualizará los drivers con la versión presente. Estos drivers USB de Atmel son compatibles con sus antecesores.
  • 167. Por supuesto que estos drivers son confiables, son de los mejores.
  • 168. Instalación de Atmel Studio 6 Y pensar que recién vamos a instalar Atmel Studio 6 propiamente (creo mi Windows se ha instalado más rápido ;).
  • 171. Entorno de Atmel Studio 6
  • 172. Entorno de trabajo de Atmel Studio 6. Ésa es la distribución por defecto de las ventanas de Atmel Studio 6. En primer plano tenemos aStart Page o Página de Inicio, a la derecha está la ventana Solution Explorer y abajo, la ventanaOutput. Hay más ventanas que iremos conociendo en el camino pero mientras te familiarizas con el entorno puedes reacomodarlas según tu preferencia. Para ello puedes arrastrarlas tomándolas por su barra de títulos y
  • 173. colocarlas donde indica el guía o puedes dejarlas flotando. Esto último no suele ser muy útil, aunque a mí me servirá para mostrar las capturas de pantalla más adelante Reubicación de las ventanas del entorno de Atmel Studio 6. Hacer estas reubicaciones es divertido sobre todo si tenemos en cuenta que podemos regresar a la distribución inicial yendo al menú Window Reset Window Layout. Y si queremos volver a tener la página Start Page para ver a la mariquita de nuevo vamos
  • 174. al menú View Start Page. Nota que esta opción también tiene un icono en la barra de herramientas. Es el menú View donde también puedes encontrar las otras ventanas y no en el menú Window. Antes que mostrar una típica descripción de cada uno de los elementos del entorno de Atmel Studio 6 los iré presentando a medida que hagan su aparición y sean requeridos. Trabajando con Proyectos y Soluciones en C A diferencia de los compiladores Basic, donde basta con crear un archivo BAS suelto y luego compilarlo, los programas en C siempre forman parte de un proyecto. Actualmente desarrollar proyectos en C con Atmel Studio 6 es más sencillo que en versiones anteriores debido en gran parte a que está adaptado para trabajar especialmente con los compiladores libres GCC AVR32 y AVR GCC (WinAVR). Una Solución (Solution) es un conjunto formado por uno o varios proyectos, así que todo proyecto debe pertenecer a alguna Solución.Atmel Studio 6 puede trabajar con uno solo o con todos los proyectos de la Solución al mismo tiempo, pero solo puede administrar una Solución a la vez. Hay más consideraciones respecto a los Proyectos y las Soluciones, pero creo que será mejor describirlas mientras creamos el Proyecto. Creación de un Proyecto en C Solo hay una forma de crear un proyecto, esto para simplificar las cosas. Abierto Atmel Studio 6 vamos al menú File Project deStart Page. New Project o hacemos clic en New
  • 175. Creando un proyecto desde Start Page. De cualquier modo llegaremos al mismo asistente, donde empezamos por seguir lo que indica la siguiente figura. El nombre y la ubicación del proyecto pueden ser los que desees.
  • 176. Elección del nombre y ubicación del proyecto. Ten en cuenta que Atmel Studio 6 creará una nueva carpeta (con el nombre del proyecto) dentro de la ubicación indicada. Observa que el nombre de la Solución es el mismo que el del proyecto. Todo proyecto debe pertenecer a alguna Solución, y como estamos creando el primer proyecto,Atmel Studio 6 le asignará una Solución automáticamente. Cuando creemos los siguientes proyectos tendremos la opción de elegir si pertenecerán a ésta o a una Solución nueva. Si activas la casilla Create directory for Solution, la Solución (que es un archivo a fin de cuentas) se alojará en la carpeta que debía ser para el proyecto, y el proyecto junto con sus archivos generados irán a parar a una sub carpeta con el mismo nombre. Esto puede ser conveniente cuando se tiene una Solución con varios proyectos. Yo sé que parece enredado pero con un par de prácticas lo entiendes mejor y te acostumbras.
  • 177. Normalmente prefiero tener un proyecto por cada Solución, de modo que no tengo que marcar la casilla indicada y puedo tener todos los archivos del proyecto y de la Solución dentro de la misma carpeta sin que se confundan. Es así como están conformadas todas las prácticas de cursomicros.com. Le damos clic a OK y tendremos una ventana mucho menos traumática. Solo seleccionamos el AVR con el que trabajaremos. En el futuro podremos cambiar este microcontrolador por otro. También puedes aprovechar esta ventana para reconocer las memorias de cada dispositivo y para ver las herramientas que tienen disponibles desde Atmel Studio 6. Elección del microcontrolador del proyecto. En seguida tendremos nuestro proyecto con el Editor de Código mostrándonos el archivo de código fuente principal listo para que lo editemos. Observa que el Explorador de la Solución oSolution Explorer muestra los Proyectos de la Solución
  • 178. así como los archivos de cada Proyecto. Debajo está el marco Properties que informa las propiedades de cada archivo seleccionado arriba, como su ubicación. De todas las ventanas aquí mostradas o las que puedan surgir en adelante la que no deberías perder de vista es el Explorador de la Solución. Desde allí accedes a todos los archivos del proyecto (los abres con doble clic) y también puedes construir el proyecto así como establecer su configuración. Si se te llega a perder esta ventana la puedes visualizar yendo al menú View Esquema del proyecto creado. Edición del Código Fuente Solution Explorer.
  • 179. El editor de códigos se llama simplemente Code y es accesible desde el menú View. También se muestra automáticamente cada vez que abrimos un archivo. Como lo dijimos al inicio, la podemos cerrar, reubicar o dejar flotando como se ve en la siguiente figura. El código mostrado corresponde al programa del LED parpadeante ledflasher3, que es con el que vamos a trabajar. Todavía no lo podemos compilar porque faltan agregar al proyecto los archivos mencionados en las directivas include. Hablamos de los archivos avr_compiler.h, usart.hy usart.c. Pero eso lo veremos en la siguiente sección. Edición del programa Ledflasher3.
  • 180. Trabajando con Proyectos y Soluciones en C A diferencia de los compiladores Basic, donde basta con crear un archivo BAS suelto y luego compilarlo, los programas en C siempre forman parte de un proyecto. Actualmente desarrollar proyectos en C con Atmel Studio 6 es más sencillo que en versiones anteriores debido en gran parte a que está adaptado para trabajar especialmente con los compiladores libres GCC AVR32 y AVR GCC (WinAVR). Una Solución (Solution) es un conjunto formado por uno o varios proyectos, así que todo proyecto debe pertenecer a alguna Solución.Atmel Studio 6 puede trabajar con uno solo o con todos los proyectos de la Solución al mismo tiempo, pero solo puede administrar una Solución a la vez. Hay más consideraciones respecto a los Proyectos y las Soluciones, pero creo que será mejor describirlas mientras creamos el Proyecto. Creación de un Proyecto en C Solo hay una forma de crear un proyecto, esto para simplificar las cosas. Abierto Atmel Studio 6 vamos al menú File Project deStart Page. Creando un proyecto desde Start Page. New Project o hacemos clic en New
  • 181. De cualquier modo llegaremos al mismo asistente, donde empezamos por seguir lo que indica la siguiente figura. El nombre y la ubicación del proyecto pueden ser los que desees. Elección del nombre y ubicación del proyecto. Ten en cuenta que Atmel Studio 6 creará una nueva carpeta (con el nombre del proyecto) dentro de la ubicación indicada. Observa que el nombre de la Solución es el mismo que el del proyecto. Todo proyecto debe pertenecer a alguna Solución, y como estamos creando el primer proyecto,Atmel Studio 6 le asignará una Solución automáticamente. Cuando creemos los siguientes proyectos tendremos la opción de elegir si pertenecerán a ésta o a una Solución nueva. Si activas la casilla Create directory for Solution, la Solución (que es un archivo a fin de cuentas) se alojará en la carpeta que debía ser para el proyecto, y el proyecto junto con sus archivos generados irán a parar a una sub carpeta con el mismo nombre. Esto
  • 182. puede ser conveniente cuando se tiene una Solución con varios proyectos. Yo sé que parece enredado pero con un par de prácticas lo entiendes mejor y te acostumbras. Normalmente prefiero tener un proyecto por cada Solución, de modo que no tengo que marcar la casilla indicada y puedo tener todos los archivos del proyecto y de la Solución dentro de la misma carpeta sin que se confundan. Es así como están conformadas todas las prácticas de cursomicros.com. Le damos clic a OK y tendremos una ventana mucho menos traumática. Solo seleccionamos el AVR con el que trabajaremos. En el futuro podremos cambiar este microcontrolador por otro. También puedes aprovechar esta ventana para reconocer las memorias de cada dispositivo y para ver las herramientas que tienen disponibles desde Atmel Studio 6. Elección del microcontrolador del proyecto.
  • 183. En seguida tendremos nuestro proyecto con el Editor de Código mostrándonos el archivo de código fuente principal listo para que lo editemos. Observa que el Explorador de la Solución oSolution Explorer muestra los Proyectos de la Solución así como los archivos de cada Proyecto. Debajo está el marco Properties que informa las propiedades de cada archivo seleccionado arriba, como su ubicación. De todas las ventanas aquí mostradas o las que puedan surgir en adelante la que no deberías perder de vista es el Explorador de la Solución. Desde allí accedes a todos los archivos del proyecto (los abres con doble clic) y también puedes construir el proyecto así como establecer su configuración. Si se te llega a perder esta ventana la puedes visualizar yendo al menú View Esquema del proyecto creado. Solution Explorer.
  • 184. Edición del Código Fuente El editor de códigos se llama simplemente Code y es accesible desde el menú View. También se muestra automáticamente cada vez que abrimos un archivo. Como lo dijimos al inicio, la podemos cerrar, reubicar o dejar flotando como se ve en la siguiente figura. El código mostrado corresponde al programa del LED parpadeante ledflasher3, que es con el que vamos a trabajar. Todavía no lo podemos compilar porque faltan agregar al proyecto los archivos mencionados en las directivas include. Hablamos de los archivos avr_compiler.h, usart.hy usart.c. Pero eso lo veremos en la siguiente sección.
  • 185. Edición del programa Ledflasher3. Adición de Archivos o Librerías al Proyecto Los proyectos en AVR GCC o AVR IAR C raras veces constan de un solo archivo. Casi siempre hay archivos de configuración o librerías que añadir. De hecho, en cursomicros.com todas las prácticas incluyen al menos el archivoavr_compiler.h, el cual permite que los códigos de programa se puedan construir indistintamente para los compiladores AVR GCC o AVR IAR C. Este archivo forma parte del paquete ASF y lo puedes hallar en el directorio de instalación de Atmel Studio 6. También puedes encontrar una copia suya (con ligeras modificaciones) en todos los proyectos de cursomicros.com. Las librerías en el lenguaje C se suelen dividir en dos archivos: uno (con extensión .c) que suele contener los códigos ejecutables de las funciones y otro (con extensión .h) donde se escriben las definiciones y los prototipos de las funciones, básicamente. Así por ejemplo, en cursomicros.com se usan las librerías para displays LCD (con archivos lcd.h y lcd.c), para el puerto serie (con archivos usart.h y usart.c) o para el bus I2C (con archivos i2c.h e i2c.c). En los códigos fuente los archivos se invocan mediante la directiva include. Adicionalmente en los proyectos de WinAVR o AVR IAR C deben incluirse desde sus entornos de desarrollo. En esta ocasión veremos cómo hacer esto con los archivos avr_compiler.h, usart.h y usart.c y el procedimiento descrito es el mismo que debes seguir cuando añadas otros archivos. Una vez ubicados los archivos avr_compiler.h, usart.h y usart.c, colócalos en la carpeta de tu proyecto. Esto no es necesario porque que se le podría incluir dondequiera que esté desde el Explorador de la Solución. Sin embargo habrá proyectos en los que se quiera editar el archivo incluido y para que dichos cambios no afecten a los demás proyectos lo más recomendable será tener una copia suya en la carpeta de cada proyecto, como se ve abajo.
  • 186. Los archivos avr_compiler.h, usart.h y usart.c deberían estar en la carpeta del proyecto. Ahora debemos indexar el archivo al proyecto. Para ello seleccionamos el proyecto en el Explorador de la Solución y vamos al menú Project Add Existing Item… También puedes emplear mi camino preferido, que se ilustra a continuación.
  • 188. Indexando los archivos avr_compiler.h, usart.h y usart.c al proyecto. Observa que ahora el archivo añadido también se visualiza en el Explorador de la Solución.
  • 189. Archivo avr_compiler.h indexado. Construcción del Proyecto Antes de entrar en el proceso de la construcción debemos saber que existen dos modos básicos de configuración en que se generarán los resultados, que son Debug y Release. El modo por defecto es Debug. Uno de los efectos de cambiar del modo Debug al modo Release es que la optimización del código se adaptará para obtener el archivo HEX de menor tamaño. Esta optimización es necesaria para que las funciones de delay del programa queden mejor afinadas.
  • 190. Elección entre los modos Debug y Release. También debemos considerar que cada modo generará los archivos de salida en sus correspondientes carpetas. Hasta ahora solo hemos visto la carpeta Debug pero al compilar el proyecto en modo release se creará también una carpeta Release. Una opción para evitar marearnos entre estos modos es ajustar la optimización del código directamente. Aprendemos eso en una próxima sección. Por el momento cambiemos a release y sigamos. Bueno, para construir un proyecto podemos tomar varios caminos, dos de los cuales se muestran abajo. Un tercer camino es seleccionar el proyecto en el Explorador de la Solución, hacer clic derecho y en el menú emergente aparecerán las mismas opciones del menú Build. Build Solution (construir la Solución). Una Solución puede estar compuesta por varios proyectos. De ser el caso, con esta opción se construirán todos los proyectos de laSolución.
  • 191. Rebuild Solución (reconstruir la Solución). Reconstruye todos los proyectos de la Solución haciendo previamente una limpieza de los archivos de salida generados antes. Clean Solution (Limpiar Solución). Para todos los proyectos de la Solución actual, limpia o borra todos los archivos de salida, como HEX, LSS, MAP, etc., que se crearon durante una construcción previa. Build ledflasher (construir ledflasher). En el entorno del lenguaje C construir en realidad involucra varios procesos, como compilar, enlazar (linkar) y ensamblar. Normalmente nos referimos a esta acción simplemente como compilar. Rebuild ledflasher (reconstruir ledflasher). Vuelve a construir el proyecto indicado pero haciendo una limpieza previa de sus archivos de salida. Clean ledflasher (limpiar ledflasher). Borra todos los archivos de salida del proyecto indicado. Compile (compilar). La opción compilar aparece en el menú Build cuando se selecciona un archivo C. Al compilar un archivo no obtendremos los archivos HEX o ELF, sino solo un archivo objeto, que es previo al HEX o ELF. Puesto que nuestra Solución de ejemplo solo tiene un proyecto, dará lo mismo si elegimos construir el proyecto o construir la Solución. Tras construir el proyecto del LED parpadeante nos aparecerá la ventana de salida Output con los resultados de éxito, como se aprecia abajo. Probablemente tu ventana Output aparezca en otra disposición pero como aprendimos antes, eso es solo cuestión de reubicación.
  • 192. Resultados de la construcción. Entre los archivos de salida puedes ver que aparecieron los esperados HEX (para grabar el microcontrolador) y ELF (para la depuración o simulación en programas como Proteus). Todos estos archivos se depositan en la carpeta Release del proyecto porque lo compilamos en ese modo.
  • 193. Lo archivos de salida van a las carpetas Release o Debug. Renombrar los Archivos del Proyecto Por defecto los nombres de la Solución, del proyecto y del archivo de código fuente principal son el mismo. Esto es opcional, pero yo prefiero que mi archivo de código principal se llame siempremain.c para hacer una mejor distinción de los otros archivos de código como las librerías. Renombrar un archivo es sencillo. Solo hay que seleccionarlo en el Explorador de la Solución, abrir su menú contextual (clic derecho) y escoger la opción Rename. Del mismo modo también es posible cambiar de nombre el proyecto y la Solución.
  • 194. Renombrando los archivos del proyecto y la Solución. Cambiar la Frecuencia del Procesador Para WinAVR normalmente las únicas rutinas que requieren la definición de la frecuencia del procesador son las funciones de delay, que se encuentran en los archivos del mismo nombre disponibles en la carpeta utils del WinAVR. Quienes utilizan dichos delays suelen editar la constante F_CPU definida en esos archivos. Pero el archivo avr_compiler.h también define la constante F_CPU antes de utilizar las funciones de delay de WinAVR. De modo que si vamos a trabajar con este archivo, es aquí donde debemos editar la constante F_CPU, que casi siempre coincidirá con la frecuencia del XTAL usado. Gracias a las macros de avr_compiler.hF_CPU también tendrá efecto en las funciones de delay del compilador IAR AVR C.
  • 195. Además, las librerías de www.cursomicros.com para los módulos síncronos del AVR como elUSART o TWI (I2C) también se basan en F_CPU. Queda claro entonces que no solo la utilizo para calibrar los delays. En el archivo avr_compiler.h del paquete ASF la constante F_CPU se define a 11059200Hz. Pero en casi todas las copias de avr_compiler.h disponibles en www.cursomicros.com F_CPU está redefinida a 8 MHz (como se aprecia en el siguiente extracto de dicho archivo) porque en la gran mayoría de mis diseños trabajo con esa frecuencia. //////////////////////////////////////////////////////////////// #ifndef F_CPU /* Define la frecuencia de CPU (en Hertz) por defecto, si no ha * sido definida. */ #define F_CPU 8000000UL // XTAL de 8 MHz #endif Reconfiguración del Proyecto WinAVR El compilador AVR GCC nació inicialmente para Linux y con el tiempo se desarrolló la versión para Windows llamada WinAVR. Hasta ahora WinAVR no tiene un entorno propio y de hecho ya no hace falta gracias a Atmel Studio 6. Pero anteriormente para usar WinAVR había que invocarlo desde otros entornos como CodeBlocks,Eclipse o el mismo Studio 4. Esos entornos no eran del todo compatibles con WinAVR y más temprano que tarde uno tenía que configurar las opciones del proyecto en el mismo archivo Makefile. Makefile es un archivo de texto lleno opciones para la construcción del proyecto. WinAVR sigue trabajando en base a él, solo que para nosotros Atmel Studio 6 lo crea y edita automáticamente. Cada cambio realizado en la ventana de propiedades del proyecto es reflejado en el archivo Makefile. Si te interesa echarle un vistazo, puedes encontrar Makefile entre los archivos de salida en las carpetas Debug y/o Release. Para acceder a la ventana de propiedades del proyecto, lo seleccionamos y vamos al menúProject Properties. Yo, como siempre, prefiero ir por el Explorador de la Solución, como se ve abajo.
  • 196. Hay varias categorías desde Build hasta Advance y en muchas de ellas podremos elegir si la configuración se aplica al modo Debug o Release. Recuerda que cada modo hace que los archivos de salida (incluyendo el Makefile) vayan a sus respectivas carpetas.
  • 197. Ventana de propiedades del proyecto. Optimización del Código Comúnmente los compiladores C ofrecen varios niveles de optimización del código. Con WinAVR tenemos los siguientes niveles: No optimizar (nivel O0). El código no se optimiza nada. Por ejemplo, el programa del LED parpadeante compilado con este nivel resulta en un código de 3714 bytes y compilado en nivel Os resulta en 118 bytes. Sí que hay diferencia, ¿verdad? Sin embargo, muchas veces se usa este modo con fines de depuración porque genera el mejor archivo ELF. No por nada es el nivel por defecto del modo Debug. Además, esa diferencia se desvanece cuando se trabaja con programas grandes. Por ejemplo un programa que utiliza funciones matemáticas con números de punto flotante y la función Printf en su máxima performance a mí me resultó en 3366 bytes sin optimización y 3038 con optimización Os. Optimizar (nivel O1). Optimiza regularmente; bastante, comparado con el nivel O0. Con este nivel mi programa de prueba resultó en 3066 bytes.
  • 198. Optimizar más (nivel O2). Con este nivel mi programa de prueba resultó en 3060 bytes. Optimizar al máximo (nivel O3). Este nivel es particular porque no optimiza para conseguir el menor código, sino el más veloz, es decir, el código que se ejecuta en el menor tiempo posible. Para esto el compilador intentará incrustar en línea todas las funciones que pueda, como las que se llaman solo una vez. Siguiendo con el ejemplo, mi programa resultó en 3102 bytes. Optimizar para tamaño (nivel Os). Optimiza para que la construcción genere el menor código máquina posible. En general el tamaño del código maquina se contrapone a su velocidad de ejecución. No se puede conseguir un código mínimo y que al mismo tiempo sea el más veloz. El manual de WinAVR recomienda utilizar este nivel combinado con la directiva -mcall-prologues, excepto cuando se quiera usar el nivel O3 para ganar algo de velocidad. El nivel Os es la opción por defecto del modo Release. Ahora abramos la ventana de propiedades del proyecto y ubiquémonos en Toolchain AVR/GNU C Compiler Optimization. Recuerda que puedes elegir si la configuración se aplica al modo Debug o Release y que cada modo hace que los archivos de salida vayan a sus respectivas carpetas.
  • 199. Cambio de la optimización del código. Había señalado anteriormente que para mejorar la optimización de código en tamaño la ayuda de WinAVR recomendaba el uso de la opción -mcall-prologues. Dice que con esto se usarán subrutinas especiales para guardar y restaurar los registros en las entradas y salidas de las funciones que usen muchos registros, a costa de un pequeño incremento en el tiempo de ejecución. (Es de suponer que se trata de bucles, ¿cierto?) En fin, sea como fuere, para habilitar la opción -mcall-prologues en Atmel Studio 6 ni siquiera tenemos que escribirla, simplemente activamos la casilla Use subroutines for function prologues and epilogues, como de ilustra en la siguiente figura.
  • 200. Cuando un programa se construye exitosamente no siempre significa que lo hizo de la mejor forma. A veces puede haber algunos mensajes o advertencias que no se muestran en la ventana de salida Output. Para ver en detalle estos errores, advertencias y mensajes debemos inspeccionar la ventana Error List, que está disponible desde el menú View Error List. Lista de errores, advertencias y mensajes de la construcción. Aquí muestro la ventana flotante pero recuerda que la puedes anclar donde desees. El caso es que la advertencia mostrada es muy frecuente. Señala que las optimizaciones del compilador están inhabilitadas y que las funciones del archivo delay.h no
  • 201. funcionarán adecuadamente. Imagino que con todo lo visto aquí a ti nunca se te debería presentar. Nosotros no invocamos al archivo delay.h directamente, sino a través de avr_compiler.h. Cambiar de Microcontrolador AVR Observa que ni en WinAVR ni en otros compiladores como IAR AVR C o ImageCraft AVR se debería seleccionar el AVR del proyecto desde el código fuente, como se suele hacer en Basic. Si queremos conocer el microcontrolador AVR para el que está hecho un proyecto o si queremos cambiarlo, debemos ir a la categoría Device. Reelección del microcontrolador del proyecto. Nos aparecerá una ventana muy parecida a la que teníamos cuando creamos el proyecto. La diferencia es que ya no están los AVR de 32 bits, así que el cambio solo es factible por otro AVR de 8 bits. Lo mismo ocurre cuando administramos un proyecto con un AVR de 32 bits. Entendemos que para cambiar de microcontrolador primero tendríamos que cambiar de compilador, de AVR GCC a AVR32 GCC o viceversa, pero eso tampoco es posible desde Atmel Studio 6. La única forma sería creando un nuevo proyecto.
  • 202. Cambio del microcontrolador del proyecto. Uso de un Archivo Makefile Externo Explicamos anteriormente lo que significa el archivo Makefile para el compilador WinAVR y que en estos días sería muy raro editarlo manualmente. Sin embargo, dada la gran flexibilidad de WinAVR, a veces incluso administrar la ventana de propiedades del proyecto en Atmel Studio 6puede resultar confuso. Has de saber que a diferencia de otros compiladores C, la configuración de compilación de un proyecto en WinAVR reside íntegramente en su archivo Makefile. Los archivos de Proyecto y de la Solución que crea Atmel Studio 6 se reducen a simples cascarones en comparación con Makefile. Si alguna vez tienes el código fuente de un gran proyecto ajeno pero sin su Makefile, probablemente no logres compilarlo como el autor lo diseñó, si es que no utilizas la
  • 203. misma configuración. Tendrías que acertar con la configuración exacta o conseguir el archivo Makefile original. Bueno, ya sea que quieras trabar con un archivo Makefile o que simplemente seas uno de los antiguos nostálgicos que extrañan editarlo a mano (como yo ;), la forma de usar un archivo Makefile diferente del creado por Atmel Studio 6 es: Uso de Makefile externo en Atmel Studio 6. Configurar el Uso de Printf ¿Quieres visualizar números de punto flotante y solo obtienes el signo ? ? Imagino que esto debe ser frustrante para los usuarios de los compiladores “fáciles” como CCS C. Espera un momento. Ni el lenguaje C ni el compilador GCC fueron diseñados para los microcontroladores. Son muy útiles las familias de funciones printf y scanf pero al mismo tiempo bastante pesadas para la arquitectura de los microcontroladores, sobre todo si son de 8 bits y no tienen chip matemático. Por las limitaciones conocidas hasta los mejores compiladores como AVR IAR C o WinAVR tienen que subdividir a printf y scanf y sus derivados en tres versiones, para
  • 204. que el usuario escoja la que más se ajuste a su demanda y a las restricciones de su microcontrolador. La versión minimizada de printf restringe los especificadores de conversión al uso de enteros de 16 bits (entre octales, hexadecimales y decimales positivos o negativos), de caracteres y de cadenas de caracteres. No están disponibles los números de punto flotante ni los números enteros de 32 bits. No se permite especificar el ancho de los números y por ende tampoco se disponen de las opciones de justificación ni de relleno con blancos o ceros. Según el manual de avr-libc, la versión minimizada de printf se consigue estableciendo las siguientes opciones de linkador: -Wl,-u,vfprintf -lprintf_min La versión predeterminada de printf ofrece todas las características de la versión completa excepto las relacionadas con los números de punto flotante. En los megaAVR demanda cerca de 350 bytes más que la versión minimizada. Es redundante decir que ésta es la versión por defecto y no requiere de ninguna instrucción al linkador. Pero si previamente se hubo establecido alguna de las otras versiones, entonces bastaría con quitar las siguientes opciones para regresar a la versión por defecto de printf. -Wl,-u,vfprintf La versión completa de printf brinda todo su potencial, incluyendo las conversiones de números de punto flotante. En los megaAVR requiere cerca de 380 bytes de memoria FLASH más que la versión predeterminada. La versión completa de printf viene con las siguientes opciones de linkador: -Wl,-u,vfprintf -lprintf_flt -lm Las preguntas son: ¿y dónde se escriben esas “cosas”?, o ¿hay que escribirlas? ¿Por qué no se arregla simplemente con un par de clics?, o ¿por qué no es como en CCS C, o aunque sea como en CodeVisionAVR? Y las respuestas son: porque WinAVR sigue siendo un huésped de Atmel Studio 6 y todavía no tienen una mejor interacción. Tal vez en el futuro (de hecho, veremos que ha habido un pequeño avance en Atmel Studio 6 respecto del Studio 5). Por otro lado, en realidad CCS C también utiliza varias plantillas de su función printf con diferente alcance cada una, solo que antes de compilar el programa detecta las características mínimas necesarias según el código fuente y selecciona automáticamente la plantilla más adecuada. Genial idea, ¿verdad? Me quito el sombrero. Sospecho que CodeVisionAVR está cerca de conseguir esa funcionalidad. Por su parte, las recientes versiones de AVR IAR C ya cuentan con una opción Auto para este propósito.
  • 205. Retomando el tema, lo que hacen esas “cosas” son dos cosas (como en El gato ;): primero, le dicen al linkador que no utilice la versión de printf por defecto, así: -Wl,-u,vfprintf -Wl indica que la directiva va para el linkador y –u,vfprintf le dice que desconozca el símbolovfprintf (que representa la función printf y similares) que se encuentra entre las librerías que usa por defecto y que más bien lo busque en otras librerías. ¿Dónde? Para la versión minimizada, en la librería indicada por: -lprintf_min Y para la versión completa, en las librerías indicadas por: -lprintf_flt -lm Según el manual de GNU GCC (que también se instaló con tu WinAVR), la opción genérica –llibrary se refiere a la librería liblibrary.a, por lo que en realidad las librerías mencionadas sonlibprintf_min.a , libprintf_flt.a y libm.a. Si todavía sigues por ahí, selecciona tu proyecto en Atmel Studio 6, muestra su ventana de propiedades, ve a la categoría Toolchain AVR/GNU C Linker General y marca la casilla de Use vprintf library (-Wl,-u,vfprintf), tal como aparece abajo.
  • 206. Indicar a WinAVR que no use el printf por defecto en Atmel Studio 6. Esa casilla de marcado solo está disponible en Atmel Studio 6. Así que las personas que aún trabajan con el Studio 5 tendrán que escribir la directiva -Wl,u,vfprintf indicada en el cuadro de texto de Other Linker Flags de la sección Toolchain AVR/GNU C Linker Miscellaneous, como se muestra abajo.
  • 207. Indicar a WinAVR que no use el printf por defecto. Ya sea que tengamos Atmel Studio 6 ó 5, ahora nos ubicamos en Toolchain AVR/GNU C Linker Libraries y añadimos la librería libprintf_min.a, así:
  • 208. Añadiendo librerías para el linkador en Atmel Studio 6. Del mismo modo, agrega las librerías libprintf_flt.a y libm.a. Te debería quedar así:
  • 209. Indicar a WinAVR que primero busque [printf] en libprintf_flt.a (versión completa). Ahora ya podemos usar printf en sus versiones minimizada o completa. Fíjate bien en el orden de las librerías añadidas. Si en la lista aparece libprintf_flt.a antes que libprintf_min.a, entonces se utilizará la versión completa de printf y similares, como se ve arriba. Pero si la librería libprintf_min.a tiene mejor nivel que libprintf_flt.a, entonces se usará la versión minimizada de printf. Esto se ve abajo. Para no confundirte en cualquiera de los casos puedes quitar la librería que no utilices. Puse este esquema solo para no estar añadiendo librerías a cada rato y para mostrar sus prioridades de llamada. La posición relativa de libm.a no importa, pues solo contiene rutinas matemáticas para printf en su versión completa.
  • 210. Indicar a WinAVR que primero busque [printf] en libprintf_min.a (versión minimizada). Configurar el Uso de Scanf La familia de funciones scanf es mucho menos recurrente que printf. A veces su empleo es inclusive contraindicado debido a que no ofrece buenos filtros. Scanf también viene en tres versiones: La versión minimizada admite el ingreso de caracteres, cadenas de hasta 256 caracteres y números enteros de 16 bits positivos o negativos. No maneja números de punto flotante, no admite el especificador [, usado para filtrar el ingreso según un conjunto de caracteres indicado por el usuario. Tampoco permite usar el flag de conversión %. Para su uso se requieren las siguientes opciones de linkador. Allí se indican inhabilitar la versión predeterminada e incluir las librerías libscanf_min.a y libm.a. -Wl,-u,vfscanf -lscanf_min -lm La versión predeterminada provee casi todas características de scanf en versión completa. No ofrece conversiones de números de punto flotante y el ingreso de caracteres se limita a 256.
  • 211. No hacen falta opciones de linkador. Si se estableció antes otra versión, habría que remover al menos las opciones: -Wl,-u,vfscanf La versión completa sí permite las conversiones de números de punto flotante. Para su uso debemos usar las siguientes opciones de linkador. Allí se indican inhabilitar la versión predeterminada e incluir las librerías libscanf_flt.a y libm.a. -Wl,-u,vfscanf -lscanf_flt -lm Ya vimos que agregar librerías y poner las opciones –Wl, u, vfscanf es muy sencillo. El punto será cómo combinar las opciones de linkador si se usan al mismo tiempo ambas familias de printf y scanf en versiones no predeterminadas. Cualquiera de las siguientes dos formas vale (no importa el orden entre vfscanf y vfprintf): -Wl,-u,vfscanf -Wl,-u,vfprintf -Wl,-u,vfscanf -u,vfprintf Indicar a WinAVR que no use el scanf por defecto.
  • 212. Indicar a WinAVR que no use ni el printf ni el scanf por defecto. No es necesario que estén presentes las dos librerías libscanf_flt.a y libscanf_min.a al mismo tiempo, pero al estar presentes las dos, se deberá tener en cuenta el orden en que aparecen, igual que para printf. Por ejemplo, de acuerdo con la siguiente figura, scanf funcionará en su versión completa porque su librería está primero, y printf también pero porque no está la librería de la versión minimizada.
  • 213. Indicar a WinAVR que primero busque [scanf] en libscanf_flt.a (versión completa). Simulación del Programa El simulador AVR Simulator permite monitorizar el curso y evolución del programa paso a paso, es decir, ejecutar el programa instrucción por instrucción si fuera necesario y visualizar el contenido de los puertos de E/S, las variables de programa, el valor de las locaciones internas de la RAM incluyendo los registros de E/S, los Registros de Trabajo, el estado de la Pila y valor actual del Contador de Programa. También se puede medir el tiempo transcurrido entre distintos puntos del programa o simular la respuesta del programa ante ciertos eventos detectados en los puertos de E/S del AVR. Se ve genial, ¿verdad? Sin embargo, no es mucho comparado con un simulador del calibre deProteus VSM. Proteus puede hacer todo lo mencionado arriba y que veremos en seguida, pero mucho mejor. Proteus no solo simula el programa del AVR, sino que nos permite examinar con mayor acercamiento su interacción con el circuito de aplicación, y muchas veces en tiempo real. Lo cierto es que Proteus VSM no es un programa gratuito, aunque debemos reconocer que bien vale su precio, siendo, de lejos, el mejor simulador de circuitos con
  • 214. microcontroladores que existe ―al menos que yo conozca―. Así que si estás entre los afortunados que pueden conseguir Proteus, puedes leer el capítulo que se le dedica en vez de las siguientes páginas. A propósito, la versión demo disponible en su web www.labcenter.co.uk no permite simular más que los ejemplos que trae incluidos y algunos circuitos muy sencillos creados por el usuario; de poco nos serviría. Para esta sección practicaremos con el pequeño secuenciador de LEDs ledflasher3. Todas las prácticas de cursomicros.com son proyectos con Solución separada, así que da lo mismo si abres el archivo del proyecto o de la solución. Puedes abrir el proyecto o solución desde Atmel Studio 6o con doble clic en cualquiera de sus archivos, *.cproj o *.atsln, respectivamente.
  • 215. Entorno de Atmel Studio 6 con el programa de ledflasher3. Para iniciar el AVR Simulator vamos al menú Debug Start Debugging and Break o presionamosAlt + F5 o mediante su icono, como se ve abajo. Se puede incluso usar cualquiera de los comandos de depuración como Step into, Step over, etc. Cualquier camino vale.
  • 216. Con la acción indicada estamos iniciando el depurador, que más que un simulador también involucra a los verdaderos depuradores como JTAGICE3. Si en este momento hubiera conectada a la PC alguna de las tarjetas hardware como la STK600 o STK500, que incorporan interfaces de depuración, entonces Atmel Studio 6 las detectaría y nos daría a elegir con cuál depurador deseamos trabajar. Pero como no es el caso, la única opción que nos presenta es el AVR Simulator. Para algunos AVR ni siquiera esta herramienta estará disponible. Así que la seleccionamos, y hacemos clic en OK. En el futuro ya no veremos esta ventana y la simulación correrá directamente.
  • 217. Herramientas de depuración disponibles actualmente para el ATmega88P. Ahora Atmel Studio 6 nos presentará un entorno con grandes cambios que describiremos. Los Comandos de Depuración La barra de herramientas de depuración contiene atajos de varias de las opciones del menú Debug y es la que usaremos con mayor recurrencia. Si se llegara a perder se le puede volver sacar del menú View Toolbars Debug. Barra de herramientas Debug con los principales comandos de depuración. Estos botones son casi estándar en todos los softwares simuladores, emuladores y depuradores. De seguro te acordarás para siempre de muchos de ellos. Lo que sí varía un poco respecto de otros programas son las teclas de atajo que los identifican. Tabla de Comandos de Depuración en Studio 6 Start Debugging and Break. Inicia el modo de Simulación/Depuración. Stop Debugging. Detiene el modo de Simulación/Depuración. Step Into. Ejecuta una instrucción o sentencia del programa, pero si se trata de una llamada a una subrutina o función, se ingresará en el interior de su código. Step Over. Con este botón se ejecuta una instrucción o sentencia del programa. Si esta instrucción/sentencia es una llamada a subrutina o función, se ejecutará toda la subrutina/función de golpe. Step Out. Si en el momento actual el flujo del programa se encuentra dentro de una subrutina o función, se puede usar esta opción para salir de ella.
  • 218. Run to Cursor. Ejecuta el programa de corrido y se detiene en la línea donde se ubica el cursor. Para que se habilite, primero debemos colocar el cursor en un punto diferente del indicado por la flecha amarilla. Reset. Regresa la ejecución del programa al inicio del código, o mejor dicho, a la primera sentencia o instrucción disponible. Continue. Ejecuta el código del programa tan rápido como se pueda; aunque sigue estando lejos del tiempo real. A diferencia de las opciones anteriores, en este modo las ventanas no se actualizan sino hasta que paremos con un Break All o en un breakpoint. Se emplea bastante junto con los breakpoints. Break All. Detiene la evolución del programa si estamos en modo Continue. En los otros modos esta opción permanece inactiva. Show Next Statement. Es la misma flechita que dirige el flujo del programa señalando la siguiente instrucción o sentencia que se ejecutará.
  • 219. Depuración en marcha del programa ledflasher3. Las Ventanas de Depuración Estas ventanas aparecen en el menú Debug Windows solo cuando se ingresa en modo de Depuración. A continuación descubriremos la utilidad de algunas de ellas.
  • 220. Ventanas del menú Debug Windows. La ventana Breakpoints muestra todos los breakpoints del programa, si es que existen. La forma más cómoda de poner breakpoints es haciendo doble clic en el margen izquierdo de la línea deseada. Con ello aparecerán las bolitas rojas que los representan. Los breakpoints son puntos de parada que no se dejan percibir cuando se recorre el programa paso a paso con comandos como Step in o Step over. Pero sí frenan la ejecución de la depuración cuando está en modo Run, que es iniciado por el comando Continue..
  • 221. La ventana Processor presenta información de los recursos asociados con el procesador del AVR, como el Contador de Programa, el Puntero de Pila (Stack Pointer), el Registro de Estado, losRegistros de Trabajo (R00 a R31) y los Punteros de RAM (X, Y y Z). Adicionalmente brinda herramientas útiles como el Contador de Ciclos y el cronómetro Stop Watch.
  • 222. La ventana IO View muestra los Periféricos del AVR junto con todos sus Registros de E/S, organizados correspondientemente en dos paneles. Por ejemplo, en la siguiente figura el panel superior selecciona el módulo del puerto D y el panel inferior lista sus registros relacionados, en este caso PIND, DDRD y PORTD. Esta ventana también está disponible y ayuda muchísimo cuando se trabaja en modo de diseño. La diferencia es que en modo de depuración es posible modificar el valor de algunos de los Registros de E/S. Se podría cambiar, por ejemplo, el valor de los bits de PIND para simular el efecto de un switch o pulsador conectado a dicho pin del AVR.
  • 223. Las ventanas Locals y Autos visualizan las variables de programa. Son muy parecidas. La ventana Locals muestra todas las variables de ámbito local de función actual. Por ejemplo en la siguiente figura Locals presenta las variables i, j, k, b, bi, ba, c y Effect porque actualmente se está ejecutando la función main. Por otro lado, la ventana Autos es más selectiva aún. Autos solo muestra las variables locales involucradas en la operación actual. Por ejemplo, en la figura mostrada Autos contiene a Effectporque está cerca la ejecución de la sentencia Effect = '1'; según lo señala la flecha amarilla. El campo type señala el tipo de dato de la variable, con el signo arroba @ indicando la localidad deRAM donde se ubican. El hecho de que en la figura se muestre el mensaje de "unimplemented location" significa que las variables indicadas no se encuentran en la RAM; Al examinar la ventana Regitstry (al final de esta página) comprobaremos que se hallan implementadas en losRegistros de trabajo.
  • 224. Las ventanas Watch (hay 4 en total) pueden desplegar cualesquiera variables del programa, sin importar a qué ámbito pertenezcan (local o global). Inicialmente Watch aparece vacía y la forma más fácil de colocar las variables en ella es seleccionar la variable en el código y arrastrarla con el mouse hasta la ventana Watch, más o menos como se ilustra en la siguiente imagen.
  • 225. Las ventanas de Memoria no solo despliegan el contenido de las memorias RAM, FLASH o EEPROM del AVR, sino también de los Registros de Trabajo (R0 a R31) y de los Registros de E/Sviéndolos mapeados en la RAM.
  • 226. La ventana Dissassembly muestra el programa en su correspondiente código ensamblador. Es interesante ver cómo evoluciona la ejecución del programa en lenguaje C y en ensamblador en paralelo.
  • 227. La ventana Registry muestra exclusivamente todos los 32 Registros de Trabajo del AVR (R00 a R31). Vemos en la siguiente figura que después de ejecutarse la sentencia Effect = '1'; aparece resaltado de rojo el valor del registro R18. Dado que los campos así resaltados corresponden a los datos que acaban de actualizarse deducimos fácilmente que la variable local Effect se almacena en el registro R18.
  • 228. Medición de Tiempos con Stop Watch El Stop Watch y Cycle Counter se encuentran en la ventana Processor. Introducción Aprender a programar microcontroladores significa aprender a usar todos sus recursos para luego aplicarlos en el diseño deseado. Es un proceso continuo, sistemático y que demanda por sobre todo bastante paciencia. En este capítulo empezaremos por conocer el hardware interno de los AVR enfocándonos en los megaAVR de las series ATmegaXX8 yATmegaXX4, que son actualmente los mejores microcontroladores de 8 bits de Atmel disponibles en
  • 229. encapsulados DIP de 28 y 40 pines respectivamente. Las XX indican los kBytes de memoria FLASH que lleva el megaAVR, por ejemplo, el ATmega168A tiene 16 kBytes de FLASH. Entre los miembros de estas familias podemos citar al ATmega48A, ATmega88P, ATmega168PA, ATmega328P, ATmega164A, ATmega324P, ATmega644PA y ATmega1284P. Como se puede notar, al final de cada nombre todavía puede aparecer un sufijo como A, P, V oPA, que identifica una característica adicional del megaAVR como ser de una sub-serie que opera a bajo voltaje (V) o que están fabricados con tecnología picoPower (P) para trabajar consumiendo menos energía. En algunas sub-series se han corregido ciertos bugs encontrados en versiones anteriores del AVR. Esto no necesariamente significa que tengan ser mejor, pues todos los microcontroladores de todas las marcas suelen tener pequeños bugs que en la gran mayoría de aplicaciones se pasan por alto. Quizá el único momento en que nos percatemos de la sub-serie específica de nuestro mega AVR sea cuando lo identifique el software programador, leyendo su código de identificación. Por lo demás no debería preocuparnos la sub-serie del megaAVR. Los programas son compatibles tanto en código fuente como en código máquina. Así que para facilitar la lectura de aquí en adelante nos referiremos a estos megaAVR simplemente como ATmegaXX4 oATmegaXX8, entendiendo que pueden tener o no el sufijo explicado. Existe mucha compatibilidad entre los microcontroladores de Atmel, por lo que la teoría expuesta es aplicable en gran parte a otras series de AVR como los tinyAVR, los megaAVR con USB o incluso a los AVR antiguos como los clásicos ATmega8535, ATmega8515, ATmega16, ATmega32, etc. A decir verdad, los AVR que estudiaremos son como las versiones mejoradas de los "clásicos" megaAVR citados anteriormente. Características Comunes de los megaAVR Citaremos las características más notables de los ATmegaXX8 y ATmegaXX4. Quizá muchas de ellas no las comprendas de plano. Puedes tomar eso como referencia para medir tu avance en el dominio del AVR. Tienen un repertorio de 131 instrucciones. Están optimizadas para generar un mejor código con los compiladores de alto nivel, en especial el C. Poseen 32 Registros de Trabajo de 8 bits cada uno. Se denominan desde R0 a R31. Esto en realidad es aplicable a todas las familias de los AVR de bits, incluyendo los XMEGA. Tienen una velocidad de ejecución de hasta 20 MIPS (20 Millones de Instrucciones Por Segundo), que se alcanzará cuando el reloj del sistema (XTAL) sea de 20 MHz. Aunque como en cualquier otro microcontrolador, en la práctica no será una velocidad sostenida, porque en el programa habrá instrucciones que se demoran 2 ó más ciclos de instrucción. Sin embargo, siguen siendo bastante rápidos si los comparamos por
  • 230. ejemplo con sus contrapartes de Microchip, los PIC18, los cuales tienen un límite de 10 o 12 MIPS. Tienen un Timer0 de 8 bits que puede trabajar como Contador o Temporizador o Generador de hasta dos canales de ondas PWM de 8 bits de resolución. Tienen un Timer1 de 16 bits que opera en modo Contador, Temporizador o como Generador de hasta dos canales de ondas PWM con resolución configurable de hasta 16 bits. Los ATmega1284 tienen adicionalmente un Timer3 idéntico al Timer1. Tienen un Timer2 de 8 bits, parecido al Timer0 pero equipado adicionalmente para operar con un XTAL externo de 32 kHz de modo asíncrono. Tienen un Comparador Analógico. Tienen un módulo TWI (Two Wire Interface) para comunicaciones con el protocolo I2C en modos Maestro y Esclavo. Tienen un módulo SPI programable. Soporta los modos Maestro y Esclavo. Sirve además como la interface de programación serial del megaAVR. Tienen un Conversor ADC de 10 bits, con hasta 8 canales de entrada. Tienen un USART0: Puerto serie Transmisor Receptor Síncrono Asíncrono Universal. Los ATmegaXX4 tienen adicionalmente un USART1, con características idénticas a las delUSART0. Operan con voltajes de alimentación entre 1.8V y 5.5V. Mientras más alta sea la frecuencia de operación del AVR más alto será el nivel de alimentación requerido, por ejemplo, para trabajar a la máxima frecuencia de 20 MHz, Vcc debe tener un valor muy estable entre 4.5V y 5V. Tienen un Oscilador RC interno configurable como oscilador principal del sistema. Tienen 6 modos Sleep, para una mejor administración del consumo de la energía. Tienen un circuito BOD o detector de bajo voltaje de alimentación. Tienen un temporizador Watchdog, para vigilar que el programa no quede colgado. Los megaAVR de la serie ATmegaXX8 tienen 3 puertos de E/S (con 23 pines en total) y los megaAVR de la serie ATmegaXX4 tienen 4 puertos de E/S (con 32 pines en total). Oscilador del sistema seleccionable, desde el Oscilador RC interno hasta cristales de cuarzo. Tienen un modo de programación paralela de alto voltaje HVPP (a 12V) y un modo de programación serial en bajo voltaje SPI (a 5V). Los ATmegaXX4 pueden
  • 231. adicionalmente ser programados por su interface de depuración JTAG. Por otro lado, la interface debugWIREde los ATmegaXX8 no ofrece la función de programación con la misma capacidad. Tienen un sistema de depuración OCD con interface JTAG en el caso de los ATmegaXX4 y con interface debugWIRE en el caso de los ATmegaXX8. En general, la interface JTAG(compuesta por 4 pines) está disponible en todos los megaAVR de 40 pines o más y la interface debugWIRE (conformada por una sola línea) está presente en los megaAVR con menos de 40 pines. Su memoria de programa FLASH tiene una sección de Boot Loader para albergar un programa cargador del mismo nombre. De los megaAVR estudiados en este curso (entre los ATmegaXX4 y ATmegaXX8) el único que no ofrece esta funcionalidad es el ATmega48. El programa de Boot Loader le permite al microcontrolador autoprogramarse sin necesidad de usar un programador externo como el USBasp o AVR ISP MkII. Es así como trabajan los módulos Arduino, por ejemplo Empaques de los megaAVR Diagrama de pines de los ATmegaXX8 en encapsulado PDIP.
  • 232. Diagrama de pines de los ATmegaXX4 en encapsulado PDIP. Diagrama de bloques de los megaAVR El siguiente diagrama muestra los principales elementos de un AVR y que tarde o temprano los tendrás que memorizar.
  • 233. Diagrama de bloques simplificado de los megaAVR ATmegaXX4 / ATmegaXX8. Ahora una somera descripción de lo que representan estos bloques. El CPU es el circuito encargado de leer, decodificar y ejecutar las instrucciones del programa. Dispone de 32 registros de trabajo y un ALU (Unidad Aritmético Lógica) con el que realiza las operaciones de suma, resta, AND lógica, OR lógica, etc. La Memoria FLASH, de Programa almacena las instrucciones del programa del AVR. Es una memoria permanente pero que se puede reprogramar para cambiar de tarea. La Memoria RAM, de Datos aloja las variables que procesa el CPU. El Contador de Programa es un registro que evoluciona para indicar cuál será la siguiente instrucción que debe ejecutar el CPU. La Pila o Stack es un segmento de la memoria RAM para guardar el valor del Contador de Programa y también variables temporales del programa cuando sea necesario. Los periféricos del AVR son elementos que se pueden usar para una determinada tarea; por ejemplo, el Timer0 sirve para temporizaciones. El USART para comunicaciones serialesRS232, etc. Casi todos ellos serán estudiados en un capítulo aparte. Los puertos de E/S, PORTA,..., PORTD, son las líneas hacia/desde el exterior donde se pueden conectar los dispositivos a controlar, como diodos LED, transistores, LCDs, etc. Los megaAVR de 40 pines tienen los 4 puertos completos, mientras que a los megaAVR de 28 pines les falta PORTA y algunos pines en los otros puertos.
  • 234. Hay más recursos presentes dentro de un AVR que también son imprescindibles pero cuyo trabajo queda en segundo plano. Algunos de ellos serán abordados en otro momento. La Memoria de Programa Es de tipo FLASH. Aquí es donde se aloja el programa que el CPU ejecutará. Se puede modificar por completo mediante un dispositivo programador por hasta 10 000 veces. Pero tampoco deberíamos tanto. No conozco a nadie que haya llegado a ese límite con un solo microcontrolador. Lo más probable es que, por más cuidado que tengas, llegues a freír tu AVR antes de tiempo en algún accidente. Eso es algo muy “normal”. En los AVR las instrucciones de programa son de 16 ó de 32 bits. Pero siendo la gran mayoría de 16 bits, podemos decir que un AVR de N bytes de memoria FLASH puede almacenar hasta N/2 instrucciones de código ensamblador. Antiguamente parecía sencillo reconocer la cantidad de memoria FLASH que tenían algunos AVR. Por ejemplo, un ATmega32 tiene 32 k-bytes, un ATmega8L tiene 8 kbytes. Los megaAVR de ahora todavía conservan esa correspondencia entre el número que aparece en su nombre y la cantidad de k-bytes de FLASH, solo que las nuevas series también llevan un número que puede entrar a confundir un poco. En la siguiente tabla apreciamos los AVR de las series ATmegaXX8, ATmegaXX4, ATmegaXX5 yATmegaXX50. Las XX representan la cantidad de FLASH de megaAVR. Al final de cada nombre aparece el sufijo yy representando a las letras P, V, A o PA. Personalmente, creo que al haber varias series, es más fácil separar primero los números que representan la capacidad de FLASH porque deben ser números redondos (digitalmente hablando), es decir, deben ser potencias de 2, como 4, 8, 16, 32, 64 ó 128. Por ejemplo, ¿cuánta memoria FLASH tendrá un ATmega3290P?, ¿3290 kbytes, 329 kbytes, 32 kbytes o 3 kbytes? Como el único número redondo es 32, la respuesta es 32 kbytes y deducimos que este megaAVR es de la serie 90P. Se llega más rápido a la respuesta si leemos las opciones empezando por la izquierda. Tabla AVR AVR Memoria FLASH Memoria RAM Memoria EEPROM Pines de E/S ATmega48yy 4K 512 256 23 ATmega88yy 8K 1K 512 23 ATmega168yy 16 K 1K 512 23
  • 235. Tabla AVR AVR Memoria FLASH Memoria RAM Memoria EEPROM Pines de E/S ATmega328yy 32 K 2K 1K 23 - - - - - ATmega164yy 16 K 1K 512 32 ATmega324yy 32 K 2K 1K 32 ATmega644yy 64 K 4K 2K 32 ATmega1284yy 128 K 16 K 4K 32 - - - - - ATmega165yy 16 K 1K 512 54/69 ATmega325yy 32 K 2K 1K 54/69 ATmega3250yy 32 K 2K 1K 54/69 ATmega645yy 64 K 4K 2K 54/69 ATmega6450yy 64 K 4K 2K 54/69 Secciones de Aplicación y de Boot Loader Los AVR ofrecen la posibilidad de escribir en su memoria de programa FLASH incluso en tiempo de ejecución. Esta función puede ser aprovechada para almacenar datos procesados por el usuario o para permitir la auto-programación del AVR. Para facilitar y robustecer el proceso de auto-programación los AVR dividen su memoria FLASH en dos segmentos lógicos, de Aplicación y de Boot Loader. La Sección de Aplicación está destinada a almacenar el programa que el AVR ejecuta habitualmente, como leer sensores, controlar motores, etc. La Sección de Boot Loader está diseñada para almacenar el código del Boot loader, que es un pequeño programa para cargar el programa del AVR en la Sección de Aplicación, así como Windows carga en la RAM de la PC el programa que vamos a utilizar. Un Boot Loader no es precisamente un mini S.O. porque ya hay S.O. para microcontroladores conocidos como RTOS(Real Time Operating System).
  • 236. Además, a diferencia de un S.O. para PC, un Boot Loader debe ser el programa más pequeño posible y se debe ubicar al final de la memoria FLASH. No todos los megaAVR tienen Sección de Boot Loader, como el ATmega48. Secciones de Aplicación y de Boot Loader de la memoria de programa del AVR. La Sección de Boot Loader siempre se ubica al final de la memoria FLAH pero su tamaño varía de acuerdo con el AVR y con la configuración establecida por los fuses BOOTSZ1 y BOOTSZ0. Por ejemplo, el ATmega644PA puede tener una Sección de Boot Loader entre 512 palabras y 4096 palabras, según la siguiente tabla. Tabla BOOTSZ1 BOOTSZ1 BOOTSZ0 Tamaño del Boot Loader Dirección del Boot Loader 1 1 512 palabras 0x7E00 - 0x7FFF 1 0 1024 palabras 0x7C00 - 0x7FFF
  • 237. Tabla BOOTSZ1 BOOTSZ1 BOOTSZ0 Tamaño del Boot Loader Dirección del Boot Loader 0 1 2048 palabras 0x7800 - 0x7FFF 0 0 1096 palabras 0x7000 - 0x7FFF Los fuses BOOTSZ1 y BOOTSZ0 solo se pueden modificar al grabar el AVR. Su configuración por defecto siempre establece el tamaño mayor elegible de la Sección de Boot Loader. El hecho de que la Sección de Boot Loader esté diseñada para alojar el programa cargador no significa que esté limitada a esa tarea. Si no la vamos a usar para su propósito primigenio, podemos emplearla como si fuera parte de la Sección de Aplicación, o sea, como si no existiera la división entre estas dos Secciones y por ende no tendrá importancia el valor que pongamos en los fuses BOOTSZ1 y BOOTSZ0.
  • 238. Configuración de los Fuses de Boot loader en el programa grabador. A propósito, la configuración de los fuses BOOTSZ1 y BOOTSZ0 en Proteus no tiene ningún efecto porque Proteus aún no soporta simulaciones con Boot Loader. Así que están como decorativos. Configuración de los Fuses de Boot Loader en Proteus. La Memoria de Datos SRAM Actualmente suena redundante especificar memoria SRAM (Static RAM) porque casi todas las RAM de los microcontroladores son estáticas. Decimos simplemente RAM, a la memoria cuya función “tradicional” es alojar temporalmente los datos que se procesan en el programa.
  • 239. La cantidad de RAM disponible internamente depende del modelo de AVR y, a diferencia de la memoria FLASH, no tiene una directa correspondencia con el nombre del dispositivo. Aun así, podemos observar en la siguiente tabla que en muchos modelos existe una relación que se repite (los ATmega128nn rompen la relación, y pueden no ser los únicos). Los megaAVR con 4K de FLASH tienen 512 bytes de RAM, los megaAVR con 8 K y 16 K de FLASH tienen 1 K de RAM, y así, tal como se ve en la tabla. Tabla AVR AVR Memoria FLASH Memoria RAM Algunos Modelos ATmegaNNN 4 K 512 ATmega48A, ATmega48PA, ATmega48P/V, ATmega48/V ATmegaNNN 8 K 1K ATmega88A, ATmega88PA, ATmega88P/V, ATmega88/V 1K ATmega168A, ATmega168PA, ATmega168P/V, ATmega168/V ATmega164A, ATmega164PA ATmega165A, ATmega165PA ATmega169, ATmega169P 2K ATmega328, ATmega328P ATmega324A, ATmega324PA ATmega325A, ATmega325PA ATmega3250A, ATmega3250PA ATmega329P, ATmega3290P ATmegaNNN 64 K 4K ATmega644A, ATmega644PA ATmega645A, ATmega645P ATmega6450A, ATmega6450P ATmegaNNN 128 K 4K, 8K o 16K ATmega128, ATmega1281 ATmega1284, ATmega1284P ATmegaNNN 16 K ATmegaNNN 32 K En los modelos listados y en general en todos los megaAVR de las series más recientes el espacio de la memoria RAM (entendida en su concepto tradicional) empieza en la dirección0x0100 y termina en 0x02FF, 0x04FF, 0x08FF, 0x10FF, 0x20FF o 0x40FF, según el modelo. La verdad, no importa mucho saber dónde termina como la cantidad
  • 240. misma. La dirección de inicio sí es de consideración pero cuando se programa en lenguaje ensamblador. Quizá alguien pudiera preguntar por qué la dirección de inicio no es 0x0000, como en otros microcontroladores. Porque las primeras direcciones, desde 0x0000 hasta 0x00FF, están reservadas para acceder a los Registros de Trabajo y a los Registros de E/S del AVR en „modo de memoria‟. Normalmente los 32 Registros de Trabajo se acceden directamente con instrucciones como LDI o MOV. Pero también se les puede acceder direccionándolos como si fueran parte de la memoria RAM, con instrucciones como LD o ST y con las direcciones presentadas. En ocasiones esto facilitará mover bloques de datos entre la RAM y los registros de trabajo aprovechando la potencia de los punteros. Del mismo modo, los Registros de E/S, que tampoco ocupan posiciones reales en la RAM, pueden ser accedidos como si en verdad fueran RAM con las instrucciones como LD y ST, para lo cual emplean las direcciones mostradas en la figura. Los Registros de E/S y los Registros de E/S extendidos son hermanos, por decirlo de algún modo, y tienen funciones análogas. Están separados solo por tener diferente modo de acceso, pero eso se explicará mejor en su sección respectiva. El direccionamiento y distinción de los espacios de la RAM solo son de preocupación al trabajar en ensamblador. Al programar en lenguajes de alto nivel los compiladores se encargan de toda la administración de la RAM, salvo que reciban directivas avanzadas.
  • 241. Espacio de la Memoria RAM del megaAVR. El Contador de Programa, PC El PC es un registro que indica la siguiente instrucción que debe ejecutar el CPU. Si vale 0x0000, ejecutará la primera instrucción de la memoria; si vale 0x0002 ejecutará la tercera instrucción, y así... Al arrancar microcontrolador, el PC vale 0x0000 y se va incrementando automáticamente, con lo que el AVR debería ejecutar una a una desde la primera hasta la última instrucción del programa. En realidad, en el código habrá instrucciones que modifiquen el valor del PC de modo que el programa nunca termine. La Pila y el Puntero de Pila La Pila o STACK es una memoria que almacena temporalmente el valor del PC (Program Counter) cuando el programa llama a una subrutina o cuando salta a un Vector de Interrupción. También sirve para guardar datos temporalmente cuando los 32 Registros de Trabajo no sean suficientes.
  • 242. Al igual que en una PC, la Pila forma parte de RAM Interna. No es un pedazo de RAM con características especiales, es una simple área cuya dirección de inicio la puede establecer el usuario y cuyo tamaño es indefinido porque crece y decrece en tiempo de ejecución. Las que sí son especiales son las instrucciones que trabajan con la Pila, como PUSH y POP. Estas instrucciones no necesitan conocer la locación en la Pila a/de donde guardarán/recuperarán los datos. Aprovechan, en cambio, su acceso de tipo LIFO (Last In First Out), que significa “el último dato en entrar será el primero en Salir”. Es por eso que siempre se recurre a la analogía con una pila de platos de donde no podemos tomar un plato que se encuentra en el fondo o en la mitad. Para llegar a él primero tendríamos que quitar los platos que están encima. Pero hasta ahí llega la analogía porque a diferencia de las pilas de platos, las pilas en RAM crecen de arriba abajo, es decir, cada dato que se va depositando en la Pila ocupa una dirección inferior a la anterior. Esta dirección la va marcando el Puntero de Pila, el cual se decrementa cada vez que se coloca un dato en la Pila y se incrementa cada vez que se toma un dato de ella. La Pila trabaja de cabeza para evitar que sus datos colisionen (se solapen) con las variables accedidas aleatoriamente, las cuales se van mapeando en la RAM normalmente de abajo arriba. Registro SPH SPH SP15 SP14 SP13 SP12 SP11 SP10 SP9 SP8 SP7 SP6 SP5 SP4 SP3 SP2 SP1 SP0 Registro SPL SPL El Puntero de Pila está representado por los registros de E/S SPH y SPL, que concatenados actúan como un registro de 16 bits. Como se dijo anteriormente, este registro tiene un comportamiento de auto-incremento y auto-decremento. La única intervención por parte del usuario debería ser su inicialización, esto es, cargarle la última dirección de la RAM. Pero esta operación solo es indispensable al programar en ensamblador. Los compiladores como el C inicializan la Pila automáticamente. Sin embargo, incluso en C es importante conocer estos conceptos para entender por ejemplo por qué un programa para un ATmega328P nunca funcionará en un ATmega168P, incluso si el tamaño del código es pequeño y le puede caber sobradamente. Los Registros de Trabajo y los Punteros X, Y y Z
  • 243. Todos los AVR de 8 bits, desde los tinyAVR hasta los XMEGA cuentan con 32 Registros de Trabajonombrados desde R0 hasta R31. Los Registros de Trabajo tienen la función de alojar los datos más inmediatos que el CPU procesa. ¿Acaso ésa no era tarea de la RAM?. Bueno, sucede que en todos los microcontroladores inspirados en la arquitectura de los procesadores de Intel (como los AVR, ARM y Freescale entre otros) el acceso a la memoria RAM toma más ciclos que el acceso a los Registros de Trabajo. En los AVR de 8 bits, por ejemplo, se puede acceder a los Registros de Trabajo en un solo ciclo, puesto que todos están directamente conectados al CPU, o mejor dicho, son parte del CPU. En cambio, la mayoría de las instrucciones ensamblador que acceden a la RAM consumen 2 ciclos de instrucción. No es posible cargar datos en la RAM directamente ni moverlos entre locaciones diferentes de la RAM (a menos que tengan DMA, como los AVR32). Para esas operaciones los Registros de Trabajo actúan como intermediarios. Pero quizá la participación más notable de Los Registros de Trabajo sea en el ALU (Unidad Aritmético Lógica) para computar las operaciones aritméticas y lógicas. Por ejemplo imaginemos que deseamos obtener la raíz cuadrada de un número de punto flotante ubicado en la RAM y almacenar el resultado de nuevo en la RAM. En lenguaje C bastaría con escribir una sola sentencia, pero el código máquina generado involucra una tarea más compleja: al inicio el CPU mueve los 4 bytes del número desde la RAM a los Registros de Trabajo, luego viene el trabajo pesado, que implica el procesamiento de varios datos intermedios. En lo posible todos estos datos también estarán en los registros de trabajo para aprovechar su velocidad y eficacia. Solo al terminar el cómputo el CPU depositará el resultado en la RAM. Los compiladores de alto nivel también suelen emplear los Registros de Trabajo para pasar los argumentos de sus funciones. Creo que con esos ejemplos debe quedar clara la razón de ser de los Registros de Trabajo. En la siguiente figura podemos notar que los Registros de Trabajo se parten por la mitad. La diferencia está en que los primeros 16 registros (R0 a R15) no admiten la instrucción LDI, que sirve para cargar constantes al registro (otro aspecto primordial de la programación en ensamblador9. Los registros R26 a R31 tienen la capacidad adicional de funcionar como punteros de 16 bits cada uno.
  • 244. Los Registros de Trabajo de los AVR. El par de registros R27-R26 forma el Puntero X, el par R29-R28 forma el Puntero Y, y el parR31-R30 forma el Puntero Z. Los punteros pueden apuntar a (contener la dirección de) cualquier locación del espacio de RAM. Esto junto con las instrucciones adecuadas conforman el direccionamiento indirecto más potente, muy útil por ejemplo para mover grandes bloques de datos.
  • 245. Terminamos esta sección explicando las direcciones que figuran en el mapa de los Registros de Trabajo. En principio a los Registros de Trabajo no les debería hacer falta tener direcciones porque están directamente unidos al CPU. Hay instrucciones adecuadas como LDI y MOV para acceder a ellos. Sin embargo, los AVR les brindan direcciones para adicionalmente poder ser accedidos como si fueran parte de la RAM, es decir, con instrucciones que están diseñadas para la RAM, como LD y ST. De esta manera se hacen más flexibles las operaciones de transferencias de datos entre los diferentes espacios de memoria. Cuando manipulamos los Registros de Trabajo utilizando sus direcciones podemos decir que los estamos accediendo “en modo RAM”, pero sin perder de vista que los Registros de Trabajo no pertenecen a la RAM porque no están implementadas físicamente allí. Los Registros de E/S Anteriormente se dijo que para programar el AVR primero había que conocer sus recursos. Pues bien, todos ellos se pueden controlar mediante los Registros de E/S. por ejemplo, si queremos manejar el puerto B, debemos conocer los registros PORTB, DDRB y PINB. Si queremos programar el USART0, debemos conocer los registros UDR0, UCSR0A, UCSR0B, UCSR0C,UBRR0L y UBRR0H. Si queremos… En tiempo de ejecución los registros de E/S (Entrada Salida) lo controlan todo, no solo las operaciones de los módulos periféricos, como se podría inferir a partir de la denominación E/S, sino que también controlan la performance del mismo CPU. En este caso los registros a conocer serían MCUCR, MCUSR o SMCR.
  • 246. Entenderás que no tiene caso seguir mencionando las funciones de otros Registros de E/S. es por eso que cada módulo se trata por separado estudiando con detenimiento cada registro y cada uno de los bits que lo componen. Espero que los mapas de memoria de los Registros de E/S que presento no te hagan pensar que están divididos en una suerte de bancos, como en los PICmicro. No, señor, nada de eso. Todos los espacios de memoria en los AVR son lineales. Yo los quise subdividir para una mejor presentación.
  • 248. Cada registro es de 8 bits y en total se cuentan 224 registros de E/S, aunque ni siquiera la mitad están implementados físicamente. Las locaciones que aparecen sin nombre estánRESERVADAS y no nunca deberían ser accedidas. De modo similar, hay registros con algunos bits sin nombre ni funcionalidad que también se consideran reservados. Un ejemplo es el registro SMCR, mostrado abajo. No está prohibido acceder a dichos bits pero se recomienda dejarlos en 0 por compatibilidad con futuros AVR. Estas aclaraciones aparecen por doquier en los datasheets. Yo prefiero decirlas ahora para no tener que estar repitiéndolo a cada rato después. Registro SMCR SMCR --- --- --- --- SM2 SM1 SM0 SE Los antiguos microcontroladores AVR solo tenían el espacio de los 64 primeros Registros de E/S. A ellos les alcanzaba ese espacio aunque a veces a duras penas porque todos los registros estaban allí apretujados. Los Registros de E/S tenían sus propias instrucciones de acceso, IN y OUT, que de hecho todavía se usan. Las instrucciones de ensamblador IN y OUT utilizan el rango de direcciones 0x00 a 0x3F para acceder a los Registros de E/S. En el esquema mostrado estas direcciones aparecen fuera de los paréntesis. Con la aparición de nuevos periféricos en los AVR, aumentaron los registros de E/S y se sobrepasaba el alcance de las instrucciones IN y OUT. Así fue necesario diseñar los Registros de E/S para que también pudieran ser direccionados como si fueran parte de la RAM, o sea, con instrucciones como LD o ST, las cuales tienen mayor cobertura y permiten repotenciar las transferencias de datos. Para este tipo de acceso, llamado “en modo RAM”, las instrucciones como LD o ST utilizan el rango de direcciones 0x20 a 0xFF, que en los mapas de memoria aparecen dentro de los paréntesis. A juzgar por sus direcciones, ya podrás deducir que la única diferencia entre los Registros de E/S(estándar) y los Registros de E/S Extendidos es que estos últimos se acceden exclusivamente en “modo de memoria” con instrucciones como LD y ST, y los primeros todavía admiten el uso de las instrucciones clásicas como IN y OUT.
  • 249. El direccionamiento y distinción de los Registros de E/S estándar o extendidos son de especial preocupación al trabajar en ensamblador. Al programar en lenguajes de alto nivel loscompiladores son quienes escogen el modo de acceso y las instrucciones que consideren más convenientes, salvo que reciban directivas contrarias. Registros Generales del Microcontrolador Estos son registros de E/S que no están relacionados con una parte exclusiva del microcontrolador. Por tanto los volveremos a ver al mencionar en otros capítulos, aunque sea para referirnos a uno solo de sus bits. El Registro de Estado SREG Todo microprocesador tiene un registro para reflejar el estado de las operaciones lógicas y aritméticas del módulo ALU. SREG también incluye dos bits con funciones disímiles. El registro SREG es bastante utilizado en los programas en ensamblador. Cuando se trabaja con compiladores de alto nivel su presencia solo sería justificable por el uso del bit I. Registro SREG SREG I T H S V N Z C Registro de Microcontrolador I Global Interrupt Enable 1. Habilitar las interrupciones individuales habilitadas. Las interrupciones individuales serán habilitadas en otros registros de control. 0. No se habilita ninguna de las interrupciones sin importar sin importar sus configuraciones individuales. El bit I se limpia por hardware después de ocurrir una interrupción, y se setea por la instrucción para habilitar posteriores interrupciones. El bit I también se puede setear o limpiar por la aplicación con las instrucciones SEI y CLI. T Bit Copy Storage Las instrucciones para copiar bits (Bit LoaD) y BST (Bit STore) usan el bit T como inicio o destino para el bit de la operación. Con la instrucción BST se puede copiar un bit de un registro de trabajo al bit T y con la instrucción BLD se puede copiar el bit T a un bit de un registro de trabajo.
  • 250. H Half Carry Flag El flag H indica un medio acarreo en algunas operaciones aritméticas. El bit H es muy útil en aritmética BCD. S Sign Bit, S = N ? V El bit S es siempre un or exclusivo entre los flags N y V. Leer abajo. V Two’s Complement Overflow Flag El flag V soporta aritmética de complemento a dos. N Negative Flag El flag N indica un resultado negativo en una operación lógica o aritmética. Z Zero Flag El flag Z indica un resultado cero en una operación lógica o aritmética. C Carry Flag El flag C indica un acarreo en una operación lógica o aritmética. El Registro MCUCR MCUCR = MCU Control Register. MCU a su vez significa Micro Controller Unit. Aunque probablemente de este registro solo vayamos a usar el bit PUD, es bueno conocerlo ahora aprovechando que hace referencia a muchas de las características del microcontrolador que se estudiaron en este capítulo. Ampliaremos la funcionalidad del bit PUD en el capítulo deentrada y salida generales y los bits IVSEL junto con IVCE serán mejor expuestos en el capítulo de interrupciones. Registro MCUCR MCUCR JTD BODS BODSE PUD --- --- IVSEL IVCE Registro de Microcontrolador JTD JTAG Interface Disable 0. La interface JTAG estará habilitada si está programado el fuse JTAGEN. 1. la interface JTAG está deshabilitada. Para evitar deshabilitaciones no intencionadas de la interface JTAG, se debe seguir
  • 251. una secuencia especial para cambiar este bit: el software de aplicación debe escribir este bit dos veces el valor deseado dentro de cuatro ciclos de reloj. Este bit no se debe alterar cuando se esté usando el sistema OCD (On Chip Debug) BODS BOD Sleep Sirve para deshabilitar el circuito del BOD durante el modo Sleep, si estaba activo. Recuerda que se activa por los fuses BODLEVEL2-0. Para poner 1 en el bit BODS se requiere seguir una secuencia especial que involucra el bit BODSE. Primero se escribe 1 en los bits BODS y BODSE. Luego se debe escribir 1 en el bit BODS y 0 en el bit BODSE dentro de los siguientes cuatro ciclos de reloj. El bit BODS estará activo durante tres ciclos de reloj después de haberse seteado. Para apagar el circuito BOD para el modo Sleep actual se debe ejecutar la instrucción Sleep mientras el bit BODS esté activo. El bit BODS se limpia automáticamente después de tres ciclos de reloj. BODSE BOD Sleep Enable El bit BODSE habilita la configuración del bit de control BODS, como se explicó en la descripción del bit BODS. PUD Pull-up Disable 1. Se deshabilitan las pull-up de todos los pines de los puertos, sin importar el valor de los registros DDRx y PORTx. 0. Las pull-up se habilitaran por los registros DDRx y PORTx. IVSEL Interrupt Vector Select 0. Los Vectores de Interrupción se ubican en el inicio de la memoria FLASH. 1. Los Vectores de Interrupción se mueven al inicio de la Sección de Boot Loader de la memoria FLASH. Para evitar cambios no intencionados en este bit se debe seguir una secuencia especial: a. Escribir 1 en el bit IVCE. b. Dentro de los cuatro ciclos de reloj siguiente, escribir el valor deseado en el bit IVSEL mientras se escribe un 0 en el bit IVCE. Las interrupciones se deshabilitarán automáticamente durante la ejecución de esta secuencia. Las interrupciones se deshabilitan en el ciclo en que se setea el bit IVCE y
  • 252. permanecen deshabilitadas hasta después de la instrucción que escribe el bit IVSEL. Si no se escribe el bit IVSEL, las interrupciones permanecen deshabilitadas por cuatro ciclos de reloj. El bit I en el registro de estado SREGno se afecta por la deshabilitación automática. Nota: si los Vectores de Interrupción están colocados en la Sección de Boot Loader, y el bit de candado BLB02 está programado, las interrupciones se deshabilitan durante la ejecución del programa desde la Sección de Boot Loader. IVCE Interrupt Vector Change Enable Se debe escribir 1 en el bit IVCE para habilitar el cambio del bit IVSEL. El bitIVCE se limpia por hardware cuatro ciclos después de que se haya escrito o cuando se escribe el bit IVSEL. Setear el bit IVCE deshabilitará las interrupciones, como se explicó en la descripción del bit IVSEL. El Registro MCUSR MCUSR = MCU Status Register. Es el registro de estado del microcontrolador, MCU. MCUSR está conformado por bits de Flag que sirven para identificar la causa del reset del microcontrolador. Para averiguar la fuente de reset el programa debe leer el registro MCUSR tan pronto como sea posible y luego limpiar sus Flags. Observa que a diferencia de los Flags de las Interrupciones, los Flags de MCUSR se limpian escribiendo un cero y no un uno. Registro MCUSR MCUSR --- --- --- JTRF WDRF BORF EXTRF PORF Registro de Microcontrolador JTRF JTAG Reset Flag Este bit se pone a uno si se produce un reset por un uno lógico en el Registro JTAG Reset seleccionado por la instrucción de JTAG AVR_RESET. Este bit se pone a cero por un Reset POR, o escribiendo un cero lógico en el flag. WDRF Watchdog Reset Flag Este bit se pone a uno cuando se produce un Reset por el Watchdog. Este bit se pone a cero por un reset Power-on o escribiendo un cero lógico en el flag. BORF Brown-out Reset Flag Este bit se pone a uno cuando se produce un Reset Brown-out. Este bit se pone a
  • 253. cero por un reset Power-on o escribiendo un cero lógico en el flag. EXTRF External Reset Flag Este bit se pone a uno cuando se produce un Reset Externo. Este bit se pone a cero por un reset Power-on o escribiendo un cero lógico en el flag. PORF Power-on Reset Flag Este bit se pone a uno cuando se produce un Reset Power-on. Este bit se pone a cero escribiendo un cero lógico en el flag. Los Fuses de los AVR Los fuses del microcontrolador establecen una característica importante en su operación, tanto que solo se puede modificar en el momento de programarlo. Por ejemplo, no se podría escoger el tipo de oscilador a usar después de haber iniciado el programa. Sería como cambiarle los neumáticos a un automóvil en marcha. Cada aplicación puede requerir una configuración particular y si no se establecen los fuses correctos, el programa puede funcionar mal, suponiendo que funcione. :) A diferencia de los PICmicro, en los AVR los fusesno pueden formar parte del código HEX, así que siempre será necesario configurarlos directamente en el entorno del programa grabador de AVR. Los fuses sí pueden incluirse en los archivos de depuración como ELF, para simulaciones o depuraciones. Por otra parte, y esto es para bien, hay muy poca variación en los fuses de los AVR, inclusive de distintas series. Por eso serán de fácil recordación, en contraste con los fuses de los PICmicro, donde son casi in-memorizables. La comparación es siempre con los PIC18, por supuesto, porque los PIC16 en general no están a la altura. Los fuses están contenidos en los denominados Bytes de Fuses, que son registros (de 8 bits obviamente) implementados en EEPROM (no en la EEPROM de datos de AVR). Por eso un 1 es el valor por defecto cuando un bit no está programado. Los megaAVR más recientes tienen 3 Bytes de Fuses llamados Byte de Fuses Bajo, Alto yExtendido. El Byte de Fuses Bajo es el mismo en todos ellos. Los otros empiezan a cambiar un poco, en orden más que en funciones, según cada modelo en particular. Mientras más reciente sea la serie, se encontrarán menos divergencias. El conjunto de Bytes de Fuses mostrado a continuación pertenece a los AVR de la serieATmegaXX4. En otras series pueden aparecer, desaparecer o cambiar algunos fuses, por ejemplo los AVR de la serie ATmegaXX5 tienen adicionalmente el fuse RSTDISBL. En lo sucesivo se describirán las funciones de los fuses más conocidos, no solo los presentados aquí.
  • 254. Tabla Byte de Fuses Bajo Byte de Fuses Bajo Bit Descripción Valor por Defecto CKDIV8 7 Divide clock by 8 0 (programado) CKOUT 6 Clock output 1 (sin programar) SUT1 5 Select start-up time 1 (sin programar) SUT0 4 Select start-up time 0 (programado) CKSEL3 3 Select Clock source 0 (programado) CKSEL2 2 Select Clock source 0 (programado) CKSEL1 1 Select Clock source 1 (sin programar) CKSEL0 0 Select Clock source 0 (programado) Tabla Byte de Fuses Alto Byte de Fuses Alto Bit Descripción Valor por Defecto OCDEN 7 Enable OCD 1 (sin programar) JTAGEN 6 Enable JTAG 0 (programado) SPIEN 5 Enable Serial Programming 0 (programado) WDTON 4 Watchdog Timer always on 1 (sin programar) EESAVE 3 EEPROM memory is preserved 1 (sin programar) BOOTSZ1 2 Select Boot Size 0 (programado) BOOTSZ0 1 Select Boot Size 0 (programado) BOOTRST 0 Select Reset Vector 1 (sin programar)
  • 255. Tabla Byte de Fuses Extendido Byte de Fuses Extendido Bit Descripción Valor por Defecto – 7 – 1 – 6 – 1 – 5 – 1 – 4 – 1 – 3 – 1 BODLEVEL2 2 Brown-out Detector trigger level 1 (sin programar) BODLEVEL1 1 Brown-out Detector trigger level 1 (sin programar) BODLEVEL0 0 Brown-out Detector trigger level 1 (sin programar) Pongo en relieve estos detalles porque hay muchos programadores, como el famoso AVRDUDE, donde los fuses se configuran bit a bit al momento de grabar el AVR. ¿Cómo haríamos si tuviéramos que configurar el uso de un oscilador de XTAL de 16 MHz en un ATmega324P? Primero tendríamos que conocer los fuses correspondientes, ¿cierto? En este caso son CKSEL0,CKSEL1, CKSEL2 y CKSEL3. Luego tendríamos que descubrir cuáles son los bits que corresponden a esos fuses y la combinación correcta para un XTAL de 16MHz. Lamentablemente la posición de los bits de fuses suele variar entre los distintos ejemplares de AVR, cosa que complica la labor en entornos donde debemos establecer el código de los fuses en valor hexadecimal. En la siguiente imagen se muestra la grabación de los fuses desde la línea de comandos de AVRDUDE.
  • 256. Grabación de los fuses desde AVRDUDE. AVRDUDE es muy bueno pero no tiene un entorno gráfico. Se le han desarrollado algunas máscaras que facilitan su uso como SinaProg o AVR Burn-O-Mat pero no son del todo convincentes. También existen programadores con software de mejor interface. Los mejores son desde luego los mismos productos de Atmel como el programador STK500 que es comandado desde Atmel Studio 6 como se ilustra abajo. Allí vemos que podemos configurar los fuses de forma individual simplemente marcando las casillas correspondientes. Los cambios hechos se reflejarán en los Bytes de Fuses mostrados en el panel inferior. También es posible modificar los fuses directamente desde allí. Configuración de los fuses desde Atmel Studio 6.
  • 257. A propósito de la pregunta previa, la siguiente toma nos indica que hay hasta 8 opciones para usar un XTAL mayor de 8MHz. Podrías elegir cualquiera, a menos que sepas lo que significan los retardos de reset y de arranque, y estés seguro de que necesitas uno de ellos. Por supuesto que examinaremos todos estos aspectos más adelante. Configuración de los fuses de reloj desde Atmel Studio 6. CKSEL3-0. Selección del Reloj
  • 258. Este fuse se representa por los bits CKSEL3, CKSEL2, CKSEL1 y CKSEL0. Sirve para adaptar el circuito interno del oscilador según el componente externo o interno que se usará como fuente del reloj del sistema. Reloj Externo. En este caso la fuente de reloj será una señal de onda cuadrada externa aplicada al pin XTAL1 del AVR. Su frecuencia podrá estar en todo el rango posible, desde 0 hasta 20 MHz. Se establece con los bits CKSEL3-0 = 0000, respectivamente. Oscilador RC Interno Calibrado. Con esta opción no se necesitarán añadir componentes externos. El reloj será el oscilador RC Interno, el cual tiene una frecuencia cercana a 8MHz, y adicionalmente ofrece al diseñador la posibilidad de ajustar por software dicha calibración hasta en 1%. Es útil para sistemas de bajo costo aunque de menor nivel de estabilidad. Es la opción por defecto. Se establece poniendo los bits CKSEL3-0 = 0010, respectivamente. XTAL Externo de Baja Frecuencia. Para utilizar un XTAL externo de 32.768 kHz con capacitores de estabilización opcionales. Es una configuración diseñada para aplicaciones de reloj. En los ATmegaXX8 y ATmegaXX4 se establece poniendo los bits CKSEL30 = 0100 o 0101. En otros megaAVR puede variar. Oscilador RC Interno de 128kHz. Este oscilador es parecido al RC de 8MHz, solo que ofrece menor precisión y no se puede calibrar. En los ATmegaXX8 y ATmegaXX4 se establece con los bits CKSEL3-0 iguales a 0011. En otros megaAVR puede no estar disponible. XTAL Externo de Baja Potencia. Esta configuración soporta cristales de 0.9 MHz hasta 16 MHz. Estos cristales ahorran energía pero su uso es recomendado solo en ambientes libres de ruido. El XTAL se coloca entre los pines XTAL1 y XTAL2 del AVR y debe usar capacitores externos de entre 12pf y 22pf, similar al circuito con XTAL estándar de alta frecuencia. En los ATmegaXX8 y ATmegaXX4 los valores de los bits CKSEL3-0 dependerán del rango de frecuencia del XTAL, según la siguiente tabla. Tabla Rango del XTAL en MHz Rango del XTAL en MHz CKSEL3-0 0.9 - 3.0 1011 o 1010
  • 259. Tabla Rango del XTAL en MHz Rango del XTAL en MHz CKSEL3-0 3.0 - 8.0 1101 o 1100 8.0 - 16.0 1111 o 1110 XTAL Externo Estándar. Con esta configuración el XTAL usado estará entre 0.4 MHz y 20 MHz (el máximo admisible). El XTAL se coloca entre los pines XTAL1 y XTAL2 del AVR y debe tener capacitores externos de entre 12pF y 22pF. En los ATmegaXX8 y ATmegaXX4 se establece con los bits CKSEL3-0 = 0111 o 0110, dependiendo de la frecuencia del cristal usado, según la siguiente tabla. Tabla Rango del XTAL en MHz Rango del XTAL en MHz CKSEL3-0 0.4 - 16 0111 o 0110 0.4 - 20 0111 Observa que con los 4 bits CKSEL3-0 es posible formar hasta 16 combinaciones. Entre las no citadas, algunas están reservadas y no deberían usarse, y otras corresponden a configuraciones relacionadas con el uso de un Resonador Cerámico en lugar del XTAL externo estándar o de baja potencia. Excluí las combinaciones del resonador cerámico para evitar una sobrecarga de números innecesaria. En la mayoría de las prácticas de cursomicros.com se usa un XTAL de 8 MHz. Puede ser estándar o de baja potencia. El XTAL de cuarzo brinda el reloj más preciso y estable, lo que es ideal para aplicaciones que usan los módulos síncronos como el USART o TWI (I2C). Debemos recordar que un 0 es un bit programado y un 1 es un bit sin programar. En las interfaces gráficas de los programadores 0 suele ser una casilla marcada. Por otro lado, en el entorno de Atmel Studio 6 la elección es directa. La siguiente imagen nos muestra las opciones de reloj disponibles para el ATmega324P, incluyendo los retardos de reset y de arranque que trataremos a continuación.
  • 260. Configuración de los fuses de reloj más los retardos de RESET y de Arranque en Atmel Studio 6. SUT1-0. Retardos de Reset y de Arranque Se representa por los bits SUT1 y SUT0. En realidad se trata de dos temporizadores. El Retardo de Reset funciona con el circuito RC del Watchdog y se encarga de mantener el AVR en estado de reset por 4.1 ms ó 65 ms después de un RESET, por ejemplo, después de conectar la alimentación Vcc del AVR. Esto serviría para que se estabilice el nivel de Vcc antes de que el CPU del AVR empiece a trabajar. Se representa por .
  • 261. Mecanismo del Retardo de RESET en los AVR. Pero ahí no termina. Para asegurarse de que el oscilador del sistema también se haya estabilizado, habrá un Retardo de Arranque hasta que transcurran 1K o 16K ciclos de reloj CK. Durante ese lapso el AVR también se mantiene en estado de reset y luego recién el procesador empezará a ejecutar el programa. El Retardo de Arranque no solo se activa después de un reset sino también después de que el AVR salga de los estados Power-save o Power-down, que son dos de los seis modos SLEEP que tienen los megaAVR. La configuraciones de los retardos de reset y de arranque varían de acuerdo con la fuente de reloj del sistema. La siguiente tabla muestra solo las opciones admisibles para un XTAL estándar o de baja potencia. Tabla CKSEL0 CKSEL0 SUT1 SUT0 0 10 14CK 1K CK XTAL baja frecuencia, BOD enabled 0 11 4.1ms + 14CK 1K CK XTAL baja frecuencia, fast rising power 1 00 65ms + 14CK 1K CK XTAL baja frecuencia, slowly rising power 1 01 14CK 16K CK XTAL, BOD enabled 1 10 4.1ms + 14CK 16K CK XTAL, fast rising power Retardo de reset Retardo de arranque Fuente de Oscilador, Condiciones de alimentación
  • 262. Tabla CKSEL0 CKSEL0 SUT1 SUT0 1 11 Retardo de reset 65ms + 14CK Retardo de arranque 16K CK Fuente de Oscilador, Condiciones de alimentación XTAL, slowly rising power En esta tabla XTAL baja frecuencia no se refiere al XTAL de reloj de 32.768 kHz, sino a un XTAL estándar o de baja potencia cuya frecuencia no esté cercana a la máxima admisible por el AVR, que para los ATmegaXX8 o ATmegaXX4 es de 20MHz. Esta condición concuerda con el valor del bit CKSEL0 dado en las tablas de los bits CKSEL30 vistas anteriormente. Como se ve algo confuso, vamos a poner un ejemplo de diseño. Supongamos que nuestra aplicación utilizará un ATmega644P a una frecuencia muy estable de 20 MHz. En primer lugar debemos utilizar un XTAL estándar de 20MHz, para lo cual debemos programar los bits CKSEL3-0con 0111 (ver las tablas de arriba). Como el bit CKSEL0 en este caso es 1, descartamos las configuraciones de la tabla donde CKSEL0 es 0. Así mismo, como 20MHz no es una frecuencia baja, también descartamos la configuración de XTAL baja frecuencia, slowly rising power. Ahora todavía tenemos las tres opciones resaltadas en la siguiente tabla. Tabla CKSEL0 CKSEL0 SUT1 SUT0 0 10 14CK 1K CK XTAL baja frecuencia, BOD enabled 0 11 4.1ms + 14CK 1K CK XTAL baja frecuencia, fast rising power 1 00 65ms + 14CK 1K CK XTAL baja frecuencia, slowly rising power 1 01 14CK 16K CK XTAL, BOD enabled 1 10 4.1ms + 14CK 16K CK XTAL, fast rising power 1 11 65ms + 14CK 16K CK XTAL, slowly rising power Retardo de reset Retardo de arranque Fuente de Oscilador, Condiciones de alimentación
  • 263. ¿Cuál elegir? Vemos que los ciclos de reloj son todos iguales a 16K CK. Entonces nuestra decisión dependerá del retardo de reset requerido. Si nuestro circuito tiene una alimentación Vcc que se eleva lentamente (slowly rising power), “debemos” elegir un retardo de 65ms con SUT1-0 = 11. Pero si en nuestro circuito el nivel de Vcc se eleva rápidamente (fast rising power), podemos optar por el retardo de 4.1ms con SUT1-0 = 10. Por supuesto que también en este caso podemos optar por el retardo de 65ms. Por último, explicamos lo que significa XTAL, BOD enabled. BOD es un circuito interno que detecta cuando la tensión de Vcc cae debajo de ciertos niveles. También sirve para que el AVR no esté trabajando con una alimentación defectuosa. Así que configurar el retardo de 14 CK (lo mismo que nada) equivale a no utilizar retardo de reset y solo debe utilizarse cuando nuestra aplicación trabaje con un circuito BOD externo o interno. El BOD interno del AVR se configura con los bits BODLEVEL2-0. CKDIV8. Prescaler del Reloj del Sistema Los megaAVR recientes, como los tratados en este capítulo, tienen un prescaler en el sistema de reloj, que dividirá la frecuencia del reloj cualquiera que sea su fuente (XTAL, RC Interno etc.). El Prescaler puede dividir la frecuencia del reloj entre 256, entre 128, entre 64,… hasta 1, dependiendo del valor que se cargue en el Registro de E/S CLKPR, el cual se puede modificar en cualquier momento en tiempo de ejecución. En este momento basta con saber que el bit CKDIV8 configura el registro CLKPR para que el reloj se divida inicialmente entre 8 o entre 1. Tabla CKDIV8 CKDIV8 Factor del Prescaler del Reloj 0 8 1 1 El valor por defecto del bit CKDIV8 es 0 = programado = reloj dividido entre 8. Si lo dejamos así, nuestro AVR operará 8 veces más lento. Es más sencillo desmarcar esa casilla que escribir el código que reconfigura el Prescaler. A propósito, el Wizard de CodeVisionAVR genera ese código automáticamente. BODLEVEL2-0. Voltaje de Disparo del BOD
  • 264. Es un reset por baja tensión. Esta característica le permite al AVR auto resetearse cada vez que detecte una caída de tensión en la alimentación Vcc, por debajo de un nivel establecido por los bits BODLEVEL2, BODLEVEL1 y BODLEVEL0. En la figura subsiguiente la señal de RESET INTERNO se activa cuando el nivel de Vcc cae por debajo de VBOT-. Luego para que el procesador del AVR vuelva a ejecutar el programa no solo bastará que Vcc supere el valor de VBOT+, sino que transcurra el RETARDO DE RESET. La diferencia entre VBOT+ y VBOT- constituye el mecanismo de histéresis. El AVR tiene un filtro pasa-bajas interno para evadir el ruido y evitar un reset ante los micro valles de tensión en Vcc. La caída de tensión tiene que ser mayor a 2us (valor típico). Tabla BODLEVEL2-0 BODLEVEL2-0 Voltaje de disparo de BOD (valor típico) 111 BOD Desactivado 110 1.8 101 2.7 100 4.3 Mecanismo del BOD o Reset interno del AVR producido por la caída de tensión en Vcc.
  • 265. Configuración de los Fuses de BOD desde Atmel studio 6. WDTON. Habilitación del Watchdog El Watchdog o WDT es un temporizador que puede monitorizar el funcionamiento fluido del microcontrolador. El WDT lo estudiaremos al final porque no es imprescindible. Por el momento diremos que se trata de un temporizador que una vez habilitado debemos resetear periódicamente en el programa. Si no lo hacemos, causará un reset en el AVR y el programa se volverá a ejecutar desde cero. Cuando el WDT no está habilitado por su fuse WDTON (hardware) todavía es posible activarlo por software. Pero una vez activado por su fuse no habrá rutina software que lo apague. En la mayoría de las prácticas no querremos estar preocupándonos del estado del WDT, así que la mejor decisión será tenerlo inhabilitarlo, que felizmente es el valor por defecto del bit WDTON, así que ni siquiera tendríamos que tocarlo. CKOUT. Salida de Reloj
  • 266. Cuando este fuse está programado la señal de reloj del sistema saldrá por el pin CLKO de los megaAVR (ver más abajo). Esta señal incluye el efecto del Prescaler de reloj. Puede servir para sincronizar el microcontrolador con otros dispositivos de la aplicación, pero se usa muy raramente, así que lo habitual es dejar CKOUT con su valor por defecto 1, o sea sin programar. Si se programa accidentalmente y el diseño utiliza el pin CLKO para una función de E/S, se puede producir un corto circuito que dañe el pin o hasta el megaAVR completo. Pines de salida de reloj en los AVR de las series ATmegaXX8 y ATmegaXX4.
  • 267. OCDEN y JTAGEN. Habilitar Depuración JTAG El fuse OCDEN sirve para habilitar el módulo OCDinterno del megaAVR. El módulo OCD (On Chip Debug) es el sistema de depuración que poseen los microcontroladores. Está constituido por todo un circuito que puede monitorizar el estado del CPU, de los periféricos internos, de todos los registros del AVR y de las memorias RAM, FLASH y EEPROM. Los resultados serán enviados a una computadora a través de una interface que puede ser JTAG, debugWIRE,PDIo aWire, según el tipo de microcontrolador. Del lado de la computadora estará corriendo un programa como Atmel Studio 6 en mododepuración para recibir todos los datos e ir visualizándolos en la pantalla. También es posible enviar desde la PC comandos de ejecución del programa como Step into, Step over, etc. En otras palabras, es como correr el simulador deAtmel Studio 6 o Proteus VSM, pero esto será real y a veces en tiempo real. En el caso de los ATmegaXX4 el módulo OCD se conecta al exterior mediante la interface JTAG, la cual está conformada por los pines TMS, TCK, TDI y TDO (ver más abajo). La interface JTAG debe ser habilitada por separado porque tiene doble función: Además de la depuración también sirve para programar el AVR. La interface JTAG se puede habilitar/deshabilitar de dos formas: por hardware, programando el fuse JTAGEN y por software, mediante el bit JTD del registro MCUCR. En consecuencia, si vamos a programar nuestro AVR por JTAG solo es necesario activar el fuseJTAGEN y tener el bit JTD del registro MCUCR en cero. De hecho, el AVR viene de fábrica conJTAGEN activado y el registro MCUCR inicia con todos sus bits en cero. Pero si además vamos a utilizar la interface JTAG para depurar el programa del AVR, también tenemos que activar el fuse OCDEN. El valor predeterminado de OCDEN es desactivado y si no vamos a entrar en depuración, debemos dejarlo así para que su circuito no consuma energía innecesariamente. Para programación o para depuración, si la interface JTAG está habilitada, los pines TMS, TCK,TDI y TDO no podrán ser usados como pines de E/S convencionales. Son 4 pines que solo los megaAVR de varios puertos pueden ostentar.
  • 268. Pines de interface JTAG en los ATmegaXX4. DWEN. Habilitar Depuración debugWIRE No todos los AVR tienen un sistema de depuración sofisticado con interface JTAG porque demanda muchas líneas de E/S. Para los AVR que no disponen de tantos pines Atmel diseñó interfaces más sencillas como debugWIRE o aWire, conformadas por una sola línea, que es el mismísimo pin de RESET del AVR, aunque normalmente no se grafica así en sus diagramas de pines. aWire es propio de los AVR de 32 bits y sirve para depuración como para depuración. En cambio,debugWIRE solo se encuentra en los AVR de 8 bits y no permite la programación del AVR. A diferencia de JTAG, para la depuración debugWIRE solo se requiere activar el fuse DWEN. Con ese hecho se pone en acción el circuito OCD interno del AVR al tiempo que el pin DW / RESETqueda destinado como pin de E/S para la depuración. La configuración predeterminada del fuse DWEN es deshabilitada y debemos dejarla así si no lo vamos a usar. De lo contrario, el circuito de monitoreo del OCD interno también estará funcionando activamente, consumiendo energía innecesaria; y lo peor de todo, perderemos la opción de reprogramar nuestro AVR a bajo voltaje. Para rehabilitarlo tendríamos que utilizar una interface de programación a alto voltaje. Si retomamos nuestro enfoque sobre los megaAVR de 3 y 4 puertos, diremos que losATmegaXX4 poseen depuración con interface JTAG y los ATmegaXX8, depuración con interfacedebugWIRE.
  • 269. Pin de interface debugWIRE en los ATmegaXX8. EESAVE. Preservar Contenido de la EEPROM Cada vez que grabamos nuestro AVR con un nuevo programa se ejecutará previamente un borrado completo de la memoria FLASH y de la EEPROM interna. Si queremos que la EEPROM no se borre en este proceso debemos programar este fuse. Su configuración predeterminada es sin programar, como se muestra abajo. SPIEN. Habilitación de Programación SPI Este fuse permite habilitar o deshabilitar la programación SPI. Para entender esto primero debemos saber que los microcontroladores AVR tienen los siguientes modos de programación (sin contar la programación por la interface de depuración): La programación mediante la interface SPI (Serial Port Interface) en bajo voltaje, esto es, con 0V aplicados al pin RESET. Es la interface más común y está presente en casi todos los AVR, incluyendo por supuesto en nuestros estudiados ATmegaXX4 y ATmegaXX8. La programación mediante la interface Paralela. Aquí el voltaje aplicado al pin RESET debe estar comprendido 11.5 V y 12.5 V. Este modo permite programar algunos fuses, comoSPIEN y RSTDISBL, que no son accesibles desde la interface SPI en bajo voltaje. Sin embargo, la interface paralela tiene el inconveniente de requerir cerca de 15 pines del AVR. La programación mediante la interface SPI en alto voltaje, donde se aplica 12V al pin RESET. Solo se encuentra en los pequeños AVR que no tienen los suficientes pines para soportar la programación paralela, en algunos tinyAVR para ser más precisos.
  • 270. Por lo tanto, la programación en alto voltaje, serial o paralela, siempre estará disponible pero la programación serial SPI se puede deshabilitar mediante el fuse SPIEN. La modificación del bitSPIEN solo es posible desde la programación en alto voltaje. Los AVR vienen con su programación serial activada de fábrica, así que podemos deshabilitarla en cualquier momento y desde cualquier interface. Sin embargo, dados los peligros que esto supone algunos softwares de programación nos previenen de su acceso. Este es el caso del programador AVRFLASH de Mikroe, como se aprecia en la siguiente imagen. BOOTSZ1-0. Tamaño de la Sección de Boot Loader Este fuse configura el tamaño que tendrá la Sección de Boot Loader, estudiada previamente. Por defecto establece el máximo tamaño disponible. Pero si no vamos a usar esta característica del megaAVR, no interesan los valores que tengan los bits BOOSZ1 y BOOTSZ0. BOOTRST. Ubicación del Vector de Reset
  • 271. El Vector de Reset es la dirección de la memoria FLASH por donde se empezará a ejecutar el programa. Normalmente es 0x0000 porque el código del programa empieza a mapearse desde la primera dirección. La única situación en que esto debe cambiar es cuando se usa un programa de Boot Loader. Cuando el fuse BOOTRST está activado el vector de reset será la primera dirección de la Sección de Boot Loader. Como es de esperar, la configuración por defecto de este fuse es sin programar y no debería modificarse a menos que se sepa bien lo que se hace. Configuración de los fuses BOOTSZ1-0 y BOOTRST desde Atmel Studio 6. RSTDISBL. Deshabilitar Reset Externo Suele estar disponible en los AVR que cuentan con pocos puertos. Por defecto este fuse no está programado y el pin de RESET cumple su función habitual de reiniciar la
  • 272. ejecución del programa cuando se pone a nivel bajo, además de permitir al AVR entrar en modo de programación de bajo voltaje. Pero si programamos el bit RSTDISBL, el pin RESET trabajará como PC6, o sea, como el séptimo pin de E/S del puerto C. Yo no suelo programar este bit porque me gusta resetear el AVR a cada rato para asegurarme de que programa siempre inicia bien. Pero más allá de gustos, debemos insistir en que si el pin RESET pierde su función habitual, ya no podremos reprogramar nuestro AVR con bajo voltaje. De llegar a esa fatalidad, deberemos utilizar un programador que trabaje a alto voltaje (12V) para reponer el estado del fuse RSTDISBL. Pin de RESET multiplexado en los ATmegaXX8. SELFPRGEN. Habilitar Auto programación Este fuse solo está disponible en los megaAVR que no dividen su memoria en secciones de Aplicación y de Boot Loader, es decir, en los AVR que no soportan el Boot Loader convencional, por ejemplo, el ATmega48P y la mayoría de los tinyAVR, es decir, en los AVR con poca memoria FLASH. Con el fuse programado se podrá utilizar la instrucción de ensamblador SPM en cualquier parte del programa. En caso contrario SPM no tendrá efecto. Por defecto el fuse SELFPRGEN está sin programar. Ya sea que se trabaje en C o en ensamblador, el programador normalmente sabe si va a acceder a la memoria FLASH para escritura. En ese caso se deberá activar este fuse. Y si no tienes idea de lo que estoy hablando, casi te puedo asegurar que no interesa el valor que le des a este fuse y hasta te recomendaría que lo actives.
  • 273. El fuse SELPPRGEN solo está disponible en los AVR de poca memoria FLASH. Lock Bits o Bits de Candado No por estudiarlos al final del capítulo significa que los Bits de Lock sean irrelevantes. Todo lo contrario. Su configuración es más importante que la programación de la memoria FLASH y de los fuses porque implica precisamente el tipo de protección que se dará a esos espacios. Los Bits de Lock establecen un nivel de protección para el contenido de la memoria FLASH, de los fuses y de los propios Bits de Lock. Por eso el nombre Lock (candado, en inglés). Los Bits de lock están contenidos en el llamadoByte de Lock. Este es un registro independiente cuya programación se lleva a cabo al margen de la programación de los fuses y de las memorias FLASH y EEPROM del AVR. No hay condiciones especiales para ello. Si no están bloqueados por ellos mismos, se pueden modificar desde cualquier interface de programación, sin necesidad de un programador especial. Para mayor alivio, diremos que solo puede ser necesario marcarlos en un producto terminado. En el resto de aplicaciones, sobre todo de experimentación, ni siquiera nos acordaremos de ellos.
  • 274. Todos los megaAVR disponen de los Bits de Lock generales LB1 y LB2 y, obviamente, los Bits de Lock de Boot Loader solo están presentes en los megaAVR con soporte de Boot Loader. Tabla Byte de Bits de Lock Byte de Bit Descripción Bits de Lock Valor por defecto - 7 – 1 (sin programar) - 6 – 1 (sin programar) BLB12 5 Boot Lock bit (Bit de candado de Boot Loader) 1 (sin programar) BLB11 4 Boot Lock bit (Bit de candado de Boot Loader) 1 (sin programar) BLB02 3 Boot Lock bit (Bit de candado de Boot Loader) 1 (sin programar) BLB01 2 Boot Lock bit (Bit de candado de Boot Loader) 1 (sin programar) LB2 1 Lock bit (Bit de candado general) 1 (sin programar) LB1 0 Lock Bit (Bit de candado general) 1 (sin programar) Los Bits de Lock generales LB1 y LB2 establecen tres modos de protección de las memorias del AVR, de sus fuses y de los mismos Bits de Lock, como se describen en la tabla de abajo. Para entender esto debemos saber que a diferencia de algunos otros microcontroladores donde la programación de la memoria FLASH suele ir unida con la programación de sus fuses, en los AVR es posible programar y leer por separado los diferentes espacios de memoria [no volátiles] que poseen. El modo por defecto es 1. Allí podremos seguir reprogramando por separado las memorias FLASH y EEPROM del AVR así como los fuses. Sobre los mismos Bits de Lock, todavía podremos programarlos para pasar al modo 2 y 3. En el modo 2 las opciones disponibles desde las interfaces de programación serial y paralela serán: primero, leer las memorias FLASH, EEPROM y los fuses (para propósitos de verificación seguramente) y segundo, programar los Bits de Lock, siempre y cuando vayamos a establecer el modo 3 o el mismo 2, es decir, no podremos regresar al modo 1 por esta vía. La interface JTAG todavía nos permitirá escribir en la FLASH y EEPROM, aunque es una puerta que debe haber sido abierta previamente programando el fuse JTAGEN, lo cual será poco probable.
  • 275. Si elegimos el modo 3 nuestro AVR quedará con su programa actual y no podremos volver a programarlo. Con esta opción tampoco se podrá leer el código de programa del AVR por ninguna interface desde ningún dispositivo externo. Este modo se usa para proteger el código del programa. Para quienes conozcan los PIC, es como programar el fuse de protección de código. Desde el modo 1 siempre podremos pasar al modo 2 o 3, y desde el modo 2 al modo 3. Pero para regresar un nivel de seguridad atrás la única forma es aplicando una instrucción chip erase, que borrará las memorias EEPROM y FLASH, y los Bits de Lock regresarán al modo 1. Obviamente, por cuestiones de seguridad los Bits de Lock son los últimos en borrarse. Tabla Modo de LB Modo LB2 Tipo de protección de LB LB1 1 2 3 11 No se habilita ninguna característica de Candado. 10 Se deshabilitan las posteriores programaciones de las memorias FLASH y EEPROM tanto en modo de programación Serial y Paralela. También se bloquean los bits de los fuses. 00 Se deshabilitan las posteriores programaciones y verificaciones de las memorias FLASH y EEPROM tanto en modo de programación Serial, Paralela y JTAG (si se tiene). También se bloquean los bits de los Fuses y los bits de candado de Boot Loader en los modos de programación Serial y Paralela. Para comprender el uso de los 4 bits de candado de Boot Loader debemos conocer primero la función de las Secciones de Aplicación y de Boot Loader, además de las instrucciones de ensamblador LPM y SPM, que participan activamente en la autoprogramación del AVR. LPM (Load from Program Memory) sirve para leer un byte de dato de la memoria FLASH. SPM (Store to Program Memory) sirve para escribir un byte de dato en la memoria FLASH. Ambas instrucciones, LPM y SPM, trabajan con el puntero Z para direccionar la memoria FLASH. Tabla Modo de BLB0
  • 276. Modo BLB02 de BLB0 BLB01 Tipo de protección 1 11 No habrá restricciones para SPM o LPM en su acceso a la Sección de Aplicación. 2 10 No se permite el uso de SPM para escribir en la Sección de Aplicación. 00 No se permite el uso de SPM para escribir en la Sección de Aplicación, ni el uso de LPM para leer la Sección de Aplicación desde la Sección de Boot Loader. Si los Vectores de Interrupción están ubicados en la Sección de Boot Loader, las interrupciones se deshabilitan durante la ejecución desde la Sección de Aplicación. 01 No se permite el uso de LPM para leer la Sección de Aplicación desde la Sección de Boot Loader. Si los Vectores de Interrupción están ubicados en la Sección de Boot Loader, las interrupciones se deshabilitan durante la ejecución desde la Sección de Aplicación. 3 4 Tabla Modo de BLB1 Modo BLB12 de BLB1 BLB11 Tipo de protección 1 11 No habrá restricciones para SPM o LPM en su acceso a la Sección de Boot Loader. 2 10 No se permite el uso de SPM para escribir en la Sección de Boot Loader. 00 No se permite el uso de SPM para escribir en la Sección de Boot Loader, ni el uso de LPM para leer la Sección de Boot Loader desde la Sección de Aplicación. Si los Vectores de Interrupción están ubicados en la Sección de Aplicación, las interrupciones se deshabilitan durante la ejecución 3
  • 277. Tabla Modo de BLB0 Modo BLB02 de BLB0 BLB01 Tipo de protección desde la Sección de Boot Loader. 4 01 No se permite el uso de LPM para leer la Sección de Boot Loader desde la Sección de Aplicación. Si los Vectores de Interrupción están ubicados en la Sección de Aplicación, las interrupciones se deshabilitan durante la ejecución desde la Sección de Boot Loader. El valor predeterminado de todos Bits de Lock es 1 ó sin programar. En ciertos softwares de programación significa que las casillas respectivas están desmarcadas. En otros software comoAVRFLASH se indican las configuraciones por sus modos (1, 2, 3 ó 4). Atmel Studio 6 por su parte ofrece una configuración basada en la habilitación o deshabilitación de las instrucciones SPM y LPM. En cualquier caso, se establecen por defecto el modo de protección 1 (sin ninguna protección) y deberían quedar así, a menos que estemos seguros de lo que hacemos.
  • 278. Configuración y programación de los Bits de Lock desde AVRFLASH.
  • 279. Configuración y programación de los Bits de Lock desde Atmel Studio 6. Los Puertos de los AVR Los puertos se conforman por las líneas del microcontrolador donde se pueden conectar los dispositivos de Entrada/Salida a controlar, por ejemplo LEDs, displays, transistores, otros ICs o, mediante relés u optoacopladores, cargas de 110V/220V como medianos motores. Los ATmegaXX4 tienen 4 puertos, llamadosPORTA, PORTB, PORTC y PORTD. Todos ellos están completos, así que disponen de 4 x 8 = 32pines en total. Los ATmegaXX8 tienen 3 puertos, PORTB, PORTCy PORTD. En total suman 20 pines con capacidad de ampliarse a 23 pines. Los pines de los puertos tienen nombres compuestos, como PC4 ADC4/SDA/PCINT12. Los nombres compuestos implican que tienen funciones multiplexadas. Por ejemplo, este pin PC4, además de pin digital convencional puede funcionar como el canal 4 del conversor analógico digital ADC4, como línea serial de datos SDA del módulo TWI (I2C) o como pin de interrupción por cambio de estado PCINT12. Cuando los pines no son interface de algún periférico, como el USART, el ADC, los Timers, etc., es decir, cuando son manual y directamente controlados por sus
  • 280. registros PORTx, PINx y DDRx se dice que actúan como Entradas y Salidas Generales, y es a lo que nos dedicaremos en este capítulo. En principio todos los pines son bidireccionales cuando actúan como líneas de Entrada y Salida Generales. La dirección es configurable por software. Algunos pines pierden esa función cuando su control es asumido por algún módulo relacionado, por ejemplo, una vez configurado el puerto serie, el USART asumirá el control de los pines TXD y RXD. Puertos de los ATmegaXX4.
  • 281. Puertos de los ATmegaXX8. Capacidades de Voltaje y Corriente Cuando actúan como salidas, los pines pueden entregar tensiones de hasta Vcc. Cuando actúan como entradas pueden manejar niveles de hasta 0.5V por encima de Vcc. El diseño de los pines incluye diodos internos de sujeción que les permiten soportar tensiones mucho mayores que Vcco inferiores que GND, siempre que la corriente no sobrepase del orden de los micro Amperios. Cada pin de E/S puede soportar picos de corriente de hasta 40 mA, pero en estado estable cada pin de puerto puede suministrar o recibir hasta 20 mA de corriente cuando Vcc = 5V y hasta 10 mA cuando Vcc = 3V. Sin embargo, esta capacidad no puede estar presente en todos los pines al mismo tiempo. En seguida tenemos los límites de corriente total que soportan los puertos en losATmegaXX4: La suma de las corrientes suministradas por lo pines PB0 - PB7, PD0 - PD7 y XTAL2 no debería exceder los 100 mA. La suma de las corrientes suministradas por los pines PA0 - PA3 y PC0 - PC7 no debería exceder los 100 mA. La suma de las corrientes recibidas por los pines PB0 - PB7, PD0 - PD7 y XTAL2 no debería exceder los 100 mA. La suma de las corrientes recibidas por los pines PA0 - PA3 y PC0 - PC7 no debería exceder los 100 mA. Para los ATmegaXX8 la distribución de límites es un poco diferente. (Los pines ADC7 y ADC6 no están presentes en encapsulados DIP.)
  • 282. La suma de las corrientes suministradas por lo pines PC0 - PC5, ADC7, ADC6 no debería exceder los 100 mA. La suma de las corrientes suministradas por los pines PB0 - PB5, PD5 PD7, XTAL1 y XTAL2no debería exceder los 100 mA. La suma de las corrientes suministradas por los pines PD0 - PD4 y RESET no debería exceder los 100 mA. La suma de las corrientes recibidas por los pines PC0 - PC5, PD0 PD4, ADC7 y RESET no debería exceder los 150 mA. La suma de las corrientes recibidas por los pines PB0 - PB5, PD5 PD7, ADC6, XTAL1 yXTAL2 no debería exceder los 150 mA. Las Resistencias de Pull-up Una de las cualidades que distinguen a los microcontroladores de los microprocesadores es que encierran en un solo chip todos los elementos posibles de un sistema de control. Con este fin los AVR incorporan en todos sus puertos transistores a manera de fuente de corriente que en la práctica funcionan como resistencias de pullup. Estas pull-ups nos pueden ahorrar el uso resistencias de sujeción externas en los pines de los puertos configurados como entradas. Laspull-ups se podrían equiparar con resistencias de entre 20 K y 50 K. a partir de dichos valores podemos calcular la corriente que puede fluir por ellas si están activadas. Las pull-ups se pueden habilitar pin por pin independientemente escribiendo un 1 en su registro de salida PORT. Las-pull ups solo serán efectivas en los pines que actúan como entradas; en los pines configurados como salidas las pull-ups quedan automáticamente deshabilitadas. Existe un bit llamado PUD en el registro MCUCR cuya función es deshabilitar todas las pull-ups de todos los puertos si su valor es 1. El bit PUD (Pull-Ups Disable) inicializa a 0 y un posible interés por setearlo puede ser eliminar la pequeña corriente que puede fluir por las pull-ps cuando los pines en cuestión se conectan a 0 lógico. La siguiente figura muestra la conexión de un pulsador al AVR aprovechando la pull-up de un pin de E/S. Fíjate en que las pull-ups no se pueden usar como resistencias para excitar dispositivos como LEDs, relés, etc.
  • 283. Ejemplo de uso de las resistencias de pull-up. La figura de ejemplo muestra la pull-up de un solo pin pero están presentes en todos los pines de E/S del AVR. Configuración y Manejo de los Puertos Cuando los pines trabajan como entradas y salidas generales su control descansa principalmente en los Registros de E/S MCUCR, DDRx, PORTx y PINx, donde x puede ser A, B, C o D. Del registro MCUCR solo interviene el pin PUD, cuya función es deshabilitar las pull-ups de todos los pines cuando PUD = 1. Pero si PUD = 0, la habilitación de las pull-ups todavía requiere de cierta configuración por parte de los registros DDRx, PORTx y PINx. Registro MCUCR MCUCR JTD BODS BODSE PUD --- --- IVSEL IVCE Cada puerto tiene sus correspondientes registros DDRx, PORTx y PINx, así por ejemplo el puerto A tiene sus registros DDRA, PORTA y PINA. Lo mismo es aplicable a los otros puertos. Registro PORTA PORTA Registro PINA PORTA7 PORTA6 PORTA5 PORTA4 PORTA3 PORTA2 PORTA1 PORTA0
  • 284. PINA PINA7 PINA6 PINA5 PINA4 PINA3 PINA2 PINA1 PINA0 DDA7 DDA6 DDA5 DDA4 DDA3 DDA2 DDA1 DDA0 Registro DDRA DDRA El registro PORTx es para escribir datos en los pines del puerto x que están configurados como salida. Ése es su uso habitual. Sin embargo, escribir en los bits de PORTx cuyos pines estén configurados como entradas significa activar o desactivar las pull-ups de dichos pines (ver la tabla de abajo). Ésta es la segunda función de PORTx. Leer el registro PORTx solo significa obtener el último valor que se escribió en este registro. El registro PINx es para leer el estado de los pines del puerto x, sin importar si los pines están configurados como entradas o como salidas. La función alternativa de este registro es para conmutar el estado de los pines configurados como salidas cuando se escribe un 1 en su bit correspondiente de PINx. El registro DDRx es para configurar la dirección del puerto x, es decir, para establecer cuáles pines serán entradas y cuáles serán salidas. (Data Direction Register = Registro de Dirección de Datos). Después de un reset todos los puertos inician con sus pines configurados como entradas, pero se pueden reconfigurar en cualquier punto del programa. Si se escribe un 0 en un bit de DDRx, entonces el pin correspondiente en el puerto x será de entrada y si se escribe un 1, el pin será de salida. Detesto mencionar a los PICmicro, pero creo que te puede servir saber que la implicancia del 1 y el 0 en los PICmicros es al revés. 0 → entrada 1 → salida Por ejemplo, si escribimos el valor 11110000 en DDRB, entonces los cuatro pines de menor peso del puerto B serán entradas digitales y los cuatro pines superiores serán de salida. Si escribimos 11111111 en DDRA, todos los pines del puerto A serán de salida, y si escribimos00000000 en DDRB todo el puerto B será de entrada. La codificación de lo expuesto sería así: DDRA =0xFF;// 0xFF = 0b11111111 DDRB =0x00;// 0x00 = 0b00000000 Luego podremos leer y escribir en los puertos mediante PORTA y PINB, así. unsignedchar regval; PORTA =0x73;// Escribir 0b01110011 en el puerto A regval = PINB;// Leer puerto B
  • 285. Hasta aquí estuvo todo muy fácil porque los puertos completos estaban configurados para entrada o salida. Ahora veremos casos de configuración mixta y lo que sucede, por ejemplo, si escribimos en PINx o si leemos de PORTx. Si además trabajamos con el pin PUD para habilitar las resistencias de pull-up, tendremos que valernos de una tabla para no enredarnos. Tabla Caso Caso Bit en DDRx Bit en PORTx Bit PUD (en MCUCR) Dirección de PullEstado de Pin Pin up 1 0 0 X Entrada No Tri-Estado (Alta impedancia) 2 0 1 0 Entrada SÍ Alto, debido a la pullup. 3 0 1 1 Entrada No Tri-Estado (Alta impedancia) 4 1 0 X Salida No Bajo (0 lógico) 5 1 1 X Salida No Alto (1 lógico) Casos 4 y 5. Son los más simples. Si un pin está configurado como salida, de ningún modo tendrá pull-up y su estado de salida será 1 ó 0 lógico, dependiendo del valor escrito en su respectivo bit de PORTx. Caso 1. Si un pin está configurado como entrada y su bit respectivo en PORTx vale 0, el pin tampoco tendrá pull-up y su estado será de alta impedancia. Caso 2. Si un pin está configurado como entrada y su bit respectivo en PORTx vale 1, el pin tendrá pull-up y su estado se leerá como 1 lógico (por la pull-up), siempre que el bit PUDdel registro MCUCR valga 0. Caso 3. Raramente se suele poner PUD a 1, pero si se hace, se deshabilitarán las pullups de todos los pines. Por tanto los pines de entrada serán siempre de alta impedancia. Más adelante está una práctica de interface con teclado matricial donde se utiliza. La notación de números binarios empleando el prefijo 0b no es parte del C Estándar y aunqueAVR GCC la admita, AVR IAR C no la reconoce. Por tanto solo es recomendable el uso de la notación hexadecimal. Control de Dispositivos Básicos
  • 286. Secuenciador de 3 Efectos Este programa está inspirado en el led flasher 2 de Seiichi Inoue y lo puedes ubicar en el sitio web http://www.piclist.com/images/www/hobby_elec/e_pic6_b.htm. Estos son los tres efectos que se pueden escoger. Tabla Tabla secuenciador de tres efectos Cada efecto se podrá seleccionar ingresando su correspondiente número mediante el teclado. El reto es que el primer LED alumbra a su plenitud, el segundo LED, un poquito menos y el tercero, como la colita de un cometa, brilla menos aún. ¿Cómo conseguiremos variar la intensidad de brillo de un LED? Regulando la cantidad de corriente que fluye por él. Y ¿cómo variamos su corriente sin emplear un potenciómetro o algo por el estilo? Bueno, las dos formas más habituales se conocen como modulaciones PWM y PRM, a la segunda de las cuales se apega la onda generada por el patrón de efecto de nuestro secuencial. PRM es la sigla de Modulación por Frecuencia de Pulsos, en inglés. Ondas de corriente en los LEDs y su valor promedio. Dado que los LEDs que parpadean dan miles de centelleos por segundo, nuestros ojos no lo podrán percibir así, solo veremos una disminución en su luminosidad. Es el valor promedio de la corriente lo que cuenta para el brillo del LED. No obstante, tampoco hay una proporción directa entre estos dos parámetros. Así un LED aparezca prendido la sexta parte del tiempo que otro, no significa que vaya a brillar 6 veces menos. Por otro lado, este hecho es aprovechable por otras aplicaciones como el control de varios displays de 7 segmentos o los letreros matriciales de LEDs, que veremos más adelante.
  • 287. Circuito para los LEDs y el microcontrolador AVR. Para generar los efectos de desplazamiento de los LEDs y las ondas PRM de cada LED se emplea una matriz llamada Pattern. Es una gran matriz pero el software emplea índices relativos para dividirla en varios bloques. La separación de los bloques en tres grupos, uno para cada efecto, también es una división lógica. Cada uno de los bloques de la matriz Pattern es una posición en el desplazamiento de los LEDs. Por ejemplo, en la posición 2 del primer efecto se ejecutará el bloque 2 por 100 veces cíclicamente. El bloque 2 es el tercero porque en el mundo digital se empieza desde 0 y su patrón es el conjunto de datos 0xe7,0x24,0x24,0x66,0x24,0x24. No dice mucho porque está en hexadecimal, pero si lo pasamos a binario, que es como originalmente lo elaboré, y lo ordenamos de arriba abajo, tenemos esto 11100111 00100100 00100100 01100110 00100100 00100100 Como se ve, este bloque indica que en este lapso los bits 2 y 5 siempre estarán activos, los bits 1 y 6 se prenden en dos de seis partes, y los bits 0 y 7 solo se prenden
  • 288. la sexta parte del tiempo. Si no tenemos un osciloscopio de 8 canales, una gráfica de Proteus nos puede mostrar las ondas correspondientes. En la sección Gráficos de Simulación del capítulo de Proteus se describe paso a paso cómo obtener este resultado. La única diferencia es que en esta ocasión la gráfica es de tipo DIGITAL en vez de ANALÓGICO. /************************************************************************ ****** * FileName: * Purpose: vía USART * Processor: main.c LED Flasher 3. Secuenciador de 3 efectos seleccionables ATmega164P * Compiler: AVR IAR C y AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com. * Basado en el led flasher 2 de Seiichi Inoue localizado en * http://hobby_elec.piclist.com/index.htm. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. *
  • 289. * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta * licencia y las notas de autor y copyright de arriba. ************************************************************************* ****/ #include "avr_compiler.h" #include "usart.h" /************************************************************************ ****** Patrón que contiene el patrón de efecto del secuencial led1 111111111111111111111111111111... Prendido 6 de 6 partes led2 100100100100100100100100100100... Prendido 2 de 6 partes led3 100000100000100000100000100000... Prendido 1 de 6 partes - Cada bloque tiene 6 items - Cada bloque se repite 100 veces - Hay una pausa de 150 µs entre los ítems - Hay 12/11 bloques en total -> Cada barrido dura 6 * 100 * 150 * 12 = 1.08 segundos aprox. ************************************************************************* *****/ PROGMEM const char Pattern[]= { // Efecto 1. 12 bloques de 6 items 0x81,0x81,0x81,0x81,0x81,0x81,0xc3,0x42,0x42,0xc3,0x42,0x42, 0xe7,0x24,0x24,0x66,0x24,0x24,0x7e,0x18,0x18,0x3c,0x18,0x18, 0x3c,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, 0x3c,0x24,0x24,0x3c,0x24,0x24,0x7e,0x42,0x42,0x66,0x42,0x42,
  • 290. 0xe7,0x81,0x81,0xc3,0x81,0x81,0xc3,0x00,0x00,0x81,0x00,0x00, 0x81,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // Efecto 2. 11 bloques de 6 items 0x80,0x80,0x80,0x80,0x80,0x80,0xc0,0x40,0x40,0xc0,0x40,0x40, 0xe0,0x20,0x20,0x60,0x20,0x20,0x70,0x10,0x10,0x30,0x10,0x10, 0x38,0x08,0x08,0x18,0x08,0x08,0x1c,0x04,0x04,0x0c,0x04,0x04, 0x0e,0x02,0x02,0x06,0x02,0x02,0x07,0x01,0x01,0x03,0x01,0x01, 0x03,0x00,0x00,0x01,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00, // Efecto 3. 11 bloques de 6 items 0x01,0x01,0x01,0x01,0x01,0x01,0x03,0x02,0x02,0x03,0x02,0x02, 0x07,0x04,0x04,0x06,0x04,0x04,0x0e,0x08,0x08,0x0c,0x08,0x08, 0x1c,0x10,0x10,0x18,0x10,0x10,0x38,0x20,0x20,0x30,0x20,0x20, 0x70,0x40,0x40,0x30,0x40,0x40,0xe0,0x80,0x80,0xc0,0x80,0x80, 0xc0,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00 }; /************************************************************************ ***** * Main function ************************************************************************* ***/ int main(void) { unsignedint i, j, k, b, bi, ba; char c, Effect; DDRB =0xFF;// Setup PORTB for output
  • 291. usart_init();// Initialize USART @ 9600 N 1 puts("nr Escoja un efecto "); puts("nr (1) Barrido simétrico"); puts("nr (2) Barrido a la derecha"); puts("nr (3) Barrido a la izquierda"); Effect = '1';// Por defecto iniciar con efecto 1 while(1) { start: /* Establecer parámetros de barrido del efecto actual */ switch(Effect) { case '1': ba =0; b=12;break; case '2': ba =6*12; b=11;break; case '3': ba =6*(12+11); b=11;break; } /* Iniciar barrido del efecto actual */ for(i=0; i<b; i++)// Para barrer b bloques { for(j=0; j<100; j++)// Cada bloque se repite 100 veces { bi = ba +6*i; for(k=0; k<6; k++)// Cada bloque tiene 6 items
  • 292. { // PORTB = Pattern[bi+k]; PORTB =pgm_read_byte(&(Pattern[bi+k])); /* Este bloque sondea el puerto serie esperando recibir una * opción válida para cambiar de efecto */ if(kbhit())// Si hay datos en el USART,.. { c = getchar();// Leer dato if((c<='3')&&(c>='1'))// Si es una opción válida,... { Effect = c;// Tomar opción goto start;// y reiniciar } } delay_us(150); } } } } } Delays Antir rebote Cuando apretamos un pulsador o movemos un switch, la señal de tensión relacionada no cambiará su valor establemente, sino que se darán pequeños rebotes en el contacto que generarán ondas irregulares. A veces se puede implementar un sencillo filtro pasabajas para evadir estos rebotes. Como este circuito puede resultar algo incómodo de armar, casi siempre se prefiere añadir una rutina anti rebote en el programa.
  • 293. Sencillo filtro anti rebote para un pulsador. De los tantos mecanismos realizables poner un delay es el más simple. Una vez detectado el cambio de tensión se espera un lapso de tiempo hasta que la señal se estabilice y luego se vuelve testear la entrada. Una variante es, luego de responder al primer pulso esperar un tiempo para que se estabilice la señal. En cuanto al enunciado del programa: cada vez que presionemos un botón (pulsador) se prenderá un LED más del puerto B y con otro botón se hará lo mismo para apagar un LED más. De modo que aprovecharemos el circuito de la práctica anterior. Para comprobar la utilidad de la aparente irrelevante rutina anti rebote , puedes probar lo que sucede si le quitas los Delays al programa.
  • 294. Circuito para el microcontrolador AVR. /************************************************************************ ****** * FileName: main.c * Purpose: Delays antirrebote * Processor: ATmega164P * Compiler: AVR IAR C y AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. * * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta * licencia y las notas de autor y copyright de arriba. ************************************************************************* ****/ #include "avr_compiler.h" int main(void) { char port =0; DDRD =0x00;// PORTD como entrada PORTD =0x0C;// Activar pull-ups de pines PD2 y PD3 DDRB =0xFF;// PORTB como salida PORTB =0x00;// Limpiar PORTB
  • 295. while(1)// Bucle infinito { if((PIND &0x04)==0)// Si PD2 vale 0 (pulsador presionado) { port <<=1;// Desplazar port a la izquierda port |=0x01;// Setear bit 0 de port PORTB = port;// Colocar en PORTB delay_us(30000);// Delay antirrebote, 30ms while((PIND &0x04)==0)// Mientras PD2 valga 0, esperar continue; } if((PIND &0x08)==0)// Si PD3 vale 0 (pulsador presionado) { port >>=1;// Desplazar port a la derecha port &=0x7F;// Limpiar bit 7 de port PORTB = port;// Colocar en PORTB delay_us(30000);// Delay antirrebote, 30ms while((PIND &0x08)==0)// Mientras PD3 valga 0, esperar continue; } } } Control de Displays 7 segmentos Un display 7 segmentos no es más que una matriz de 7 diodos LED dispuestos de forma que encendiéndolos apropiadamente se pueden formar los números del 0 al 9 y algunas letras del alfabeto. Se dividen en dos grupos: de ánodo común y de cátodo común. El que vamos a emplear en esta práctica pertenece al segundo grupo y se muestra abajo.
  • 296. Display 7 segmentos de cátodo común. Como ves arriba, cada LED del display está identificado por una letra entre a y g. Algunos displays también cuentan con un led en la parte inferior llamado dp (decimal point), pensado para representar un punto decimal. Yendo a la práctica. Este programa controla 4 displays de 7 segmentos multiplexados. Esta técnica es muy conocida y consiste en prender los displays uno a uno en tiempos sucesivos pero con la suficiente frecuencia como para que parezcan que están prendidos los cuatro al mismo tiempo. Los datos que visualizarán los displays serán ingresados por el puerto serie de la PC.
  • 297. Circuito para los displays 7 segmentos y el microcontrolador AVR. En el programa se emplea el array bcdcodes para almacenar los códigos 7 segmentos de los números del 0 al 9. // Matriz de códigos hexadeximales. Formato: xgfedcba , x = don't care constchar bcdcodes[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f}; Aquí tampoco se distingue mucho por estar en hexadecimal, pero convertido en binario y ordenado de arriba abajo lo notaremos mejor. Hexa binario ---------------0x3f=00111111// 7-segment code of 0 0x06=00000110// 7-segment code of 1
  • 298. 0x5b=01011011// 7-segment code of 2 0x4f=01001111// 7-segment code of 3 0x66=01100110// 7-segment code of 4 0x6d=01101101// 7-segment code of 5 0x7d=01111101// 7-segment code of 6 0x07=00000111// 7-segment code of 7 0x07=01111111// 7-segment code of 8 0x7f=01101111// 7-segment code of 9 De acuerdo con el circuito, el display está conectado a los 7 primeros pines de PORTB del AVR (a con 0, b con 1... y g con 6). El pin 7 queda libre, por eso el bit 7 de cada dato no interesa, don‟t care. De acuerdo con el circuito, el display está conectado a los 7 primeros pines de PORTB del AVR (a con 0, b con 1... y g con 6). El pin 7 queda libre, por eso el bit 7 de cada dato no interesa, don‟t care. /************************************************************************ ****** * FileName: main.c * Overview: Control de 4 displays 7 segmentos con interface USART * Processor: ATmega164P * Compiler: AVR IAR C y AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. * * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta * licencia y las notas de autor y copyright de arriba. ************************************************************************* ****/ #include "avr_compiler.h"
  • 299. #include "usart.h" char GetNumStr(char* buffer,unsignedchar len);// Prototipo de función int main(void) { // Matriz de códigos hexadeximales. Formato: xgfedcba , x = don't care constchar bcdcodes[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f}; unsignedint d, digit; signedchar i; char buff[10]="1234";// Iniciar con 1234 char buff2[10]; DDRB =0xFF;// Todo PORTB para salida DDRD =0xF0;// Nible alto de PORTD para salida usart_init(); puts("rn Escribe un número para el display 7 segmentos rn"); while(1) { for(d=0; d<4; d++)// Para pasar por los 4 displays { if(GetNumStr(buff2,4))// Si se leyó una cadena de 4 númeroa strcpy(buff, buff2);// Actualizar cadena para visualizar i = strlen(buff)-d-1;// Calcular posición de dígito
  • 300. if(i >=0) digit = buff[i];// Obtener dígito else digit = '0';// Rellenar con ceros a la izquierda PORTD =0xf0;// Apagar displays temporalmente PORTB = bcdcodes[digit-'0'];// Poner nuevo dato en display PORTD = PORTD &(~(0x10<<d));// Activar nuevo display delay_us(100); } } } //*********************************************************************** ***** // Lee una cadena de texto de 'len' números // El tamaño de 'buffer' debe ser mayor que 'len'. //*********************************************************************** ***** char GetNumStr(char* buffer,unsignedchar len) { char c; staticunsignedchar i=0; if(kbhit()) { c = getchar(); if((c<='9'&&c>='0')&&(i<len))// Si c está entre 0 y 9 { buffer[i++]= c;// Guardar en buffer putchar(c);// Eco
  • 301. } elseif((c=='b')&&(i))// Si c es RETROCESO y si i>0 { i--;// putchar(c);// Eco } elseif((c=='r')&&(i))// Si c es ENTER y si i>0 { buffer[i]= '0';// Poner un 0x00 (fin de cadena) putchar(c);// Eco i =0;// Resetear contador return1;// Retornar con 1 } } return0;// Retornar con 0 } Retraso y Frecuencia de Repetición Antes, en los tiempos analógicos, gran parte de la interface de usuario de los aparatos electrónicos implicaba la presencia de algún tipo de potenciómetros y switches. Actualmente, una característica de los sistemas digitales es su manipulación basada en botones. Un botón para esto, otro para aquello... Inclusive a un mínimo de ellos se le puede dotar de múltiples funciones para el completo control de algún proceso. El programa de esta práctica implementa el concepto que tantas veces leemos como retraso de repetición - frecuencia de repetición. De hecho, podemos encontrar este mecanismo en casi cualquier artefacto digital. Aprovechando que ya tenemos armado nuestro circuito de 4 displays 7 segmentos, haremos un contador que se incremente cada vez que se pulse un botón y se decremente cuando se pulse otro. Si un botón permanece apretado por más de cierto tiempo (retraso de repetición), se iniciará un incremento/decremento continuo, según la razón establecida (frecuencia de repetición). Pero aquí no termina la cosa. Con criterio de buen diseñador deberíamos prever lo que pasaría si alguien presionara un botón sin antes haber soltado el anterior. Eso dependerá de la aplicación. En este programa yo decidí dejar sin efecto al segundo
  • 302. botón mientras el primero esté pulsado. Está de más decir que los rebotes deben ser filtrados. Circuito para los displays 7 segmentos y el microcontrolador AVR. La tarea de la función CheckButtons la puedes leer en su respectivo encabezado. Allí dice que si se llama periódicamente cada 100us el retraso de repetición será de 600ms y la frecuencia de repetición de 1/100ms = 10 incrementos/decrementos por segundo. Estos parámetros se pueden modificar editando los límites de conteo de las variables a y b. Tras ser detectada la señal negativa (botón pulsado), el programa seguirá testeando el pin continuamente durante 25 ms. Solo se reconocerá como válido el pulso que permaneció estable en este lapso. Eso servirá para filtrar los pequeños pulsos de rebote.
  • 303. Una característica notable de este mecanismo, comparable solo con las interrupciones, es que permite atender a los botones sin necesidad de que el programa se trabe esperando a que se pulsen o liberen. Sin embargo, debo aclarar que el tiempo de los 100us no se cumple estrictamente y puede variar bastante de acuerdo con la aplicación. En esta práctica ni se nota, pero si realmente llegara a interesar se puede ajustar o superar con las interrupciones de algún Timer. /************************************************************************ ****** * FileName: main.c * Purpose: Retraso de repetición y velocidad de repetición * Processor: ATmega164P * Compiler: AVR IAR C y AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. * * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta * licencia y las notas de autor y copyright de arriba. ************************************************************************* ****/ #include "avr_compiler.h" /* Prototipos de función */ void CheckButtons(void); void IncCNT(void); void DecCNT(void);
  • 304. /* Definiciones */ #define MaxCNT (arbitrario) 5000 // Máximo Valor de CNT #define ButtonI ( PIND & (1<<2) ) // PD2 Botón de incremento #define ButtonD ( PIND & (1<<3) ) // PD3 Botón de decremento /* Variables globales */ volatileunsignedint CNT;// Contador /************************************************************************ ***** * Main function ************************************************************************* ***/ int main(void) { // Matriz de códigos hexadeximales. Formato: xgfedcba , x = don't care constchar bcdcodes[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f}; unsignedint i, j, digit; signedchar idx; char buff[10]; DDRB =0x7F;// Los 7 bits altos de PORTB para salida PORTB =0x80;// 0 a pines de salida, sin Pull-up en pin de entrada DDRD =0xF0;// Nible alto de PORTD para salida PORTD =0x0C;// Activar Pull-ups de pines PD2 y PD3 CNT =123;// Inicializar contador
  • 305. while(1) { for(j=0; j<100; j++) { for(i=0; i<4; i++)// Para pasar por los 4 displays { CheckButtons(); itoa(CNT, buff,10);// Convertir número en cadena idx = strlen(buff)-i-1;// Calcular posición de carácter if(idx >=0) digit = buff[idx];// Obtener carácter else digit = '0';// Poner 0 en displays sin número PORTD |=0xf0;// Apagar displays temporalmente PORTB = bcdcodes[digit-'0'];// Poner nuevo dato en display PORTD &=(~(0x10<<i));// Activar nuevo display } } } } /************************************************************************ ***** * Mide el tiempo que los botones I y D están pulsados. Si dicho tiempo * llega a 25ms llamará a IncCNT (o DecCNT) y luego de 600ms la llamará * continuamente tras cada 100ms. Un botón no responderá mientras el * anterior no sea liberado.
  • 306. * Para cumplir los tiempos citados debería ser llamada cada 100us. ************************************************************************* ***/ void CheckButtons(void) { staticunsignedint a;// Cuenta el tiempo que ButtonI está pulsado staticunsignedint b;// Cuenta el tiempo que ButtonD está pulsado if((ButtonI==0)&&(b==0))// Si ButtonI pulsado y ButtonD libre { a++;// Incrementar contador if(a==250)// Si llegó a 25 ms { IncCNT();// Procesar por primera vez } elseif(a>6250)// Si pasaron más de 600 ms { IncCNT();// Procesar por segunda y demás veces a -=1000;// Siguientes incrementos serán cada 100 ms } } else a=0;// Resetear contador if((ButtonD==0)&&(a==0))// Si ButtonD pulsado y ButtonI libre { b++;// Incrementar contador if(b==250)// Si llegó a 25 ms
  • 307. { DecCNT();// Procesar por primera vez } elseif(b>6250)// Si pasaron más de 600 ms { DecCNT();// Procesar por segunda y demás veces b -=1000;// Siguientes incrementos serán cada 100 ms } } else b=0;// Resetear contador } /************************************************************************ ***** * Incrementan o decrementan CNT. * El valor de CNT está limitado al rango [0 : MaxCNT] ************************************************************************* ***/ void IncCNT(void) { if(CNT<MaxCNT)// Si CNT < MaxCNT CNT++;// Incrementar CNT } void DecCNT(void) { if(CNT>0)// Si Duty > 0 CNT--;// Decrementar CNT
  • 308. } Control de Motor Paso a Paso A diferencia de un motor DC, que solo tiene una bobina y que empieza a girar apenas se le conecta la alimentación, con una velocidad que varía de acuerdo con el voltaje aplicado; los motores de pasos tienen cuatro bobinas y avanzan o retroceden solo un pequeño ángulo de giro, llamado ángulo de paso, por cada combinación de voltaje aplicada en sus boninas. Para mantener la marcha del motor es necesario cambiar periódicamente la combinación de voltajes en sus terminales. Con 4 bobinas un motor PAP (Paso A Paso) puede presentar hasta 8 terminales, 2 por cada bobina. Los terminales se pueden unir interna o externamente dando lugar a dos tipos de motores PAP: los unipolares y los bipolares. Puedes ver la siguiente figura para identificar el origen de tu motor PAP. Observa que si tienes un motor unipolar de 8 ó 6 terminales, lo puedes convertir en un motor bipolar; pero no es posible arreglar un motor bipolar para que trabaje como unipolar, a menos que separes sus bobinas internamente.
  • 309. Motores de pasos Unipolares y Bipolares. Aunque tengan más terminales, los motores PAP Unipolares son más fáciles de manejar por el hardware requerido. En estos motores las bobinas se polarizan en una sola dirección, por lo que basta con un switch o transistor por cada bobina para energizarla. En cambio en los motores PAP bipolares cada bobina debe polarizarse en ambas direcciones. Esto es equivalente a hacer girar un motor DC en ambas direcciones; y tú sabes que para eso se requiere de un circuito llamado Puente-H. Para controlar un motor PAP Unipolar las bobinas deben ser polarizadas secuencialmente de acuerdo con la orientación que se le quiera dar al rotor. Para comprender esto debes recordar la ley de atracción y repulsión entre polos magnéticos, así como la ley de Lens, que explica la orientación del campo magnético generado por la corriente que fluye por una bobina. En el motor PAP unipolar no deben polarizarse las 4 bobinas al mismo tiempo porque generarían un campo magnético simétrico y el rotor no sabría a dónde girar. A partir de esto se deduce que existen hasta tres modos de hacer girar un motor PAP unipolar, pero los dos principales son: Modo Full Step o de paso completo. Es cuando las bobinas se polarizan de dos en dos. En la siguiente animación los terminales medios están unidos interna o externamente al círculo que representa la alimentación positiva. Entonces para polarizar las bobinas se ponen a nivel bajo (azul) sus terminales largos. He resaltado en amarillo cada bobina polarizada. Eso ayuda a distinguir la secuencia de polarización y a descubrir que no existen más que 4 combinaciones de polarización aplicables, las cuales deben repetirse cíclicamente. Los números hexadecimales al lado de la tabla se obtienen asumiendo que los puntos azules son ceros y los rojos unos. De hecho esa suposición no importa mucho, como tampoco importa que los terminales medios estén conectados a la alimentación positiva o a GND, ni que la secuencia de pasos inicie en 0x09. Puedes invertir las polarizaciones y al final te sorprenderá que el motor siga girando en la misma dirección. Lo único que hará que cambie de dirección es que reciba la secuencia de pasos en orden invertido.
  • 310. Operación de un motor de pasos unipolar en modo Full step. Modo Half Step o de paso medio. Es cuando las bobinas se polarizan de a una y de a dos intercaladamente. Si te fijas bien en la tabla de pasos, verás que también incluye los 4 pasos del modo full step. Obviamente esos son los momentos en que hay dos bobinas polarizadas, en los otros 4 pasos solo se polariza una bobina. La ventaja de este mecanismo respecto del modo Full step es que se pueden realizar movimientos de giro más finos. La desventaja es que puede disminuir el torque, o fuerza de giro del rotor.
  • 311. Operación de un motor de pasos unipolar en modo Half step. El programa de esta práctica soporta ambos modos de control, los cuales se establecen desde la consola mediante la tecla „*‟. Por cada vez que se pulse la tecla „+‟ el motor avanzará 1 ó medio paso, dependiendo del modo actual seleccionado y por cada vez que se pulse la tecla „-‟ el motor retrocederá 1 ó medio paso.
  • 312. Circuito para el motor paso a paso y el microcontrolador AVR. Puesto que la tabla de pasos del modo Half Step también incluye los pasos del modo Full Step, no fue necesario crear dos tablas separadas. Los 8 pasos del motor se encuentran en el array steps. /* Matriz de pasos */ constchar steps[]={0x09,0x0D,0x0C,0x0E,0x06,0x07,0x03,0x0B}; /************************************************************************ ****** * FileName: * Overview: USART main.c Control de motor PAP en modos Full step y Half step, vía * Processor: ATmega164P * Compiler: AVR IAR C y AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com.
  • 313. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. * * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta * licencia y las notas de autor y copyright de arriba. ************************************************************************* ****/ #include "avr_compiler.h" #include "usart.h" int main(void) { /* Matriz de pasos */ constchar steps[]={0x09,0x0D,0x0C,0x0E,0x06,0x07,0x03,0x0B}; signedchar step;// Marca el paso actual char modo, c; DDRB =0x0F;// Configurar Nibble bajo para salida PORTB =0x00;// Limpiar nibble bajo. Sin Pull-ups en nibble alto step =1;// Iniciar en cualquier paso modo =0xff;// Esto será Full step (0x00 para half step) usart_init();// Inicializar USART0 @ 9600 N 1
  • 314. puts("rn Control de Motor PAP en Full step y Half steprn"); puts("rn (*): Conmutar entre Full step y Half step"); puts("rn (+): Avanzar 1 o 1/2 paso"); puts("rn (-): Retroceder 1 o 1/2 paso"); while(1)// bucle infinito { if(kbhit())// Esperar a que llegue un dato del puerto serie { c = getchar();// Leer dato recibido switch(c) { case '*': modo =~modo;// Conmutar modo if(modo) puts("rn Full step"); else puts("rn Half step"); break; case '+': step++;// Medio paso adelante if( modo &&(step&0x01))// En modo Full step y si es necesario... step++;// ... medio paso más if(step>7) step =0;// Dar la vuelta PORTB = steps[step];// Ejecutar el paso break; case '-': step--;// Medio paso atrás
  • 315. if( modo &&(step&0x01))// En modo Full step y si es necesario... step--;// ... medio paso más if(step<0) { step =7;// Dar la vuelta if(modo)// En full step empezar desde 6 step--; } PORTB = steps[step];// Ejecutar el paso break; } } } } Control de Teclado Matricial Un teclado matricial es un conjunto de botones (switches) dispuestos en forma de malla, de modo que no se requieran de muchas líneas para su interface. De hecho, la mayoría de los teclados (incluyendo quizá el de tu computadora) funciona con una estructura similar. En esta práctica trabajaremos con un teclado de 4×4. Como se aprecia en la siguiente imagen, cada botón del teclado está conectado a alguna de las filas Row, por un lado; y por el otro, a alguna de las columnas Col. Teclado Matricial
  • 316. Aspecto físico y estructura interna de un teclado. La siguiente figura esboza la conexión entre un microcontrolador y un teclado de 4×4. Obviamente, no se puede leer el estado de una tecla como un pulsador cualquiera. Pero es fácil darse cuenta de que una tecla pulsada establece la conexión entre una de las filas Row y una de las columnas Col. Conexión de un teclado a un microcontrolador. Por ejemplo, al presionar la tecla „6‟ se unen las líneas Row 1 y Col 2. O sea, si sacamos un 1 (ó 0) por el pin de Row 1, también deberíamos leer un 1 (ó 0) en el pin de Col 2, o viceversa. Generalizando, solo hay un par Row-Col que identifica cada tecla. En consecuencia, para saber cuál fue la tecla pulsada debemos sondear una a una todas las combinaciones Row-Col. Una vez detectada la condición de circuito cerrado, se usa el parRow-Col para deducir la posición de la tecla pulsada.
  • 317. Luego de expuesta la relativa sencillez de este teclado podemos sentirnos ansiosos empezar a codificar el programa de control. Solo hay que poner especial cuidado en la dirección de los pines y su conexión. Un mínimo descuido causaría un cortocircuito que dañaría el AVR. La funcionalidad del programa no puede ser más sencilla. El valor de cada tecla pulsada será enviado a la consola de puerto serie de la PC. Circuito para el teclado matricial y el microcontrolador AVR. La función de bajo nivel para el teclado eskeypad_scan. En el interior de esta función el nibble bajo de PORTB se configura como salida y el nibble alto, como entrada, con sus correspondientes pull-ups. Al salir, todo PORTB queda como entrada para facilitar su posible posterior uso para otras rutinas. Según mi código, el valor leído en las columnas cuando no hay teclas pulsadas debería ser „1‟ lógico, y „0‟ cuando si las hay. Para eso es necesario que dichas líneas estén sujetas a Vcc por medio de resistencias, de pull-ups. Si se deshabilitan las pull-ups, el programa no funcionará a menos que se las sustituyan por resistencias externas con la misma función.
  • 318. /************************************************************************ ****** * FileName: main.c * Purpose: Control de teclado matricial de 4x4 * Processor: ATmega164P * Compiler: AVR IAR C y AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. * * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta * licencia y las notas de autor y copyright de arriba. ************************************************************************* ****/ #include "avr_compiler.h" #include "usart.h" #define kpd_PORT PORTB // Port write #define kpd_PIN PINB // Port read #define kpd_DDR DDRB // Dirección de puerto char keypad_read(void); void keypad_released(void); char keypad_scan(void); int main(void) {
  • 319. char tecla; usart_init();// Inicializar USART0 @ 9600 N 1 puts("rn Control de Teclado Matricial de 4x4 rn"); while(1) { nop();// Alguna otra tarea tecla = keypad_read();// Leer teclado if(tecla)// Sí hubo Tecla pulsada (si es diferente de 0) { putchar(tecla); keypad_released();// Esperar teclado libre } } } //*********************************************************************** ***** // Escanea el teclado y retorna el valor ASCII de la tecla presionada por // al menos 25ms. En otro caso retorna 0x00. //*********************************************************************** ***** char keypad_read(void) { char c1, c2; c1 = keypad_scan();// Escanear teclado if(c1)// Si hubo alguna tecla pulsada
  • 320. { delay_us(25000);// Delay antirrebote c2 = keypad_scan();// Escanear otra vez if( c1==c2 )// Si Ambas teclas leídas son iguales return c2;// entonces aceptarla } return0x00; } //*********************************************************************** ***** // Espera hasta que el teclado quede libre. //*********************************************************************** ***** void keypad_released(void) { delay_us(10);// while(keypad_scan())// Mientras se detecte alguna tecla pulsada continue;// seguir escaneando. } //*********************************************************************** ***** // Escanea el teclado y retorna el valor ASCII de la primera tecla que // encuentre pulsada. De otro modo retorna 0x00. //*********************************************************************** ***** char keypad_scan(void) { unsignedchar Col, Row;
  • 321. char RowMask, ColMask; // Col0 Col1 Col2 Col3 conststaticchar keys[]={'7', '8', '9', 'A',// Row 0 '4', '5', '6', 'B',// Row 1 '1', '2', '3', 'C',// Row 2 '.', '0', '#', 'D'};// Row 3 kpd_DDR =0x0F;// Nibble alto entrada, nibble bajo salida kpd_PORT =0xF0;// Habilitar pull-ups del nibble alto RowMask =0xFE;// Inicializar RowMask a 11111110 for(Row=0; Row<4; Row++) { kpd_PORT = RowMask;// delay_us(10);// Para que se estabilice la señal ColMask =0x10;// Inicializar ColMask a 00010000 for(Col=0; Col<4; Col++) { if((kpd_PIN&ColMask)==0)// Si hubo tecla pulsada { kpd_DDR =0x00;// Todo puerto entrada otra vez return keys[4*Row+Col];// Retornar tecla pulsada } ColMask <<=1;// Desplazar ColMask para escanear }// siguiente columna
  • 322. RowMask <<=1;// Desplazar RowMask para escanear RowMask |=0x01;// siguiente fila } // Se llega aquí si no se halló ninguna tecla pulsada kpd_DDR =0x00;// Todo puerto entrada otra vez return0x00;// Retornar Código de no tecla pulsada } Letrero Matricial de LEDs Un letrero matricial es uno de los proyectos más atractivos de la electrónica. Su elaboración puede ser sencilla por su diseño, aunque algo engorrosa por la implementación del hardware. En esta oportunidad aprenderemos a diseñar un panel de 8 filas y de 64 columnas, es decir, de 512 LEDs, pero verás que ampliar o reducir el tamaño será tan simple como añadir o quitar registros en el hardware o cambiar un solo número en el software. El letrero desplegará un mensaje estático incluido en el firmware. En el capítulo de las memorias FLASH y EEPROM se presenta unletrero de LEDs programable cuyos mensajes se ingresan vía el puerto serie. El hardware Sabemos que para encender un LED necesitamos de una señal de control, aparte de la alimentación (Vcc o GND), ¿cierto? Con esto en mente deberíamos suponer que para un letrero de 515 LEDs se necesitarían de 512 señales saliendo del microcontrolador. Pero no es así. Podemos resolver parte del problema multiplicando las señales del microcontrolador con ayuda de dispositivos como multiplexores, decodificadores o registros serie-paralelo como el 74164,74595 o el CD4094. Los dos últimos son muy parecidos y son a la vez mejores porque cuentan con doble buffer. Uno para almacenar internamente los datos seriales que van ingresando al registro y otro que se conecta al exterior..
  • 323. Diagrama funcional del registro de desplazamiento 74HC595. Todos estos registros son de 8 bits pero tienen la característica de poder ser montados en cascada para multiplicar sus salidas. Por ejemplo, abajo vemos cómo conectar 8 registros 74HC595 en cascada, lo que equivale a un "mega-registro" serie-paralelo de 64 bits con DATAcomo principal entrada serial de datos, CLOCK como reloj común y STROBE como la señal que deja salir por los pines Qx (al mismo tiempo) todos los datos ingresados serialmente. Del mismo modo se pueden ir añadiendo tantos registros como salidas paralelas se deseen.
  • 324. Conexión en cascada de 8 registros 74HC595 para el letrero matricial de LEDs. Para nuestro letrero DATA, CLOCK y STROBE serán las señales que saldrán del microcontrolador. Las 64 salidas col0 a col63 controlarán los LEDs. Si nuestro letrero va trabajar con duty cycle de 1/4 debemos duplicar el circuito mostrado. Eso del duty cycle lo entenderemos en el camino. DS es la entrada serial de datos. SH_CP es el reloj de los bits de datos, es decir, con cada pulso aplicado al pin SH_CP ingresará en el registro 74HC595 el bit presente en su pin DS. Los bits así cargados van siendo almacenados en el registro interno 8STAGE SHIFT REGISTER (ver el diagrama funcional del 75HC595). Recién cuando apliquemos un pulso en el pin ST_CP todos los datos cargados pasarán al segundo registro interno 8-BIT STORAGE REGISTER y de allí salen a los pines Qx. Si el pin OE vale 1 lógico, todas las salidas estarán en alta impedancia. En nuestro circuito está conectado a GND de modo que el estado de los pines Qx es siempre 1 ó 0. El pin MR es reset, y pone a cero todos los pines Qx si vale 0. Para nuestro diseño no necesitamos resetear los registros de esta forma, así que lo mantenemos conectado a VCC. Los pines Q7' y Q7 tienen el mismo valor. Q7' permite la conexión en cascada sin mermar la corriente del pin Q7. Los registros 74HC595 deben ser de la serie HC, o sea, de tecnología CMOS porque son los que pueden manejar la mayor corriente, que según el datasheet es de 35mA en los pines Qx actuando como fuentes o sumideros, aunque esta corriente no puede estar presente en todos los pines Qx al mismo tiempo Debemos saber que esos 35mA son un valor extremo que no debería permanecer constante, pero como en el letrero matricial los LEDs nunca están prendidos todos al mismo tiempo, será perfecto para bombear todos los picos de corriente posibles. Por otro lado, si nos basamos solo en este mecanismo para ampliar nuestras señales, para controlar los 512 LEDs tendríamos que usar 512/8 = 64 registros 74HC595, lo cual nos llevaría a un circuito muy difícil de implementar. La técnica para salvar este segundo inconveniente es un artificio que consiste en encender grupos de LEDs en
  • 325. tiempos diferentes pero con la suficiente frecuencia como para dar la impresión de estar encendidos todos al mismo tiempo. Obviamente, en un letrero matricial los LEDs quedan mejor agrupados en filas y/o columnas. En la siguiente figura los ánodos de los LEDs se unen formando las columnas (cols) y los cátodos se unen formando las filas (rows). También se puede armar una configuración alternativa invirtiendo la polaridad de todos los LEDs. En ese caso los transistores serán de tipo PNP. Tablero de LEDs para el letrero matricial Los valores de las resistencias R1 a R64 dependen de la corriente que tiene que fluir por los LEDs, la cual a su vez depende de los mismos LEDs. Hay que tener en cuenta que los LEDs no estarán prendidos al 100 % sino la octava parte (por las ocho filas) y también que la corriente promedio no siempre es proporcional al brillo del LED prendido, es decir, que un LED esté prendido la octava parte no significa que vaya a brillar ocho veces menos. Sobre los transistores: deben tener la capacidad de controlar la corriente proveniente de todos los LEDs de cada fila. Podrían ser MOSFETs como el IRF520 y si el tablero es pequeño, hasta unULN2803 podría ser suficiente. Los transistores trabajarán en modo
  • 326. corte-saturación, por lo que sus resistencias de base (que no están presentes en este circuito) pueden tranquilamente ser de 10k ó 4.7k. Los barridos Una vez estructurado el hardware de la matriz de LEDs nos damos cuenta de que podemos encender los LEDs que queramos de cualquier fila o de cualquier columna simplemente activando las coordenadas de dichos LEDs. Por ejemplo, si queremos prender los LEDs de las columnas 0, 3, 4, 7 y 63 de la fila 5 se vería así: Encendiendo los LEDs del letrero matricial Sin embargo, no es posible encender varios LEDs que pertenezcan a diferentes filas y diferentes columnas al mismo tiempo. Es aquí donde entra a jugar el software. Por ejemplo en la siguiente animación se muestra cómo para visualizar la letra G se encienden los LEDs correspondientes pero en tiempos diferentes. La primera figura muestra la secuencia del barrido en cámara lenta pero en la práctica los barridos serán tan rápidos que los LEDs se verán como en la segunda figura.
  • 327. Animación en slow motion de la visualización de figuras en el tablero de LEDs.
  • 328. Visualización en tiempo real de figuras en el tablero de LEDs. Cuando encendemos de esta forma los LEDs, es decir, activando solo 1 de las 8 filas cada vez, se dice que el letrero trabaja con un duty cycle de 1/8 porque en promedio cada LED permanece prendido la octava parte del tiempo. Los letreros de LEDs disponibles en el mercado como enwww.ledauthority.com suelen utilizar un duty cycle de 1/4, es decir, encienden 2 filas de LEDs al mismo tiempo en cada barrido. De ese modo, se consigue una mayor iluminación de los LEDs. Armar un letrero con duty cycle de 1/4 es tan simple como juntar varios letreros de 4 filas cada uno. Por ejemplo, si queremos construir un letrero de 8x64 LEDs, armamos dos letreros de 4x64 y los ponemos uno encima del otro. El hardware es casi el mismo y aunque deberemos utilizar el doble de registros 74HC595, bien vale la pena por la buena iluminación que se consigue.
  • 329. Matriz de LEDs para un letrero con duty cycle de 1/4 Los caracteres De lo visto anteriormente queda claro que encendiendo los LEDs convenientemente podemos formar en el letrero la figura que deseemos. Será el microcontrolador quien de acuerdo con su programa se encargue de generar los barridos activando las filas y columnas adecuadamente según un patrón establecido. Este patrón corresponde a letras, figuras o números que queramos y se puede estructurar de diversas formas. Vamos a representar el patrón con un array de datos, donde cada dato represente una columna del panel de LEDs. De esta forma, si asignamos un 0 a un LED apagado y un 1 a un LED prendido, podemos establecer a partir de cada columna un byte de dato.
  • 330. Luego podremos agrupar ordenadamente todos estos datos y escribirlos en un formato conocido. Por ejemplo, para el pequeño texto de arriba el array escrito en lenguaje C quedaría algo así: const char matrix[] = {0xFC, 0x12, 0x11, 0x12, 0xFC, 0x00, 0x3F, 0x40, 0x80, 0x40, 0x3F, 0x00, 0xFF, 0x11, 0x11, 0x31, 0xCE, 0x00}; Este array puede tener cualquier nombre pero de aquí en adelante me referiré a el como matrix. Generación automática de matrix Ya vimos que para generar el array matrix que se visualizará en el panel de LEDs hace falta conocer el sistema binario y/o hexadecimal. Pero para quienes no tengan la paciencia de componerla manualmente sobre todo si quieren experimentar con textos grandes, les presento una de varias herramientas que encontré en Internet. Se llama LCD font maker y, aunque fue diseñado para componer patrones de caracteres o gráficos para displays LCD o GLCD, también nos servirá para nuestro panel de LEDs. Su uso es tan fácil como seguir los tres pasos que se nos indican en la siguiente figura.
  • 331. En el Paso 1 escogemos la fuente que deseemos. Yo escogí Verdana-Negrita-11 porque vi que da letras que se ajustan bien a la altura del letrero. Antes de ir al siguiente paso es preferible poner ahora en Char input el texto que mostrará el letrero. Yo dejé unos espacios al principio para que el texto empiece a aparecer desde el costado derecho. Con Offset puedes centrar y ajustar vertical y horizontalmente el patrón del texto. Hay que establecer Height (altura) a 8 y Width (ancho) lo ajustamos hasta que cubra todo el texto, en mi caso fue de 230.
  • 332. En el Paso 2 sale una ventana donde debemos establecer los parámetros que se indican abajo.
  • 333. En el Paso 3 se nos genera el código del array que representa nuestro texto. El resultado aparecerá como se muestra en la siguiente figura.
  • 334. Podemos seleccionar el código generado mediante el botón Copy all para luego pegarlo en el código fuente de nuestro proyecto. El código del array está declarado como unsigned char code Bmp001 pero como nuestro programa estará escrito para los compiladores AVR IAR C y AVR GCC, debemos luego cambiarlo por PROGMEM const char matrix, como se ve abajo (matrix es un nombre a escoger). Para el compilador CodeVisionAVR los calificadores PROGMEM const los debemos reemplazar por __flash. PROGMEMconst charmatrix[]= { /*---------------------------------------------------------------------------Source file / text : www.cursomicros.com Width x Height (pixels) :230X8 ----------------------------------------------------------------------------*/
  • 335. 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x1F,0xFC,0xE0, 0x7C,0x0F,0x0F,0x7C,0xE0,0xFC,0x1F,0x03,0x00,0x03,0x1F,0xFC,0xE0,0x7C,0x0F,0x0F, 0x7C,0xE0,0xFC,0x1F,0x03,0x00,0x03,0x1F,0xFC,0xE0,0x7C,0x0F,0x0F,0x7C,0xE0,0xFC, 0x1F,0x03,0x00,0x00,0xE0,0xE0,0x00,0x00,0x3C,0x7E,0xC3,0x81,0x81,0x81,0x42,0x00, 0x7F,0xFF,0x80,0x80,0x80,0x40,0xFF,0xFF,0x00,0xFF,0xFF,0x02,0x03,0x03,0x03,0x00, 0x4E,0x9F,0x99,0x99,0x99,0xF9,0x72,0x00,0x3C,0x7E,0xC3,0x81,0x81,0xC3,0x7E,0x3C, 0x00,0xFF,0xFF,0x02,0x01,0x01,0xFF,0xFE,0x02,0x01,0x01,0xFF,0xFE,0x00,0x00,0xFF, 0xFF,0x00,0x00,0x3C,0x7E,0xC3,0x81,0x81,0x81,0x42,0x00,0xFF,0xFF,0x02,0x03,0x03, 0x03,0x00,0x3C,0x7E,0xC3,0x81,0x81,0xC3,0x7E,0x3C,0x00,0x4E,0x9F,0x99,0x99,0x99, 0xF9,0x72,0x00,0x00,0xE0,0xE0,0x00,0x00,0x3C,0x7E,0xC3,0x81,0x81,0x81,0x42,0x00, 0x3C,0x7E,0xC3,0x81,0x81,0xC3,0x7E,0x3C,0x00,0xFF,0xFF,0x02,0x01,0x01,0xFF,0xFE, 0x02,0x01,0x01,0xFF,0xFE,0x00 }; El código fuente Escribir el programa dependerá de varios factores, como el tamaño del letrero, la forma cómo se presenta el texto (efecto), la longitud de los mensajes, de si los mensajes son estáticos o programables en tiempo de ejecución, etc. En esta ocasión el letrero solo muestra un mensaje en desplazamiento. Por tanto el código fuente será muy simple. En la siguiente práctica tendremos un letrero programable. /****************************************************************************** * FileName: main.c * Overview: LED sign. Letrero Matricial de LEDs. * Processor: ATmega164P * Compiler: AVR IAR C y AVR GCC (WinAVR)
  • 336. * Author: Shawn Johnson. http://www.cursomicros.com. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. * * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta * licencia y la nota de autor de arriba. *****************************************************************************/ #include "avr_compiler.h" #define SDATA_HIGH() PORTD |= (1<<5) #define SDATA_LOW() PORTD &= ~(1<<5) #define SCLOCK_HIGH() PORTD |= (1<<6) #define SCLOCK_LOW() PORTD &= ~(1<<6) #define SOEN_HIGH() PORTD |= (1<<7) #define SOEN_LOW() PORTD &= ~(1<<7) #define WIDTH 64 PROGMEMconst charmatrix[]= { /*---------------------------------------------------------------------------Source file / text : www.cursomicros.com
  • 337. Width x Height (pixels) :230X8 ----------------------------------------------------------------------------*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x1F,0xFC,0xE0, 0x7C,0x0F,0x0F,0x7C,0xE0,0xFC,0x1F,0x03,0x00,0x03,0x1F,0xFC,0xE0,0x7C,0x0F,0x0F, 0x7C,0xE0,0xFC,0x1F,0x03,0x00,0x03,0x1F,0xFC,0xE0,0x7C,0x0F,0x0F,0x7C,0xE0,0xFC, 0x1F,0x03,0x00,0x00,0xE0,0xE0,0x00,0x00,0x3C,0x7E,0xC3,0x81,0x81,0x81,0x42,0x00, 0x7F,0xFF,0x80,0x80,0x80,0x40,0xFF,0xFF,0x00,0xFF,0xFF,0x02,0x03,0x03,0x03,0x00, 0x4E,0x9F,0x99,0x99,0x99,0xF9,0x72,0x00,0x3C,0x7E,0xC3,0x81,0x81,0xC3,0x7E,0x3C, 0x00,0xFF,0xFF,0x02,0x01,0x01,0xFF,0xFE,0x02,0x01,0x01,0xFF,0xFE,0x00,0x00,0xFF, 0xFF,0x00,0x00,0x3C,0x7E,0xC3,0x81,0x81,0x81,0x42,0x00,0xFF,0xFF,0x02,0x03,0x03, 0x03,0x00,0x3C,0x7E,0xC3,0x81,0x81,0xC3,0x7E,0x3C,0x00,0x4E,0x9F,0x99,0x99,0x99, 0xF9,0x72,0x00,0x00,0xE0,0xE0,0x00,0x00,0x3C,0x7E,0xC3,0x81,0x81,0x81,0x42,0x00, 0x3C,0x7E,0xC3,0x81,0x81,0xC3,0x7E,0x3C,0x00,0xFF,0xFF,0x02,0x01,0x01,0xFF,0xFE, 0x02,0x01,0x01,0xFF,0xFE,0x00 }; constunsignedintLEN=sizeof(matrix); /***************************************************************************** * Main function ****************************************************************************/ intmain(void) {
  • 338. unsignedintbad;// Base index unsignedintidx;// Index unsignedchardato;// dato unsignedcharrow;// Fila unsignedcharcol;// Columna unsignedchari; DDRD=0xE0; DDRB=0xFF; PORTD=0x00; PORTB=0x00; while(1) { for(bad=0;bad<LEN;bad++)// Bucle para LEN "frames" { for(i=0;i<6;i++)// Bucle de 6 barridos por "frame" { for(row=1;row;row<<=1)// Cada barrido pasa por las 8 filas { for(col=WIDTH;col;col--)// Cada fila iene WIDTH columnas { idx=bad+col-1;// Calcular índice de elemento if(idx<LEN)// Si está dentro del rango dato=pgm_read_byte(&matrix[idx]);// Extraer dato
  • 339. else dato=0x00;// si no, asumir 0x00 if(dato&row)// Si el bit de row es 1 SDATA_HIGH();// colocar 1 en DS else SDATA_LOW();// o, colocar 0 nop();nop(); SCLOCK_HIGH();// Pulso de reloj para nop();nop();// validar el dato colocado SCLOCK_LOW(); } PORTB=0x00// Desactiva todas las filas SOEN_HIGH();// Pulso para sacar todos nop();nop();// los datos cargados SOEN_LOW(); PORTB=row;// Activar la fila actual } } } } }
  • 340. descargar La simulación Dudo que todos los lectores consigan implementar en la práctica real un letrero matricial completo debido a la relativa complejidad del hardware, pero creo que al menos podrán ver su diseño en una buena simulación gracias a Proteus. Debemos notar que para la simulación en Proteus no es necesario armar el circuito completamente. Para este diseño por ejemplo he ignorado las resistencias y los transistores de las columnas y filas del panel. Se puede (o debe) editar el parámetro Minimum Trigger Time de las matrices de LEDs para mejorar la visualización de los LEDs sobre todo si se cambia la frecuencia del XTAL. Simulación del letrero matricial de LEDs en Proteus El realismo de simulación también dependerá de la potencia de la computadora. En computadoras lentas el contenido del panel se desplaza más lentamente, aunque se puede mejorar la animación modificando algunos parámetros, como la cantidad de barridos por frame en el código fuente, solo para fines de la simulación. Introducción
  • 341. Este capítulo está dedicado a los LCDs alfanuméricos con controlador Hitachi HD44780 o compatible, es decir, la mayoría. Hay diversas firmas, como Optrex, Sharp, Crystalfontz America,Tianma, etc., que producen muchísimos LCDs de este tipo. Los hay desde 1 a 4 líneas, desde 8 a 40 letras por línea, algunos con iluminación de fondo, con diferente tecnología de fabricación, etc. Dada la compatibilidad en el control de todos ellos, la elección de un modelo en particular queda a tu cargo. El LCD utilizado en este curso es de 2 líneas, de 16 letras cada una. Un display LCD de 2 líneas, de 16 caracteres cada una. Si bien es necesario conocer un dispositivo para sacarle el máximo provecho, en primera instancia a la mayoría de los aficionados solo le interesa ponerlo en práctica aunque sea de forma limitada. Si eres uno de ellos, y por el momento quieres ahorrarte algo de tiempo, puedes saltar a la sección Interface de un Display LCD. Pines del LCD Pines del display LCD. Tabla Número de Pin Número de Pin Símbolo 1 Vss 2 Vcc o Vdd 3 Vee o Vo 4 RS
  • 342. 5 R/W 6 E 7...14 DB0...DB7 15 y 16 AyK Pines del LCD. Los pines 15 y 16 corresponden a la iluminación de fondo del LCD, pero aquí el orden varía mucho. Sea como fuere, los 14 primeros pines siempre deberían coincidir. Tabla Nombre de señal Nombre de Función señal DB0-DB7 o D0-D7 8 líneas de bus de datos. Para transferencia bidireccional de datos entre el MCU y el LCD. DB7 también se puede usar como bit busy flag. En operación de 4 bits solo se usa el nibble alto. E Enable – Señal de inicio de operación de lectura/escritura. R/W Señal para seleccionar operación de lectura o escritura. 0 : Escribir en LCD 1 : Leer de LCD RS Register Select 0 : Registro de comandos (escritura). : Busy flag + puntero de RAM (lectura). 1 : Registro de datos (escritura, lectura). Acceso a DDRAM o CGRAM. Vee o Vo Ajuste de contraste del LCD. Vee = GND es máximo contraste. Vdd o Vcc Alimentación = +5 V típicamente. Vss Alimentación = 0 V (GND). AyK Son los pines de Ánodo y Cátodo de la iluminación de fondo que tienen algunos LCD.
  • 343. Un modo de operación del LCD (con ventajas y desventajas) le permite trabajar sin conectar el pin RW al microcontrolador. En ese modo pin RW siempre debe plantarse a GND. LCDs con iluminación de fondo Algunos LCDs tienen iluminación de fondo. Esta característica se basa en diferentes tecnologías, siendo la más habitual el empleo de una matriz de diodos LED colocados detrás de la pantalla. La iluminación basada en LEDs suele activarse con los pines 15 y 16, identificados como A (de ánodo) y K (de cátodo) pero no necesariamente en ese orden. Estos pines son independientes del controlador interno del LCD así que de poco sirve que nuestro LCD diga ser compatible con HD44780. La polaridad varía tanto que en los diagramas he puesto 15/16 para no especificar. En todo caso, la independencia de los pines A y K permitirá que todas las prácticas de este curso funcionen con iluminación o sin ella. Si los pines de iluminación en tu LCD no están marcados como A y K puedes consultar su datasheet para ver cuáles son o averiguarlo manualmente del mismo modo que compruebas la polaridad de un LED: aplica 5 V entre los pines 15 y 16 y si prende, eureka! ya lo tienes. Como en todo LED, no debes olvidar ponerle una resistencia en serie, como se ve arriba. ¿Resistencia de cuánto? Tú sabes que hay todo tipo de diodos LED: algunos prenden a penas, mientras que otros, con la misma energía, alumbran como una linterna (bueno, casi :). Creo que eso da cuenta de su divergencia, pero en términos generales, los LEDs de la iluminación requieren cerca de 4.3V y consumen algo de 300 mA. De aquí calculamos que el valor de la resistencia debe andar por los 5 a 20 ohms. Queda a tu criterio hacer los ajustes para que alumbren tanto como quieras. Memorias del LCD CGROM - Character Generator ROM Es la zona de memoria donde se encuentran grabados los patrones de todos los caracteres que puede visualizar el LCD de fábrica. Tiene grabados cerca de 200 (varía
  • 344. mucho) tipos de caracteres de 5×7 puntos (lo más común) o 32 caracteres de 5×10 puntos. Este último modo es raramente usado porque no todos los modelos lo soportan. Tabla estándar de caracteres de la CGROM. DDRAM - Display Data RAM La DDRAM almacena los códigos de las letras que se visualizan en la pantalla del LCD. Tiene capacidad de 80 bytes, un byte por carácter si la fuente es de 5×7 puntos. Observa que no siempre se podrán visualizar los 80 caracteres.
  • 345. Por ejemplo, si quisiéramos mostrar el mensaje Hello en la pantalla, deberíamos enviar a laDDRAM los códigos ASCII de cada letra de esa palabra. El controlador interno del LCD tomará esos códigos para buscar en la CGROM sus correspondientes patrones de visualización y luego los mostrará en la pantalla. La siguiente figura muestra la correspondencia entre las locaciones de la DDRAM y las posiciones de las letras que vemos en la pantalla de un LCD de 2 líneas, particularmente de uno de 2×16. Fíjate en que los 80 bytes de la DDRAM se dividen en dos sectores de 40 bytes, un sector por línea, así: Línea 1, con sector de DDRAM desde 0x00 hasta 0x27. Línea 2, con sector de DDRAM desde 0x40 hasta 0x67. Por lo tanto, podemos entender que siempre tenemos un LCD virtual de 2×40; aunque solo podamos ver 8, 16 ó 20 letras por cada línea. Los otros datos escritos en la DDRAM permanecen allí aunque no se visualicen. Posiciones en DDRAM de las letras de la pantalla (números en hexadecimal). CGRAM - Character Generator RAM La CGRAM es una RAM de 64 bytes donde el usuario puede programar los patrones de nuevos caracteres gráficos, ya sean de 5×7 puntos (hasta 8 caracteres) o de 5×10 puntos (hasta 4 caracteres). Este tema lo detallaré en la práctica final. El Puntero de RAM Llamado también Address Counter, es un registro que sirve para acceder a las memorias RAM del LCD. Por ejemplo, si el Puntero de RAM vale 0x00, accedemos a la locación de DDRAM (o CGRAM) de esa dirección. Ahora bien, solo hay un puntero de RAM que trabaja con las dos RAMs del LCD, y para saber a cuál de ellas accede actualmente debemos ver la instrucción enviada más recientemente. Las instrucciones Clear Display, Return Home y Set DDRAM Address designan el Puntero de RAM a la DDRAM, mientras que Set CGRAM Address lo designa a la CGRAM.
  • 346. Afortunadamente, en la gran mayoría de los casos, el Puntero de RAM estará apuntando a la DDRAM. Además, en este caso viene a representar la posición del cursor (visible o no) del LCD en la pantalla. Set de Instrucciones del Display LCD Es el controlador interno HD44780 (u otro) del LCD quien ejecutará las operaciones de mostrar las letras en la pantalla, mover el cursor, desplazar el contenido de la pantalla, etc. Lo que nos toca a nosotros es enviarle los códigos de esas operaciones. A continuación, un resumen. Instrucciones del Display LCD Código Instrucciones RS R/W DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0 Clear Display 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 × Entry Mode Set 0 0 0 0 0 0 0 1 I/D S Display ON/OFF Control 0 0 0 0 0 0 1 D C B Cursor or Display Shift 0 0 0 0 0 1 S/C R/L × × Function Set 0 0 0 0 1 DL N × Set CGRAM Address 0 0 0 1 Puntero de RAM (CGRAM) Set DDRAM Address 0 0 1 Puntero de RAM (DDRAM) Read Busy Flag & RAM Pointer Instrucciones de datos 0 Return Home Instrucciones de comando 0 0 0 1 BF Puntero de RAM (DDRAM o CGRAM) Write to CGRAM or DDRAM 1 0 Escribir dato F ×
  • 347. Instrucciones del Display LCD Código Instrucciones RS R/W DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0 Read from CGRAM or DDRAM 1 1 Leer dato Conviene saber que las instrucciones Clear Display y Return Home tienen un tiempo de ejecución de cerca de 1.52 ms. Las demás toman algo de 40 µs. El LCD cuenta con dos registros internos principales, que dividen, grosso modo, las instrucciones en de datos y de comando. Poniendo el pin RS = 1 accedemos al registro de datos y mediante él a cualquier locación de la DDRAM o CGRAM, para operaciones de lectura y escritura de datos. Con RS = 0 accedemos al registro de comandospara escribir instrucciones de control del LCD (Clear Display, Function Set, etc.). En el caso de una lectura, obtenemos un dato particular que contiene el valor del puntero de RAM junto con el bit Busy flag. Clear display: 0 0 0 0 0 0 0 1 Limpia toda la pantalla del LCD. También retorna el cursor a su posición inicial (cima izquierda), esto es, designa el puntero de RAM a la dirección 0x00 de la DDRAM. Return home: 0 0 0 0 0 0 1 x Regresa el cursor a su posición inicial pero sin alterar el texto del display, es decir, solo designa el puntero de RAM a la dirección 0x00 de la DDRAM. Entry mode set: 0 0 0 0 0 1 I/D S Establece el modo de incremento o decremento y modo de desplazamiento del LCD. I/D = 1: El puntero de RAM se incrementa en 1 después de leer o escribir un dato. Así accedemos automáticamente a la siguiente locación de DDRAM o CGRAM. Si es DDRAM, este puntero representa la posición del cursor en la pantalla y el incremento significa su avance a la derecha. I/D = 0: El puntero de RAM se decrementa en 1 después de leer o escribir un dato. S = 1: Si se escribe un nuevo dato de carácter en el LCD, entonces el display entero se desplaza a la derecha cuando I/D = 0 o a la izquierda cuando I/D = 1. S = 0: El display no se desplaza luego de escribir en la DDRAM. Esto es lo usual.
  • 348. Display on/off control: 0 0 0 0 1 D C B Prende o apaga el Display, el Cursor y la función Blink del cursor. D = 1: El display se prende. D = 0: Apaga el display. (No significa que los datos de las RAMs se vayan a borrar.) C = 1: Despliega el cursor. C = 0: No despliega el cursor B = 1: La letra indicada por el cursor parpadea. B = 0: La letra no parpadea. Cursor or display shift: 0 0 0 1 S/C R/L x x Desplaza el cursor o el display a la derecha o la izquierda sin escribir o leer datos. Tabla S/C S/C R/L Operación 0 0 Mueve el cursor a la izquierda (puntero de RAM se decrementa en 1) 0 1 Mueve el cursor a la derecha (puntero de RAM se incrementa en 1) 1 0 El Cursor y el display entero se desplazan a la izquierda 1 1 El Cursor y el display entero se desplazan a la derecha Function set: 0 0 1 DL N F x x Configura la longitud del bus de datos, el número de líneas y el tipo de fuente. DL = 1 : La interface con el LCD es mediante un bus de datos de 8 bits. DL = 0 : La interface con el LCD es mediante un bus de datos de 4 bits. N = 1: Configura un display de 2 líneas. N = 0: Configura un display de 1 línea. F = 0: Fuente de carácter de 5×7 puntos. F = 1: Fuente de carácter de 5×10 puntos. Set DDRAM address: 1AAAAAAA
  • 349. Designa el puntero de RAM a la nueva dirección AAAAAAA de la DDRAM. Digamos que sirve para controlar la posición del cursor del LCD. Ejemplo, para escribir un texto en la segunda línea del display (que tiene dirección inicial 0x40), primero habría que enviar el comando Set DDRAM Address con el número 0x40 en el parámetro AAAAAAA. Set CGRAM address: 01AAAAAA Designa el puntero de RAM a la nueva dirección AAAAAAA de la CGRAM. Read Busy Flag & RAM Pointer: BF AAAAAAA Leer bit Busy Flag (BF) y el valor del puntero de RAM. BF = 1 indica que una operación interna está en progreso. El LCD no aceptará una nueva instrucción hasta que BF sea 0. El valor de AAAAAAA leído representa el valor del puntero de RAM. Es posible prescindir del bit BF. Para ello debemos esperar el tiempo adecuado antes de enviar la siguiente instrucción. Write data to CGRAM / DDRAM: DDDDDDDD Escribe el dato de 8 bits DDDDDDDD en la DDRAM o CGRAM, dependiendo de cuál de las dos esté siendo direccionada actualmente. Después de la escritura el puntero de RAM se incrementa o decrementa, según se haya configurado el display. Ver instrucción Entry Mode Set. Read data from CGRAM / DDRAM: DDDDDDDD Lee un dato de 8 bits de la DDRAM o CGRAM, dependiendo de cuál de ellas esté siendo direccionada actualmente. Después de la lectura el puntero de RAM se incrementa o decrementa en uno, según la configuración del display. Ver instrucción Entry Mode Set. Inicialización del LCD os LCDs tienen un circuito interno de reset que los inicializan automáticamente tras la alimentación. Lo cierto es que la auto-inicialización no siempre es fiable. Por eso existe la inicialización por software, que permite una completa configuración de los parámetros del LCD. Se constituye de una serie de pasos aparentemente bastante exóticos, sobre todo los primeros, pero que se justifican si tratamos de entender que este procedimiento debe ser capaz de configurar el LCD para que funcione con bus de datos de 4 u 8 bits, sin importar cómo estuvo operando antes, es decir, podemos cambiar "al vuelo" entre un modo y otro. Además de ello cada nueva instrucción debe ser enviada al LCD asegurándonos de que no se encuentre ocupado. El LCD indica su disponibilidad mediante el llamado
  • 350. bit BF (Busy Flag). BF = 1 indica LCD ocupado y BF = 0 es LCD listo. BF es el MSbit del byte que se lee del LCD cuando el pin RS = 0. Obviamente la lectura implica que debemos poder controlar el pin RW. De no usar este pin en nuestra conexión, debemos hacer pausas entre una instrucción y otra. Pero incluso si usamos el bit BF, al inicio debemos poner pausas porque se supone que el LCD aún no sabe si va trabajar con bus de datos de 4 u 8 bits y no sabe cómo responder a las instrucciones de lectura (no sabe si entregar bytes enteros o en nibbles). Que enredo!, ¿verdad? Por eso los siguientes flowcharts se ven tan complicados pese a tratarse de los LCD más simples del mundo. La inicialización de los LCD gráficos por ejemplo es más pequeña.
  • 351. Inicialización del LCD con bus de datos de 4 bits.
  • 352. Inicialización del LCD con bus de datos de 8 bits.
  • 353. Interface entre un Microcontrolador y un display LCD Esta presentación es poco usual. Los libros o los manuales de los compiladores suelen resaltar solo la interface de la librería que proveen. Esta exposición va pensando en los noveles usuarios del Arduino, que encuentran algo confusa la inicialización de su librería de LCD por contemplar todos los modos de operación viables. Aunque los LCDs parezcan simples de usar, para bien o para mal sus características abren puertas a diversos modos de interface. Considerando que el bus de datos puede ser de 8 o 4 bits y que se puede usar o prescindir de la línea de control RW, podemos obtener los siguientes 4 tipos de conexión. (Nota, la conexión de los pines de alimentación, de contraste y de la iluminación de fondo se estudia en la sección pines del LCD) Interface de display LCD 8 líneas de Datos 4 líneas de Datos 3 líneas de Control (conRW) Interface de 11 líneas Interface de 7 líneas 2 líneas de Control (sinRW) Interface de 10 líneas Interface de 6 líneas La interface de 11 líneas se trabaja con los 8 bits del bus de datos y las 3 líneas de Control. El uso del pin RW controla las operaciones de escritura (RW = 0) y lectura (RW = 1) del LCD. Las lecturas nos permiten por un lado conocer si el LCD está ocupado o no para saber si podemos enviar la siguiente instrucción de escritura, así como leer la posición actual del cursor. La otra finalidad de las lecturas es obtener los datos de las memorias DDRAM y CGRAM del LCD. Los datasheets dicen que el acceso bidireccional a las rams del LCD permiten utilizarlas como memoria extendida del sistema. Ahora parece hasta ridículo pero tiene cierto sentido considerando que estos LCDs nacieron en la prehistoria de los microcontroladores, donde los microcontroladores tenían muy poca RAM o en su lugar se usaban microprocesadores (que simplemente no tienen RAM).
  • 354. Interface de 11 líneas entre un microcontrolador y un LCD En la interface de 10 líneas el pin RW del LCD va siempre plantado a GND (RW = 0). Ello significa que el LCD solo aceptará operaciones de escritura del microcontrolador. Renunciar a la lectura de las memorias RAM es un hecho que pasa casi desapercibido. El punto clave de no controlar el pin RW es no enviar al LCD una nueva instrucción sin que haya terminado de procesar la anterior. Ya que no podemos leer del LCD para saber su estado, debemos calcular su disponibilidad a partir de los tiempos que demoran en ejecutarse las instrucciones. Por ejemplo, una vez inviada la instrucción Clear Display debemos esperar al menos 1.6 ms (que es su tiempo de ejecución) antes de enviar la siguiente instrucción.
  • 355. Interface de 10 líneas entre un microcontrolador y un LCD En la interface de 7 líneas el bus de datos del LCD se conecta con el microcontrolador por sus 4 pines más altos: D4, D5, D6 y D7. Como todas las instrucciones (de datos y de comando) son de un byte, los bytes deben ser transferidos en dos mitades. Primero se envía o recibe el nibble alto y luego el nibble bajo, siendo cada nibble validado por un pulso del pin Enable. Esas rutinas extras harán crecer un poco el firmware (programa del microcontrolador). En la contraparte, con el microcontrolador aún disponiendo de las tres líneas de control, podemos realizar cualquier operación de lectura y escritura, lo mismo que en la interface completa de 11 líneas pero ahorrándonos 4 pines. Este beneficio suele prevalecer sobre el hándicap derivado del firmware. Los LCDs están fabricados con tecnología CMOS, por lo que algunos modelos sugieren conectar los pines de entrada no usados a alguna señal estable para evitar que por ellos se filtre algún ruido que pueda perturbar la operación del LCD.
  • 356. Interface de 7 líneas entre un microcontrolador y un LCD Por último tenemos la interface de 6 líneas. Aquí se nos juntan todas las desventajas software de tener que trabajar a base de nibbles y de renunciar a las lecturas del LCD para obtener datos de sus memorias RAM o para saber si el LCD está ocupado o no antes de poder enviarle una nueva instrucción. A pesar de todo eso, pueden darse ocasiones, como disponer de un microcontrolador de muy pocos pines, donde tengamos que recurrir a esta conexión.
  • 357. Interface de 6 líneas entre un microcontrolador y un LCD Librería Para Display LCD Tenemos a continuación una librería para controlar un LCD con una interface de 4 bits y usando el bit BF (Busy Flag). Si tuviste la paciencia de leer las páginas anteriores, verás que es un claro reflejo de todo lo expuesto. Y si no, de todos modos en seguida citaré ligeramente cómo utilizarla. La librería trabaja para los compiladores IAR C yAVR GCC y consta de dos archivos lcd.h y lcd.c. Ambos deberán ser indexados al proyecto en el entorno de AVR IAR C o Atmel Studio 6 paraWinAVR (ante alguna duda puedes ir a la secciónAdición de Archivos o Librerías al Proyecto). En el código fuente, sin embargo, solo se debe indicar el archivo de cabecera i2c.h mediante la directiva #include. #include "lcd.h" La librería utiliza por defecto el puerto B del AVR tanto para el bus de datos del LCD como para las líneas de control E, RS y R/W. Esta interface se puede modificar en los #defines del archivo i2c.h. Nota que por cada puerto se deben cambiar los tres registros, PORT, DDR y PIN. Esa podría ser una configuración de cierta recurrencia, en cambio, no deberíamos tocar lcd.c, salvo que por alguna razón deseemos editar su código.
  • 358. Funciones Básicas Disponibles lcd_init(). Obviamente es la primera función a llamar. Tras ejecutarse el LCD debe quedar inicializado, con la pantalla limpia y con el cursor en el primer casillero. lcd_gotorc(r,c). El LCD tiene un cursor que, si bien puede mostrarse en pantalla, suele configurarse para que permanezca oculto. Bien, visible o no, el cursor avanza automáticamente tras cada letra que se escribe. Con lcd_gotorc(r,c) podemos mover el cursor manualmente a la fila r y columna c. El parámetro r puede valer entre 1 y 2, y el valor de c va de 1 en adelante. lcd_puts(s). Visualiza la cadena s en el LCD empezando en la posición actual del cursor. La cadena s es almacenada en RAM. No se suelen mostrar grandes datos en un LCD, así que no implemente una función análoga que opere sobre la memoria FLASH. lcd_clear(). Limpia la pantalla del LCD y coloca el cursor al inicio, en la fila 1, columna 1. lcd_data(c). Escribe una sola letra en el LCD, en la posición actual del cursor. lcd_data() también permite crear caracteres gráficos, como se muestra en una práctica más adelante. lcd_cmd(com). Ocasionalmente también usaremos esta función para enviar comandos al LCD, por ejemplo: lcd_cmd(LCD_LINE2); // Mover cursor al inicio de línea 2 lcd_cmd(LCD_CLEAR); // Limpiar pantalla lcd_cmd(LCD_CURBLK); // Mostrar Cursor + Blink lcd_cmd(LCD_CURSOR); // Mostrar solo Cursor lcd_cmd(LCD_CGRAM+7); // Mover Puntero de RAM a dirección 7 de la CGRAM Las constantes LCD_CLEAR y algunas más se hallan definidas en el archivo lcd.h. Por supuesto que también se pueden formar cualesquiera códigos de instrucciones de los estudiados antes. /****************************************************************************** * FileName: lcd.h * Purpose: Librería de funciones para controlar un display LCD con chip * Hitachi HD44780 o compatible. La interface es de 4 bits.
  • 359. * Processor: ATmel AVR * Compiler: AVR IAR C y AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. * * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta * licencia y las notas de autor y copyright de arriba. *****************************************************************************/ #include "avr_compiler.h" //**************************************************************************** // CONFIGURACIÓN DE LOS PINES DE INTERFACE //**************************************************************************** /* Define el puerto a donde se conectará el bus de datos del LCD * Se utilizará el nible alto del puerto escogido (ejem. PB4-DB4,...,PB7-DB7) */ #define lcd_DATAout PORTB #define lcd_DATAin PINB #define lcd_DATAddr DDRB // Registro PORT del puerto // Registro PIN del puerto // Registro DDR del puerto /* Define el puerto a donde se conectarán las líneas de control del LCD * E, RW y RS. Puede ser el mismo puerto del bus de datos.
  • 360. */ #define lcd_CTRLout PORTB #define lcd_CTRLin PINB // Registro PORT del puerto // Registro PIN del puerto #define lcd_CTRLddr DDRB // Registro DDR del puerto /* Define los números de los pines del puerto anterior que corresponderán a * las líneas E, RW y RS del LCD. */ #define lcd_E #define lcd_RW #define lcd_RS 3 // Pin Enable 2 1 // Pin Read/Write // Pin Register Select //**************************************************************************** // CÓDIGOS DE COMANDO USUALES //**************************************************************************** #define LCD_CLEAR 0x01 // Limpiar Display #define LCD_RETHOM 0x02 // Cursor a inicio de línea 1 #define LCD_LINE1 0x80 // Línea 1 posición 0 #define LCD_LINE2 0xC0 // Línea 2 posición 0 #define LCD_DDRAM 0x80 // Dirección 0x00 de DDRAM #define LCD_CGRAM 0x40 // Dirección 0x00 de CGRAM #define LCD_CURSOR 0x0E // Mostrar solo Cursor #define LCD_BLINK 0x0D // Mostrar solo Blink #define LCD_CURBLK 0x0F // Mostrar Cursor + Blink #define LCD_NOCURBLK 0x0C // No mostrar ni Cursor ni Blink
  • 361. //**************************************************************************** // PROTOTIPOS DE FUNCIONES //**************************************************************************** voidlcd_init(void);// Inicializa el LCD voidlcd_puts(char*s);// Envía una cadena ram al LCD voidlcd_gotorc(charr,charc);// Cursor a fila r, columna c voidlcd_clear(void);// Limpia el LCD y regresa el cursor al inicio voidlcd_data(chardat);// Envía una instrucción de dato al LCD voidlcd_cmd(charcom);// Envía una instrucción de comando al LCD charlcd_read(charRS);// Lee un dato del LCD voidlcd_write(charinst,charRS);// Escribe una instrucción en el LCD voidlcd_nibble(charnibble); voidldelay_ms(unsignedchar); /****************************************************************************** * FileName: lcd.c * Purpose: Librería de funciones para controlar un display LCD con chip * Hitachi HD44780 o compatible. La interface es de 4 bits. * Processor: ATmel AVR * Compiler: AVR IAR C y AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. *
  • 362. * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta * licencia y las notas de autor y copyright de arriba. *****************************************************************************/ #include "lcd.h" //**************************************************************************** // Ejecuta la inicialización software completa del LCD. La configuración es // de: interface de 4 bits, despliegue de 2 líneas y caracteres de 5x7 puntos. //**************************************************************************** voidlcd_init(void) { /* Configurar las direcciones de los pines de interface del LCD */ lcd_DATAddr|=0xF0; lcd_CTRLddr|=(1<<lcd_E)|(1<<lcd_RW)|(1<<lcd_RS); /* Secuencia de inicialización del LCD en modo de 4 bits*/ lcd_CTRLout&=~((1<<lcd_E)|(1<<lcd_RW)|(1<<lcd_RS)); ldelay_ms(45);// > 40 ms lcd_nibble(0x30);// Function Set: 8-bit ldelay_ms(5);// > 4.1 ms lcd_nibble(0x30);// Function Set: 8-bit ldelay_ms(1);// > 100 µs lcd_nibble(0x30);// Function Set: 8-bit ldelay_ms(1);// > 40 µs
  • 363. lcd_nibble(0x20);// Function Set: 4-bit ldelay_ms(1);// > 40 µs lcd_nibble(0x20);// Function Set: 4-bit, 2lines, 4×7font lcd_nibble(0x80);// lcd_write(0x0C,0);// Display ON/OFF Control: Display on, Cursor off, Blink off lcd_write(0x01,0);// Clear Display lcd_write(0x06,0);// Entry Mode Set } //**************************************************************************** // Escribe una instrucción en el LCD: // Si RS = 0 la instrucción es de comando (Function Set, Entry Mode set, etc). // Si RS = 1 la instrucción es de dato y va a la DDRAM o CGRAM. //**************************************************************************** voidlcd_write(charinst,charRS) { while(lcd_read(0)&0x80);// Esperar mientras LCD esté ocupado if(RS) lcd_CTRLout|=(1<<lcd_RS);// Para escribir en DDRAM o CGRAM else lcd_CTRLout&=~(1<<lcd_RS);// Para escribir en Registro de Comandos delay_us(5);// Permite actualizar el Puntero de RAM lcd_nibble(inst);// Enviar nibble alto lcd_nibble(inst<<4);// Enviar nibble bajo }
  • 364. //**************************************************************************** // Envía el nibble alto de 'nibble' al LCD. //**************************************************************************** voidlcd_nibble(charnibble) { lcd_CTRLout&=~(1<<lcd_RW);// Establecer Modo de escritura lcd_DATAddr|=0xF0;// Hacer nibble alto de bus de datos salida lcd_DATAout=(nibble&0xF0)|(lcd_DATAout&0x0F);// Colocar dato delay_us(2);// tAS, set-up time > 140 ns lcd_CTRLout|=(1<<lcd_E);// Pulso de Enable delay_us(2);// Enable pulse width > 450 ns lcd_CTRLout&=~(1<<lcd_E);// lcd_DATAddr&=0x0F;// Hacer nibble alto entrada } //**************************************************************************** // Lee un byte de dato del LCD. // Si RS = 1 se lee la locación de DDRAM o CGRAM direccionada actualmente. // Si RS = 0 se lee el 'bit de Busy Flag' + el 'Puntero de RAM'. //**************************************************************************** charlcd_read(charRS) { charhigh,low; if(RS) lcd_CTRLout|=(1<<lcd_RS);// Para leer de DDRAM o CGRAM else
  • 365. lcd_CTRLout&=~(1<<lcd_RS);// Para leer Busy Flag + Puntero de RAM lcd_CTRLout|=(1<<lcd_RW);// Establecer Modo Lectura lcd_DATAddr&=0x0F;// Hacer nibble alto entrada delay_us(2);// tAS, set-up time > 140 ns lcd_CTRLout|=(1<<lcd_E);// Habilitar LCD delay_us(2);// Data Delay Time > 1320 ns high=lcd_DATAin;// Leer nibble alto lcd_CTRLout&=~(1<<lcd_E);// Para que el LCD prepare el nibble bajo delay_us(2);// Enable cycle time > 1200 ns lcd_CTRLout|=(1<<lcd_E);// Habilitar LCD delay_us(2);// Data Delay Time > 1320 ns low=lcd_DATAin;// Leer nibble bajo lcd_CTRLout&=~(1<<lcd_E);// return(high&0xF0)|(low>>4);// Juntar nibbles leídos } //**************************************************************************** // Envían cadenas RAM terminadas en nulo al LCD. //**************************************************************************** voidlcd_puts(char*s) { unsignedcharc,i=0; while(c=s[i++]) lcd_write(c,1);// Instrucción 'Write Data to DDRAM/CGRAM' }
  • 366. //**************************************************************************** // Ubica el cursor del LCD en la columna c de la línea r. //**************************************************************************** voidlcd_gotorc(charr,charc) { if(r==1)r=LCD_LINE1; elser=LCD_LINE2; lcd_write(r+c-1,0);// Instrucción 'Set DDRAM Address' } //**************************************************************************** // Limpia la pantalla del LCD y regresa el cursor a la primera posición // de la línea 1. //**************************************************************************** voidlcd_clear(void) { lcd_write(LCD_CLEAR,0);// Instrucción 'Clear Display' } //**************************************************************************** // Envían instrucciones de comando y de datos al LCD. //**************************************************************************** voidlcd_cmd(charcom) { lcd_write(com,0);// Cualquier instrucción de comando }
  • 367. voidlcd_data(chardat) { lcd_write(dat,1);// Instrucción 'Write Data to DDRAM/CGRAM' } //**************************************************************************** // Genera un delay de n milisegundos //**************************************************************************** voidldelay_ms(unsignedcharn) { while(n--) delay_us(1000); } Prácticas con LCD “Hello World” Mostrar un mensaje de “Hello World” en el LCD es un programa casi tan trillado como hacer parpadear un LED. Según la configuración por defecto de la librería para el LCD, debemos usar la conexión mostrada en el esquema de abajo. La configuración de puertos y de pines a usar se puede cambiar en archivo lcd.h. El pin VEE (o Vo) del LCD establece el contraste de la pantalla. Muchas veces se prefiere quitar el potenciómetro y conectar VEE a tierra para fijar el máximo contraste. En los siguientes circuitos haremos algo parecido.
  • 368. Circuito para el LCD y el microcontrolador AVR. /************************************************************************ ****** * FileName: main.c * Purpose: LCD - Visualización de texto * Processor: ATmega164P * Compiler: AVR IAR C y AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. * * License: Se permiten el uso y la redistribución de este código con
  • 369. * modificaciones o sin ellas, siempre que se mantengan esta * licencia y las notas de autor y copyright de arriba. ************************************************************************* ****/ #include "avr_compiler.h" #include "lcd.h" void delay_ms(unsignedint t) { while(t--) delay_us(1000); } int main(void) { lcd_init();// Inicializar LCD while(1) { lcd_gotorc(1,7);// Cursor a fila 1 posición 7 lcd_puts("Hello");// Escribir Hello lcd_gotorc(2,7);// Cursor a fila 2 posición 7 lcd_puts("World");// ... delay_ms(600);// Pausa de 600 ms lcd_clear();// Limpiar pantalla delay_ms(400);// ... }
  • 370. } Visualización de Números Los LCDs solo entienden de caracteres alfanuméricos y algunos otros, pero no saben reconocer números. En esta práctica veremos cómo hacerlo implementando un sencillo reloj. No será el más preciso, pero servirá de buen ejemplo parar formatear números. Para el circuito, de ahora en adelante, en vez del potenciómetro, colocaremos un diodo 1N4148 en el pin VEE para fijar la tensión (VDD-VEE) a cerca de 4.3 V. En la mayoría de los LCDs este valor brinda un muy aceptable nivel de contraste de la pantalla. Circuito para el LCD y el microcontrolador AVR. La función lcd_puts recibe como parámetro un array de tipo char, que en su forma más usada sería una cadena texto.
  • 371. Para visualizar números en el LCD primero debemos convertirlos en cadenas de texto. La conocida función sprintf (acrónimo de string print formatted) puede formatear cadenas y números en diferentes bases y colocarlas en el array que recibe como primer parámetro. Sprintf está basada en printf, así que tiene las mismas características y limitaciones. En este programa solo se convierten números enteros. Pero si deseas utilizar números de punto flotante tendrás que habilitar el uso de la librería que contiene printf en versión completa. Para más información puedes revisar la secciónConfiguración de printf del capítulo Atmel Studio 6 y WinAVR. Sprintf soporta varios formatos de números e incluso en su modo básico requiere de cierta memoria que a veces podría ser de consideración. Para ese caso también se pueden usar otras funciones de la librería C estándar stdlib.h, como itoa, por ejemplo. Normalmente no las uso porque tienen variaciones entre los compiladores y al menos para las prácticas como ésta prefiero no tocar esas divergencias. /************************************************************************ ****** * FileName: main.c * Purpose: LCD - Visualización de números * Processor: ATmega164P * Compiler: AVR IAR C y AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. * * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta * licencia y las notas de autor y copyright de arriba. ************************************************************************* ****/ #include "avr_compiler.h" #include "lcd.h" void delay_ms(unsignedint t)
  • 372. { while(t--) delay_us(1000); } int main(void) { char buff[17];// Array de 17 elementos tipo char unsigned seg, min, hor; seg = min =0; hor =12; lcd_init();// Inicializar LCD lcd_gotorc(1,4);// Cursor a fila 1 posición 4 lcd_puts("easy clock"); for(;;) { sprintf(buff,"%2d:%02d:%02d ", hor, min, seg);// Formatear lcd_gotorc(2,5);// Cursor a fila 2 posición 5 lcd_puts(buff);// Enviar buffer a LCD if(++seg >59) { seg =0; if(++min >59) { min =0;
  • 373. if(++hor >12) hor =1; } } delay_ms(998); } } Mostrar Texto en Desplazamiento en LCD Como parte de su funcionalidad, el controlador interno del LCD puede ejecutar instrucciones para desplazar lo mostrado en la pantalla una posición hacia la izquierda o la derecha. Los códigos para desplazar la pantalla (ver la sección referida) son 0x1C y 0x18. Con eso en el código solo tendríamos que escribir lcd_cmd(0x1C); Para mover todo el display incluyendo el cursor a la derecha, y lcd_cmd(0x18); Para mover el display a la izquierda. Puede parecer interesante, pero como lo comprobarás en la práctica Comunicación PCAVR-LCD, funciona en un rango restringido y no es útil cuando solo queremos desplazar el texto de una sola línea. Estas limitaciones llevan a muchos a realizar esos efectos mediante rutinas software, como lo que haremos en esta práctica: Mostrar por el LCD un mensaje que pasa como una marquesina.
  • 374. Circuito para el LCD y el microcontrolador AVR. El Código fuente /****************************************************************************** * FileName: main.c * Purpose: LCD - Textos en desplazamiento * Processor: AVR ATmegaXX4 * Compiler: AVR IAR C & AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
  • 375. * * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta * licencia y las notas de autor y copyright de arriba. *****************************************************************************/ #include "avr_compiler.h" #include "lcd.h" #define LCD_LEN 16 // Para LCD de 2×16 PROGMEMconstcharTaine[]=" "EL HAMBRE PRODUCE POEMAS INMORTALES. LA ABUNDANCIA, SOLAMENTE INDIGESTIONES Y TORPEZAS" voiddelay_ms(unsignedintt) { while(t--) delay_us(1000); } intmain(void) { unsignedcharj;// Índice relativo unsignedchari;// Índice base charc; ";
  • 376. lcd_init(); lcd_puts(" Scrolling text ");// Escribir "Scrolling text" en LCD while(1) { start: i=0; for(;;) { lcd_cmd(LCD_LINE2);// Cursor a inicio de línea 2 for(j=0;j<LCD_LEN-1;j++) { c=pgm_read_byte(&Taine[i+j]);// Obtener dato de matriz if(c)// Si es dato válido, lcd_data(c);// enviarlo a LCD else// Si no (c = 0x00 = fin), gotostart;// salir de los dos bucles for } delay_ms(400); i++; } } } Descripción del programa
  • 377. El texto de la pantalla se desplaza una posición cada 400 ms. Si te parece que avanza muy lento, puedes disminuir esta pausa. No obstante, podrías empezar a ver como si hubiera dos letras por casillero de la pantalla. Ello se debe a que el carácter enviado al LCD no se muestra ni se borra de inmediato. Es lo que sus datasheets indican como tiempo de respuesta de visualización. En general, a diferencia del Basic, en C es muy mal visto el uso de un goto, salvo un caso extremo. goto funciona como en el ensamblador: salta a otro punto del programa, identificado con una etiqueta. Mi goto salta a la etiqueta start para salir de dos bucles al mismo tiempo. Dicen que ése es uno de los pocos casos considerados extremos: salir intempestivamente de varios bucles anidados. A decir verdad, siempre hay algoritmos alternativos para evitar el goto. Caracteres gráficos en LCD La creación de caracteres gráficos puede ser un tema superfluo. Aun así, suponiendo que no faltarán algunas personas obsesivas como yo, que siempre quieren probarlo todo, he preparado esta práctica para ir cerrando el capítulo. Hagamos un poco de memoria. Cuando enviamos el código de un carácter alfanumérico a la DDRAMdel LCD, su chip interno buscará en la CGROM el patrón correspondiente y luego lo visualizará en la pantalla. Así se escriben todos textos (y así hemos trabajado hasta ahora). Ahora bien, si el código enviado vale entre 0x00 y0x07 (o 0x08 y 0x0F), el chip interno buscará su patrón de visualización en la CGRAM. Siendo ésta una RAM de lectura/escritura, podemos programar en ella los diseños que se nos ocurran.
  • 378. Mapa de memoria para la creación de nuevos caracteres. La CGRAM (Character Generator RAM) consta de 64 bytes en los que se pueden escribir los patrones de 8 nuevos caracteres de 5×7 puntos ó 4 caracteres de 5×10 puntos. Aquí veremos el primer caso. Cuando los caracteres son de 5×7 puntos los 64 bytes se dividen en 8 bloques de 8 bytes cada uno, y cada bloque almacena el patrón de un nuevo carácter. El esquema mostrado arriba indica que: El primer bloque de CGRAM, con direcciones desde 0b00000000 hasta 0b00000111, corresponde al código 0x00 (ó 0x80) de la DDRAM. El segundo bloque CGRAM, con direcciones desde 0b00001000 hasta 0b00001111, corresponde al código 0x01 (ó 0x88) de la DDRAM; y así sucesivamente.
  • 379. Por ejemplo, la figura de arriba indica que se han rellenado los dos primeros bloques con los patrones de dos pacman. Hasta ahí solo se han creado dos nuevos caracteres. Para mostrarlos en el LCD habría que escribir un código así: lcd_data(0x00); // Visualizar primer pacman lcd_data(0x01); // Visualizar segundo pacman La práctica Se muestra en el LCD un pacman que se desplaza de uno a otro lado. Circuito para el LCD y el microcontrolador AVR. El código fuente /****************************************************************************** * FileName: main.c
  • 380. * Purpose: LCD - Creación de caracteres gráficos personalizados * Processor: AVR ATmegaXX4 * Compiler: AVR IAR C & AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. * * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta * licencia y las notas de autor y copyright de arriba. *****************************************************************************/ #include "avr_compiler.h" #include "lcd.h" #define LCD_LEN 16 // Para LCD de 2×16 /* Identificadores de los nuevos caracteres */ #define PacR1 0x00 #define PacR2 0x01 #define PacL1 0x02 #define PacL2 0x03 /* Patrones de los nuevos caracteres a crearse */ PROGMEMconstcharPattern1[]={0x0F,0x1C,0x18,0x10,0x18,0x1C,0x0F,0x00}; PROGMEMconstcharPattern2[]={0x00,0x0E,0x1F,0x10,0x1F,0x0E,0x00,0x00};
  • 381. PROGMEMconstcharPattern3[]={0x1E,0x07,0x03,0x01,0x03,0x07,0x1E,0x00}; PROGMEMconstcharPattern4[]={0x00,0x0E,0x1F,0x01,0x1F,0x0E,0x00,0x00}; voiddelay_ms(unsignedintt) { while(t--) delay_us(1000); } voidcreate_char(unsignedchara,PGM_Pp) { unsignedchari; lcd_cmd(LCD_CGRAM+a);// Instrucción Set CGRAM Address for(i=0;i<8;i++) lcd_data(pgm_read_byte(p+i)); } intmain(void) { signedchari;// Debe ser variable con signo lcd_init();// Inicializar LCD /* Crear los nuevos caracteres (los pacman's) * Esto es volcar los patrones de los pacman en la CGRAM */ create_char(PacR1*8,Pattern1);
  • 382. create_char(PacR2*8,Pattern2); create_char(PacL1*8,Pattern3); create_char(PacL2*8,Pattern4); lcd_clear();// Limpiar pantalla y regresar a DDRAM lcd_puts(" Hungry Pacman ");// Escribir "Hungry Pacman" en LCD while(1) { /* Pacman desplazándose a la derecha */ for(i=0;i<LCD_LEN;i++) { lcd_cmd(LCD_LINE2+i);// Cursor a posición i de línea 2 if(i&0x01)// Si bit 0 de i es 1, lcd_data(PacR1);// enviar pacman abierto else// Si no, lcd_data(PacR2);// enviar pacman cerrado lcd_cmd(LCD_LINE2+i-1);// Cursor a posición anterior de línea 2 lcd_data(' ');// para borrar pacman previo delay_ms(200); } /* Pacman desplazándose a la izquierda */
  • 383. for(i=LCD_LEN;i>=0;i--) { lcd_cmd(LCD_LINE2+i);// Cursor a posición i de línea 2 if(i&0x01)// Si bit 0 de i es 1, lcd_data(PacL1);// enviar pacman abierto else// Si no, lcd_data(PacL2);// enviar pacman cerrado //lcd_cmd(LCD_LINE2+i+1); // Cursor a posición anterior de línea 2 lcd_data(' ');// para borrar pacman previo delay_ms(200); } } } Descripción del programa Después de iniciado el LCD, los datos que se le envíen irán a la DDRAM (para mostrar caracteres en la pantalla). Como los patrones de los pacman deben ir en la CGRAM necesitamos establecerla como destino. Para eso enviamos el comando Set CGRAM Address con la dirección de CGRAM que queremos acceder. La función create_char rellena con 8 bytes del array p que se le pasa como parámetro el segmento de CGRAM que empieza en la dirección a. voidcreate_char(unsignedchara,PGM_Pp) { unsignedchari; lcd_cmd(LCD_CGRAM+a);// Instrucción Set CGRAM Address for(i=0;i<8;i++)
  • 384. lcd_data(pgm_read_byte(p+i)); } Notemos que al término de la función create_char el puntero de RAM sigue dirigido a la CGRAM. Por tanto para visualizar los caracteres en la pantalla, incluyendo los nuevos caracteres creados, tenemos que volver a seleccionar la DDRAM. Para esto tenemos la instrucción Set DDRAM Address, Return Home y Clear Display. Todas estas instrucciones están relacionadas con el cursor, el cual a su vez no es otra cosa que el puntero RAM trabajando sobre la DDRAM. Yo usé la tercera opción con sentencia lcd_clear() que aparentemente no tenía sentido porque la pantalla ya está limpia tras la inicialización. Como hemos creado los 4 pacman en los 4 primeros bloques (de 8 bytes) de la CGRAM, los códigos para accederlos serán 0 (PacR1), 1 (PacR2), 2 (PacL1) y 3 (PacL2), respectivamente. Por si no quedó claro cómo se forman los patrones de los pacman, aquí tenemos los dos primeros. (Los bits × no importan, pueden ser 1s o 0s.) PROGMEM constcharPattern1[]={0x0F,0x1C,0x18,0x10,0x18,0x1C,0x0F,0x00}; PROGMEM constcharPattern2[]={0x00,0x0E,0x1F,0x10,0x1F,0x0E,0x00,0x00}; Solo debemos tener un poco de paciencia para elaborar los arrays de los patrones. Ahora que si no la tienes o prefieres un atajo, puedes utilizar una de las tantas herramientas que abundan (me gusta la flexibilidad de LCD Font Creator). Inclusive los mismos compiladores comoCodeVisionAVR o MikroC proveen los suyos. Abajo se ve cómo el generador de caracteres de MikroC nos cambia el descifrado de código binario por simples clics. El código generado incluye una función que crea y visualiza el nuevo carácter, pero a mí solo me interesa el array.
  • 385. Comunicación PC – AVR – LCD Aquí tenemos un programa cliché en los ejemplos de interface entre un microcontrolador y una computadora mediante el puerto serie. El programa terminal envía por el puerto serie las letras que presionemos en el teclado. El AVR los recibirá, los reflejará al PC y también los visualizará en el display LCD. Haremos que algunas teclas generen instrucciones especiales: La tecla Escape, de código 27, sirve pare limpiar la pantalla del LCD. La tecla Retroceso o Backspace, de código0x08 = „b‟, lleva el cursor del LCD una posición atrás. La tecla + desplaza toda la pantalla a la derecha.
  • 386. La tecla - desplaza toda la pantalla a la izquierda. En esta ocasión será de utilidad tener el cursor a la vista. Circuito para el LCD y el microcontrolador AVR. El código fuente /****************************************************************************** * FileName: main.c * Purpose: LCD - Control de LCD desde PC mediante AVR * Processor: AVR ATmegaXX4 * Compiler: AVR IAR C & AVR GCC (WinAVR)
  • 387. * Author: Shawn Johnson. http://www.cursomicros.com. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. * * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta * licencia y las notas de autor y copyright de arriba. *****************************************************************************/ #include "avr_compiler.h" #include "lcd.h" #include "usart.h" intmain(void) { lcd_init();// Ver interface en "lcd.h" lcd_cmd(LCD_CURBLK);// Mostrar Cursor + Blink usart_init();// 9600 - 8N1 puts("nr Acceso a LCD desde PC nr"); puts("nr Escribe en el LCD... nr"); while(1) { if(kbhit())// Si hay algún dato en el buffer de recepción {
  • 388. charc=getchar();// Obtener un dato switch(c) { case'+':lcd_cmd(0x1C);// Desplazar display a la derecha break; case'-':lcd_cmd(0x18);// Desplazar display a la izquierda break; case'b':lcd_cmd(0x10);// Mover cursor atrás lcd_data(' ');// Escribir espacio blanco lcd_cmd(0x10);// Cursor atrás putchar(c);// Devolver dato break; case0x1B:lcd_clear();// Limpiar LCD puts("nr"); break; default:lcd_data(c);// Escribir c en LCD putchar(c);// Devolver dato } } } } Descripción del programa El archivo lcd.h contiene los códigos de los comandos más usuales del LCD como 0x01(LCD_CLEAR), 0x80 (LCD_DDRAM) o 0x0F (LCD_CURBLK) que utilizamos en este programa. De hecho si intentamos etiquetar cada una de las instrucciones derivadas del LCD, formaríamos una lista casi interminable de códigos con nombres
  • 389. que lejos de ayudar nos enredaría. Algunos lo hacen pero yo prefiero regresar a la descripción de las instrucciones y tomar los códigos que me interesan. Para mí eso es más sencillo. En este programa los códigos de interés fueron: 0x1C para desplazar la pantalla a la derecha. 0x18 para desplazar la pantalla a la izquierda 0x10 para mover el cursor una posición atrás y 0x14 para mover el cursor una posición adelante (no se usó en el código). Animación Gráfica en Display LCD Hace algún tiempo vi en forosdeelectronica.comun post donde se presentaba un programa de animación en un display LCD de 2x16. Ya anteriormente vimos cómo crear animaciones con texto o caracteres gráficos pero lo que tenía de especial este proyecto era que la figura animada era un caballo que ocupaba varias casillas del LCD. No soy muy seguidor de los foros, ni siquiera del foro de cursomicros, y hasta donde sé nadie había presentado su versión del caballo animado. El proyecto, hecho por los chicos de microgenios, me llamó la atención no por la complejidad que podía presentar sino, por el contrario, por lo sencillo que podía resultar si se ideaba un algoritmo adecuado con ayuda del software adecuado. Por lo demás el circuito es el mismo de siempre.
  • 390. Circuito para el LCD y el microcontrolador AVR. El algoritmo, como cualquiera lo podría imaginar, es mostrar las diversas posiciones de un caballo corriendo en diferentes lugares del LCD. Podemos armar la figura de un caballo juntando varios caracteres gráficos como si de un rompecabezas se tratara. Hasta ahí el reto se pone interesante. Pero crear un pacman de 5x8 pixeles es una cosa y otra muy diferente es crear un caballo de varias piezas de ese tamaño. Hay quienes podrían recurrir a un programa como Excel para confeccionar los patrones de las piezas de sus caballos. No es mala idea. Yo por mi parte reafirmo lo que dije sobre LCD Font Maker en las prácticas de los letreros matriciales de LEDs, que es el mejor programa de su clase que he podido encontrar. Lo primero que se me vino a la mente fue crear las imágenes individuales de los caballos que luego compaginadas le darían vida. Son solo tres marcos o frames en total. Los puedes ver abajo.
  • 393. LCD Font Maker mostrando los tres fotogramas que constituirán la animación. En cada caso el alto y el ancho son de 16 y 24 pixeles respectivamente. Si dividimos cada caballo en 8 piezas, cada una será de 8x6, lo que en la práctica derivará en caracterss de 5x7. Esto es porque no todos los LCD soportan los caracteres de 5x8 pixeles.
  • 394. Cada fotograma se dividirá en 8 piezas de 6 bytes cada una. La figura de arriba nos devela que un fotograma queda mejor dividido en 8 piezas si cada una de ellas se compone de 6 bytes, es decir, cada carácter personalizado debe ser formado por bytes tomados del esquema de forma vertical. En principio esto es contrario a la estructura horizontal de los patrones que deben formar los nuevos caracteres en la CGRAM, pero eso se puede arreglar por vía software. Esta división es la mejor forma de ahorrar memoria y sobre todo es un esquema que se ajusta perfectamente a una de las opciones de generación de matrices que ofrece el programa LCD Font Maker. En el paso 2 de este programa debemos escoger la cuarta opción, como se muestra abajo.
  • 395. Elección del tipo de barrido para la generación de matrices. En el paso 3 obtendremos las matrices de cada caballo que dibujamos. Hacemos las modificaciones necesarias en ellas considerando que por su tamaño irán almacenadas en la memoria FLASH y las colocamos en nuestro código fuente. Puedes revisar la sección variables en FLASH si tienes dudas sobre este tema. /* Patrones de los nuevos caracteres a crearse */ PROGMEMconstunsignedcharHorse0[]={ 0x00,0x60,0x70,0x78,0x18,0x00,0x70,0x78,0x78,0x78,0x78,0x00,0x70,0x70,0x70,0x78, 0x78,0x00,0x7E,0x1E,0x0E,0x3E,0x3F,0x00,0x06,0x03,0x01,0x00,0x38,0x00,0x4F,0x07, 0x0F,0x0F,0x0B,0x00,0x13,0x03,0x03,0x3F,0x47,0x00,0x03,0x13,0x0F,0x00,0x00,0x00 }; PROGMEMconstunsignedcharHorse1[]={ 0x40,0x60,0x70,0x70,0x30,0x00,0x70,0x78,0x78,0x78,0x70,0x00,0x70,0x70,0x78,0x78, 0x78,0x00,0x7E,0x1E,0x0E,0x3F,0x3E,0x00,0x01,0x00,0x00,0x00,0x38,0x00,0x4F,0x07, 0x03,0x0F,0x77,0x00,0x03,0x03,0x03,0x03,0x3F,0x00,0x53,0x0B,0x06,0x00,0x00,0x00 };
  • 396. PROGMEMconstunsignedcharHorse2[]={ 0x40,0x70,0x78,0x38,0x18,0x00,0x70,0x78,0x78,0x78,0x78,0x00,0x70,0x70,0x70,0x78, 0x78,0x00,0x7E,0x3E,0x0E,0x3E,0x3F,0x00,0x07,0x00,0x20,0x1F,0x07,0x00,0x03,0x0B, 0x1F,0x1F,0x27,0x00,0x49,0x13,0x17,0x1F,0x1F,0x00,0x07,0x0E,0x18,0x20,0x40,0x00 }; El código fuente /****************************************************************************** * FileName: main.c * Purpose: LCD - Creación de caracteres gráficos personalizados * Processor: AVR ATmegaXX4 * Compiler: AVR IAR C & AVR GCC (WinAVR) * Author: * Shawn Johnson. http://www.cursomicros.com. Inspirado en un proyecto de www.microgenios.br. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. * * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta * licencia y las notas de autor y copyright de arriba. *****************************************************************************/ #include "avr_compiler.h" #include "lcd.h" voiddraw_horse(unsignedcharp); voidcreate_horse(PGM_Phorse);
  • 397. voiddelay_ms(unsignedintt); /* Patrones de los nuevos caracteres a crearse */ PROGMEMconstunsignedcharHorse0[]={ 0x00,0x60,0x70,0x78,0x18,0x00,0x70,0x78,0x78,0x78,0x78,0x00,0x70,0x70,0x70,0x78, 0x78,0x00,0x7E,0x1E,0x0E,0x3E,0x3F,0x00,0x06,0x03,0x01,0x00,0x38,0x00,0x4F,0x07, 0x0F,0x0F,0x0B,0x00,0x13,0x03,0x03,0x3F,0x47,0x00,0x03,0x13,0x0F,0x00,0x00,0x00 }; PROGMEMconstunsignedcharHorse1[]={ 0x40,0x60,0x70,0x70,0x30,0x00,0x70,0x78,0x78,0x78,0x70,0x00,0x70,0x70,0x78,0x78, 0x78,0x00,0x7E,0x1E,0x0E,0x3F,0x3E,0x00,0x01,0x00,0x00,0x00,0x38,0x00,0x4F,0x07, 0x03,0x0F,0x77,0x00,0x03,0x03,0x03,0x03,0x3F,0x00,0x53,0x0B,0x06,0x00,0x00,0x00 }; PROGMEMconstunsignedcharHorse2[]={ 0x40,0x70,0x78,0x38,0x18,0x00,0x70,0x78,0x78,0x78,0x78,0x00,0x70,0x70,0x70,0x78, 0x78,0x00,0x7E,0x3E,0x0E,0x3E,0x3F,0x00,0x07,0x00,0x20,0x1F,0x07,0x00,0x03,0x0B, 0x1F,0x1F,0x27,0x00,0x49,0x13,0x17,0x1F,0x1F,0x00,0x07,0x0E,0x18,0x20,0x40,0x00 }; intmain(void) { unsignedchari,h;// lcd_init();// Inicializar LCD while(1) {
  • 398. /* Horse desplazándose a la derecha */ for(i=0;i<16;i++)// Para LCD de 2×16 { h=i%3;// Caballo que corresponde switch(h) { case0:create_horse((PGM_P)Horse0);break;// Crear caballo 0 case1:create_horse((PGM_P)Horse1);break;// Crear caballo 1 case2:create_horse((PGM_P)Horse2);break;// Crear caballo 2 } draw_horse(i);// Dibujar en posición i el caballo recién creado delay_ms(400); } } } /***************************************************************************** * Dibuja el caballo presente en CGRAM a partir de la columna p. * Cada caballo se forma por 8 caracteres gráficos identificados de 0 a 7. ****************************************************************************/ voiddraw_horse(unsignedcharp) { unsignedchari; lcd_clear();// Limpiar pantalla y regresar a DDRAM for(i=0;i<8;i++)// 8 piezas por cada caballo {
  • 399. if(i<4) lcd_cmd(LCD_LINE1+p+i); else lcd_cmd(LCD_LINE2+p+i-4); lcd_data(i); } } /****************************************************************************** * Crear los caracteres que componen los caballos. Volcar sus patrones en CGRAM. * Previamente se limpia el LCD para evitar parapadeos. *****************************************************************************/ voidcreate_horse(PGM_Phorse) { unsignedchari,j,b,dato; lcd_clear();// Limpiar pantalla lcd_cmd(LCD_CGRAM);// Instrucción Set CGRAM Address for(i=0;i<8;i++)// 8 piezas por caballo { /**************************************************** * Bucle para crear un carácter gráfico. * 6 bytes por pieza, pero se toman los 5 primeros. ***************************************************/ for(b=0;b<8;b++)// 8 bits
  • 400. { dato=0x00; for(j=0;j<5;j++) { if(pgm_read_byte(horse+6*i+j)&(1<<b)) dato|=(1<<(4-j)); } lcd_data(dato); } } } voiddelay_ms(unsignedintt){ while(t--) delay_us(1000); } Descripción del programa Probablemente muchos habrían pensado en crear cada uno de los 8 caracteres gráficos de cada caballo por separado creando 8 matrices por caballo y luego repetir todo el proceso para cada uno de los caballos que componen la animación. A mí ese tipo de algoritmos me parecen tediosos e inflexibles. Como es más fácil armar un rompecabezas de piezas grandes, yo preferí crear matrices por cada caballo. Será cosa del programa saber descomponer cada matriz en los 8 caracteres gráficos. De hecho, pude haber creado una sola matriz conteniendo todos los fotogramas de la animación pero quizá hubiera sido un poco exagerado. La función que se encarga de esto es create_horse. Es fácil ver cómo se hace aquí la división en 8 partes. Lo que parece algo intrincado es la forma como se transpone cada una de esas piezas. Estoy hablando del bucle que convierte la estructura vertical de cada una de esas piezas en una estructura horizontal antes de ser enviadas a la memoria CGRAM del LCD. Este bucle solo considera los 5 primeros bytes porque el sexto representa el espacio que hay entre los casilleros de todo display LCD.
  • 401. /****************************************************************************** * Crear los caracteres que componen los caballos. Volcar sus patrones en CGRAM. * Previamente se limpia el LCD para evitar parapadeos. *****************************************************************************/ voidcreate_horse(PGM_Phorse) { unsignedchari,j,b,dato; lcd_clear();// Limpiar pantalla lcd_cmd(LCD_CGRAM);// Instrucción Set CGRAM Address for(i=0;i<8;i++)// 8 piezas por caballo { /**************************************************** * Bucle para crear un carácter gráfico. * 6 bytes por pieza, pero se toman los 5 primeros. ***************************************************/ for(b=0;b<8;b++)// 8 bits { dato=0x00; for(j=0;j<5;j++) { if(pgm_read_byte(horse+6*i+j)&(1<<b)) dato|=(1<<(4-j)); } lcd_data(dato);
  • 402. } } } create_horse vuelca los 8 patrones de cada caballo en los 64 primeros bytes de la CGRAM. Esta es toda la memoria disponible así que el proceso se repite cada vez que se va a mostrar un caballo diferente. Imprimir los nuevos caracteres en la CGRAM no necesariamente significa visualizarlos en el LCD. El LCD muestra lo que le indica su DDRAM y al principio ésta le dice mostrar blancos, i.e. LCD limpio. Pero una vez iniciada la animación la DDRAM ya contiene los códigos del 0 al 7 que representan el caballo, y como la visualización es un proceso de barrido con una constante lectura de la DDRAM, los siguientes nuevos caracteres sí serían visualizados de inmediato. Es para evitar los parpadeos que esto pudiera producir que al inicio de la función create_horse se pone un lcd_clear(). La visualización de cada fotograma es un comando explícito de la función draw_horse. En esta función se envían los 8 códigos, del 0 al 7, a la DDRAM del LCD distribuyéndolos los 4 primeros en la fila superior y los cuatro siguientes en la fila inferior, todos empezando por la casilla cuyo número se le envía como parámetro p. /***************************************************************************** * Dibuja el caballo presente en CGRAM a partir de la columna p. * Cada caballo se forma por 8 caracteres gráficos identificados de 0 a 7. ****************************************************************************/ voiddraw_horse(unsignedcharp) { unsignedchari; lcd_clear();// Limpiar pantalla y regresar a DDRAM for(i=0;i<8;i++)// 8 piezas por cada caballo { if(i<4) lcd_cmd(LCD_LINE1+p+i); else lcd_cmd(LCD_LINE2+p+i-4);
  • 403. lcd_data(i); } } Para terminar, cabe destacar la utilidad que ha tenido el operador módulo %. Como sabemos, este operador devuelve el residuo de la división entre dos números, en este caso entre i y 3, donde i es el recorrido del caballo por las 16 casillas del LCD y 3 son los fotogramas que constituyen la animación. De este modo, mientras i avanza de 0 a 15, h se incrementa cíclicamente en el rango 0...2, con lo que se consigue la animación repitiendo sus tres fotogramas básicos en las diferentes posiciones del LCD. while(1) { /* Horse desplazándose a la derecha */ for(i=0;i<16;i++)// Para LCD de 2×16 { h=i%3;// Caballo que corresponde switch(h) { case0:create_horse((PGM_P)Horse0);break;// Crear caballo 0 case1:create_horse((PGM_P)Horse1);break;// Crear caballo 1 case2:create_horse((PGM_P)Horse2);break;// Crear caballo 2 } draw_horse(i);// Dibujar en posición i el caballo recién creado delay_ms(400); } } Introducción
  • 404. Hay una analogía que siempre recuerdo desde que la leí en un buen libro de Turbo Pascal cuando aprendía a programar en dicho lenguaje. Cuando vamos a recibir una visita en nuestra casa podemos ir a la puerta a cada momento para ver si ya llegó y atenderla apropiadamente, o podemos quedarnos haciendo nuestras labores cotidianas esperando a que sea la visita quien llame a la puerta para ir a recibirla. Ir a la puerta constantemente se compara por ejemplo con testear los puertos del AVR para ver si se presionó algún pulsador o algún teclado y actuar en consecuencia. Eso se conoce como técnica Polling o de sondeo e implica el desperdicio de recursos y ciclos de CPU. En este capítulo aprenderemos a atender nuestras visitas justo cuando llamen a la puerta para que el AVR no se canse en vano y que se ponga a “dormir”, si fuera posible. Ésta es solo una pequeña muestra de lo que se puede conseguir con las interrupciones. ¿Qué son las Interrupciones? Una interrupción es una llamada “inesperada”, urgente e inmediata a una función especial denominada Interrupt Service Routine (ISR). El mecanismo funciona así: sin importar lo que esté haciendo en main o cualquier función relacionada con main, cuando ocurra la interrupción el CPU hará una pausa y pasará a ejecutar el código de ISR. Tras terminarlo, el CPU regresará a la tarea que estaba realizando antes de la interrupción, justo donde la había suspendido. Aunque es posible provocar interrupciones desde el programa ejecutándolas como si fueran funciones ordinarias, las interrupciones son disparadas (llamadas) por eventos del hardware del microcontrolador. El evento puede ser algún cambio en cierto pin de E/S, el desbordamiento de un Timer, la llegada de un dato serial, etc. Se puede deducir por tanto que las fuentes de interrupción están relacionadas con la cantidad de recursos del microcontrolador. Los Vectores de Interrupción Cada interrupción está identificada por un Vector de Interrupción, que no es otra cosa que una dirección particular en la memoria FLASH. Todos los Vectores están ubicados en posiciones consecutivas de la memoria FLASH y forman la denominada Tabla de Vectores de Interrupción. El RESET no es una interrupción pero su dirección 0x0000 también se conoce como Vector de Reset. Por defecto, la Tabla de Vectores de Interrupciónestá ubicada en las primeras posiciones de la memoria, tal como se ve abajo. Solo cuando se habilita el uso de la Sección de Boot Loader toda la tabla se desplazará al inicio de dicha sección. Enseguida se presenta una tabla con las 35 interrupciones posibles en los ATmegaXX4. Debemos recordar que solo los ATmega1284 tienen el Timer3 y por tanto las 4 interrupciones relacionadas con el Timer3 no estarán disponibles en los otros megaAVR
  • 405. de esta serie. Aprenderemos de a poco y para empezar en este capítulo nos ocuparemos de las 7 interrupciones externas, desde INT0 hasta PCINT3. Las restantes serán estudiadas en sus módulos respectivos. Tabla Num Num Vector Dirección de Programa Nombre de Fuente de interrupción Vector de Interrupción 1 0x0000 RESET External Pin, Power-on Reset, Brown-out Reset, Watchdog Reset, and JTAG AVR Reset 2 0x0002 INT0 External Interrupt Request 0 3 0x0004 INT1 External Interrupt Request 1 4 0x0006 INT2 External Interrupt Request 2 5 0x0008 PCINT0 Pin Change Interrupt Request 0 6 0x000A PCINT1 Pin Change Interrupt Request 1 7 0x000C PCINT2 Pin Change Interrupt Request 2 8 0x000E PCINT3 Pin Change Interrupt Request 3 9 0x0010 WDT Watchdog Time-out Interrupt 10 0x0012 TIMER2_COMPA Timer/Counter2 Compare Match A 11 0x0014 TIMER2_COMPB Timer/Counter2 Compare Match B 12 0x0016 TIMER2_OVF Timer/Counter2 Overflow 13 0x0018 TIMER1_CAPT Timer/Counter1 Capture Event 14 0x001A TIMER1_COMPA Timer/Counter1 Compare Match A 15 0x001C TIMER1_COMPB Timer/Counter1 Compare Match B
  • 406. Tabla Num Num Vector Dirección de Programa Nombre de Fuente de interrupción Vector de Interrupción 16 0x001E TIMER1_OVF 17 0x0020 TIMER0_COMPA Timer/Counter0 Compare Match A 18 0x0022 TIMER0_COMPB Timer/Counter0 Compare match B 19 0x0024 TIMER0_OVF Timer/Counter0 Overflow 20 0x0026 SPI_STC SPI Serial Transfer Complete 21 0x0028 USART0_RX USART0 Rx Complete 22 0x002A USART0_UDRE USART0 Data Register Empty 23 0x002C USART0_TX USART0 Tx Complete 24 0x002E ANALOG_COMP Analog Comparator 25 0x0030 ADC ADC Conversion Complete 26 0x0032 EE_READY EEPROM Ready 27 0x0034 TWI 2-wire Serial Interface 28 0x0036 SPM_READY Store Program Memory Ready 29 0x0038 USART1_RX USART1 Rx Complete 30 0x003A USART1_UDRE USART1 Data Register Empty 31 0x003C USART1_TX USART1 Tx Complete 32 0x003E TIMER3_CAPT Timer/Counter3 Capture Event 33 0x0040 TIMER3_COMPA Timer/Counter3 Compare Match A Timer/Counter1 Overflow
  • 407. Tabla Num Num Vector Dirección de Programa Nombre de Fuente de interrupción Vector de Interrupción 34 0x0042 TIMER3_COMPB Timer/Counter3 Compare Match B 35 0x0044 TIMER3_OVF Timer/Counter3 Overflow Para entender cómo funciona el mecanismo de las interrupciones en bajo nivel, recordemos que el Contador de Programa es un registro que dirige cada una de las instrucciones que ejecuta el CPU. Pues bien, al dispararse la interrupción el hardware guardará en la Pila el valor actual delContador de Programa y lo actualizará con el valor del Vector de Interrupción respectivo, de modo que el CPU pasará a ejecutar el código que se encuentre a partir de esa dirección. Al final del código de la interrupción debe haber una instrucción de retorno que restaure el Contador de Programa con el valor que se había guardado en la Pila. La instrucción es reti y la pone el compilador. Si se llegara a producir el evento excepcional en que se disparen dos o más interrupciones al mismo tiempo, se ejecutarán las interrupciones en orden de prioridad. Tiene mayor prioridad la interrupción cuyo Vector se ubique más abajo, es decir, entre todas, la interrupción INT0 tiene siempre las de ganar. La estructura y características de la Tabla de Vectores de Interrupción pueden variar entre las diferentes familias de megaAVR y a veces entre diferentes partes de una misma serie. Por ejemplo, los megaAVR de la serie ATmegaXX8 no tienen la interrupción externa INT2 y tampoco las interrupciones PCINT3 (porque les falta el puerto A). Además, el ATmega48 no dispone de la funcionalidad de Boot Loader, así que este AVR no puede desplazar su Tabla de Vectores de Interrupción. La ausencia de algunas interrupciones hace que los otros Vectores cambien de valor. En cualquier caso, para nosotros, los programadores en C o Basic, es suficiente tener en cuenta los nombres de los Vectores de Interrupción, que en la tabla de arriba se resaltan con enlaces en en azul. Los nombres de los Vectores de Interrupción presentados corresponden al datasheet y no necesariamente son idénticos a los que utilizan los compiladores AVR GCC o AVR IAR C. Estos nombres se encuentran definidos en los archivos de dispositivo de cada AVR, ubicados en la carpeta include de cada compilador. La instalación por defecto de AVR GCC con Atmel Studio 6 en Windows 7 marca la ruta C:Program Files (x86)AtmelAtmel Studio 6.0extensionsAtmelAVRGCC3.4.0.65AVRToolchainavrincludeavr. Allí los puedes ubicar, y de hecho es recomendable examinarlos de vez en cuando. Pero si por el momento deseas ahorrarte el trabajo te diré que la única diferencia es el apéndice_vect. Es decir, en todos los archivos de dispositivo de AVR IAR C y de AVR GCC (en sus versiones actuales) los nombres de los Vectores de Interrupción son los
  • 408. mismos que aparecen en el datasheet pero con el añadido _vect, como se muestra en la siguiente tabla de ejemplo. Está de más decir que en nuestros programas debemos usar la forma con _vect. Tabla Nombre de Vector de Interrupción en datasheet Nombre de Nombre de Vector de Interrupción Vector de Interrupción en datasheet en archivo de dispositivo INT0 INT0_vect INT1 INT1_vect INT2 INT2_vect PCINT0 PCINT0_vect PCINT1 PCINT1_vect PCINT2 PCINT2_vect PCINT3 PCINT3_vect TIMER0_COMPA TIMER0_COMPA_vect TIMER0_COMPB TIMER0_COMPB_vect TIMER0_OVF TIMER0_OVF_vect USART0_RX USART0_RX_vect USART0_UDRE USART0_UDRE_vect USART0_TX USART0_TX_vect Lamentablemente para quienes programan en CodeVisionAVR, Pavel Haiduc decidió – no sé por qué– usar otros nombres para los Vectores de Interrupción. No solo son diferentes de los indicados en los datasheets sino que la Tabla de Vectores empieza en 2 y no en 1 (sin incluir el Vector de reset, claro está). Así que ellos no tendrán más remedio que recurrir a los archivos de dispositivo de sus AVR, los cuales se hallan en la fácilmente ubicable carpeta inc creada porCodeVisionAVR en su directorio de instalación.
  • 409. Las Funciones de Interrupción La Función de Interrupción o ISR va siempre identificada por su Vector de Interrupción, y su esquema varía ligeramente entre un compilador y otro, puesto que no existe en el lenguaje C un formato estándar. Lo único seguro es que es una función que no puede recibir ni devolver ningún parámetro. En el compilador AVR GCC (WinAVR) la función de interrupción se escribe con la palabra reservadaISR acompañada del Vector_de_Interrupcion. En las versiones anteriores del compilador se solía usar SIGNAL en vez de ISR pero actualmente ese método está considerado «deprecated» (censurado). Recordemos que el Vector_de_Interrupcion debe tener la terminación _vect, como se indicó anteriormente, y si tienes dudas puedes buscar en la carpeta include del directorio de instalación de AVR GCC (WinAVR). ISR(Vector_de_Interrupcion) { // Código de la función de interrupción. // No requiere limpiar el flag respectivo. El flag se limpia por hardware } Por otro lado, en AVR IAR C y CodeVisionAVR la construcción es un poquito más elaborada. Requiere el empleo de la directiva #pragma vector y la palabra reservada __interrupt. ElNombre_de_Interrupcion queda a la libertad del programador. #pragma vector = Vector_de_interrupcion __interruptvoidNombre_de_Interrupcion(void) { // Código de la función de interrupción. // No requiere limpiar el flag respectivo. El flag se limpia por hardware } Por fortuna, estas divergencias entre AVR IAR C y AVR GCC solo se presentan en el encabezado de la función. La implementación del cuerpo de la función es idéntica en ambos compiladores. Además existe la posibilidad de utilizar macros que adapten el esquema de la función de interrupción de AVR IAR C al formato de AVR GCC. Estas
  • 410. macros están escritas en el archivoavr_compiler.h del Atmel Software Framework o ASF y que siempre se usa en los programas de cursomicros.com. En realidad, CodeVisionAVR ofrece otras formas adicionales de implementar una función de interrupción pero, por más que se parezcan en forma, lamentablemente ninguna de ellas es compatible con el código de AVR IAR C o AVR GCC, debido a las diferencias en los nombres de losVectores de Interrupción. Control de las Interrupciones Hay dos tipos de bits para controlar las interrupciones: los Bits Enable, que habilitan las interrupciones, y los Bits de Flag, que indican cuál interrupción se ha producido. Bueno, eso para decirlo a grandes rasgos. Hay un bit enable individual para cada interrupción y además hay un bit enable general I(ubicado en el registro SREG) que afecta a todas las interrupciones. Para habilitar una interrupción hay que setear su bit enable individual como el bit enable generalI. También se pueden habilitar varias interrupciones del mismo modo. Ninguna habilitación individual tendrá efecto, es decir, no disparará una interrupción si el bit I está en cero. Por otro lado, cada interrupción tiene un Bit de Flag único, que se setea automáticamente por hardware cuando ocurre el evento de dicha interrupción. Eso pasará independientemente de si la interrupción está habilitada o no. Si la interrupción fue previamente habilitada, por supuesto que se disparará. Cada interrupción habilitada y disparada, saltará a su correspondiente Función de Interrupción oISR, de modo que a diferencia de algunos otros microcontroladores no será necesario sondear los flags de interrupción para conocer la fuente de interrupción. Los AVR van más lejos y tienen un hardware que limpia automáticamente el bit de Flag apenas se empiece a ejecutar la función de interrupción. Pero puesto que los flags se habilitan independientemente de si las interrupciones están habilitadas o no, en ocasiones será necesario limpiarlos por software y en ese caso debemos tener la especial consideración de hacerlo escribiendo un uno y no un cero en su bit respectivo. Sí, señor, dije, uno. Al ejecutarse la función de interrupción también se limpia por hardware el bit enable general Ipara evitar que se disparen otras interrupciones cuando se esté ejecutando la interrupción actual. Sin embargo, la arquitectura de los AVR le permite soportar ese tipo de interrupciones, llamadas recurrentes o anidadas, y si así lo deseamos podemos setear en el bit I dentro de la ISR actual. A propósito, el ya famoso bit enable general I se puede escribir como cualquier otro bit de un registro de E/S. Pero dada su especial importancia, existen dos exclusivas instrucciones de ensamblador llamadas sei (para setear I) y cli (para limpiar I). El archivo avr_compiler.h ofrece las macros sei() y cli() para llegar a esas instrucciones.
  • 411. Práctica: Uso de la interrupción INTx En estas prácticas de ejemplo evitaremos programas sofisticados con códigos grandes que desvíen la atención hacia una breve aplicación de la teoría expuesta. Por eso no nos vendrá mal volver a los socorridos LEDs parpadeantes. El programa tendrá dos tareas: la rutina principal se encargará de parpadear un LED y la función de interrupción hará bascular otro LED cada vez que presionemos un pulsador. Esto será como fusionar dos programas que alguna vez hicimos. “Correr dos programas a la vez…” Dicen que algo así le paso por la cabeza a Bill Gates cuando pensó en MS Windows. De las señales que se generan al presionar el botón escogeremos el flanco de bajada para disparar la interrupción INT0. Circuito para probar las interrupciones externas del microcontrolador AVR. El código fuente Cada aplicación puede tener sus propias especificaciones, pero, en general, un buen hábito de programación es poner la sentencia sie(); que setea el bit I del registro SREG cuando ya todo esté listo para atender a la interrupción. Al analizar la estructura del programa, notamos que la función ISR es totalmente independiente de main, es decir, no es referenciada desde ningún punto de main. Una vez habilitada, la interrupción se disparará cuando alguien presione el botón (en el flanco de bajada). En ese preciso instante (quizá cuando se esté ejecutando PINC = 0x02 o quizá en algún punto dentro de delay_ms(600)) el CPU pasará a ejecutar la
  • 412. función ISR. Al salir de ISR, el CPU regresará a continuar la tarea que estaba ejecutando antes de la interrupción. /****************************************************************************** * FileName: main.c * Purpose: Uso de la interrupción INTx * Processor: megaAVR * Compiler: AVR IAR C y AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. * * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta * licencia y las notas de autor y copyright de arriba. *****************************************************************************/ #include "avr_compiler.h" voiddelay_ms(unsignedintt) { while(t--) delay_us(1000); } //**************************************************************************** // Interrupt Service Routine, ISR
  • 413. // Esta función se ejecuta cuando se detecta un flanco de bajada en el pin INT0 //**************************************************************************** ISR(INT0_vect) { PINC=0x01;// Conmutar pin PC0 delay_ms(40);// Para pasar los rebotes } //**************************************************************************** // Función principal //**************************************************************************** intmain(void) { DDRC=0x03;// Pines PC0 y PC1 para salida (LEDs) PORTD=0x04;// Habilitar pull-up de pin PD2/INT0 (pulsador) /* Habilitar y configurar la interrupción INT0 para que se dispare con * cada flanco de bajada detectado en el pin INT0 (PD2) */ EIMSK=(1<<INT0);// Habilitar INT0 EICRA=(2<<INT0*2);// Elegir flanco de bajada (modo 2) sei();// Habilitación general de interrupciones while(1)// Bucle infinito {
  • 414. PINC=0x02;// Conmutar pin PC1 delay_ms(600);// Pausa de 600ms } } El Modo Sleep El modo Sleep es un estado en que se detiene el oscilador del sistema y, por tanto, dejan de funcionar todas las partes del microcontrolador que dependen de él, incluyendo en algunos casos el mismo procesador, es decir, se “congela” la ejecución del programa. Sin embargo los valores de todos los registros y puertos del microcontrolador permanecerán inalterables. En este estado se dice que el microcontrolador está durmiendo, por el término sleep = sueño, en inglés. La pregunta es ¿para qué sirve un microcontrolador con su hardware congelado? Pues hay aplicaciones donde el microcontrolador debe atender ciertas tareas solo cuando ocurre un evento externo como por ejemplo la pulsada de un botón. El resto del tiempo no hace nada útil. Al hacer que el microcontrolador se ponga a dormir y que despierte solo cuando cierto evento se lo demande, se consigue ahorrar muchísima energía que se perdería con el CPU y demás periféricos estando activos en vano. Esto es clave sobre todo en circuitos alimentados por baterías. Los microcontroladores AVR tienen un sistema oscilador sofisticado que divide el reloj en varias ramificaciones que van a los diferentes módulos del AVR. De esa forma, su modo Sleep tiene hasta 6 diferentes niveles dependiendo de las ramificaciones del reloj que se pongan a congelar. Detallar cada una de ellas en este momento extendería tanto el tema que movería el enfoque de las interrupciones. Solo diré que el mayor nivel, es decir, donde el hardware del AVR se congela por completo se denomina Power-down. El modo Power-down se configura escribiendo el valor 0x02 en el registro SMCR y luego solo bastará con ejecutar la instrucción de ensamblador sleep para que el AVR “cierre sus ojos”. El modo Sleep es muy propio de los microcontroladores y no existe en el lenguaje C una sentencia para la instrucción sleep. Cada compilador la implementa a su modo. En el archivoavr_compiler.h original se utiliza la macro sleep_enter(), pero yo le añadí otra definida comosleep(), a secas. Tocamos el modo Sleep ahora porque el evento por excelencia que puede despertar al microcontrolador es el disparo de una interrupción proveniente de una parte del microcontrolador que no esté congelado. Puesto que las
  • 415. interrupciones INTx y PCINTx son externas, ellas pueden sacar al AVR incluso del sueño más profundo, o sea del modo Power-down. Cuando se dispare una interrupción lo primero que hará el CPU al despertar es ejecutar la primera instrucción de ensamblador que sigue a sleep, e inmediatamente después pasará a ejecutar la función de interrupción ISR. Si aún recuerdas el retardo de arranque, te diré que es aquí donde entra en acción: esto es, después de descongelarse el oscilador del sistema, habrá un tiempo llamado retardo de arranque en que el AVR espera en estado de RESET hasta que el oscilador se haya estabilizado por completo. Luego, recién, el CPU reiniciará su trabajo. De los 6 modos Sleep del AVR, el retardo de arranque solo actúa en los niveles Powerdown y Power-save porque en los demás niveles, el oscilador del AVR no está del todo detenido. Práctica: Interrupciones Múltiples + Modo Sleep Si al programa anterior le quitásemos la tarea de la rutina principal, el AVR ya no tendría nada que hacer allí. Éste puede ser un buen momento para tomar una siesta. Por otro lado, en esta ocasión experimentaremos con las tres interrupciones, INT0, INT1 e INT2, al mismo tiempo como ejemplo de uso de interrupciones múltiples. Circuito para probar las interrupciones externas del microcontrolador AVR. El código fuente /******************************************************************************
  • 416. * FileName: main.c * Purpose: Uso de las interrupciones INTx + Modo Sleep * Processor: ATmega164P * Compiler: AVR IAR C y AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. * * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta * licencia y las notas de autor y copyright de arriba. *****************************************************************************/ #include "avr_compiler.h" voiddelay_ms(unsignedintt) { while(t--) delay_us(1000); } //**************************************************************************** // Gestor de Interrupción INT0 // Esta función se ejecuta cuando se detecta un flanco de bajada o de subida // en el pin INT0 //****************************************************************************
  • 417. ISR(INT0_vect) { PINC=0x01;// Conmutar pin PC0 delay_ms(40);// Para pasar los rebotes } //**************************************************************************** // Gestor de Interrupción INT1 // Esta función se ejecuta cuando se detectan flancos de subida en el pin INT1 //**************************************************************************** ISR(INT1_vect) { PINC=0x02;// Conmutar pin PC1 delay_ms(40);// Para pasar los rebotes } //**************************************************************************** // Gestor de Interrupción INT2 // Esta función se ejecuta cuando el pin INT2 se encuentra en nivel bajo //**************************************************************************** ISR(INT2_vect) { PINC=0x04;// Conmutar pin PC2 delay_ms(40);// Para pasar los rebotes }
  • 418. //**************************************************************************** // Función principal //**************************************************************************** intmain(void) { DDRC=0x07;// Pines PC0, PC1 y PC2 para salida (LEDs) /* Habilitar pull-ups de pines INT0/PD2, INT1/PD3 e INT2/PB2 para los * pulsadores. Se asume que estos pines están configurados como entradas */ PORTD=0x0C;// PORTB=0x04;// /* Habilitar las interrupciones INT0, INT1 e INT2 y configurarlas para que * se disparen: * INT0 con cada flanco (de bajada o subida) en el pin INT0/PD2 (modo 1) * INT1 con cada flanco de subida en el pin INT1/PD3 (modo 3) * INT2 mientras haya un nivel bajo en el pin INT2/PB2 (modo 0) */ EIMSK=(1<<INT0)|(1<<INT1)|(1<<INT2);// Habilitar INT0, INT1 e INT2 EICRA=(1<<INT0*2)|(3<<INT1*2)|(0<<INT2*2);// Elegir flancos sei();// Habilitación general de interrupciones while(1)// Bucle infinito {
  • 419. /* Entrar en modo sleep (Power-Down mode) */ SMCR=(1<<SM1)|(1<<SE); sleep(); nop(); } } El AVR despertará con el disparo de la interrupción, ejecutará nop() (que también equivale a una instrucción de ensamblador) y luego llamará a la función ISR respectiva. Lo demás es historia conocida. Aunque en algunos casos el nop() es recomendable, en esta ocasión lo puse solo para esta explicación. Interrupciones de Cambio de Pin, PCINTx Esta interrupción se dispara cada vez que se detecta un cambio de nivel lógico 1 a 0 o viceversa en cualquiera de los pines de los puertos del AVR, sin importar si están configurados como entrada o como salida. Aunque no es propiamente reconocido, también se podría decir que se dispara con los flancos de subida y de bajada en los pines de puertos. En ese sentido, se parece bastante a las interrupciones INTx. La interrupción de Cambio de Pin también puede sacar al AVR del modo Sleep. Esta interrupción no está presente en los AVR antiguos como los ATmega32, ATmega16, ATmega8535, etc. Aparte del bit enable general I, del registro SREG, las interrupciones de cambio de pin se habilitan pasando por las dos siguientes etapas, no necesariamente en el orden citado. Primero, se debe setear el bit que identifica el puerto donde se encuentran los pines que generarán las interrupciones. Estos son bits de enable ubicados en el registro PCICR (Pin Change Interrupt Control Register). Para los megaAVR de 4 puertos la correspondencia es la siguiente. Registro PCICR PCICR --- --- --- --- PCIE3 PCIE2 PCIE1 PCIE0
  • 420. Y para los megaAVR de 3 puertos como los de la serie 8xx, la correspondencia entre los bits PCIEx (Pin Change Interrupt Enable) y los puertos del AVR es Registro PCICR PCICR --- --- --- --- --- PCIE2 PCIE1 PCIE0 Luego se deben setear los bits enable que identifican individualmente los pines de los puertos. Estos bits se encuentran en los registros de máscara PCMSK (Pin Change Mask Register). Hay un registro de máscara por cada puerto del AVR aunque la relación varía según el número de puertos del AVR, como se indica en la siguiente tabla. Tabla Registro de máscara megaAVR de 4 puertos megaAVR de 3 puertos Registro de máscara Puerto Registro de máscara Puerto PCMSK0 PORTA PCMSK0 PORTB PCMSK1 PORTB PCMSK1 PORTC PCMSK2 PORTC PCMSK2 PORTD PCMSK3 PORTD --- --- Cada bit del registro de máscara PCMSK corresponde a su respectivo pin de PORT. Por ejemplo, si en un AVR de 4 puertos seteamos los bits 4 y 7 de PCMSK2, estaremos habilitando las interrupciones de los pines 4 y 7 de PORTC. Esta correspondencia se cumple incluso en los AVR cuyos puertos no tengan los 8 pines completos.
  • 421. Otra forma de seleccionar los pines de interrupción es ubicándolos directamente por sus nombres PCINTx. Para esto también debes estar revisando el diagrama de pines del AVR. Registro PCMSK0 PCMSK0 PCINT7 PCINT6 PCINT5 PCINT4 PCINT3 PCINT2 PCINT1 PCINT0 Registro PCMSK1 PCMSK1 PCINT15 PCINT14 PCINT13 PCINT12 PCINT11 PCINT10 PCINT9 PCINT8 Registro PCMSK2 PCMSK2 PCINT23 PCINT22 PCINT21 PCINT20 PCINT19 PCINT18 PCINT17 PCINT16 Registro PCMSK3 PCMSK3 PCINT31 PCINT30 PCINT29 PCINT28 PCINT27 PCINT26 PCINT25 PCINT24 Observa que cada bit PCINTx corresponde a un pin del AVR con el mismo nombre. Una vez producido el cambio de nivel en uno o varios de los pines habilitados para interrupción, se activará el flag respectivo PCIF (Pin Change Interrupt Flag) del registro PCIFR y luego se llamará a la función de interrupción ISR. Así como hay un bit enable para cada puerto, en este nivel también hay un bit de flag correspondiente. Es de prever que el siguiente esquema pertenece a los AVR de 4 puertos. Allí, por ejemplo, si un pin de PORTB cambia de nivel, entonces se activará el flag PCIF1. Registro PCIFR
  • 422. PCIFR --- --- --- --- PCIF3 PCIF2 PCIF1 PCIF0 Por supuesto, en los AVR de 3 puertos hay variación en el mapa del registro PCIFR (Pin Change Interrupt Flag Register). Registro PCIFR PCIFR --- --- --- --- --- PCIF2 PCIF1 PCIF0 Como de costumbre, el flag PCIF será limpiado por el hardware al ejecutarse el gestor de interrupción ISR. Sin embargo, como este flag puede activarse sin necesidad de que esté seteado el bit enable general I (del registro SREG), a veces se tendrá que limpiar por software. En ese caso se limpia escribiendo un 1. Para evitar llegar a esta situación es recomendable habilitar la Interrupción de Cambio de Pin después de realizar en los puertos todas las operaciones necesarias que pudieran ocasionar cambios de nivel en sus pines, por ejemplo, activar las pull-ups. Finalmente, debemos tener en cuenta que hay un Vector de Interrupción [de Cambio de Pin] por cada puerto de AVR, llamados PCINT0_vect, PCINT1_vect, PCINT2_vect y PCINT3_vect. La activación del flag PCIFx conducirá a la ejecución de la función ISR identificada por el vectorPCINTx_vect. Práctica: Interrupciones de Cambio de Pin En el programa el AVR permanece en estado Sleep (Power-down) y despierta cada vez que se presionen los pulsadores conectados a los pines PD5, PD6 y PD7. (Puede haber más o menos pulsadores y se pueden elegir cualesquiera otros) Cada pulsador hará conmutar un LED conectado al puerto B. Los diodos LED deben conmutar solo al presionar los pulsadores y no al soltarlos.
  • 423. Circuito para probar las interrupciones del microcontrolador AVR. El código fuente /****************************************************************************** * FileName: main.c * Purpose: Uso de las Interrupciones de Cambio de Pin, PCINTx * Processor: megaAVR * Compiler: AVR IAR C y AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. * * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta * licencia y las notas de autor y copyright de arriba. *****************************************************************************/
  • 424. #include "avr_compiler.h" //**************************************************************************** // Interrupt Service Routine // Esta función se ejecuta cuando se detectan cambios de nivel (flancos de // subida o de bajada) en los pines PD5, PD6 o PD7. //**************************************************************************** ISR(PCINT3_vect) { chardato=~(PIND|0x1F);// Enmascarar bits PD5, PD6 y PD7 switch(dato) { case(1<<5):PINC=0x01;break;// Cambio de pin PD5 case(1<<6):PINC=0x02;break;// Cambio de pin PD6 case(1<<7):PINC=0x04;break;// Cambio de pin PD7 default:nop();// Cambio de más de un pin } delay_us(40000);// 40ms para pasar los rebotes } //**************************************************************************** // Función principal //**************************************************************************** intmain(void) {
  • 425. DDRC=0x07;// Pines PC0, PC1 y PC2 para salida (LEDs) /* Habilitar pull-up de pines PD5, PD6 y PD7 (pulsadores) */ PORTD=(1<<5)|(1<<6)|(1<<7); /* Habilitar interrupciones PCINT de PORTD en los pines PD5, PD6 y PD7 */ PCICR=(1<<PCIE3); PCMSK3=(1<<5)|(1<<6)|(1<<7); sei();// Habilitación general de interrupciones while(1)// Bucle infinito { /* Entrar en modo sleep (Power-Down mode) */ SMCR=(1<<SM1)|(1<<SE); sleep(); } } Práctica: Control de Teclado por Interrupciones Alguna vez leí en uno de los documentos de Microchip que la interrupción de Cambio de PORTB de sus PICmicros fue pensada para controlar los pequeños teclados matriciales. En ese entonces los megaAVR aún no tenían una característica similar. Pero como sabemos ahora, los AVR como los que estamos estudiando llegaron más lejos y nos permiten manejar no solo uno sino varios teclados matriciales con el mínimo hardware, en estado Sleep y desde cualquier puerto del microcontrolador.
  • 426. En este programa el AVR debe permanecer durmiendo (modo Power-down) y despertar solo cuando se pulsa una tecla para leerla y mostrarla en el terminal serial. Circuito para el teclado matricial y el microcontrolador AVR. El código fuente /****************************************************************************** * FileName: main.c * Purpose: Control de Teclado mediante Interrupciones PCINTx * Processor: ATmega164P * Compiler: AVR IAR C y AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
  • 427. * * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta * licencia y las notas de autor y copyright de arriba. *****************************************************************************/ #include "avr_compiler.h" #include "usart.h" #include "keypad.h" voidSetupInt(void); //**************************************************************************** // Interrupt Service Routine // Esta función se ejecuta cuando se detectan cambios de nivel (flancos de // subida o de bajada) en los pines PB4...PB7. //**************************************************************************** ISR(PCINT1_vect) { chardato=keypad_read();// Leer teclado if(dato)// Si fue tecla válida { /* Esperar a que haya espacio en el buffer de transmisión */ while((UCSR0A&(1<<UDRE0))==0); /* Colocar dato en el buffer de transmisión */ UDR0=dato;
  • 428. /* Esperar a que el teclado quede libre */ keypad_released(); } SetupInt(); } //**************************************************************************** // Función principal //**************************************************************************** intmain(void) { usart_init();// Setup USART0 @ 9600-8N1 puts("rn Control de Teclado mediante Interrupciones PCINTx rrn"); SetupInt(); /* Habilitar las interrupciones PCINT de PORTB en los pines PB4...PH7 */ PCICR=(1<<PCIE1); PCMSK1=0xF0; sei();// Habilitación general de interrupciones while(1)// Bucle infinito { /* Entrar en modo sleep (Power-Down mode) */
  • 429. SMCR=(1<<SM1)|(1<<SE); sleep(); } } //**************************************************************************** // Prepara el puerto B para que detecte un cambio de tensión producido al // presionar una tecla. //**************************************************************************** voidSetupInt(void) { /* Configurar PORTB: Nibble de Rows salida, Nibble de Rows bajo * nible alto entrada y habilitar pull-ups de nibble alto */ PORTB=0xF0; DDRB=0x0F; } Según el circuito y la librería del teclado, cuando no hay teclas pulsadas las líneas Col (nibble alto de PORTB) se leen como „1‟ lógico (gracias a las pull-ups), y así deberían permanecer mientras el AVR está “soñando”. Por tanto, para que haya un cambio de nivel al pulsar una tecla, las líneas de Row (nibble bajo de PORTB) deberían sacar „0‟ lógico. De esto se encarga la funciónSetupInt. //**************************************************************************** // Prepara el puerto B para que detecte un cambio de tensión producido al // presionar una tecla. //**************************************************************************** voidSetupInt(void) {
  • 430. /* Configurar PORTB: Nibble de Rows salida, Nibble de Rows bajo * nible alto entrada y habilitar pull-ups de nibble alto */ PORTB=0xF0; DDRB=0x0F; } Introducción La interface con una computadora se puede realizar por cualquiera de sus puertos externos más conocidos: serie, paralelo o el USB. El paralelo casi ni se encuentra en las computadoras de hoy y por el momento el puerto USB nos queda fuera de alcance por la complejidad del desarrollo del firmware (programa del microcontrolador). Así nos quedamos con el puerto serie. Aprenderemos a volcar datos desde nuestro microcontrolador a la pantalla de la computadora (data logging), así como a enviar datos mediante el teclado del PC hacia el microcontrolador. El Estándar RS-232 Toda comunicación elaborada entre dos dispositivos requiere conocer el protocolo que la gobierna a nivel hardware y software. Para el puerto serie se trata del Estándar RS232, o más bien EIA/TIA-232 por las siglas de Electronics Industry Association y Telecommunications Industry Association, sus desarrolladores. El RS-232 fue originariamente pensado para regir las comunicaciones entre computadoras y equipos de módem de la época (hace más de 40 años). Con el tiempo han surgido otras versiones como RS-232-C, RS-232-D, RS-232-E, etc., una más reciente que la otra, pero con variaciones inapreciables por ser uno de los estándares menos estrictos. Después de todo, es solo un Estándar Recomendado o “Recommended Standard”; de ahí la RS. En la literatura técnica se acostumbra mucho utilizar los términos DTE y DCE para referir a los dispositivos que se comunican según el Estándar RS-232. DTE (Data Terminal Equipment) suele representar a la computadora y DCE (Data Circuitterminating Equipment) designa a cualquier dispositivo conectado a la computadora (un módem se sobrentendía antes). Sin embargo, estos conceptos no quedan del todo claros en redes del tipo computadora-computadora o microcontrolador-microcontrolador usando el puerto serie. Así que por comodidad en adelante hablaremos de computadora y módem, viendo como módem ¾hasta donde quepa¾ a cualquier dispositivo conectable al puerto serie (el circuito de nuestro microcontrolador).
  • 431. Ahora pasemos a describir los principales aspectos que nos “recomienda” el estándar. Voltajes de los Niveles Lógicos RS-232 En las comunicaciones seriales RS-232 los valores para representar los 1‟s y 0‟s lógicos son muy diferentes de los que estamos acostumbrados a usar en el mundo TTL. Allí no existen los 5V (para el 1) y 0V (para el 0). Para entenderlo más fácilmente veamos la siguiente figura, donde se compara la forma de onda de una señal RS-232 con la forma de onda de una señal digital convencional. Niveles de tensión para los 1s y 0s lógicos. Puedes notar la enorme diferencia: los 1 lógicos se representan con voltajes negativos y los 0 lógicos, por voltajes positivos; además del amplio rango de los voltajes. Un 1 lógico se expresa por una tensión de –5V a –15V. Este estado se llama spacing. Un 0 lógico se da cuando la tensión en cualquiera de las líneas es de +5V hasta +15V. Este estado se conoce como marking. Formato de Transferencia de Datos Como en toda comunicación serial, los datos viajan en grupos de bits. En este caso cada grupo o carácter consta de un bit Start, los bits de Datos (8 por lo general), un bit de Paridad (opcional) y finaliza con uno o dos bits de Stop.
  • 432. Formato de un byte de dato en el Estándar RS-232. Bit Start. Es la transición de 1 a 0 e indica el inicio de una transferencia. En la lógica RS-232 podría significar una transición de -15V a +15V y en lógica TTL es una transición de 5V a 0V. Bits de Datos. Forman los datos en sí que se desean transmitir. Cada dato puede ser de 5, 6, 7 u 8 bits. Por supuesto, siempre preferimos trabajar con 8 bits (1 byte). El primer bit a transmitir es el menos significativo o LSbit (Least Significant Bit). Bit de Paridad. Este bit es opcional y se puede enviar después de los bits de datos. Sirve para ayudar a detectar posibles errores en las transferencias de datos. Es muy raramente usado, primero, porque es poco efectivo (solo podría detectar errores, no corregirlos) y, segundo, porque hay mejores formas de tratamiento de errores. Bits Stop. Los bits de Stop son estados de 1 lógico. El Estándar dice que puede haber 1, 1.5 ó 2 bits de Stop al final de los datos (o del bit de paridad si lo hubiera). Velocidad de Transmisión (Baud Rate) El Baud Rate es el número de bits que se transmiten por segundo. Debido a que estamos hablando de un tipo de transmisión asíncrona, no existe una señal de reloj que sincronice los bits de datos. Para que los dispositivos transmisor y receptor se entiendan correctamente también es necesario que operen con el mismo baud rate. Los valores más comunes que fija el Estándar RS-232 son: 1200, 2400, 4800, 9600, 19200, 38400, 56000, 57600, 115200, 128000, 256000. Aunque las versiones más recientes del Estándar ponen un límite de 20 kbits, es común emplear los valores altos como 115200 (siempre que sea posible). Sabemos que no es lo mismo la interface entre una computadora y un microcontrolador usando un cable de 2 m de longitud que conectarlo a un PLC a 8 m de distancia: la longitud del cable y la interferencia presente en el ambiente son factores a considerar a la hora de escoger el baud rate. Señales del Puerto Serie Internamente el puerto serial de una computadora es controlado por un circuito integrado (por ejemplo el 16750, de 40 pines). De esas líneas solo 9 salen al exterior y desembocan en un conector DB9 macho (el que nosotros vemos y donde
  • 433. conectábamos nuestro programador serial). Raras veces se ve que salen más líneas para llegar a un conector DB25. El uso de las 9 señales tiene más sentido cuando se trabaja con un módem. Por eso vamos a seguir hablando de módem, pese a que bien puede ser reemplazado por otro dispositivo. Pines del conector DB9 (macho) del puerto serie. En la figura mostrada las direcciones de las flechas señalan si los pines son de entrada o de salida. Del mismo modo, los colores ayudan a asociar los pines con funciones análogas o complementarias, así: TD y RD se encargan de transmitir y recibir los datos, respectivamente. RTS y CTS sirven para controlar el Control del Flujo de Datos (Handshaking) hardware. DTR, DSR y DCD intervienen en el establecimiento de la comunicación. Además de ellos, están la infaltable tierra (SG) y RI, usada exclusivamente en conexiones con un módem. Ahora bien, cuando vamos a conectar la computadora a un microcontrolador nuestro interés se puede reducir a tres líneas: TD, RDy SG. Las demás: o pueden ignorarse, o pueden conectarse al mismo puerto serie artificiosamente para evitar problemas de comunicación, o pueden usarse para implementar un Control del Flujo de Datos (Handshaking) hardware, con el microcontrolador emulando algunas funciones de módem. En cualquiera de los tres casos, eso dependerá del software de computadora usado para controlar el puerto serie. En el conector hembra la posición de los pines se puede ver diferente a fin de establecer una conexión cruzada, es decir, para que el TD de un dispositivo se una con elRD del otro y viceversa. Lo mismo debe pasar con los pares RTS-CTS y DTR-DSR. Control del Flujo de Datos (Handshaking) Generalmente la computadora superará a su interlocutor (módem u otro) tanto en velocidad de transferencia como en los buffers de recepción de datos. Para que el
  • 434. módem no empiece a perder los datos llegados el Estándar contempla mecanismos de control de flujo de datos ya sea vía hardware o software. El control de flujo de datos por software se identifica por el uso de los caracteres Xon(ASCII 0x11) y Xoff (ASCII 0x13). El diálogo es así: cuando, por alguna razón, el módem ya no desea recibir más datos de la computadora entonces le envía el carácter Xoffdiciéndole que suspenda la transmisión al menos temporalmente. Cuando el módem esté nuevamente dispuesto a aceptar más datos le enviará el carácter Xon y la computadora reanudará la transferencia. Lo bueno del método es que el hardware requerido es el mínimo (ver la siguiente figura) y lo malo es que ambos dispositivos deben soportar las transferencias full dúplex que exige el estándar RS232 para este caso. Conexión básica para el Handshaking software. El control de flujo por hardware hace participar activamente a las líneas RTS y CTS. En un tipo de comunicación simplex el protocolo es éste: cuando la computadora quiere enviar datos al módem pone un 1 en RTS. Si el módem está dispuesto a recibir esos datos, le responderá con un 1 por la línea CTS y la computadora empezará a transmitir los datos; de otro modo, le responderá con un 0 y la computadora tendrá que posponer el envío de datos. Cuando la comunicación es half dúplex o full dúplex el protocolo varía, pero eso ya no lo tocaremos aquí para no seguir enredándolo. Solo vemos lo suficiente del estándar, en este caso para entender las conexiones alternativas entre una computadora y un microcontrolador que se usan en algunas ocasiones y que veremos en la siguiente sección.
  • 435. Cableado para el handshaking hardware entre dos dispositivos. En el diagrama se han sumado las líneas DTR, DSR y DCD (de color marrón), por las que los dispositivos se informan el uno al otro si están listos o no para iniciar la comunicación. nterface Serial Microcontrolador-computadora Como es de esperar, el enfoque se divide en dos partes: Requerimientos Hardware Nos vamos a enfocar en dos aspectos. Primero veamos el tema del transceiver. Dado que los niveles de tensión en el Estándar RS-232 (de –12V, 0V y +12V en la computadora) no son compatibles con los niveles habituales de los microcontroladores (de 0 y 5V), se requiere de un transceiver que convierta estas tensiones de unos niveles a otros y viceversa. Sin duda, el MAX232 es el más famoso de todos. Como se ve en su esquema, mostrado abajo, el MAX232 puede trabajar con una fuente de alimentación de 5V y provee dos canales de transmisión y dos de recepción, aunque solo se suele usar un par. A su gran tamaño se suma como desventaja el uso de condensadores externos, para “bombear” la carga necesaria en los circuitos doblador e inversor de voltaje.
  • 436. Interface entre un microcontrolador y un computador mediante el transceiver MAX232. El mismo fabricante del MAX232, Dallas Semiconductors, ofrece sus versiones mejoradas como el MAX203, que no requiere de capacitores externos, o el MAX202, que brinda protección contra cargas electrostáticas. Mejor aun para pequeños circuitos sería el DS275 (de 8 pines), el cual tampoco requiere de capacitores externos y cuenta con el par justo de drivers de transmisión y recepción de datos. Su principal inconveniente es que está diseñado para operar solo en transferenciashalf dúplex . Para conocer más del funcionamiento interno de los transceivers es recomendable que consultes sus respectivos datasheets.
  • 437. Interface entre un microcontrolador y un computador mediante el transceiver DS275. El segundo aspecto hardware que interesa es el relacionado con el Control del Flujo de Datos (Handshaking): en los dos esquemas presentados anteriormente las retroconexiones en el conector DB9 (de color violeta) son opcionales. Solo harán falta cuando el programa terminal de la computadora esté configurado para utilizar los pines indicados, así: RTS (7) se conecta a CTS (8) para que siempre que la computadora desee enviar datos al microcontrolador, se responda a sí mismo con un “permiso concedido”. Análogamente, DTR (4) se une a DSR (6) para que cuando la computadora informe un “estoy listo para la comunicación”, su eco (haciéndose pasar por el microcontrolador) le responda con un “yo también lo estoy”. A veces DTR también se dirige a DCD (1). Requerimientos Software Por un lado necesitamos unas rutinas para el microcontrolador que gestionen las funciones del Estándar RS-232. Éstas pueden implementarse tranquilamente a nivel software debido a su simplicidad o mediante el módulo USART, el cual por supuesto ofrecerá mucha más eficiencia y flexibilidad. Por otro lado, necesitaremos un programa de computadora que se encargue de controlar su puerto serie. A su vez, este programa puede ser uno desarrollado por nosotros mismos, que nos permitiría tener el control total del puerto serie y podríamos transmitir y recibir todo tipo de datos (binarios o de texto). También podríamos implementar técnicas alternativas de control de flujo de datos (aparte de los descritos arriba), o sofisticados mecanismos para el control de errores en la transferencias de datos. Como ves, se ve muy atractivo, pero también requiere de conocimientos a mediano nivel sobre programación en lenguajes como Visual C++, Delphi o Visual Basic. Como alternativa práctica, podemos usar softwares como el Hyperterminal de Windows,Serial Port Monitor, Putty o Tera Term. Estos son programas de tipo consola que nos permiten visualizar los datos que se transfieren hacia/desde el puerto serie. Por no ofrecer tanta flexibilidad nos limitaremos a trabajar con datos de texto. Conforme vamos escribiendo los caracteres en la consola, se irán enviando hacia nuestro microcontrolador. Así mismo, los caracteres enviados desde el microcontrolador se irán mostrando en la consola, todo en tiempo real.
  • 438. Interface del programa Tera Term Uso del Programa Tera Term Ya tenemos todo listo para el microcontrolador. Ahora nos falta ejecutar algún programa terminal en nuestra computadora para empezar el intercambio de datos. De los muchos que hay vamos a elegir el Tera Term, que es lo mejor que he podido encontrar y no lo digo precisamente porque sea de licencia GPL. Lo puedes bajar libremente desde su webhttp://ttssh2.sourceforge.jp, o haciendo clic aquí. “Tera Term es un emulador de terminal (programa de comunicaciones) que soporta: Conexiones con el puerto serie. Conexiones TCP/IP (telnet, SSH1, SSH2). Comunicaciones IPv6 Emulación de VT100 y de VT200/300. Emulación de TEK4010. Protocolos de Transferencia de Archivos (Kermit, XMODEM, YMODEM, ZMODEM, BPLUS y Quick-VAN). Scripts usando el „Lenguaje Tera Term‟. Sets de caracteres Japonés, Inglés, Ruso y Coreano.
  • 439. Codificación de caracteres UTF-8.” Bien, una vez descargado, los instalas aceptando lo que se te pida hasta llegar a Si no los vas a utilizar o si no tienes idea de lo que significan, te recomiendo desmarcar las casillas de TTSSH, CygTerm+, LogMeTT, TTEdit y TTProxy. De lo contario, la instalación te pedirá aceptar los términos y la configuración de cada aplicación por separado. Suponiendo que seguiste mi recomendación, la instalación terminará enseguida y solo se creará un icono de acceso en el escritorio. Cada vez que iniciemos Tera Term se nos presentará la siguiente ventana, donde debemos escoger la opción Serial y en Port debemos seleccionar el puerto serie COM a usar. Normalmente las computadoras actuales solo tienen un puerto COM disponible, el COM1. Después de hacer clic en OK se abrirá el puerto serial. Tera Term es muy potente y será capaz de quitarle el control del puerto a alguna otra aplicación que lo esté utilizando.
  • 440. Y así de rápido estaremos al frente de la siguiente ventana, que ya podemos utilizar para nuestras comunicaciones seriales, ya que su configuración por defecto suele ser la más habitual. La barra de título indica que el baud rate usado es de 9600 y que se tiene seleccionado el puerto COM1. De vez en cuando será necesario cambiar la configuración de comunicación que usa Tera Term por defecto. Para ello debemos ir al menú Setup Serial Port… El parámetroTransmit delay es el retardo que habrá entre cada uno de los datos que se envíen y/o entre cada línea de caracteres. Quizá interese incrementar este retardo cuando el microcontrolador no tenga la capacidad de recibir los datos con la prestancia necesaria. En cuando a los otros parámetros, ya los discutimos de sobra en el Estándar RS232.
  • 441. Otra ventana donde encontrar algunas opciones útiles está en menú Setup Terminal…New-line establece si el cursor pasará a la siguiente línea con los caracteres CR (Carriage Return = 0x0D = „r‟) y/o LF (LineFeed = Newline= 0x0A = „n‟). Por lo general estos caracteres se manejan a nivel firmware (desde el programa del microcontrolador), del mismo modo el eco lo hace e microcontrolador y raras veces será recomendable marcar la casilla de Local echo. Ahora vamos a modificar el aspecto del programa. Puede que sea un tema superfluo pero al menos para mí esta apariencia vale mucho. Primero cambiaremos el tipo de fuente yendo al menú Setup Font… Y bueno, qué te puedo decir, escoge la fuente de tu preferencia.
  • 442. Para quienes deseen que la consola luzca como el terminal simulado de Proteus pueden ir al menú Setup Window. Allí escogemos la forma del cursor (Cursor Shape) como Horizontal line. Luego en el marco Color empezamos por cambiar el color de fondo (Background) a negro y después el color de fuente (Text) a verde como se indica en la figura.
  • 443. Ya te habrás dado cuenta de que la ventana del terminal se limpia cada vez que cambias su tamaño. Para evitar que lo haga debemos ir al menú Setup Aditional settings… y desmarcar la opción Clear display when window resized, como se ve abajo.
  • 444. Para limpiar la ventana podemos usar las opciones Clear screen y Clear buffer del menú Edit. La primera opción simplemente recorre el contenido de la ventana, por eso es preferible usar Clear buffer, que resetea todo el contenido de la ventana. Las otras opciones de este menú hablan por sí solas. Para terminar configuraremos el uso del teclado numérico. Esto no es un aspecto decorativo, es muy importante y lo deje para el final solo por ser algo engorroso. Por
  • 445. defecto, Tera Term emula el teclado VT100, por lo que algunas teclas como „+‟ o „-‟ no funcionarán como en un teclado convencional. Bueno, para ir directo al grano, seguimos el directorio de instalación de Tera Term, que en Windows 7 suele ser C:Program Files (x86)teraterm/ y utilizamos un editor de texto como el bloc de notas o Notepad++ para abrir el archivo KEYBOARD.CNF. Este archivo tiene varias secciones; nosotros nos ubicamos en [VT numeric keypad], que empieza en la línea 29 como se ve abajo.
  • 446. Las líneas precedidas de punto y coma (;) son comentarios y no cuentan. Son los números mostrados en naranja los que debemos editar: debemos cambiarlos todos por la palabraoff, desde Num0 hasta PF4, 18 en total, y debe quedar como en la siguiente figura. Ahora guarda el archivo con los cambios realizados y reinicia el programa Tera Term. O también puedes ir al menú Setup Load key map… y recargar manualmente el archivoKEYBOARD.CNF.
  • 447. Finalmente, puesto que usaremos este programa con tanta frecuencia y no querremos estar configurándolo cada vez que lo ejecutemos, debemos guardar la configuración realizada yendo al menú Setup Save setup… en la ventana presentada simplemente hacemos clic en Guardar y con eso bastará para que Tera Term se abra siempre con la configuración actual. El USART, USART0 y USART1 de los AVR USART es la sigla de Universal Synchronous Asynchronous Receiver Transmitter. Es el periférico que incorporan muchos microcontroladores para comunicarse con dispositivos que soportan el estándar RS-232. De su nombre se deprende que puede trabajar en modo Síncrono o Asíncrono. En esta presentación nos enfocaremos exclusivamente al modo asíncrono. Algunos AVR (como los viejos ATmega32, ATmega8535, etc.) poseen un solo módulo USART llamado simplemente USART. Los AVR mejorados como los que utilizamos en cursomicros tienen uno o dos módulos, llamados USART0 y USART1, con características de control idénticas. Por ejemplo, los megaAVR de la serieATmegaXX8 tienen USART0 y los de la familia ATmegaXX4 tienen adicionalmente el USART1. El USART0 y el USART1 son gemelos. Entre ellos y los viejos USART de los AVR hay una mínima diferencia que muchas veces se pasará por alto sin notarlo. De hecho, aparte de los nombres de los registros de relacionados, el control del USART es compatible en todos los AVR que lo tienen; incluso hay un gran parecido con los de otros microcontroladores. Así que creo que valdrá la pena demorarnos un poco en esta teoría. Las características comunes del USART en los AVR son: Asíncronamente pueden trabajar en modo full-dúplex , esto es transmitir y recibir datos, al mismo tiempo inclusive. Pueden generar interrupciones al recibir datos o después de enviarlos. Puede operar en modo SPI maestro. Esta opción no está disponible en los viejos AVR. Operan en background (detrás del escenario) para que las transferencias de datos se lleven a cabo mientras el CPU realiza otras tareas. El baud rate es configurable por el usuario. Los datos pueden ser de 5, 6, 7, 8 ó 9 bits. Puede trabajar con uno o dos bits de Stop. Y además de aceptar el bit paridad puede computar a nivel hardware su valor para el dato actual. Los Registros del USART Además de estos registros todavía faltan por citar los que controlan las interrupciones del USART. Aunque ya los conocemos bastante bien los veremos por separado.
  • 448. Los nombres de los registros y de sus bits varían de acuerdo con el número de USART. En toda mi exposición tomo como referencia el USART0 y por ello verás el número 0 acompañando a cada registro. Si utiliza el USART1 se debe cambiar el 0 por el 1 en cada registro y en cada bit; y si se utiliza el USART viejo simplemente se quita el 0. Por ejemplo, en el USART1 el registro UCSR0A se debe suprimir por UCSR1A. Lo mismo debe aplicarse a los nombres de cada bit. UDR0. El USART tiene dos buffers para las transferencias de datos: un buffer de transmisión (donde se cargan los datos a trasmitir) y un buffer de recepción (donde se almacenan los datos recibidos). Mediante el registro UDR0 se accede a ambos buffers. Esto es, se accede al buffer de transmisión (en las operaciones de escritura) y al buffer de recepción (en las operaciones de lectura). UDR0 significa USART Data Register 0. Su nombre en los USART1 es UDR1 y en los USART viejos se llama simplemente UDR. UCSR0A, UCSR0B y UCSR0C. (USART Control and Status Register A, B y C). Son los Registros de Control y Estado del USART0. Creo que los nombres lo dicen todo. Para los que aún usan los viejos AVR, deben saber que los registros UCSRC y UBRRH comparten la misma locación en espacio de los registros de E/S. Ellos deben setear el bit 7 para escribir en el registro UCSRC y limpiarlo para escribir en UBRRH. Las operaciones de lectura son inusuales. UBRR0L y UBRR0H. Son los registros generadores de Baud Rate del USART0. Juntos forman un registro de 16 bits cuyo valor establece la velocidad de transferencia de datos. Todos estos registros son de 8 bits. UDR0 y el último par no tienen formatos preestablecidos y podrían aceptar cualesquiera valores. Los registros de control y estado tienen los siguientes mapas de bits: Registro UCSR0A UCSR0A RXC0 TXC0 UDRE0 FE0 DOR0 UPE0 U2X0 MPCM0 RXCIE0 TXCIE0 UDRIE0 RXEN0 TXEN0 UCSZ02 RXB80 TXB80 UPM00 USBS0 UCSZ01 UCSZ00 UCPOL0 Registro UCSR0B UCSR0B Registro UCSR0C UCSR0C UMSEL01 UMSEL00 UPM01 En lo sucesivo iremos describiendo las funciones de estos dos registros y de cada uno de sus bits. Algunos bits están relacionados con la operación del USART en modo síncrono, tema que por el momento no nos compete, y serán ignorados. Inicialización del USART Lo primero que debemos hacer con el USART es configurar su operación: esto es, establecer el modo de operación, fijar el formato de los datos, poner la velocidad de
  • 449. las transferencias baud rate y habilitar los módulos Receptor y/o Transmisor. Los bits de los registros de Control y Estado relacionados con la configuración de la librería [usart.h y usart.c] usada en cursomicros.com son los siguientes. Los bits no mencionados los trataremos de cerca al estudiar los modos de operación Síncrono y SPI Master. MPCM0. Recordemos que inicialmente el estándar RS232 permite comunicaciones solo entre dos dispositivos (DTE y DCE). Actualmente los USART también soportan comunicaciones con otros protocolos como del RS485, donde se pueden engarzar al bus varios dispositivos en relaciones maestro-esclavo y con direcciones que los identifican (parecido al bus I2C). Para que el USART del AVR pueda integrarse a esas redes debemos setear el bit MPCM0 (Multiprocessor Communication Mode). Para comunicaciones RS232 este bit se debe mantener en cero. TXEN0 y RXEN0. Son los bits para habilitar los módulos Transmisor y Receptor del USART. No es necesario habilitar los dos módulos al mismo tiempo. Al habilitar un módulo (seteando el bit TXEN0 o RXEN0), éste asumirá el control del pin respectivo (TXD0 para el transmisor y RXD0 para el receptor) y por tanto dichos pines dejarán de funcionar como entrada y salida generales, sin importar el valor de los bits correspondientes en los registros DDR, PORT y PIN. UMSEL01 y UMSEL00. Estos bits establecen uno de los tres modos en que puede trabajar el USART0: Modo Asíncrono, modo Síncrono y modo SPI Maestro. La configuración por defecto es Asíncrono, con ambos bits iguales a cero, así que por el momento no tendremos que tocarlos. USBS0. Si este bit vale 0, el módulo transmisor enviará un bit Stop al final de cada dato. Si vale 1, enviará 2 bits Stop. El módulo receptor solo evalúa el primer bit Stop recibido, así que no le interesa esta configuración. Tampoco tocaremos este bit puesto que trabajaremos con un solo bit Stop porque es el valor preestablecido en todos sistemas RS232, y así nos ahorramos settings adicionales ;). UCSZ02, UCSZ01 y UCSZ00. Estos bits establecen el tamaño de los datos que se utilizarán en las transferencias, los cuales pueden ser desde 5 hasta 9 bits. El formato seleccionado será empleado por ambos módulos, transmisor y receptor. Si los datos son de 8 bits se trabajará con el valor completo del registro UDR0; si los datos son de menos bits se obviarán los bits de mayor peso del registro UDR0; y si los datos son de 9 bits, los 8 bits de UDR0 se complementarán con el bit TXB80 (para las transmisiones) y RXB80 (para las recepciones). Por defecto todos estos bits inician a cero, así que observando la siguiente concluimos en que deberemos cambiarlos a 011. Tabla UCSZ02
  • 450. UCSZ02 UCSZ01 UCSZ00 Tamaño del Carácter 0 0 0 5 bits 0 0 1 6 bits 0 1 0 7 bits 0 1 1 8 bits 1 0 0 Reservado 1 0 1 Reservado 1 1 0 Reservado 1 1 1 9 bits U2X0. Como habíamos estudiado, el baud rate es la velocidad de transferencias de datos, se mide en bits/segundo y hay valores estándar que debemos respetar (9600, 19200, 115200, etc.). Seteando el bit U2X0 se divide el prescaler generador de baud rate de modo que la velocidad de transferencia de datos se multiplique por dos. Este bit solo tiene efecto en el modo Asíncrono y dependiendo de su valor se disponen de dos fórmulas para calcular el valor del baud rate final. F_CPU es la frecuencia del procesador y recuerda que es una constante definida en el archivo avr_compiler.h. Tabla Baud rate del USART del megaAVR U2X0 = 1 (Velocidad Doble) U2X0 = 0 (Velocidad Normal) UBRR0 está conformado por la unión de dos registros de E/S: UBRR0H y UBRR0L. Con su capacidad de 16 bits y la fórmula de U2X0 = 1 se pueden obtener los suficientes valores de baud rates como para hacernos olvidar de la fórmula con U2X0 = 0. De hecho, analizando las fórmulas se deduce que cualquier valor de baud con U2X = 0 también se puede conseguir con la fórmula de U2X0 = 1, aunque a veces esto devendrá en una menor performance en el muestreo de la señal de recepción. En la práctica no interesa tanto el hecho de que U2X0 = 1 “incremente” la velocidad de las transferencias, sino que se pueden obtener baud rates más precisos.
  • 451. En consecuencia, la mejor decisión en la mayoría de los casos será escoger la primera fórmula, con U2X = 1. De allí debemos despejar UBRR0 para calcular su valor. Esto nos da siguiente expresión, que escrita en forma lineal sería UBRR0 = F_CPU/(8*BAUD) – 1. El valor generado para UBRR0 raras veces será exacto y en tales casos se deberá escoger el más cercano y recalcular el valor del baud rate. Debemos tener cuidado con los resultados obtenidos puesto que hay valores impuestos por el estándar RS232 y que debemos respetar. En todo caso, puedes revisar las tablas de baud rate presentes en los datasheets. Con todo lo expuesto ya podemos implementar la siguiente función de inicialización del USART0. Es irónico que tanta teoría se condense en tan poco código, pero es más gracioso saber que la configuración establecida se puede resumir con la notación BAUD 8N1, que leído en orden significa Baud rate = BAUD, formato de datos = 8 bits, No (sin) bit de paridad y 1 bit de Stop. Deberemos recordar esos parámetros a la hora de configurar elsoftware terminal del lado de la computadora. //*********************************************************************** ***** // Inicializa el USART0. //*********************************************************************** ***** void usart_init(void) { /* Configurar baud rate */ UCSR0A |=(1<<U2X0); UBRR0 = F_CPU/(8*USART_BAUD)-1; /* Configurar modo de operación Asíncrono y formato de frame a * 8 bits de datos, 1 bit de stop y sin bit de paridad. UCSR0C =(1<<UCSZ01)|(1<<UCSZ00); /* Habilitar módulos Receptor y Transmisor UCSR0B =(1<<RXEN0)|(1<<TXEN0); #if defined( __GNUC__ ) */ */
  • 452. /* Asociar las funciones 'putchar' y 'getchar' con las funciones de entrada * y salida (como printf, scanf, etc.) de la librería 'stdio' de AVRGCC */ fdevopen((int(*)(char, FILE*))putchar,(int(*)(FILE*))getchar); #endif } Una aclaración para quienes usen los viejos AVR: en dichos AVR los registros UBRRH yUBRRL se encuentran bastante lejos uno del otro en el espacio de los registros de E/S. Como consecuencia la sentencia de asignación UBRR = F_CPU/(8*USART_BAUD)-1; no será válida. Tendrán que escribirlos por separado Transmisión de Datos El dato que el USART0 transmitirá debe ser previamente cargado en el registro UDR0. Con ello el dato pasará al Registro de Desplazamiento de Transmisión si es que está vacío. Luego el dato saldrá serialmente bit a bit por el pin TXD0. Pero si el Registro de deslazamiento se encuentra transmitiendo un dato previo, el nuevo dato permanecerá enUDR0 hasta que el registro de desplazamiento quede disponible. En ese lapso de tiempo el registro UDR0 no podrá aceptar un nuevo dato. Módulo de Transmisión del USART0. Mientras el registro UDR0 contenga algún dato, el bit UDRE0 (del registro UCSR0A) valdrá 0. Cuando UDR0 esté nuevamente libre (cuando haya descargado su contenido en el registro de desplazamiento), el flagUDRE0 se seteará automáticamente por hardware. Por tanto, será necesario comprobar que el bit UDRE0 valga 1 antes de intentar escribir un nuevo dato en UDR0. La activación del flag UDRE0 puede generar una interrupción si es que está habilitada. Detallaremos las interrupciones del USART en otra sección más adelante. Una conclusión de lo descrito es que es posible escribir en UDR0 hasta dos datos sin pausa intermedia, el primero irá al registro de desplazamiento y el segundo se quedará
  • 453. enUDR0. Así podemos decir que el USART0 tiene un buffer FIFO de transmisión de dos niveles. Con lo expuesto ya se puede codificar la siguiente función putchar. El encabezado de esta función está de acuerdo con su definición en la librería stdio.h del compilador C. Allí se indica que putchar debe tener un parámetro de entrada y uno de salida, ambos de tipo int, aunque no en la práctica no parezca que sea necesario. //*********************************************************************** ***** // Transmite el byte bajo de 'dato' por el USART //*********************************************************************** ***** int putchar(int dato) { /* Esperar a que haya espacio en el buffer de transmisión */ while((UCSR0A &(1<<UDRE0))==0); /* Colocar dato en el buffer de transmisión */ UDR0 = dato; return dato; } Existe un flag adicional llamado TXDC (en el registro UCSR0A) que se setea cuando se haya terminado de transmitir el dato del registro de desplazamiento y al mismo tiempo no exista otro dato presente en el registro UDR0. Este flag también tiene la capacidad de disparar una interrupción ante dicho evento. En ese caso el flag TXDC se limpiará automáticamente al ejecutarse la función ISR; pero siendo de lectura/escritura también se puede limpiar por software escribiéndole uno sobre él. Recepción de Datos Los datos seriales que llegan ingresan bit a bit por el pin RXD0 y se van depositando en el Registro de Desplazamiento de Recepción. Cuando el dato esté completo (cuando llegue su bit Stop) pasará paralelamente al registro UDR0. Luego podremos leer el dato del registro UDR0. Pero si el registro UDR0 está ocupado con datos previos que no han sido leídos por el procesador, el nuevo dato permanecerá en el registro de desplazamiento hasta que haya espacio en UDR0.
  • 454. Módulo de Recepción del USART0. Apenas haya un dato en UDR0 se seteará el flag RXC0 (del registro UCSR0A). Así que para saber si hay un dato allí pendiente de ser leído debemos comprobar el bit RXC0. Esté flag es de solo lectura y se limpiará automáticamente cuando hayamos terminado de leer todos los datos del buffer UDR0. La activación del flag RXC0 también puede generar una interrupción si está previamente habilitada. No debemos confundir el UDR0 de recepción con el UDR0 de transmisión. Aunque tengan el mismo nombre, corresponden a dos registros diferentes. Es más, el UDR0 de recepción en realidad es un buffer de dos niveles (puede almacenar hasta dos datos). Ahora podemos decir que el USART0 tiene un buffer FIFO capaz de almacenar hasta tres datos al mismo tiempo: dos en el buffer UDR0y uno en el registro de desplazamiento. Si en este momento se detectara la llegada de un nuevo dato por el pin RXD0, dicho dato se perdería y como señal se activaría el flag de desbordamiento DOR0 (Data OverRun). Como no querremos que ocurra dicho evento trágico será recomendable que leamos de inmediato cada nuevo que llegue al USART. Siguiendo esta recomendación la función de recepción tendrá el siguiente aspecto. Esta implementación también toma como referencia la definición de getchar presente en el archivo stdio.h del compilador C. //*********************************************************************** ***** // Recibe un byte de dato del USART //*********************************************************************** ***** int getchar(void) { /* Esperar a que haya al menos un dato en el buffer de recepción */
  • 455. while((UCSR0A &(1<<RXC0))==0); /* Leer y retornar el dato menos reciente del buffer de recepción */ return UDR0; } Registros del USART0 El Registro UCSR0A Registro UCSR0A UCSR0A RXC0 TXC0 UDRE0 FE0 DOR0 UPE0 U2X0 MPCM0 Registro de Microcontrolador RXC0 USART Receive Complete Este bit de flag vale uno cuando hay datos no leídos en el buffer de recepción y vale cero cuando el buffer de recepción está vacío (esto es, no contiene datos por leer). Si el módulo receptor está deshabilitado, el buffer de recepción será liberado y consecuentemente el bit RXC0 se pondrá a cero. E flag RXC0 se puede usar para generar una Interrupción de Recepción Completada (ver la descripción del bit RXCIE0). TXC0 USART Transmit Complete Este bit de flag se pone a uno cuando un dato completo ha terminado de salir del Registro de Desplazamiento de Transmisión y no hay ningún dato presente en el registro UDR0. El bit TXC0 se limpia automáticamente cuando se ejecuta la función de interrupción, o se puede limpiar por software escribiendo uno sobre él. El flag TXC0 puede generar la Interrupción de Transmisión Completada (ver la descripción del bit TXCIE0). UDRE0 USART Data Register Empty El flag UDRE0 indica si el buffer de transmisión (UDR0) está listo para recibir un nuevo dato. Si el bit UDRE0 vale uno, el buffer está vacío, y por tanto está listo para ser escrito. El flag UDRE0 puede generar una Interrupción de Registro de Dato Vacío (ver descripción del bit UDRIE0). El bit UDRE0 se pone a uno después de un reset para indicar que el Transmisor está listo.
  • 456. FE0 Frame Error Este bit se pone a uno si el siguiente carácter en el buffer de recepción tuvo un error de frame en la recepción del dato. Esto es, cuando el primer bit Stop del siguiente carácter en el buffer de recepción es cero. Este bit será válido hasta que se lea el buffer de recepción UDR0. El bit FE0 vale cero cuando el bit Stop del dato recibido es uno. Siempre que se escriba en el registro UCSR0A este bit se debe mantener en cero. DOR0 Data OverRun Este bit se pone a uno cuando se detecta una condición de desbordamiento de dato. Ocurre un desbordamiento de dato (Data OverRun) cuando el buffer de recepción está lleno (con dos caracteres), contiene un nuevo carácter esperando en su Registro de Desplazamiento, y se detecta un nuevo bit Start. Este bit es válido hasta que se lea el buffer UDR0. Siempre que se escriba en el registro UCSR0A este bit se debe mantener en cero. UPE0 USART Parity Error Este se pone a uno al detectarse un Error de Paridad en el buffer de recepción cuando están habilitados la recepción del bit de Paridad y su comprobación (UPM01 = 1). Este bit será válido hasta que se lea el buffer UDR0. Siempre que se escriba en el registro UCSR0A este bit se debe mantener en cero. U2X0 Double the USART Transmission Speed Este bit solo tiene efecto en el modo de operación asíncrono. Se debe escribir cero en este bit cuando se usa el modo de operación síncrono. En las comunicaciones asíncronas, si se escribe uno en este bit se reducirá el divisor del generador de baud rate de 16 a 8, dando como resultado la multiplicación por dos de la velocidad de transferencia de datos. MPCM0 Multi-processor Communication Mode Este bit habilita el modo de Comunicación Multiprocesador. Cuando se escribe uno en el bit MPCM0, serán ignorados todos los frames recibidos por el USART que no contengan información de dirección. El módulo Transmisor no queda afectado por la configuración de los bits MPCM0. El Registro UCSR0B Registro UCSR0B
  • 457. UCSR0B RXCIE0 TXCIE0 UDRIE0 RXEN0 TXEN0 UCSZ02 RXB80 TXB80 Registro de Microcontrolador RXCIE0 RX Complete Interrupt Enable Al escribir uno en este bit se habilita la Interrupción de Recepción Completada cada vez que se active el flag RXC0. Para que se genere la interrupción será necesario que también el bit enable general I de SREG valga uno. TXCIE0 TX Complete Interrupt Enable Al escribir uno en este bit se habilita la Interrupción de Transmisión Completada cada vez que se active el flag TXC0. Para que se genere la interrupción será necesario que también el bit enable general I de SREG valga uno. UDRIE0 USART Data Register Empty Interrupt Enable Al escribir uno en este bit se habilita la interrupción al activarse el flag UDRE0. Se generará una Interrupción de Dato de Registro Vacío solo si valen uno el bit UDRIE0, el flag de Global de Interrupciones en el registro SREG y el bit UDRE0 en el registro SREG. RXEN0 Receiver Enable Al escribir uno en este bit se habilita el módulo Receptor del USART. El receptor tomará el control del pin RXD0. Al deshabilitar el receptor se liberará el buffer de recepción invalidando los flags FE0, DOR0 y UPE0. TXEN0 Transmitter Enable Al escribir uno en este bit se habilita el módulo Transmisor del USART. El transmisor tomará el control del pin TXD0. La des-habilitación del transmisor (escribiendo 0 en TXEN0) no se hará efectiva hasta que se completen las transmisiones en marcha y las pendientes, esto es, cuando el registro UDR0 y el registro de desplazamiento estén vacíos. Cuando se deshabilite el Transmisor el pin TXD0 quedará en libertad. UCSZ02 Character Size El bit UCSZ02 combinado con los bits UCSZ01 y UCSZ00 del registro UCSRC establece el número de los bits de datos (tamaño del carácter) en los frames que usarán el Transmisor y el Receptor. RXB80 Receive Data Bit 8
  • 458. RXB80 es el noveno bit del dato recibido cuando se trabaja con datos seriales de 9 bits. Se debe leer antes de leer los bits bajos del registro UDR0. TXB80 Transmit Data Bit 8 TXB80 es el novena bit del dato a transmitir cuando se trabaja con datos de 9 bits. Se debe escribir antes de escribir los bits bajos del registro UDR0. El Registro UCSR0C Registro UCSR0C UCSR0C UMSEL01 UMSEL00 UPM01 UPM00 USBS0 UCSZ01 UCSZ00 UCPOL0 Registro de Microcontrolador UMSEL0 1 UMSEL0 0 USART Mode Select Estos bits seleccionan el modo de operación del USART como se muestra en la siguiente tabla. Tabla UMSEL01 UMSEL01 UMSEL00 Mode 0 USART Asíncrono 0 1 USART Síncrono 1 0 Reservado 1 UPM01: UPM00 0 1 Master SPI (MSPIM) Parity Mode Estos bits habilitan y configuran la generación y comprobación del bit de paridad. Si está habilitado, el Transmisor generará y enviará automáticamente el bit de paridad de cada dato. El Receptor calculará el bit de paridad de cada dato recibido y lo comparará con la configuración del bit UPM00. Si se detecta una discordancia, se seteará el flag UPE0 del registro UCSR0A. Tabla UPM01 UPM01 UPM00 Modo de Paridad
  • 459. 0 Deshabilitado 0 1 Reservado 1 0 Habilitado, Paridad Par 1 USBS0 0 1 Habilitado, Paridad Impar Stop Bit Select Este bit selecciona el número de bits Stop que utilizará el Transmisor. El Receptor “ignora” esta configuración. Tabla USBS0 USBS0 Bits Stop 0 1 UCSZ01: UCSZ00 1 bit 2 bits Character Size Estos bits se combinan con el bit UCSZ02 del registro UCSR0B para establecer el número de bits de los datos (tamaño de carácter) que utilizarán el Transmisor y el Receptor. Tabla UCSZ02 UCSZ02 UCSZ01 UCSZ00 Tamaño del Carácter 0 0 0 5 bits 0 0 1 6 bits 0 1 0 7 bits 0 1 1 8 bits 1 0 0 Reservado 1 0 1 Reservado
  • 460. 1 0 Reservado 1 UCPOL0 1 1 1 9 bits Clock Polarity Este bit solo se usa en el modo de operación Síncrono. En el modo Asíncrono debe permanecer en cero. El bit UCPOL0 establece la relación entre el cambio de los datos de salida y la señal de reloj XCK0, y la relación entre el muestreo de los datos de entrada y la señal de reloj XCK0. Tabla UCPOL0 UCPOL0 El Cambio de Datos ocurre (A la salida del pin TxD0) El Muestreo de Datos ocurre (A la entrada del pin RxD0) 0 En el flanco de Subida del pin XCK0 En el flanco de Bajada del pin XCK0 1 En el flanco de Bajada del pin XCK0 En el flanco de Subida del pin XCK0 Los Registros UBRR0L y UBRR0H Registro UBRR0H UBRR0H --- --- --- --- Bit 11 Bit 10 Bit 9 Bit 8 Registro UBRR0L UBRR0L Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0 Registro de Microcontrolador Bit 15:12 Reserved Bit 11:0 UBRR11:0: USART Baud Rate Register Estos bits están reservados para usos futuros. Cuando se escriba en UBRR0H este bit debe permanecer en cero por compatibilidad con futuros dispositivos. Este es un registro de 12 bits que contiene el baud rate del USART. El registro UCRR0H contiene los 4 bits más significativos, y UBRR0L contiene los 8 bits menos significativos del baud rate del USART. Si se cambia el baud rate cuando haya
  • 461. transmisiones o recepciones de datos en curso, estas operaciones serán estropeadas. La escritura en UBRR0L actualizará de inmediato el prescaler del baud rate. Tablas de Baud Rate Algunos compiladores como CodeVisionAVRpueden calcular automáticamente el valor de los registros UBRR para generar el baud rate que le indiquemos, pueden incluso avisar el error producido. En otros casos será recomendable ver por nosotros mismos si el error es aceptable para nuestras aplicaciones. Si es una cuestión crucial, también se puede optar por cambiar de XTAL por otro cuya frecuencia sea múltiplo del baud rate deseado; por ejemplo, para los baud rates múltiplos de 9600 (que son la gran mayoría) los XTALes de 3.6884 MHz, 11.0592 MHz ó 18.4320 MHz derivan en errores de 0.00%.
  • 463. Librerías para el USART Una vez más las librerías presentadas son las compaginaciones de los códigos elaborados previamente, con los añadidos que demanda separarlos en dos archivos, uno de código y otro de configuraciones: usart.c y usart.h. En realidad no hay mucho que comentar. Es una librería muy corta que consta apenas de las funciones básicas de entrada y salida de un byte de dato, aparte de la función de inicialización, usart_init. usart_init(). Inicializa el USART0 para el uso de datos de 8 bits, sin bit de paridad y 1 bit Stop. El baud rate o velocidad de transferencias de datos se establece en el archivo usart.h, puesto que normalmente será el único parámetro a editar. /* Define la velocidad de transmisión del USART (en bits/segundo), * si aún no ha sido definida. */ #ifndef USART_BAUD #define USART_BAUD #endif 9600UL
  • 464. getchar(). Lee un byte de dato del buffer de recepción del USART. Si no hay un dato allí presente, la función esperará hasta que llegue uno. Así que para no perder tiempo en una espera innecesaria se recomienda antes usar la macro kbhit() descrita más adelante. Es posible hacer que esta función “emita” eco del dato recibido, para que se visualice en el terminal. En general esto no es requerido porque el programa podría devolver los datos individualmente si se desease, pero hay funciones como scanf o gets de la librería stdio.h para las que será muy conveniente configurar el eco de getchar, simplemente des-comentando la directiva que define la constante__GETCHAR_ECHO__ del archivo usart.h. /* Descomentar el siguiente #define para que la funcion 'getchar' haga * eco de los caracteres recibidos. */ //#define __GETCHAR_ECHO__ putchar(). Coloca un byte de dato en el buffer de transmisión del USART. Si el buffer está disponible la escritura será inmediata, de lo contrario, esperará a que esté nuevamente disponible, espera que será muy corta (a lo sumo de 1 ms para un baud rate de 9600). Las funciones getchar y putchar están escritas para encajar con sus definiciones en el archivo sdtio.h del compilador C, de modo que podremos utilizar todas las funciones disponibles en las librerías sdtio.h de AVR IAR C y AVR GCC, como puts(), printf(), scanf(), etc. kbhit(). En el archivo usart.h se ha colocado adicionalmente la macro kbhit(), que en el lenguaje C es una función que se usa para saber si se ha presionado una tecla, o sea para saber si hay un dato en el buffer del teclado. De ahí su nombre kb = keyboard = teclado + hit = acción de presionar un botón. Obviamente los microcontroladores no tienen teclados de computadora, pero ya que de algún modo se conectan a uno, es común usar esta función para comprobar si la computadora ha enviado un dato (que probablemente se originó en su teclado). /* Usar kbhit para ver si hay algún dato en el buffer de recepción * antes de llamar directamente a la función getchar para evitar * esperas innecesarias. */ #define kbhit() (UCSR0A & (1<<RXC0))
  • 465. /************************************************************************ ****** * FileName: * Overview: Asíncrono * Processor: usart.h Macros y prototipos de funciones para el USART0 en modo ATmel AVR con USART0 * Compiler: AVR IAR C y AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. * * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta * licencia y las notas de autor y copyright de arriba. ************************************************************************* ****/ #include "avr_compiler.h" /* Define la velocidad de transmisión del USART (en bits/segundo), si aún no * ha sido definida. */ #ifndef USART_BAUD #define USART_BAUD #endif 9600UL
  • 466. /* Usar kbhit para ver si hay algún dato en el buffer de recepción antes de * llamar directamente a la función getchar para evitar esperas innecesarias. */ #define kbhit() (UCSR0A & (1<<RXC0)) /* Descomentar el siguiente #define para que la funcion 'getchar' haga eco de * los caracteres recibidos. */ //#define __GETCHAR_ECHO__ /* Macros para AVR GCC */ #if defined( __GNUC__ ) #ifdef putchar #undef putchar #endif #ifdef getchar #undef getchar #endif #endif /* Definiciones de funciones */ void usart_init(void); int putchar(int); int getchar(void);
  • 467. /************************************************************************ ****** * FileName: usart.c * Overview: Implementa funciones para el USART0 en modo Asíncrono * Processor: ATmel AVR con USART0 * Compiler: AVR IAR C y AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. * * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta * licencia y las notas de autor y copyright de arriba. ************************************************************************* ****/ #include "usart.h" //*********************************************************************** ***** // Inicializa el USART0. //*********************************************************************** ***** void usart_init(void) { /* Configurar baud rate */ UCSR0A |=(1<<U2X0); UBRR0 = F_CPU/(8*USART_BAUD)-1;
  • 468. /* Configurar modo de operación Asíncrono y formato de frame a * 8 bits de datos, 1 bit de stop y sin bit de paridad. */ UCSR0C =(1<<UCSZ01)|(1<<UCSZ00); /* Habilitar módulos Receptor y Transmisor */ UCSR0B =(1<<RXEN0)|(1<<TXEN0); #if defined( __GNUC__ ) /* Asociar las funciones 'putchar' y 'getchar' con las funciones de entrada * y salida (como printf, scanf, etc.) de la librería 'stdio' de AVRGCC */ fdevopen((int(*)(char, FILE*))putchar,(int(*)(FILE*))getchar); #endif } //*********************************************************************** ***** // Transmite el byte bajo de 'dato' por el USART //*********************************************************************** ***** int putchar(int dato) { /* Esperar a que haya espacio en el buffer de transmisión */ while((UCSR0A &(1<<UDRE0))==0); /* Colocar dato en el buffer de transmisión */ UDR0 = dato; return dato; }
  • 469. //*********************************************************************** ***** // Recibe un byte de dato del USART //*********************************************************************** ***** int getchar(void) { /* Esperar a que haya al menos un dato en el buffer de recepción */ while((UCSR0A &(1<<RXC0))==0); /* Leer y retornar el dato menos reciente del buffer de recepción */ #if defined ( __GETCHAR_ECHO__ ) return(putchar(UDR0)); #else return UDR0; #endif } Práctica: Hello El programa maneja el ingreso y salida de cadenas de texto.
  • 470. Circuito para probar el USART del microcontrolador AVR. El código fuente /****************************************************************************** * FileName: main.c * Purpose: Comunicación básica por USART0 * Processor: ATmel AVR con módulo TWI * Compiler: AVR IAR C y AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. * * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta * licencia y las notas de autor y copyright de arriba. *****************************************************************************/
  • 471. #include "avr_compiler.h" #include "usart.h" intmain(void) { charname[20]; usart_init();// Inicializar USART0 @ 9600-8N1 puts("nr www.cursomicros.com "); puts("nr =================== nr"); puts("nr - ¿Cuál es tu nombre?... nr - "); scanf("%s",name); printf(" - Gusto en conocerte, %s",name); while(1); } Para que el programa responda como se muestra en la siguiente imagen será necesario que configurar el uso del eco ya sea en el entorno de Tera Term, como se indicó en la sección Uso de Tera Term, o en el archivo usart.h, como se indicó en la sección Librerías para el USART. De lo contrario la función scanf no mostrará el texto que vayas ingresando por el teclado.
  • 472. Las Interrupciones del USART Seguiremos considerando que todo lo expuesto es igualmente válido para el USART1 e incluso para el USART de los viejos AVR. Con eso aclarado… El USART0 puede generar tres interrupciones, a citar: Interrupción de Recepción Completada. Cuando se acaba de recibir un nuevo dato, esto es, cuando un dato recién llegado acaba de pasar del Registro de desplazamiento al buffer del registro UDR0 se activará el flag RXC0 el cual podrá disparar la interrupción, si es que está habilitada. Esta interrupción se habilita seteando el bit RXCIE0, aparte del bit enable general I, claro está. El flag RXC0 es algo especial porque es de solo lectura y no se limpia automáticamente al ejecutarse la función de interrupción ISR, sino únicamente cuando el buffer de recepción esté vacío. Esto significa que si la interrupción está habilitada se disparará sucesivamente mientras haya uno o más datos por leer. Registro UCSR0A
  • 473. UCSR0A RXC0 TXC0 UDRE0 FE0 DOR0 UPE0 RXCIE0 TXCIE0 UDRIE0 RXEN0 TXEN0 UCSZ02 U2X0 MPCM0 Registro UCSR0B UCSR0B RXB80 TXB80 Interrupción de Registro de Datos Vacío. Se refiere al registro de datos UDR0 del módulo transmisor. Esta interrupción se habilita seteando el bit UDRIE0 (aparte deI), y se disparará cuando se active a uno el flag UDRE0. Este evento ocurre cuando el registro UDR0 está vacío, sea después de un reset o después de depositar su contenido en el Registro de desplazamiento de modo que está dispuesto a aceptar un nuevo dato. El flag UDRE0 se limpia automáticamente al ejecutarse la función de interrupción ISR. También se puede limpiar escribiéndole un uno. Interrupción de Transmisión Completada. Esta interrupción se habilita seteando el bit TXEN0 (además de I), y se disparará cuando se active el flag TXC0, es decir, cuando un dato se haya terminado de transmitir, o dicho de otro modo, cuando el dato haya salido por completo del Registro de desplazamiento. Creo que con eso queda clara su diferencia respecto de la Interrupción de Registro de Dato Vacío. En la práctica significa que los datos se envían más rápido usando la Interrupción de Registro de Datos Vacío porque los espacios entre datos se reducen al mínimo, de modo que la Interrupción de Transmisión completada será raramente usada. El flag TXC0 se limpia automáticamente al ejecutarse la función de interrupción ISRo puede limpiarse por software escribiéndole un uno. Práctica: Interrupciones del USART Cada dato que llegue al USART es vuelto a enviar al terminal serial. Por otro lado, se envía todo un buffer (una cadena de texto) pero usando solo interrupciones. Usaremos el mismo circuito de la práctica anterior.
  • 474. Circuito para probar las interrupciones del USART del microcontrolador AVR. El código fuente /****************************************************************************** * FileName: main.c * Purpose: Uso de las interrupciones del USART * Processor: ATmel AVR con módulo TWI * Compiler: AVR IAR C y AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. * * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta * licencia y las notas de autor y copyright de arriba. *****************************************************************************/
  • 475. #include "avr_compiler.h" #include "usart.h" volatilecharbuf[]="nr Uso de interrupciones del USART nrr"; //**************************************************************************** // Gestor de interrupciones (Interrupción de Recepción Completada). // La interrupción de recepción se dispara cuando haya llegado algún dato. //**************************************************************************** ISR(USART0_RX_vect) { charc=UDR0;// Leer dato UDR0=c;// Devolver dato } //**************************************************************************** // Gestor de interrupciones (Interrupción de Registro de Datos Vacío). // La interrupción se dispara cuando se pueda enviar un nuevo dato. // La ISR deposita en el registro de transmisión el siguiente dato a enviar. //**************************************************************************** ISR(USART0_UDRE_vect) { staticunsignedchari=0; charc=buf[i]; if(c!=0)// ¿ Fin de buffer ? {
  • 476. UDR0=c; i++; } } intmain(void) { usart_init();// Inicializar USART0 @ 9600-8N1 /* Habilitar las interrupciones de 'Recepción Completada' y * de 'Registro de Datos Vacío'. */ UCSR0B|=(1<<RXCIE0)|(1<<UDRIE0); sei();// Setear bit I de SREG while(1) { /* Entrar en Modo sleep (Idle mode). * Es el único modo sleep en que el USART todavía puede * generar interrupciones */ SMCR=(1<<SE); sleep(); } }
  • 477. Observa que en la función principal main no se transfiere ningún dato. Todos viajan por interrupciones. La interrupción de recepción se usa con mucha frecuencia y es la más fácil de captar. //**************************************************************************** // Gestor de interrupciones (Interrupción de Recepción Completada). // La interrupción de recepción se dispara cuando haya llegado algún dato. //**************************************************************************** ISR(USART0_RX_vect) { charc=UDR0;// Leer dato UDR0=c;// Devolver dato } Eso es todo: cuando haya algún dato llegado lo recibimos y lo devolvemos :) La interrupción de transmisión no es tan usual como la anterior. Es una técnica algo sofisticada y muy eficiente pero que raras veces resulta realmente necesaria. En el programa funciona así: una vez habilitada, la interrupción se disparará de inmediato ya que el registro de transmisión UDR0 estará vacío. Así se empieza a enviar el primer dato de buf. La interrupción se volverá a disparar dada vez que el USART termine de enviar el dato anterior y parará en el último dato, cuando su flag se limpie por el solo hecho de empezar a ejecutarse la ISR, aunque en esa ocasión ya no se toque el registro UDR0. Fin de la historia. //*********************************************************************** ***** // Gestor de interrupciones (Interrupción de Registro de Datos Vacío). // La interrupción se dispara cuando se pueda enviar un nuevo dato. // La ISR deposita en el registro de transmisión el siguiente dato a enviar. //*********************************************************************** ***** ISR(USART0_UDRE_vect) {
  • 478. staticunsignedchar i =0; char c = buf[i]; if(c !=0)// ¿ Fin de buffer ? { UDR0 = c; i++; } } El buffer circular o buffer de anillo Recordemos que no podemos depositar un dato en el registro de transmisión del USART hasta que se termine de enviar el dato anterior. Mientras se espera a que eso pase el CPU podría perder tiempo valioso. La solución es guardar los datos en un buffer y que se vayan transmitiendo a su momento utilizando interrupciones, parecido a lo que se vio en la práctica pasada. Por otro lado, el registro de recepción, al ser un buffer de dos niveles, sí puede recibir un segundo dato antes de que se haya leído el dato previo. Pero si siguen llegando más datos sin que sean recogidos, no solo se perderían, sino que bloquearían el USART. Incluso si los datos se leyeran a tiempo utilizando interrupciones, ¿qué se haría con ellos si el CPU aún está ocupado procesando los datos previos? ¡Tienes razón! Podrían ir guardándose en un buffer. Pues bien, en ambos casos las cosas saldrán mejor si se usa un buffer de tipo circular. Un buffer circular es un recurso de programación tan viejo como útil en el intercambio de todo tipo de datos, no solo en comunicaciones del USART. No es más que un buffer o array ordinario que adopta su nombre por la forma en que se ponen y sacan sus elementos. Un buffer circular trabaja básicamente con dos índices, que aquí llamaremos Inpointer yOutpointer. No son como los punteros que define el lenguaje C, son simples variables que operan como índices para acceder a los elementos del buffer. Ambos índices tienen avance incremental y cíclico, es decir, se incrementan de uno en uno y luego de apuntar al último elemento del buffer vuelven a apuntar al primero. Eso explica su nombre.
  • 479. Estructura de un buffer circular de N elementos. Al inicio los dos índices apuntan al primer elemento del buffer. La pregunta es ¿cuándo y cómo se incrementan? Cada nuevo dato a guardar en el buffer será depositado en la casilla actualmente apuntada por Inpointer. A continuación Inpointer se incrementa en 1. (Inpointer para datos In = entrada.) Por otro lado, cada dato que salga del buffer será el de la casilla actualmente apuntada por Outpointer. A continuación Outpointer se incrementa en 1. (Outpointer para datos Out = salida.) Con todo lo expuesto ya puedes ensayar cómo funciona el buffer circular. Descubrirás que tiene un comportamiento FIFO: los primeros datos en entrar serán los primeros en salir; que en tanto haya espacio en el buffer siempre se podrán meter más datos sin importar en qué posiciones vayan, evitando el riesgo de sobrescribir posiciones ya ocupadas. ¿Podrías hacer eso con un buffer lineal? Muy difícil, ¿verdad? Ahora pasemos de los ensayos a la práctica real. Para saber si en el buffer hay espacio para meter más datos o si hay al menos un dato que sacar, se debe usar la diferencia entre las posiciones de los punteros. Por lo confuso que se ve eso, es preferible emplear una variable adicional que se incremente con cada dato ingresado y se decremente con cada dato extraído. Práctica: Buffer circular con Interrupciones El uso de un buffer circular en las recepciones de datos es una técnica robusta que se convierte en una necesidad de facto por razones ya explicadas. En las transmisiones, en cambio, brinda una eficiencia superflua y hasta innecesaria, salvo que la aplicación realmente la requiera. No quiero poner programas de esos aquí porque son demasiado grandes como ejemplos. Superficialmente esta práctica se ve igual que la primera de este capítulo. Solo que ahora todos los datos son transferidos por interrupciones pasando por buffers circulares.
  • 480. Circuito para probar el USART del microcontrolador AVR. El código fuente /****************************************************************************** * FileName: main.c * Purpose: Uso de buffers circulares con las interrupciones del USART * Processor: ATmel AVR con módulo TWI * Compiler: AVR IAR C y AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. * * License: Se permiten el uso y la redistribución de este código con
  • 481. * modificaciones o sin ellas, siempre que se mantengan esta * licencia y las notas de autor y copyright de arriba. *****************************************************************************/ #include "avr_compiler.h" #include "usart.h" #include "lcd.h" charGetFromTXBuffer(void); charGetFromRXBuffer(void); voidPutToTXBuffer(chardata); voidPutToRXBuffer(chardata); #define TXBufferSize 50 // Tamaño de buffer circular de Tx #define RXBufferSize 50 // Tamaño de buffer circular de Rx volatilecharTXBuffer[TXBufferSize];// Buffer circular de Tx volatilecharRXBuffer[TXBufferSize];// Buffer circular de Rx volatileunsignedcharTXInpointer=0; volatileunsignedcharRXInpointer=0; volatileunsignedcharTXOutpointer=0; volatileunsignedcharRXOutpointer=0; volatileunsignedcharTXBufferData=0; volatileunsignedcharRXBufferData=0; volatileunsignedcharTXBufferSpace=TXBufferSize; volatileunsignedcharRXBufferSpace=RXBufferSize;
  • 482. //**************************************************************************** // Gestor de interrupciones (Interrupción de Recepción Completada). // La interrupción de recepción se dispara cuando haya llegado algún dato. //**************************************************************************** ISR(USART0_RX_vect) { charc=UDR0;// Leer dato if(RXBufferSpace)// Si hay espacio en RXBuffer PutToRXBuffer(c); else// RXBuffer está lleno nop();// Código para TXBuffer lleno } //**************************************************************************** // Gestor de interrupciones (Interrupción de Registro de Datos Vacío). // La interrupción se dispara cuando se pueda enviar un nuevo dato. //**************************************************************************** ISR(USART0_UDRE_vect) { charc; if(TXBufferData)// Si hay datos en TXBuffer { c=GetFromTXBuffer();// Estraer dato
  • 483. UDR0=c;// Enviarlo } } intmain(void) { charc; unsignedchari=0; constchar*text="nr Escribe en el LCD... nr"; usart_init();// Inicializar USART0 @ 9600-8N1 lcd_init();// Inicializat LCD. Ver interface en "lcd.h" lcd_cmd(LCD_CURBLK);// Mostrar Cursor + Blink while(c=text[i++])// Cargar TXBuffer { if(TXBufferSpace)// Si hay espacio en TXBuffer PutToTXBuffer(c);// Meter c else nop();// Código para TXBuffer lleno } /* Habilitar las interrupciones de 'Recepción Completada' y * de 'Registro de Datos Vacío'. */
  • 484. UCSR0B|=(1<<RXCIE0)|(1<<UDRIE0); sei();// Setear bit I de SREG while(1) { if(RXBufferData)// Si hay datos en RXbuffer { c=GetFromRXBuffer();// Obtener un dato switch(c) { case0x1B:lcd_clear();// Limpiar LCD break; case0x08:lcd_cmd(0x10);// Cursor atrás lcd_data(' ');// Escribir espacio blanco lcd_cmd(0x10);// Cursor atrás break; default:lcd_data(c);// Escribir c } } // Algunas otras tareas... nop(); } } //**************************************************************************** // Extrae un dato de TXBuffer.
  • 485. // Antes de llamar se debe comprobar si hay algún dato con if(TXBufferData) //**************************************************************************** charGetFromTXBuffer(void) { charc=TXBuffer[TXOutpointer];// Extraer dato if(++TXOutpointer>=TXBufferSize)// Al pasar el límite TXOutpointer=0;// Dar la vuelta TXBufferData--;// Un dato menos TXBufferSpace++;// Un espacio más returnc;// } //**************************************************************************** // Extrae un dato de RXBuffer. // Antes de llamar se debe comprobar si hay algún dato con if(RXBufferData) //**************************************************************************** charGetFromRXBuffer(void) { charc=RXBuffer[RXOutpointer];// Extraer dato if(++RXOutpointer>=RXBufferSize)// Al pasar el límite RXOutpointer=0;// Dar la vuelta RXBufferData--;// Un dato menos RXBufferSpace++;// Un espacio más returnc;// }
  • 486. //**************************************************************************** // Ingresa un dato en TXBuffer // Antes de llamar se debe comprobar si hay espacio con if(TXBufferSpace) //**************************************************************************** voidPutToTXBuffer(chardata) { TXBuffer[TXInpointer]=data;// Ingresar dato if(++TXInpointer>=TXBufferSize)// Al pasar el límite TXInpointer=0;// Dar la vuelta TXBufferData++;// Un dato más TXBufferSpace--;// Un espacio menos } //**************************************************************************** // Ingresa un dato en RXBuffer // Antes de llamar se debe comprobar si hay espacio con if(RXBufferSpace) //**************************************************************************** voidPutToRXBuffer(chardata) { RXBuffer[RXInpointer]=data;// Ingresar dato if(++RXInpointer>=RXBufferSize)// Al pasar pasar el límite RXInpointer=0;// Dar la vuelta RXBufferData++;// Un dato más RXBufferSpace--;// Un espacio menos }
  • 487. Descripción del programa Como ves, hay dos buffers circulares, uno para transmisiones y otro para recepciones. Una vez implementados su uso es bastante simple. Solo compara esto: para enviar y recibir datos en un programa rústico se utilizan funciones como putchar para depositar un dato en el registro de transmisión, y getchar para leer del mini buffer de recepción de 2 datos. En cambio, con los buffers circulares podemos usar las funciones como PutToTXBuffer oGetFromRXBuffer para depositar/leer en/de sus “megabuffers” de transmisión y recepción. Se usan las variables como RXBufferData o TXBufferSpace para comprobar si hay datos o espacios para ellos en los buffers. Práctica: LCD serial RS232 Los LCDs seriales son muy atractivos por su fácil conexión a un microcontrolador. Los hay con interface RS232, I2C o SPI. Con solo buscar en Google verás la gran cantidad de modelos disponibles, aunque con precios que pueden desalentar a muchos. Lo sorprendente en la mayoría de los casos es que se tratan de los mismos displays LCD de interface paralela y controlador interno HD44780 que todos conocemos, solo que llevan montada una pequeña tarjeta de control basada en un microcontrolador que hace el bypass en la interface serie - paralela. Los LCDs seriales que ofrece la empresa de los PICAXE son un ejemplo de ello. Abajo se muestra un modelo que utiliza un PIC16F628 como controlador. LCD serial de PICAXE Vista del anverso y reverso del LCD serial AXE033 de Revolution Education Ltd . Si se puede conectar un AVR a una PC, dime si no se podrá conectar a otro AVR. Es tan trivial que en vez de trabajar con nuestros acostumbrados megaAVR, aprovecharemos esta oportunidad para conocer algo de la programación de los tinyAVR. Sí, en esta práctica utilizaremos los tinyAVR. Conectar dos microcontroladores puede ser útil cuando uno solo no basta ya sea quizá por la falta de algunos pines o porque se le quiere dar al segundo microcontrolador una tarea dedicada o exclusiva para que la desarrolle con la máxima eficiencia posible, o por una combinación de ambas razones, como en esta práctica.
  • 488. El circuito Una idea es tomar un LCD paralelo cualquiera y convertirlo en serial. Aunque el tamaño de su circuito resultante incomode un poco, su coste puede ser muy inferior sobre todo si el diseño será final, donde se podría escoger un AVR con los recursos mínimamente necesarios. Como el tinyAVR del circuito remoto tendrá siempre la única tarea de controlar directamente el LCD, no se escatiman los pines de interface. Así que operaremos el LCD en modo de 8 bits y comprobando el bit de Busy Flag para lograr su mejor performance. Circuito para el LCD serial (comunicación entre dos microcontroladores AVR) Código fuente de ejemplo de uso del LCD serial Como siempre, los archivos principales de cada AVR se llaman main.c. Siendo este el código de ejemplo de uso de nuestro LCD serial, puede ser compilado para cualquiermegaAVR o tinyAVR. En este caso el programa está compilado para otro tinyAVR con USART de modo que ambos programas utilizan la misma librería para el
  • 489. USART, mostrada al final. Este programa es claramente más pequeño que un programa para un LCD paralelo. Ahora pasemos a explicar cómo funciona. Como sabemos, el LCD tiene dos tipos de instrucciones: de comando y de datos de caracteres. Los datos de caracteres se envían directamente con la función putchar. Estos caracteres serán recibidos y por el otro AVR y los visualizará en la pantalla del LCD. Solo si el LCD tuviera activa la memoria CGRAM dichos datos irían allá para crear caracteres personalizados. putchar(c);// Escribir carácter Por otro lado, los datos que se envíen precedidos por el valor 0xFE serán entendidos y ejecutados por el otro AVR como instrucciones de comando, como Clear display, Set DDRAM Address, etc. Los códigos de dichos comandos son los mismos que usa el controlador interno del LCD, salvo LCD_INIT = 0x00. Usé ese valor como código personalizado al verlo libre. //**************************************************************************** // Limpia el LCD y regresa el cursor a la primera posición de la línea 1. //**************************************************************************** voidlcd_clear(void) { putchar(0xFE);// Prefijo de comando putchar(LCD_CLEAR);// Enviar instrucción 'Clear Display' } El valor 0xFE lo tomé arbitrariamente. Como consecuencia, se podrán visualizar en el LCD todos los caracteres de su tabla CGROM excepto el que tenga el código 0xFE. Dado que ese carácter corresponde a la segunda mitad de la tabla, es un “garabato” que varía de un modelo de LCD a otro, y creo que se puede vivir sin él. /****************************************************************************** * FileName: main.c * Purpose: Control de LCD serial RS232 * Processor: ATmel tinyAVR o megaAVR con USART
  • 490. * Compiler: AVR IAR C y AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. * * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta * licencia y las notas de autor y copyright de arriba. *****************************************************************************/ #include "avr_compiler.h" #include "usart.h" // Prototipos de funciones voidlcd_init(void); voidlcd_puts(char*); voidlcd_clear(void); voidlcd_gotorc(char,char); // Definiciones #define LCD_INIT 0x00 // Inicializar LCD (user command) #define LCD_CLEAR 0x01 // Limpiar Display #define LCD_LINE1 0x80 // Línea 1 posición 0 #define LCD_LINE2 0xC0 // Línea 2 posición 0 #define LCD_CURSOR 0x0E // Mostrar solo Cursor #define LCD_BLINK 0x0D // Mostrar solo Blink
  • 491. #define LCD_CURBLK 0x0F // Mostrar Cursor + Blink intmain(void) { PORTB=0x07;// Habilitar pull ups de pines PB0, PB1 y PB2 usart_init(); lcd_init(); lcd_puts("Emulacion de LCD n serial RS232 "); while(1) { if((PINB&(1<<0))==0)// Si botón de pin PB0 está pulsado... { lcd_init();// Reinicializar el LCD while((PINB&(1<<0))==0);// Esperar botón libre } elseif((PINB&(1<<1))==0)// Si botón de pin PB1 está pulsado... { lcd_clear(); lcd_puts("Programacion de n AVR y Arduino"); while((PINB&(1<<1))==0); } elseif((PINB&(1<<2))==0)// Si botón de pin PB2 está pulsado... { lcd_clear();
  • 492. lcd_puts("Web site: ncursomicros.com"); while((PINB&(1<<2))==0); } } } //**************************************************************************** // Inicializa el LCD //**************************************************************************** voidlcd_init(void) { delay_us(100);// Para que el otro AVR complete su inicialización putchar(0xFE);// Enviar prefijo de comando putchar(LCD_INIT);// Enviar comando (personalizado) delay_us(40000);// Tiempo de inicialización del LCD } //**************************************************************************** // Envía cadenas ROM terminadas en null al LCD. //**************************************************************************** voidlcd_puts(char*s) { unsignedcharc,i=0; while(c=s[i++]){ if(c=='n') lcd_gotorc(2,1);// Ir a línea 2
  • 493. else putchar(c);// Escribir carácter } } //**************************************************************************** // Limpia el LCD y regresa el cursor a la primera posición de la línea 1. //**************************************************************************** voidlcd_clear(void) { putchar(0xFE);// Prefijo de comando putchar(LCD_CLEAR);// Enviar instrucción 'Clear Display' } //**************************************************************************** // Ubica el cursor del LCD en la columna c de la línea r. //**************************************************************************** voidlcd_gotorc(charr,charc) { if(r==1)r=LCD_LINE1; elser=LCD_LINE2; putchar(0xFE);// Prefijo de comando putchar(r+c-1);// Enviar instrucción 'Set DDRAM Address' } Código fuente del AVR controlador del LCD
  • 494. La mitad del código tiene funciones para manejar el LCD en bajo nivel y no vamos a detallar en este momento cómo funciona. Todo lo relacionado a ese tema ya lo estudiamos en el capítulo el display LCD. El resto del programa se encarga de gestionar la recepción de datos del USART. En este programa el uso de interrupciones y del buffer circular es vital e imprescindible. Este AVR debe recibir todos los datos posibles del otro AVR porque no tiene forma de decirle “espérame, que estoy ocupado ejecutando la instrucción anterior”. El tamaño del buffer circular debería ser el máximo posible. Puesto que este programa está escrito para ser compilado para un tinyAVR con USART y no para nuestros conocidos ATmegaXX4 o ATmegaXX8, las rutinas relacionadas con el USART varían ligeramente, pues los nombres de los registros del USART varían ligeramente. Programar el USART de los tinyAVR es casi idéntico a hacerlo con los USART de los megaAVR, solo cambian los nombres de sus registros y del Vector de Interrupción. Hablaremos de sus registros en la siguiente sección y en cuanto al Vector de Interrupción de Recepción Completada, debo aclarar que, aunque el datasheet sugiere que se debería llamar USART0_RX_vect, solo el compilador AVR IAR C lo define de esa manera. En el archivo de dispositivo del compilador AVR GCC en cambio este Vector se define comoUSART_RX_vect. La macro #define usada en el código reconocerá de qué forma está definido el Vector de interrupción. //**************************************************************************** // Gestor de interrupciones (Interrupción de Recepción Completada). // La interrupción de recepción se dispara cuando haya llegado algún dato. //**************************************************************************** #ifdef USART0_RX_vect ISR(USART0_RX_vect) #else ISR(USART_RX_vect) #endif { charc=UDR;// Leer dato if(BufferSpace)// Si hay espacio en Buffer
  • 495. PutToBuffer(c); else// Buffer está lleno { Inpointer=0;// Código para Buffer lleno Outpointer=0;// ... BufferData=0; BufferSpace=BufferSize; PutToBuffer(c); UDR='F'; } } /****************************************************************************** * FileName: main.c * Purpose: LCD serial RS232 + Buffer circular * Processor: ATmel tinyAVR con USART * Compiler: AVR IAR C y AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. * * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta * licencia y las notas de autor y copyright de arriba. *****************************************************************************/
  • 496. #include "avr_compiler.h" #include "usart.h" //**************************************************************************** // Prototipos de función //**************************************************************************** charGetFromBuffer(void); voidPutToBuffer(char); voidlcd_init(void); voidlcd_cmd(char); voidlcd_data(char); voidlcd_write(char,char); charlcd_read(char); //**************************************************************************** // CONFIGURACIÓN DE LOS PINES DE INTERFACE //**************************************************************************** /* Define el puerto a donde se conectará el bus de datos del LCD * Se utilizará el nible alto del puerto escogido (ejem. PB4-DB4,...,PB7-DB7) */ #define lcd_DATAout PORTB #define lcd_DATAin PINB #define lcd_DATAddr DDRB // Registro PORT del puerto // Registro PIN del puerto // Registro DDR del puerto
  • 497. /* Define el puerto a donde se conectarán las líneas de control del LCD * E, RW y RS. Puede ser el mismo puerto del bus de datos. */ #define lcd_CTRLout PORTD #define lcd_CTRLin PIND // Registro PORT del puerto // Registro PIN del puerto #define lcd_CTRLddr DDRD // Registro DDR del puerto /* Define los números de los pines del puerto anterior que corresponderán a * las líneas E, RW y RS del LCD. */ #define lcd_E 6 #define lcd_RW #define lcd_RS // Pin Enable 5 4 // Pin Read/Write // Pin Register Select // Definiciones y Variables globales #define LCD_INIT 0x00 #define BufferSize 70 // Comando personalizado para inicializar LCD // Tamaño del buffer circular // Variables globales volatilecharBuffer[BufferSize];// Buffer circular volatileunsignedcharInpointer=0; volatileunsignedcharOutpointer=0; volatileunsignedcharBufferData=0; volatileunsignedcharBufferSpace=BufferSize; voiddelay_ms(unsignedintu){
  • 498. while(u--) delay_us(1000); } //**************************************************************************** // Gestor de interrupciones (Interrupción de Recepción Completada). // La interrupción de recepción se dispara cuando haya llegado algún dato. //**************************************************************************** #ifdef USART0_RX_vect ISR(USART0_RX_vect) #else ISR(USART_RX_vect) #endif { charc=UDR;// Leer dato if(BufferSpace)// Si hay espacio en Buffer PutToBuffer(c); else// Buffer está lleno { Inpointer=0;// Código para Buffer lleno Outpointer=0;// ... BufferData=0; BufferSpace=BufferSize; PutToBuffer(c); UDR='F';
  • 499. } } //**************************************************************************** intmain(void) { charc; charpf=0;// Flag de prefijo de comando usart_init();// Inicializar USART0 @ 9600-8N1 /* Habilitar la 'Interrupción de Recepción Completada' del USART */ UCSRB|=(1<<RXCIE); sei();// Setear bit I de SREG lcd_init();// Inicializat LCD while(1) { if(BufferData)// Si hay datos en Buffer { c=GetFromBuffer();// Leer if((pf==0)&&(c==0xFE))// { pf=1; }
  • 500. elseif(pf==1) { if(c==LCD_INIT)// Comando personalizado lcd_init(); else// Comando estándar lcd_cmd(c); pf=0; } else { lcd_data(c); } } } } //**************************************************************************** // Extrae un dato de Buffer. // Antes de llamar se debe comprobar si hay algún dato con if(BufferData) //**************************************************************************** charGetFromBuffer(void) { charc=Buffer[Outpointer];// Extraer dato if(++Outpointer>=BufferSize)// Al pasar el límite Outpointer=0;// Dar la vuelta BufferData--;// Un dato menos
  • 501. BufferSpace++;// Un espacio más returnc;// } //**************************************************************************** // Ingresa un dato en Buffer // Antes de llamar se debe comprobar si hay espacio con if(BufferSpace) //**************************************************************************** voidPutToBuffer(chardata) { Buffer[Inpointer]=data;// Ingresar dato if(++Inpointer>=BufferSize)// Al pasar pasar el límite Inpointer=0;// Dar la vuelta BufferData++;// Un dato más BufferSpace--;// Un espacio menos } //**************************************************************************** // Ejecuta la inicialización software completa del LCD. La configuración es: // Interface de 8 bits, despliegue de 2 líneas y caracteres de 5x7 puntos. //**************************************************************************** voidlcd_init(void) { /* Configurar las direcciones de los pines de interface del LCD */ lcd_DATAddr=0xFF;
  • 502. lcd_CTRLddr|=(1<<lcd_E)|(1<<lcd_RW)|(1<<lcd_RS); /* Secuencia de inicialización del LCD en modo de 8 bits*/ lcd_CTRLout&=~((1<<lcd_E)|(1<<lcd_RW)|(1<<lcd_RS)); delay_ms(45);// > 40 ms lcd_write(0x30,0);// Function Set: 8-bit delay_ms(5);// > 4.1 ms lcd_write(0x30,0);// Function Set: 8-bit delay_ms(1);// > 100 µs lcd_write(0x30,0);// Function Set: 8-bit delay_ms(1);// > 40 µs lcd_write(0x38,0);// Function Set: 8-bit, 2lines, 4×7font lcd_cmd(0x0C);// Display ON/OFF Control: lcd_cmd(0x01);// Clear Display lcd_cmd(0x06);// Entry Mode Set } //**************************************************************************** // Envían instrucciones de comando y de datos al LCD. //**************************************************************************** voidlcd_cmd(charcmd) { while(lcd_read(0)&0x80)// Mientras LCD ocupado continue;// esperar lcd_write(cmd,0); }
  • 503. voidlcd_data(chardata) { while(lcd_read(0)&0x80)// Mientras LCD ocupado continue;// esperar lcd_write(data,1); } //**************************************************************************** // Escribe una instrucción en el LCD: // Si RS = 0 la instrucción es de comando (Function Set, Entry Mode set, etc). // Si RS = 1 la instrucción es de dato y va a la DDRAM/CGRAM. // El LCD debe estar libre antes de llamar esta función. //**************************************************************************** voidlcd_write(chardata,charRS) { if(RS) lcd_CTRLout|=(1<<lcd_RS);// Para escribir en DDRAM o CGRAM else lcd_CTRLout&=~(1<<lcd_RS);// Para escribir en Registro de Comandos delay_us(5);// Permite actualizar el Puntero de RAM lcd_CTRLout&=~(1<<lcd_RW);// Establecer Modo de escritura lcd_DATAddr=0xFF;// Puerto para salida lcd_DATAout=data;// Colocar dato delay_us(1);// tAS, set-up time > 140 ns lcd_CTRLout|=(1<<lcd_E);// Pulso de Enable
  • 504. delay_us(2);// Enable pulse width > 450 ns lcd_CTRLout&=~(1<<lcd_E);// } //**************************************************************************** // Lee un byte de dato del LCD. // Si RS = 1 se lee la locación de DDRAM/CGRAM. // Si RS = 0 se lee el 'bit de Busy Flag' + el 'Puntero de RAM'. //**************************************************************************** charlcd_read(charRS) { chardata; if(RS) lcd_CTRLout|=(1<<lcd_RS);// Para leer de DDRAM o CGRAM else lcd_CTRLout&=~(1<<lcd_RS);// Para leer Busy Flag + Puntero de RAM lcd_CTRLout|=(1<<lcd_RW);// Establecer Modo Lectura lcd_DATAddr=0x00;// Puerto para entrada delay_us(2);// tAS, set-up time > 140 ns lcd_CTRLout|=(1<<lcd_E);// Habilitar LCD delay_us(2);// Data Delay Time > 1320 ns data=lcd_DATAin;// Leer dato lcd_CTRLout&=~(1<<lcd_E);// returndata;// }
  • 505. Códigos fuente de la librería para el USART Habíamos dicho al iniciar el capítulo que los USART de todos AVR son muy parecidos, incluso si son de otra familia. En el caso concreto del ATtiny2313A lo único que tenemos que cambiar en nuestra librería del USART para los megaAVR son los nombres de sus registros. Puesto que los conocidos ATmegaXX8 tienen dos USART (USART0 y USART1), es lógico entender que aparezcan los distintivos 0 y 1 en todos sus registros. Incluso en los ATmegaXX8, que solo tienen un USART (USART0), sus registros también se identifican por llevar el 0, por cuestiones de compatibilidad. En cambio el único USART del ATtiny2313A se llama simplemente USART, a secas, y sus registros del mismo modo se escriben sin 0 ni nada. Observa a continuación las semejanzas. Registros de los ATmegaXX4 y ATmegaXX8. Registro UCSR0A UCSR0A RXC0 TXC0 UDRE0 FE0 DOR0 UPE0 U2X0 MPCM0 RXCIE0 TXCIE0 UDRIE0 RXEN0 TXEN0 UCSZ02 RXB80 TXB80 UPM00 USBS0 UCSZ01 UCSZ00 UCPOL0 UPE U2X MPCM Registro UCSR0B UCSR0B Registro UCSR0C UCSR0C UMSEL01 UMSEL00 UPM01 Registro UDR0 UDR0 Registro UBRR0H UBRR0H Registro UBRR0L UBRR0L Registros del ATtiny2313A. Registro UCSR0A UCSRA RXC TXC UDRE FE DOR
  • 506. Registro UCSR0B UCSRB RXCIE TXCIE UDRIE RXEN TXEN UCSZ2 RXB8 TXB8 UMSEL0 UPM1 UPM0 USBS UCSZ1 UCSZ0 UCPOL Registro UCSR0C UCSRC UMSEL1 Registro UDR0 UDR Registro UBRR0H UBRRH Registro UBRR0L UBRRL Oh! hay un pequeño detalle que olvidé mencionar: A diferencia de los ATmegaXX8 yATmegaXX4, los registros de Baud rate, UBRRH y UBRRL, en el ATtiny2313A no ocupan posiciones contiguas en el espacio de los registros de E/S y por tanto el compilador no los puede juntar para tratarlos como un solo registro de 16 bits. Por eso en la rutina deconfiguración de baud rate ambos registros se cargan por separado. /****************************************************************************** /* Configurar baud rate */ UCSRA|=(1<<U2X); UBRRH=(F_CPU/(8*USART_BAUD)-1)>>8; UBRRL=(F_CPU/(8*USART_BAUD)-1); ... /****************************************************************************** * FileName: usart.h * Overview: Macros y prototipos de funciones para el USART en modo Asíncrono * Processor: ATmel tinyAVR con USART
  • 507. * Compiler: AVR IAR C y AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. * * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta * licencia y la nota de autor de arriba. *****************************************************************************/ #include "avr_compiler.h" /* Define la velocidad de transmisión del USART (en bits/segundo), si aún no * ha sido definida. */ #ifndef USART_BAUD #define USART_BAUD 9600UL #endif /* Usar kbhit para ver si hay algún dato en el buffer de recepción antes de * llamar directamente a la función getchar para evitar esperas innecesarias. */ #define kbhit() (UCSRA & (1<<RXC)) /* Descomentar el siguiente #define para que la funcion 'getchar' haga eco de * los caracteres recibidos.
  • 508. */ //#define __GETCHAR_ECHO__ /* Macros para AVR GCC */ #if defined( __GNUC__ ) #ifdef putchar #undef putchar #endif #ifdef getchar #undef getchar #endif #endif /* Definiciones de funciones */ voidusart_init(void); intputchar(int); intgetchar(void); /****************************************************************************** * FileName: usart.c * Overview: Implementa funciones para el USART en modo Asíncrono * Processor: ATmel tinyAVR con USART * Compiler: AVR IAR C y AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
  • 509. * * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta * licencia y la nota de autor de arriba. *****************************************************************************/ #include "usart.h" #if defined( __GNUC__ ) FILE_stream=FDEV_SETUP_STREAM((int(*)(char,FILE*))putchar,/* function to write one char to device */ (int(*)(FILE*))getchar,/* function to read one char from device */ _FDEV_SETUP_RW);/* flags, see below */ #endif //**************************************************************************** // Inicializa el USART0. //**************************************************************************** voidusart_init(void) { /* Configurar baud rate */ UCSRA|=(1<<U2X); UBRRH=(F_CPU/(8*USART_BAUD)-1)>>8; UBRRL=(F_CPU/(8*USART_BAUD)-1); /* Configurar modo de operación Asíncrono y formato de frame a
  • 510. * 8 bits de datos, 1 bit de stop y sin bit de paridad. */ UCSRC=(1<<UCSZ1)|(1<<UCSZ0); /* Habilitar módulos Receptor y Transmisor */ UCSRB=(1<<RXEN)|(1<<TXEN); /* Para el compilador AVR GCC, asociar las funciones 'putchar' y 'getchar' * con las funciones de entrada y salida (como printf, scanf, etc.) de la * librería 'stdio' */ #if defined( __GNUC__ ) // fdevopen((int (*)(char, FILE*))putchar, (int (*)(FILE*))getchar); stdin=stdout=&_stream; #endif } //**************************************************************************** // Transmite el byte bajo de 'dato' por el USART //**************************************************************************** intputchar(intdato) { /* Esperar a que haya espacio en el buffer de transmisión */ while((UCSRA&(1<<UDRE))==0); /* Colocar dato en el buffer de transmisión */ UDR=dato; returndato;
  • 511. } //**************************************************************************** // Recibe un byte de dato del USART //**************************************************************************** intgetchar(void) { /* Esperar a que haya al menos un dato en el buffer de recepción */ while((UCSRA&(1<<RXC))==0); /* Leer y retornar el dato menos reciente del buffer de recepción */ #if defined ( __GETCHAR_ECHO__ ) return(putchar(UDR)); #else returnUDR; #endif } Práctica: Teclado serial RS232 Conectar dos microcontroladores puede ser útil cuando uno solo no basta ya sea quizá por la falta de algunos pines o porque se le quiere dar al segundo microcontrolador una tarea dedicada o exclusiva para que la desarrolle con la máxima eficiencia posible, o por una combinación de ambas razones, como en esta práctica. El circuito
  • 512. Circuito para el teclado serial (comunicación entre dos microcontroladores AVR). Los códigos fuente Los siguientes dos programas utilizan la misma librería del usart usada en la práctica anterior. Código fuente del AVR del teclado. /****************************************************************************** * FileName: main.c * Purpose: Control de Teclado mediante Interrupciones PCINTx * Processor: AVR ATtiny2313A
  • 513. * Compiler: AVR IAR C & AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. * * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta * licencia y las notas de autor y copyright de arriba. *****************************************************************************/ #include "avr_compiler.h" #include "usart.h" //**************************************************************************** // Configuración del puerto de interface //**************************************************************************** #define kpd_PORT PORTB // Port write #define kpd_PIN PINB // Port read #define kpd_DDR DDRB // Dirección de puerto //**************************************************************************** // Prototipos de funciones //**************************************************************************** charkeypad_read(void);// Retorna el valor ASCII de la tecla presionada // o retorna 0x00 si no se presionó nada voidkeypad_released(void);// Espera hasta que el teclado esté libre
  • 514. charkeypad_scan(void);// Escanea el teclado voidSetupInt(void); //**************************************************************************** // Interrupt Service Routine // Esta función se ejecuta cuando se detectan cambios de nivel (flancos de // subida o de bajada) en los pines PB4...PB7. // Nota: // Según el datasheet del ATtiny2313A, el Vector de Interrupción para las // 'Interrupciones de Cambio de Pin' debería ser PCIN0_vect. // Sin embargo, para el compilador AVR IAR C PCIN1_vect // y para el compilador AVR GCC es PCINT_B_vect //**************************************************************************** #ifdef PCINT_B_vect ISR(PCINT_B_vect) #else ISR(PCIN1_vect) #endif { chardato=keypad_read();// Leer teclado if(dato)// Si fue tecla válida { putchar(dato); /* Esperar a que el teclado quede libre */
  • 515. keypad_released(); } SetupInt(); } //**************************************************************************** // Función principal //**************************************************************************** intmain(void) { usart_init();// Setup USART0 @ 9600-8N1 // puts("rn Control de Teclado mediante Interrupciones PCINTx rrn"); SetupInt(); /* Habilitar las interrupciones PCINT de PORTB en los pines PB4...PH7 */ GIMSK=(1<<PCIE);////////// PCMSK=0xF0;////////// sei();// Habilitación general de interrupciones while(1)// Bucle infinito { /* Entrar en modo sleep (Power-Down mode) */ MCUCR=(1<<SM0)|(1<<SE);///// sleep();
  • 516. } } //**************************************************************************** // Prepara el puerto B para que detecte un cambio de tensión producido al // presionar una tecla. //**************************************************************************** voidSetupInt(void) { /* Configurar PORTB: Nibble de Rows salida, Nibble de Rows bajo * nible alto entrada y habilitar pull-ups de nibble alto */ PORTB=0xF0; DDRB=0x0F; } //**************************************************************************** // Escanea el teclado y retorna el valor ASCII de la tecla presionada por // al menos 25ms. En otro caso retorna 0x00. //**************************************************************************** charkeypad_read(void) { charc1,c2; c1=keypad_scan();// Escanear teclado if(c1)// Si hubo alguna tecla pulsada { delay_us(25000);// Delay antirrebote
  • 517. c2=keypad_scan();// Escanear otra vez if(c1==c2)// Si Ambas teclas leídas son iguales returnc2;// entonces aceptarla } return0x00; } //**************************************************************************** // Espera hasta que el teclado quede libre. //**************************************************************************** voidkeypad_released(void) { delay_us(10);// while(keypad_scan())// Mientras se detecte alguna tecla pulsada continue;// seguir escaneando. } //**************************************************************************** // Escanea el teclado y retorna el valor ASCII de la primera tecla que // encuentre pulsada. De otro modo retorna 0x00. //**************************************************************************** charkeypad_scan(void) { unsignedcharCol,Row; charRowMask,ColMask; // Col0 Col1 Col2 Col3
  • 518. conststaticcharkeys[]={'7','8','9','A',// Row 0 '4','5','6','B',// Row 1 '1','2','3','C',// Row 2 '.','0','#','D'};// Row 3 kpd_DDR=0x0F;// Nibble alto entrada, nibble bajo salida kpd_PORT=0xF0;// Habilitar pull-ups del nibble alto RowMask=0xFE;// Inicializar RowMask a 11111110 for(Row=0;Row<4;Row++) { kpd_PORT=RowMask;// delay_us(10);// Para que se estabilice la señal ColMask=0x10;// Inicializar ColMask a 00010000 for(Col=0;Col<4;Col++) { if((kpd_PIN&ColMask)==0)// Si hubo tecla pulsada { kpd_DDR=0x00;// Todo puerto entrada otra vez returnkeys[4*Row+Col];// Retornar tecla pulsada } ColMask<<=1;// Desplazar ColMask para escanear }// siguiente columna
  • 519. RowMask<<=1;// Desplazar RowMask para escanear RowMask|=0x01;// siguiente fila } // Se llega aquí si no se halló ninguna tecla pulsada kpd_DDR=0x00;// Todo puerto entrada otra vez return0x00;// Retornar Código de no tecla pulsada } Esto sería un ejemplo de uso del teclado serial. /****************************************************************************** * FileName: main.c * Purpose: Control de teclado matricial serie de 4x4 * Processor: ATmel tinyAVR o megaAVR con USART * Compiler: AVR IAR C & AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. * * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta * licencia y las notas de autor y copyright de arriba. *****************************************************************************/ #include "avr_compiler.h" #include "usart.h"
  • 520. intmain(void) { usart_init();// Inicializar USART @ 9600 N 1 puts("rn Control de Teclado serie de 4x4 rn"); while(1) { if(kbhit())// Sí llegó un dato del teclado putchar(getchar());// Leerlo y Enviarlo a la PC } } Introducción Por más que actualmente nuestro alrededor esté repleto por la tecnología digital, sabemos que el mundo no nació así y que hay cosas que tampoco van a cambiar. No podemos cambiar la naturaleza analógica de los fenómenos naturales como la presión, la temperatura, la luminosidad, la electricidad, el magnetismo, etc. El transductor elemental que se utiliza para digitalizar las señales de estos fenómenos es el Conversor Analógico a Digital, ADC, que convierte una tensión eléctrica en un valor numérico. De algún modo, cualquier otra señal puede llegar a manifestarse eléctricamente, de allí a tensión eléctrica, y la tenemos. Así es como funcionan por ejemplo los sensores de luz, de temperatura (de calor), etc. Conceptos Básicos Antes de entrar de lleno en la programación del conversor ADC de los AVR vamos a conocer algunos conceptos que nos ayudarán para no perdernos en la teoría. Puedes saltearte este apartado si deseas pero presiento que en algún momento regresarás Resolución y Voltajes de Referencia del ADC
  • 521. Un conversor ADC es un circuito que toma valores analógicos de tensión y los convierte en códigos binarios. Los valores que definen los límites de las tensiones a medir se denominan voltajes de referencia y se representan por Vref- (el mínimo) y Vref+ (el máximo). La resolución del conversor queda determinada por la cantidad de bits que representan el resultado de la conversión. Así, se pueden encontrar conversores de 8 bits, de 12 bits, etc. Un ADC de n bits puede representar hasta valores digitales, de modo que a la entrada analógica igual a Vref- le asignará el 0 digital y la entrada igual a Vref+ le asignará el digital. A los otros valores analógicos se les asignará los otros valores digitales distribuidos equidistantemente. Entre Vref- y Vref+ se pueden concebir infinitos valores analógicos, pero con n bits solo se pueden formar valores discretos diferentes. Por lo tanto habrá valores analógicos que no podrán ser representados con exactitud. La diferencia entre dos valores analógicos correspondientes a dos valores digitales consecutivos se define como resolución de voltaje de ADC. Por ejemplo, en un ADC 10 bits con Vref- = 0 V y Vref+ = 5V, la resolución alcanzada será de (5-0)/1023 = 4.88 mV. Significa que el máximo error posible será de 4.88/2 = 2.44 mV. Es poco usual encontrar aplicaciones donde Vref- sea diferente de GND = 0V y dondeVref+ sea diferente de VCC = 5V. En estas condiciones se puede aplicar una regla de tres para deducir que una entrada analógica Vin cualquiera (entre 0 y Vref+) será convertida en un valor numérico que se puede calcular con la siguiente fórmula: Por más que actualmente nuestro alrededor esté repleto por la tecnología digital, sabemos que el mundo no nació así y que hay cosas que tampoco van a cambiar. No podemos cambiar la naturaleza analógica de los fenómenos naturales como la presión, la temperatura, la luminosidad, la electricidad, el magnetismo, etc. El transductor elemental que se utiliza para digitalizar las señales de estos fenómenos es el Conversor Analógico a Digital, ADC, que convierte una tensión eléctrica en un valor numérico. De algún modo, cualquier otra señal puede llegar a manifestarse eléctricamente, de allí a tensión eléctrica, y la tenemos. Así es como funcionan por ejemplo los sensores de luz, de temperatura (de calor), etc.
  • 522. Conceptos Básicos Antes de entrar de lleno en la programación del conversor ADC de los AVR vamos a conocer algunos conceptos que nos ayudarán para no perdernos en la teoría. Puedes saltearte este apartado si deseas pero presiento que en algún momento regresarás Resolución y Voltajes de Referencia del ADC Un conversor ADC es un circuito que toma valores analógicos de tensión y los convierte en códigos binarios. Los valores que definen los límites de las tensiones a medir se denominan voltajes de referencia y se representan por Vref- (el mínimo) y Vref+ (el máximo). La resolución del conversor queda determinada por la cantidad de bits que representan el resultado de la conversión. Así, se pueden encontrar conversores de 8 bits, de 12 bits, etc. Un ADC de n bits puede representar hasta valores digitales, de modo que a la entrada analógica igual a Vref- le asignará el 0 digital y la entrada igual a Vref+ le asignará el digital. A los otros valores analógicos se les asignará los otros valores digitales distribuidos equidistantemente. Entre Vref- y Vref+ se pueden concebir infinitos valores analógicos, pero con n bits solo se pueden formar valores discretos diferentes. Por lo tanto habrá valores analógicos que no podrán ser representados con exactitud. La diferencia entre dos valores analógicos correspondientes a dos valores digitales consecutivos se define como resolución de voltaje de ADC. Por ejemplo, en un ADC 10 bits con Vref- = 0 V y Vref+ = 5V, la resolución alcanzada será de (5-0)/1023 = 4.88 mV. Significa que el máximo error posible será de 4.88/2 = 2.44 mV. Es poco usual encontrar aplicaciones donde Vref- sea diferente de GND = 0V y dondeVref+ sea diferente de VCC = 5V. En estas condiciones se puede aplicar una regla de tres para deducir que una entrada analógica Vin cualquiera (entre 0 y Vref+) será convertida en un valor numérico que se puede calcular con la siguiente fórmula: El ADC de aproximaciones sucesivas
  • 523. Casi todos los módulos ADC de los microcontroladores son de aproximaciones sucesivas. Funcionan con cuatro elementos básicos: un comparador analógico, una lógica de control, un conversor digital analógicoDAC y el reloj que guía los pasos de la conversión. Los DAC son mucho más simples que los ADC y entregan resultados casi de inmediato. Cada número binario generado va siendo convertido en una tensión analógica Vout que luego se compara con la tensión de entrada que queremos medir Vin. Si son iguales (o los más cercanos posible), ¡eureka! Es el número binario que corresponde a Vin. Así de simple. Diagrama de básico de un conversor ADC de aproximaciones sucesivas. Ahora bien, siendo el conversor mostrado de 10 bits y pudiéndose generar hasta 1024 números binarios distintos, ¿se tendrán que hacer 1024 comparaciones? No, solo 10. El algoritmo a seguir es similar al que responde a la clásica pregunta capciosa: Si entre 100 bolitas hay solo una que pesa un poco más que las otras, ¿cuántas veces habrá que llevarlas a una balanza para encontrarla? (rpta: 6) Para entender mejor cómo funciona este ADC vamos a imitar su operación. Supongamos que el ADC trabaja con tensiones de referencia de 0 V y 5 V y que queremos medir una señal Vin de 4.00000 Volts. Como el ADC es de 10 bits, dará los 10 pasos mostrados en la siguiente tabla: Tabla Paso # Paso # Binario Generado D9 ... Vout (DAC) D0 (Voltios) Vin (ADC) (Voltios) ¿Vout > Vin ? 1 10 0000 0000 2.50244 4.00000 Sí, queda D9 2 11 0000 0000 3.75366 4.00000 Sí, queda D8
  • 524. Tabla Paso # Paso # Binario Generado D9 ... Vout (DAC) D0 (Voltios) Vin (ADC) (Voltios) ¿Vout > Vin ? 3 11 1000 0000 4.37927 4.00000 No, limpiar D7 4 11 0100 0000 4.06647 4.00000 No, limpiar D6 5 11 0010 0000 3.91000 4.00000 Sí, queda D5 6 11 0011 0000 3.98826 4.00000 Sí, queda D4 7 11 0011 1000 4.02737 4.00000 No, limpiar D3 8 11 0011 0100 4.00782 4.00000 No, limpiar D2 9 11 0011 0010 3.99804 4.00000 Sí, queda D1 10 11 0011 0011 4.00293 4.00000 No, limpiar D0 Valor final 11 0011 0010 3.99804 --- --- El primer número binario generado tiene el bit D9 seteado. Este número se convierte en el valor analógico Vout, que después se compara con Vin. Como la comparación (Vout < Vin) es positiva nos quedamos con este bit. Después se prueba seteando el siguiente bit, D8, y como ahora la evaluación (Vout < Vin) sigue siendo afirmativa también nos quedamos con este bit. En seguida se setea el bit D7; ahora la evaluación (Vout < Vin) es negativa y debemos limpiar D7. Y se sigue con el resto de manera similar hasta alcanzar el bit D0. Como vemos, al final nos quedamos con el valor 11 0011 0010, que significa una tensión de 3.99804 V y que comparado con nuestros 4.00000 V nos da un error de 0.00196 V = 1.96 mV ó de 0.049%. Nada mal. Es fácil ver que a mayor resolución en bits habrá más aproximación. El Módulo ADC de los AVR
  • 525. Los AVR de la serie megaAVR tienen un ADC de aproximaciones sucesivas de 10 bits. Es uno solo pero está multiplexado para dar cabida hasta a 8 entradas analógicas. Se convierte solo una entrada o la diferencia de dos entradas analógicas a la vez. Concretamente, los AVR de la serie ATmegaXX4 cuentan con 8 canales de conversión, todos ubicados en su puerto A. Por otro lado los AVR de la serie ATmegaXX8 destinan todo su puerto C como canales de entrada del ADC. El detalle con los ATmegaXX8 es que su puerto C solo tiene 6 pines si vienen en empaque DIP pero tiene 8 pines en los empaques TQFP y QFN/MLF. De modo que estos megaAVR pueden tener 6 u 8 canales de conversión dependiendo de su empaque. Operación del Módulo ADC En las aplicaciones ordinarias solo los registros ADCSRA y ADMUX son los que se manipulan activamente. Los registros ADCH y ADCL son de solo lectura y como no tienen formato, basta con recordar sus nombres. ADCSRA. Es el principal registro de control y estado del ADC. Manipulando los bits de este registro iniciamos la conversión, establecemos la velocidad de conversión o elegimos el formato del resultado de la conversión. Veremos los detalles en adelante. ADCSRB. Es el segundo registro de control y estado del ADC. Sus pocos bits funcionales configuran el modo de conversiones automáticas y como ese modo es raramente usado, el registro ADCSRB tampoco está muy avistado en los programas. Los viejos AVR no lo tienen. ADCH y ADCL. Son los registros que almacenan el resultado de la conversión. Uno guarda 8 bits y el otro los dos bits restantes. La forma como se distribuyen se debe especificar con el bit ADLAR del registro ADMUX. Para cuestiones de programación estos registros se fusionan para formar un único registro de 16 bits normalmente llamado ADC o ADCW. ADMUX. Es el registro que selecciona el canal de conversión y establece losvoltajes de referencia. DIDR0. Es también un nuevo registro no disponible en los viejos AVR. Su función es desconectar los pines seleccionados como canales latentes del conversor para así evitar que se desgaste corriente en parte del circuito del ADC. Registro ADCSRA ADCSRA ADEN ADSC ADATE ADIF ADIE --- ACME --- --- --- ADPS2 ADPS1 ADPS0 ADTS1 ADTS0 Registro ADCSRB ADCSRB ADTS2
  • 526. Registro ADMUX ADMUX REFS1 REFS0 ADLAR MUX4 MUX3 MUX2 MUX1 MUX0 ADC7D ADC6D ADC5D ADC4D ADC3D ADC2D ADC1D ADC0D Registro DIDR0 DIDR0 Registro ADCH ADCH Registro ADCL ADCL El ADC de los megaAVR es bastante fácil de controlar si se le emplea en su modo habitual de conversiones normales de entrada única. Tras configurar este modo, se escoge un canal a la vez y se setea el bit ADSC para iniciar la conversión. Después esperamos a que este mismo bit se limpie automáticamente por hardware como señal de que la conversión terminó, y cuando lo haga podremos leer el resultado. Eso sería todo. El proceso descrito se puede desglosar en los siguientes pasos. Los pasos 2 al 5 (que están relacionados con el registro ADMUX) no necesariamente tienen que ir en ese orden de hecho se pueden juntar todos en un solo paso. Esto es porque el registro ADMUXtrabaja con un buffer de respaldo del cual es actualizado continuamente para asegurar un buen trabajo del ADC. Esto es, cuando escribimos en ADMUX en realidad escribimos en su buffer, el cual se copia continuamente al registro ADMUX hasta que se inicia la conversión. En ese momento el copiado continuo se detiene y se reanudará cuando termine la conversión. De ese modo se evitan los resultados 'frankenstenianos' de lo que sería un mal uso del registro ADMUX. Seleccionar el reloj del conversor ADC, con los bits ADPS2:ADPS0. Seleccionar los voltajes de referencia del conversor, usando los bits REFS1 yREFS0. Establecer la justificación del resultado con el bit ADLAR. Seleccionar el canal o los canales de entrada del ADC, con los bits MUX4:MUX0. Encender el módulo ADC, seteando el bit ADEN. Iniciar la conversión, seteando el bit ADSC. Esperar a que termine la conversión. Cuando esto pase el flag ADIF se pondrá a uno y si la conversión es normal y el bit ADSC se limpiará automáticamente. Leer el resultado de la conversión del par de registros ADCH: ADCL.
  • 527. Para bien o para mal, la flexibilidad del ADC complica aún más su control, pues también existen el modo de conversiones diferenciales y el modo de conversiones auto-disparadas, el cual se puede aplicar para cada caso, es decir, el ADC puede desdoblar su operación hasta en cuatro modos. Señales para iniciar conversiones normales y auto-disparadas. Conversiones normales de entrada única, donde el ADC convierte el valor de un canal, cada vez que se setee el bit ADSC. Conversiones normales de entradas diferenciales, donde el ADC convierte la diferencia de dos canales. La conversión se inicia seteando el bit ADSC. Conversiones auto-disparadas de entrada única, donde el ADC convierte un canal cuando se produzca alguno de los eventos seleccionados por los bits ADTS2,ADTS1 y ADTS0, que puede ser, por ejemplo, el desbordamiento de un Timer o simplemente la activación del flag ADIF. Este último caso es peculiar y se conoce como modo de conversiones de corrida libre, porque el ADC realiza conversiones una tras otra sin cesar. Aquí la primera conversión se inicia seteando el bit ADSC. Conversiones auto-disparadas de entradas diferenciales. Es una combinación de los dos modos anteriores. No hay una forma explícita de establecer cada uno de estos cuatro modos de operación del ADC. Si la conversión será de entrada única o diferencial será resultado de configurar el multiplexor del ADC con los bits MUX4...MUX0. Por defecto las conversiones seránnormales. Si queremos que se disparen automáticamente habrá que configurarlas con los bits ADTS2, ADTS1 y ADTS0 del registro ADCSRB, siempre que el bit ADATE de ADCSRA valga uno. Tabla ADTS2
  • 528. ADTS2 ADTS1 ADTS0 Fuente de disparo 0 0 0 Modo de Corrida Libre 0 0 1 Comparador Analógico 0 1 0 Interrupción Externa INT0 0 1 1 Coincidencia del Timer/Counter0 1 0 0 Desbordamiento del Timer/Counter0 1 0 1 Coincidencia B del Timer/Counter1 1 1 0 Desbordamiento del Timer/Counter1 1 1 1 Evento de Captura del Timer/Counter1 Selección del Canal de Conversión Las 8 entradas analógicas del ADC son los pines del puerto A de los AVR que tienen 4 puertos o más. En los AVR de 3 puertos son los pines del puerto C; estos AVR tienen 6 entradas analógicas si vienen en empaque PDIP u 8 si vienen en empaque TQFP, QFP o MLF. Las dos entradas adicionales son pines independientes, que no forman parte de ningún puerto. El ADC solo puede tomar una o dos entradas analógicas por conversión, así que cada vez que se desee obtener un valor analógico externo se debe seleccionar previamente dicha entrada analógica mediante los bits MUX4, MUX3, MUX2, MUX1 y MUX0 del registro ADMUX. Registro ADMUX ADMUX REFS1 REFS0 ADLAR MUX4 MUX3 MUX2 MUX1 MUX0 La siguiente tabla muestra todas las opciones posibles que se pueden obtener. Parece complicado de descifrar pero enseguida lo explicaremos. Cananles del ADC del microcontrolador AVR MUX4:MUX0 Entrada Única Entrada Diferencial Positiva Entrada Diferencial Negativa Ganancia
  • 529. Cananles del ADC del microcontrolador AVR MUX4:MUX0 Entrada Única 00000 ADC0 00001 ADC1 00010 ADC2 00011 Entrada Diferencial Positiva Entrada Diferencial Negativa Ganancia ADC3 N/A 00100 ADC4 00101 ADC5 00110 ADC6 00111 ADC7 01000 ADC0 ADC0 10x 01001 ADC1 ADC0 10x 01010 ADC0 ADC0 200x 01011 ADC1 ADC0 200x 01100 ADC2 ADC2 10x 01101 ADC3 ADC2 10x 01110 ADC2 ADC2 200x 01111 ADC3 ADC2 200x 10000 ADC0 ADC1 1x 10001 ADC1 ADC1 1x N/A
  • 530. Cananles del ADC del microcontrolador AVR Entrada Diferencial Positiva Entrada Diferencial Negativa Ganancia 10010 ADC2 ADC1 1x 10011 ADC3 ADC1 1x 10100 ADC4 ADC1 1x 10101 ADC5 ADC1 1x 10110 ADC6 ADC1 1x 10111 ADC7 ADC1 1x 11000 ADC0 ADC2 1x 11001 ADC1 ADC2 1x 11010 ADC2 ADC2 1x 11011 ADC3 ADC2 1x 11100 ADC4 ADC2 1x 11101 ADC5 ADC2 1x MUX4:MUX0 Entrada Única 11110 1.1V ( ) 11111 0 V (GND) Observemos en primer lugar que la tabla se puede separar en tres secciones. La primera parte corresponde al denominado modo de conversión simple o de entrada única, la segunda parte es del modo de conversión diferencial y en la tercera parte no se elige ningún canal analógico sino que se convierte el valor de la tensión o GND. El modo de conversión de entrada única es el que se emplea en la gran mayoría de las aplicaciones. Aquí los bits del multiplexor MUX4 y MUX3 valen 0 y el ADC solo puede convertir una de las 8 entradas analógicas a la vez. El diagrama funcional del ADC en este caso se representa así.
  • 531. Diagrama básico del ADC para conversiones de entrada única. Debemos tener en cuenta que este modo es compatible en todos los módulos ADC de los megaAVR que lo tienen. Sobra decir que no tendrán ningún efecto las configuraciones deMUX4:MUX0 que eligen los canales ADC6 y ADC7 en los AVR que no tienen estos pines. De hecho los megaAVR de 3 puertos no tienen el bit MUX4 y es recomendable dejarlo siempre en 0. En el modo de conversión diferencial las entradas analógicas se agrupan de a dos. Cada par se constituye por una Entrada Diferencial Positiva y una Entrada Diferencial Negativa. Todas las combinaciones posibles están establecidas por los bits MUX4:MUX0, de acuerdo con la tabla de arriba. Se puede observar allí y en la figura mostrada abajo que en este modo interviene además un amplificador de ganancia que puede multiplicar la diferencia entre las entradas por un factor de 1x, 10x o 200x antes de ser convertido a su valor analógico. El inconveniente del amplificador es que reduce la resolución del ADC a 8 bits si se usa la ganancia de 1x o 10x y hasta a 6 bits con la ganancia de 200x. Este modo de operación del ADC solo está disponible en los megaAVR de 4 puertos o más.
  • 532. Diagrama básico del ADC para conversiones de entradas diferenciales. Finalmente, hay dos combinaciones de los bits MUX4:MUX0 que hacen que el valor analógico a convertir sea la señal de tierra GND o una tensión de referencia denominada que varía según el microcontrolador. En los megaAVR de las series 4xx y 8xx que son los enfocados con prioridad en cursomicros.com esta tensión vale 1.1V. Si vamos a trabajar con esta característica utilizando otros modelos de AVR, deberemos revisar su datasheet. Conversión de las tensiones de referencia GND y . Los Voltajes de Referencia Son los valores analógicos límites entre los que deberá estar comprendida la tensión analógica a convertir. El nivel superior se representa por Vref+ y el inferior por Vref-. Como seguramente lo habrás notado en los diagramas anteriores, el valor de Vref- es igual al negativo de Vref+ cuando el ADC realiza conversiones diferenciales y es igual a 0V en los demás casos. En cualquier modo de operación Vref+ se puede configurar por
  • 533. los bits REFS1 y REFS0, del registro ADMUX y podemos escoger una de estas tres opciones: El valor del pin AVCC El valor del pin AREF Un Voltaje de Referencia Interno (de 1.1V o 2.56 V) que provee el megaAVR. Registro DMUX ADMUX REFS1 REFS0 ADLAR MUX4 MUX3 MUX2 MUX1 MUX0 Si los bits REFS1:REFS0 eligen como voltaje de referencia Vref+ al pin AVCC, ese pin lo debemos conectar a Vcc mediante un filtro pasa-bajas. Puesto que el pin AREF queda internamente conectado a AVCC, es más que recomendable colocarle un capacitor para filtrar el ruido. Abajo se muestra un boceto del circuito descrito. Recordemos que el pin AVCC es en principio la alimentación del puerto que lleva los canales del ADC, así como la alimentación del mismo módulo ADC. Sin embargo, incluso de no usar el ADC, el pin AVCC se debe conectar a Vcc, con filtro o sin él. Si los bits REFS1:REFS0 eligen como voltaje de referencia Vref+ al pin AREF, podemos conectar a ese pin cualquier fuente de voltaje, siempre que no supere el valor de la alimentación Vcc y en caso de que trabajemos con canales diferenciales no debe ser menor de 2 V. De esto último se deduce que no se podría elegir el Voltaje de Referencia Interno de 1.1V, descrito luego. Es muy poco frecuente programar el ADC para que opere de este modo, pero si lo vamos a hacer, debemos fijarnos bien en la configuración de los bits REF1 y REF0. Si aplicamos una fuente de tensión en el pin AREF y los bits REF1 y REF0 seleccionan una referencia diferente, entonces se producirá un corto-circuito interno entre el pin AREF y la referencia seleccionada (AVCC, por ejemplo).
  • 534. Los bits REFS1:REFS0 también pueden elegir como Vref+ alguno de los Voltajes de Referencia Internos que provee el AVR, pero aquí se debe tener en cuenta que sus valores pueden variar entre las diferentes series de AVR, incluso si son de familias cercanas. También en este caso la referencia se verá reflejada por el pin AREF, de modo que deberíamos conectar allí un capacitor de estabilización. Las siguientes tablas muestran la configuración de los bits REFS1:REFS0 correspondiente a los AVR de las series ATmegaXX4 y ATmegaXX8. Podemos observar que la clara divergencia se encuentra en la elección de los Voltajes de Referencia Internos. Si vamos a utilizar estas referencias en otros AVR será recomendable revisar su datasheet respectivo. Tabla REFS1 REFS1 REFS0 Voltaje de Referencia Vref+ para los ATmegaXX4 0 0 Pin AREF 0 1 Pin AVCC con capacitor externo en el pin AREF 1 0 Voltaje de Referencia Interno de 1.1V con capacitor externo en el
  • 535. Tabla REFS1 REFS1 REFS0 Voltaje de Referencia Vref+ para los ATmegaXX4 pin AREF 1 1 Voltaje de Referencia Interno de 2.56V con capacitor externo en el pin AREF Tabla REFS1 REFS1 REFS0 Voltaje de Referencia Vref+ para los ATmegaXX8 0 0 Pin AREF 0 1 Pin AVCC con capacitor externo en el pin AREF 1 0 Reservado 1 1 Voltaje de Referencia Interno de 1.1V con capacitor externo en el pin AREF Suponiendo que trabajamos con conversiones de entrada única, esto es, Vrefconectado a tierra, GND = 0V, como se ve en la siguiente figura, entonces una entrada analógica igual a 0V o inferior se convertirá en 0x000 y las tensiones analógicas iguales a Vref+ o superiores se convertirán en el valor digital 0x3FF. Recordando la teoría estudiada al inicio de este capítulo, cualquier otro valor analógico Vin comprendido entre estos límites estará sujeto a la fórmula.
  • 536. Voltajes de Referencia del ADC para conversiones de entrada única. Por otro lado, si nuestro ADC realiza conversiones diferenciales, entonces la máxima diferencia positiva o negativa que se podrá convertir correctamente será (+/Vref+)/ganancia, siendo esta ganancia igual a 1, 10 ó 200. En este caso el resultado será un número entero con signo, es decir, positivo o negativo, formateado en complemento a dos. De acuerdo con esto, la máxima diferencia negativa corresponderá al valor digital 0x200 = -512 y la máxima diferencia positiva se convertirá en 0x1FF = +511. Cualquier otra diferencia analógica interpolada se deberá interpretar con la siguiente fórmula. Donde Vpos en la entrada diferencial positiva y Vneg es la entrada diferencial negativa. Voltajes de Referencia del ADC para conversiones de entradas diferenciales. La primera conversión después de cambiar los voltajes de referencia es imprecisa y se recomienda descartarla. Resultado de la Conversión El resultado de la conversión es una cantidad binaria de 10 bits que se deposita entre los registros ADCH y ADCL, según la justificación mostrada en la siguiente figura y de acuerdo con el bit ADLAR (ADC Left Adjust Result) del registro ADMUX. Registro DMUX ADMUX REFS1 REFS0 ADLAR MUX4 MUX3 MUX2 MUX1 MUX0
  • 537. Justificación del resultado entre los registros ADCH y ADCL. Obviamente debemos esperar a que termine una conversión antes de leer un dato válido de estos registros. Para esto podemos comprobar la activación del flag ADIF (del registro ADCSRA) o la puesta a cero del bitADSC (también de ADCSRA), si es que el ADC opera en modo normal. Los dos registros del resultado son de solo lectura y el acceso a ellos no involucra una operación atómica. Sin embargo, debemos saber que después de leer el registro ADCLqueda bloqueada la actualización de los otros registros del ADC hasta que leamos el registro ADCH. De este modo se asegura que los datos presentes en estos registros corresponden a una misma conversión. De aquí se desprende que al terminar una conversión debemos empezar por leer ADCL y luego ADCH, o simplemente podemos tomar el valor de ADCH y así permitir que se puedan depositar en ellos los valores de nuevas conversiones. Cuando se establece la justificación derecha los registros ADCH y ADCL conforman el registro de 16 bits llamado simplemente ADC y puede ser así reconocido por los compiladores C puesto que ocupan posiciones contiguas en el espacio de los registros de E/S. Reloj del ADC y Tiempo de Conversión Como todo circuito síncrono, el conversor ADC necesita de una señal de reloj para dirigir los pasos de su algoritmo de aproximaciones sucesivas, ése que describimos al principio. Este reloj deriva del oscilador del sistema F_CPU. La frecuencia del reloj del ADC dependerá de la resolución del resultado que se desee obtener. Por ejemplo, si se va a trabajar con los 10 bits de resolución , entonces se requerirá de un reloj cuya frecuencia esté entre 50kHz y 200kHz. Si se requiere de una resolución menor de 10 bits, el reloj del ADC puede superar los 200kHz.
  • 538. El reloj del ADC es una ramificación del reloj del sistema, F_CPU. De allí proviene y antes de aplicarse al ADC pasa por un prescaler programable que permite disminuir su valor. Los factores de división se establecen por los bits ADPS2, ADPS1 y ADPS0, del registro ADCSRA, de acuerdo con la tabla mostrada más abajo. Diagrama de la fuente del reloj del ADC. Tabla ADPS2 ADPS2 ADPS1 ADPS0 Factor de División 0 0 0 2 0 0 1 2 0 1 0 4 0 1 1 8 1 0 0 16 1 0 1 32 1 1 0 64 1 1 1 128
  • 539. A modo de ejemplo analicemos los valores de ADPS1, ADPS1 y ADPS0 que podríamos usar suponiendo que trabajamos con nuestro acostumbrado XTAL (F_CPU) de 8MHz. Si ADPS2:ADPS0 = 111b, el reloj del ADC tendrá una frecuencia de 8MHz/128 = 62.5kHz, valor suficiente para conseguir resultados fiables de 10 bits, tan fiables como los generados a 125kHz con el factor de prescaler de 64. Sin embargo, si escogemos el factor de 32, con ADPS2:ADPS0 = 101b, el ADC operará a 8MHz/32 = 250kHz, que es una frecuencia superior a los 200kHz que garantizan una buena conversión, de modo que deberemos evitarla, a menos tal vez que solo nos interesen los 8 bits más significativos del resultado. En este ejemplo, los factores de prescaler inferiores 32 conllevarán una operación del ADC deficiente e inaceptable. De nuestras lecciones iniciales del conversor de aproximaciones sucesivas, sabemos que por cada bit del resultado digital el ADC demora un ciclo del reloj. Esto es, si el conversor genera datos de 10 bits se requerirán de 10 ciclos de reloj por conversión. Eso es del todo cierto solo en el cómputo mismo de la conversión o en un ADC ideal; en un ADC real hay factores de establecimiento que alargan ligeramente el tiempo de conversión. En los megaAVR el tiempo de conversión depende del tipo de conversión que se realiza. La siguiente tabla muestra las cuatro posibilidades. Tabla Tipo de Conversión Tipo de Conversión Muestreo y retención (Ciclos desde Tiempo de el inicio de la conversión) Conversión (Ciclos) Primera conversión 14.5 25 Conversiones Normales, de entrada única 1.5 13 Conversiones Normales, de entrada diferencial 1.5/2.5 13/14 Conversiones autodisparadas 2 13.5 La primera conversión en cualquier modo es la que se ejecuta después de habilitar el ADC, seteando el bit ADEN; siempre demora más debido a que se debe inicializar el circuito analógico del ADC. Los ciclos de muestreo y retención se cuentan a partir del momento en que se inicia la conversión, o sea, después de setear el bit ADSC, del registro ADCSRA. Para comprender esto debemos saber que el ADC no capta la señal a convertir directamente
  • 540. del pin ADCx, sino que primero espera que dicho nivel de tensión se deposite en el capacitor de muestreo y retención , para luego iniciar la conversión desde allí. El tiempo que demora este capacitor en cargarse se denomina periodo de muestreo y retención o a veces tiempo de adquisición. Éste varía principalmente de acuerdo con la impedancia Rsdel circuito externo al canal del ADC. El ADC del AVR está optimizado para acoplarse a impedancias de 10K o inferior. Circuito de entrada del ADC de los megaAVR. Junto con su resolución el tiempo de conversión es el parámetro más importante de un ADC. Para fines prácticos esto se calcula como la suma del tiempo de conversión en sí (el que acabamos de describir) más el tiempo de adquisición. Por ejemplo, según la tabla de arriba, las conversiones normales de entrada única, que son las más usadas, demoran 13+1.5 = 14.5 ciclos. Si el ADC trabajara a su máxima frecuencia recomendada de 200kHz, cada ciclo duraría 1/200kHz = 5us. De aquí concluimos que cada una de estas conversiones en realidad toma 14.5*5 = 72.5us y que en un segundo se pueden realizar hasta 1/72.5u = 13793 conversiones => 14 kSPS. Por si acaso, la unidad kSPS significa kilo Samples Per Second. El datasheet dice que llega a 15 kSPS pero ya ves que según nuestros cálculos no sale así. Como sea, es un conversor bastante lento para mi gusto. Interrupción del ADC y Modo Sleep Recordemos que el modo Sleep es un estado en que se detienen las diversas ramificaciones del oscilador del sistema y dependiendo de las ramificaciones que se congelen se pueden conocer varios modos Sleep. Pues bien, son dos los modos Sleep que nos competen en esta ocasión. Idle mode. Es el estado de sueño menos profundo que existe en los AVR. Aquí solo se congelan los relojes del CPU y de la memoria FLASH, permitiendo que los demás periféricos como el USART, TWI, SPI, los Timers,… y, por supuesto, el ADC, continúen trabajando normalmente.
  • 541. ADC Noise Reduction Mode, o modo de reducción de ruido del ADC. Es el segundo estado de sueño y, por su nombre, fue diseñado para que el ADC opere sin interferencias, o sea, aunque normalmente se piensa en elmodo Sleep como una forma de ahorrar energía, el principal objeto de usar el ADC en este estado es tomar la señal analógica sin presencia del ruido de conmutación inherente de los otros componentes del microcontrolador. Aparte del CPU en este estado se congelan todos los periféricos relacionados con las transferencias de datos (USART, SPI, etc.) quedando como despertadores solo las interrupciones asíncronas, aparte del ADC, por supuesto. El evento que puede disparar la interrupción del ADC es la conclusión de una conversión. En ese instante, al mismo tiempo que se limpia el bit ADSC, se activará al flag ADIF. El bit ADIF se limpia por hardware al ejecutarse la función de interrupción ISR y si no se va a utilizar dicha función también se puede limpiar por software escribiendo un uno. La interrupción del ADC se habilita seteando el bit ADIE, obviamente aparte de I deSREG. Registro ADCSRA ADCSRA ADEN ADSC ADATE ADIF ADIE ADPS2 ADPS1 ADPS0 Para realizar conversiones en modo ADC Noise Reduction se deben seguir los siguientes pasos. El ADC debe estar habilitado, debe estar configurado para conversiones normales, no debe tener una conversión en curso y debe tener habilitada la interrupción del ADC. Entrar en modo ADC Noise Reduction o Idle mode. El ADC iniciará la conversión apenas se detenga el CPU. Al terminar la conversión, la interrupción del ADC despertará el CPU y se ejecutará la rutina de interrupción respectiva. Debemos observar que el ADC no se apagará automáticamente al entrar en otros modos Sleep que no sean Idle o ADC Noise Reduction. Se recomienda por tanto apagarlo manualmente escribiendo cero en el bit ADEN para evitar desperdicio de energía. Si el ADC está habilitado en dichos modos Sleep y se desea realizar conversiones diferenciales, se recomienda apagar y luego encender el ADC después de salir del modo Sleep para asegurar que se obtengan resultados válidos. Registros del Módulo ADC ADCSRA: ADC Control and Status Register A
  • 542. Registro ADCSRA ADCSRA ADEN ADSC ADATE ADIF ADIE ADPS2 ADPS1 ADPS0 Registro de Microcontrolador ADEN ADC Enable Escribiendo uno en este bit se habilita el ADC. Escribiendo un cero el ADC se apaga. Si se apaga el ADC cuando hay una conversión en progreso, se interrumpirá y terminará dicha conversión. ADSC ADC Start Conversion En modo de conversión normal escribir uno en este bit iniciará una conversión. En modo de conversiones auto-disparadas, escribir uno en este bit iniciará la primera conversión. La primera conversión después de escribir en ADSC tras iniciar el ADC o si ADSC fue escrito al mismo tiempo que se habilitó el ADC, tomará 25 ciclos de reloj del ADC en lugar de los 13 ciclos habituales. La primera conversión conlleva la inicialización del ADC. ADSC se leerá como uno mientras la conversión esté en progreso. Al terminar la conversión este bit retornará a cero. Escribir cero en este bit no tiene efecto. ADATE ADC Auto Trigger Enable Cuando se escribe uno en este bit se habilitan las conversiones automáticas del ADC. El ADC iniciará una conversión en el flanco positivo de la señal de disparo seleccionada. La fuente de disparo se selecciona por los bits ADTS en el registro ADCSRB. ADIF ADC Interrupt Flag Este bit se setea cuando termina una conversión y se actualizan los registros los registros de datos. La interrupción de Conversión de ADC Completada se ejecuta cuando valgan uno los bits ADIE e I de SREG. El flag ADIF se limpiará automáticamente por hardware cuando se ejecute la correspondiente rutina de interrupción ISR. Alternativamente, ADIF se limpia escribiéndole un uno. Observa que ejecutar una instrucción de LecturaModificación-Escritura en ADCSRA puede deshabilitar una interrupción pendiente. Esto también se aplica cuando se usan las instrucciones SBI y CBI. ADIE ADC Interrupt Enable Cuando se escribe uno en este bit y el bit I de SREG vale uno se activa la
  • 543. interrupción de Conversión de ADC Completada. ADPS2:0 ADC Prescaler Select Bits Estos bits determinan el factor de división entre la frecuencia del XTAL y el reloj de entrada del ADC. Tabla ADPS2 ADPS2 ADPS1 ADPS0 Factor de División 0 0 0 2 0 0 1 2 0 1 0 4 0 1 1 8 1 0 0 16 1 0 1 32 1 1 0 64 1 1 1 128 ADCSRB: ADC Control and Status Register B Registro ADCSRB ADCSRB --- ACME --- --- --- ADTS2 ADTS1 ADTS0 Registro de Microcontrolador ACME Analog Comparator Multiplexer Enable Al escribir uno en este bit estando el ADC apagado (ADEN en ADCSRA es cero), el multiplexor del ADC selecciona la entrada negativa del Comparador Analógico. Cuando se escribe cero en este bit, la entrada negativa del Comparador Analógico será AIN1. ADTS2:0 ADC Auto Trigger Source
  • 544. Si el bit ADATE de ADCSRA vale uno, la configuración de los bits ADTS2:0 selecciona la fuente de las conversiones auto-disparadas del ADC. Si ADATE vale cero, la configuración de ADTS2:0 no tendrá efecto. La conversión se inicia en el flanco de subida de la señal seleccionada. Observa que cambiar de una fuente de disparo que vale cero a una fuente que vale uno generará un flanco de subida en la señal de disparo, y si el bit ADEN de ADCSRA vale uno, esto iniciará una conversión. El cambio al Modo de Corrida Libre (ADTS2:0 = 000) no originará un evento de disparo, incluso si está activado el flag de interrupción del ADC. Tabla ADTS2 ADTS2 ADTS1 ADTS0 Fuente de disparo 0 0 0 Modo de Corrida Libre 0 0 1 Comparador Analógico 0 1 0 Interrupción Externa INT0 0 1 1 Coincidencia del Timer/Counter0 1 0 0 Desbordamiento del Timer/Counter0 1 0 1 Coincidencia B del Timer/Counter1 1 1 0 Desbordamiento del Timer/Counter1 1 1 1 Evento de Captura del Timer/Counter1 ADMUX: ADC Multiplexer Selection Register Registro ADMUX ADMUX REFS1 REFS0 ADLAR MUX4 MUX3 MUX2 MUX1 MUX0 Registro de Microcontrolador REFS1:0 Reference Selection Bits Estos bits seleccionan los voltajes de referencia del ADC, como se muestra en la siguiente tabla. Si estos se cambian durante una conversión, el cambio no tendrá efecto hasta que termine la conversión (hasta que se ponga a uno el flag ADIF en
  • 545. ADCSRA). Si se usa un voltaje de referencia aplicado al pin AREF, no se podrán usar las opciones de voltaje de referencia internos. Nota: Si se seleccionan canales diferenciales, solo se podrá usar 2.56V como Voltaje de Referencia Interno. Tabla REFS1 REFS1 REFS0 Voltaje de Referencia Vref+ Seleccionado (ATmegaXX4) 0 Pin AREF, Vref Interno desconectado 0 1 Pin AVCC con capacitor externo en el pin AREF 1 0 Voltaje de Referencia Interno de 1.1V con capacitor externo en el pin AREF 1 ADLAR 0 1 Voltaje de Referencia Interno de 2.56V con capacitor externo en el pin AREF ADC Left Adjust Result El bit ADLAR afecta la representación del resultado de la conversión en los registros de datos del ADC. Al escribir uno en ADLAR el resultado se justificará a la izquierda, de otro modo, el resultado se justifica a la derecha. El cambio del bit ADLAR afectará inmediatamente los registros de datos del ADC, sin importar si hay conversiones en curso. MUX4:0 Analog Channel and Gain Selection Bits El valor de estos bits selecciona la combinación de las entradas analógicas que se conectan al ADC. Estos bits también seleccionan la ganancia de los canales diferenciales. Para más información ver la sección Selección del Canal de Conversión. Si se cambian estos bits durante una conversión, el cambio no tendrá efecto hasta que se complete la conversión en curso (hasta que el bit ADIF de ADCSRA se ponga a uno). ADCH y ADCL: ADC Data Register Registro ADCH ADCH
  • 546. Registro ADCL ADCL Registro de Microcontrolador ADC Conversion Result Cuando termine una conversión del ADC el resultado será almacenado en estos dos registros. Si se usan canales diferenciales, el resultado se presentará en formato de complemento a dos. Al leer ADCL los registros de datos del ADC no se actualizan hasta que se lea ADCH. Como consecuencia, si el resultado se justifica a la izquierda y si no se requiere de más de 8 bits de precisión, bastará con leer ADCH. De otro modo, ADCL se deberá ser el primer registro en leer. El bit ADLAR del ADMUX y los bits MUXn de ADMUX afectan el modo en que se lee el resultado de estos registros. Si ADLAR vale uno, el resultado se justifica a la izquierda. Si ADLAR vale cero (valor por defecto), el resultado se justifica a la derecha. DIDR0: Digital Input Disable Register 0 Registro DIDR0 DIDR0 ADC7D ADC6D ADC5D ADC4D ADC3D ADC2D ADC1D ADC0D Registro de Microcontrolador Bit 7:0 ADC7D..ADC0D: ADC7:0 Digital Input Disable Cuando se escribe uno en este bit, se deshabilita el buffer de entrada digital correspondiente al pin ADCx. Si este bit vale uno, el correspondiente bit del registro PIN se leerá siempre como cero. Cuando se aplica una señal analógica al pin ADC7.0 y no se necesita la entrada digital de este pin, este bit se debería setear para reducir el consumo de energía en el buffer de entrada digital. Práctica: Conversiones Normales Se convierte los valores de hasta 4 potenciómetros y se visualiza en la consola RS232. El canal a leer se selecciona por medio del teclado.
  • 547. Circuito para usar el ADC del microcontrolador AVR. El código fuente /****************************************************************************** * FileName: main.c * Purpose: Uso del ADC: Conversiones Normales de Entrada Única * Processor: ATmega164P * Compiler: AVR IAR C y AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. * * License: Se permiten el uso y la redistribución de este código con
  • 548. * modificaciones o sin ellas, siempre que se mantengan esta * licencia y la nota de autor de arriba. *****************************************************************************/ #include "avr_compiler.h" #include "usart.h" voidadc_setup(void); unsignedintadc_read(charchannel); intmain() { charc;unsignedintn; usart_init();// 9600 - 8N1 adc_setup(); printf("r Test del ADC r"); printf("r Ingrese un canal [0..4] rr"); printf(" Canal Valor decimal Valor realr"); printf(" ===== ============= ==========r"); while(1) { if(kbhit())
  • 549. { c=getchar(); if((c<='3')&&(c>='0'))// Filtrar ingreso { n=adc_read(c-'0'); // Visualizar el resultado printf(" ADC%c %10d %11.3f Vr",c,n,n*5.0/1023); } } } } //**************************************************************************** // Configurar el conversor ADC //**************************************************************************** voidadc_setup(void) { // Vref+ y Vref- = VCC y GND // Justificación de resultado = Derecha ADMUX=0x40; // Reloj de ADC = F_CPU/64 = 125kHz // Estado del conversor = Habilitado // Modo de conversión = Manual ADCSRA=0x86;
  • 550. ADCSRB=0x00; } //**************************************************************************** // Lee el canal 'channel' del conversor ADC //**************************************************************************** unsignedintadc_read(charchannel) { ADMUX&=0xF8;// ADMUX|=channel;// Seleccionar canal ADCSRA|=(1<<ADSC);// Iniciar conversión while(ADCSRA&(1<<ADSC));// Esperar a que termine la conversión returnADC;// Retornar resultado de conversión } Introducción Los Timers son módulos que trabajan en paralelo con el procesador, permitiendo que las operaciones de temporización y conteo se puedan llevar a cabo de manera eficiente, mientras el procesador se ocupa de otras tareas. Normalmente los megaAVR cuentan con tres Timers, llamados Timer0, Timer1 y Timer2. A veces desaparece el Timer2 y otras veces aparece adicionalmente el Timer3 o el Timer4. Todos los Timers pueden trabajar en modo de PWM pero esta funcionalidad está mejor implementada en unos Timers (1 y 3) que en otros. Por su relativa limitación, el Timer0 está más destinado a las temporizaciones y otro tanto a los conteos. El Timer2 por su parte fue diseñado con la característica adicional para trabajar con un XTAL de reloj externo, de 32kHz.
  • 551. A pesar de sus diferencias operativas, la configuración y control de los Timers son muy similares en todos los casos, así que por más que sean varios aprenderemos a controlarlos todos con el menor esfuerzo. Bueno, eso espero. Supongo que lo común debe ser conocer los Timer de a uno, empezando por el Timer0 hasta llegar al Timer3 o 4, si es que se llega. Pero después de revisar los diversos datasheets, creo que un mejor enfoque será abarcarlos de a dos, según su número de bits. Esto nos permitirá evitar las redundancias. En primer lugar nos ocuparemos de los Timers de 8 bits, o sea, del Timer0 y del Timer2. Luego estudiaremos los Timers de 16 bits, que son el Timer1 y el Timer3. De hecho, solo trataremos el Timer1 porque el Timer3 es su perfecto clon, si vale la expresión. Como habrán notado, había tratado hasta donde me posible de describir las características de los periféricos de los megaAVR pensando también en los que la propia Atmel denomina “viejos AVR”, dado que aún son muy usados en la actualidad y hay muchos proyectos de interés que se basan en esos microcontroladores. Pero creo que en esta ocasión dejaré de lado ese enfoque múltiple para no recargar el tema demasiado. Como había dicho hay mucha similitud entre todos los Timers y bastaría con conocer bien el funcionamiento de uno de ellos para poder comprender los demás. Con todo eso, el estar mencionando a cada rato que tal bit tiene otro nombre en el otro AVR o que está pequeña función no está presente en aquel otro,... puede hacer tediosa el estudio. Suficiente será con comparar los diversos Timers que mencionamos. No obstante, puesto que todo el control y configuración de un módulo queda reflejado en sus registros de E/S, creo que una buena idea poner al final la descripción respectiva de los registros de los Timers de los “viejos megaAVR”. Yo creo que eso será más que suficiente para programarlos bien, sin necesidad de empezar a describirlos por separado. El Timer0 y el Timer2 Dada la paridad de características entre el Timer0 y el Timer2, no las vamos a mencionar simultáneamente para no fatigarnos de términos. Simplemente vamos a referirnos al Timer0 y se dará por hecho que lo mismo es aplicable para el Timer2, salvo que se indique explícitamente lo contario. Las principales diferencias se dejarán notar solo al inicio y al final, luego el tratamiento será muy parejo. Naturalmente, el control de cada Timer a nivel de programación requiere del trato específico de sus registros de configuración. Pero tampoco esto es de preocupación pues lo único que cambia es el número 0 por el número 2 en cada registro y bit de registro. Por ejemplo, los registros de E/S del Timer0 son: TCNT0. TCCR0A, TCCR0B, OCR0A, OCR0B, TIMSK0 y TIFR0. En tanto que para el Timer2 son:
  • 552. TCNT2. TCCR2A, TCCR2B, OCR2A, OCR2B, TIMSK2 y TIFR2. Aparte de ellos, tenemos al registro GTCCR, el cual es de uso común para todos los Timers desde el Timer0 hasta el Timer3, y el registro ASSR, que es de uso exclusivo del Timer2 y que controla las características distintivas del funcionamiento asíncrono de este Timer. Del mismo modo, en los nombres de los bits de cada registro, solo cambian el número 0 por el 2. Por ejemplo, los mapas de bits de los registros TCCR0A y TCCR2A son, respectivamente: Registro TCCR0A TCCR0A COM0A1 COM0A0 COM0B1 COM0B0 --- --- WGM00 ADPS0 COM2A0 COM2B1 COM2B0 --- --- WGM21 WGM20 Registro TCCR2A TCCR2A COM2A1 Queda claro entonces que bastará con referirnos al Timer0, entendiendo que las mismas características, ventajas y limitaciones citadas serán igualmente aplicables al Timer2, salvo, repito, que se indique lo contrario. Empecemos, entonces. El nombre completo del Timer0 es Timer/Counter0 o Temporizador/Contador 0, pero por comodidad nos referimos a él simplemente como Timer0. Dicen que un diagrama vale más que mil palabras, así que el siguiente esquema nos ayudará para entender la operación común del Timer0. Las operaciones específicas de cada modo particular las describiremos en su momento.
  • 553. Diagrama de bloques del Timer0. El elemento central del Timer0 es su contador, que es el mismo registro TCNT0. Como es un registro de 8 bits, decimos que el Timer0 es de 8 bits. El Timer0 puede avanzar hacia adelante o hacia atrás, según se programe, impulsado por la señal de su reloj, el cual puede ser interno o externo. Cuando nos referirnos al avance del Timer en realidad nos referimos al avance de su contador, el registro TCNT0. Con sus 8 bits, el Timer0 puede contar en todo su rango, o sea, entre 0 y 255. Cuando el Timer0 opera solo en modo ascendente y llega a su valor máximo de 255, continuará después contando desde 0 otra vez, cíclicamente. Esta transición de 255 a 0 es el famoso Desbordamiento y es un concepto clave en los Timers. El desbordamiento del Timer0 activa el bit de flag TOV0. También es posible hacer que el Timer0 cuente solo hasta un tope establecido por el registro OCR0A. El Timer0 tiene dos comparadores que en todo momento están comparando el valor del registro TCNT0 con los registros OCR0A y OCR0B. Cada igualdad detectada entre los registros indicados se conoce como Coincidencia y es el segundo concepto clave de los Timers del AVR. La coincidencia entre TCNT0 y OCR0A activa el bit de flag OCF0A y la coincidencia entre TCNT0 y OCR0B activa el bit de flag OCF0B. El Desbordamiento del Timer0 y cada una de sus dos Coincidencias se pueden programar para disparar interrupciones. Los detalles de las interrupciones serán vistos con paciencia en su sección respectiva.
  • 554. Desde el punto de vista de la programación, podemos controlar el Timer0 con tres tipos de bits: Los bits CS (de Clock Select). Los bits CS02, CS01 y CS00 se encargan de configurar todo lo relacionado con el reloj y el prescaler del Timer. Los bits WGM (de Waveform Generator Mode). Los bits WGM02, WGM01 y WGM00 trabajan con los comparadores para producir ondas cuadradas de acuerdo con la configuración de los bits. En realidad, su función implica más que eso, pues establecen el modo en que operará el Timer0, ya sea modo Normal, CTC o PWM. Los bits COM (de Compare Output Mode). Son los bits COM0A1 y COM0A0 los que en última instancia deciden si las ondas generadas por los comparadores salen o no por los pines OC0A y OC0B del AVR. El tipo de onda más popular es PWM y es habitualmente el único caso en que se dejan salir las ondas. Cuando el Timer0 va a trabajar como simple contador o temporizador, los bits COM quedan con su valor por defecto de 0, con lo cual los pines OC0A y OC0B quedan desconectados del Timer y se pueden seguir usando como puertos de E/S generales. El Reloj del Timer0 y del Timer2 La similitud entre el Timer0 y el Timer2 se comprueba fácilmente examinando sus correspondientes registros de control. Es en esta sección donde nos ocuparemos de las pocas diferencias entre ellos. El reloj del Timer0 es la señal digital, periódica o no, cuyos pulsos hacen avanzar el Timer. La fuente de reloj del Timer0 puede ser interna o externa. Reloj Interno. Aquí el reloj del Timer0 deriva del mismo oscilador interno del sistema F_CPU. Como se ve en la figura, en este caso la señal pasa previamente por el prescaler, que puede dividir la frecuencia de F_CPU por un valor seleccionado por nosotros. Los prescalers del Timer0 y del Timer2 no son idénticos, aunque tengan los bits de control similares. Pero siendo este reloj el que se usa con regularidad, ya sea para las temporizaciones o para generar ondas PWM, sobrará espacio para familiarizarnos con estas ligeras diferencias. Reloj Externo. He aquí la brecha más grande que separa al Timer0 del Timer2. La forma como la señal externa se aplica al microcontrolador depende de cada Timer. En el Timer0 la señal externa se conecta al pin T0 del megaAVR. Con esto el programador decide si el Timer0 avanzará con cada flanco de subida o de bajada detectado en dicho pin. Notemos en el diagrama que la señal externa no pasará por su prescaler. En el Timer2 su reloj externo puede ser de dos tipos: o es una señal aplicada al pinTOSC1 del megaAVR, en cuyo caso el Timer2 avanzará con cada flanco de bajada de ese pin; o es la señal de un oscilador de XTAL conectado entre los pines TOSC1y TOSC2 del megaAVR. En ambos casos, la señal de reloj pasará por el prescaler del Timer2.
  • 555. El modo donde el Timer0/2 trabaja con un reloj externo aplicado al pin T0 (para el Timer0) o TOSC1 (para el Timer2) se conoce como modo Contador porque de alguna forma el Timer contará los pulsos detectados en dicho pin. Sin embargo, el hecho de que el reloj provenga de una fuente externa no le quita sus otras funcionalidades, como por ejemplo, poder generar ondas PWM, interrupciones, etc., claro que sería conveniente que para tal caso la señal fuera periódica. Contador del Timer0 con su fuente de reloj. Contador del Timer2 con su fuente de reloj. El Prescaler del Timer0 y del Timer2 El prescaler es un circuito contador por el que se puede hacer pasar el reloj del Timer para dividir su frecuencia. De ese modo el Timer avanzará más lento, según las necesidades del diseñador. El prescaler es parte del reloj del Timer, así que para configurarlo se usan los bits de Selección de Reloj o bits CS (por Clock Select). Registro TCCR0B TCCR0B FOC0A FOC0B --- --- WGM02 CS02 CS01 CS00
  • 556. Registro TCCR2B TCCR2B FOC2A FOC2B --- --- WGM22 CS22 CS21 CS20 Como puedes ver, los bits CS se encuentran en los registros TCCRxB de cada Timer, sin embargo, por más que sean casi iguales, tiene efectos diferentes debido a que los prescalers son diferentes. El prescaler del Timer2 es más sencillo y completo, pero empezaremos por explicar el prescaler del Timer0. El prescaler del Timer0 es compartido con el Timer1 (¿y qué tiene que ver en todo esto el Timer1?). De acuerdo con la figura, es posible que los dos Timers operen simultáneamente con el prescaler y utilizando diferentes factores de división puesto que cada Timer tiene sus propios bits CS (de Clock Select). El único reparo sería que se debe tener cuidado al resetear el prescaler porque para esto se dispone de una única señalPSRSYNC. Es un reset SYNCrono porque el Timers0 y el Timer1 trabajan siempre sincronizados con el reloj del sistema F_CPU, hasta cuando su reloj proviene de los pinesT0 o T1, respectivamente. El bit PSRSYNC se encuentra en el registro GTCCR. Prescaler y Relojes del Timer0 y del Timer1. Notemos que el prescaler divide el reloj del sistema por 8, 64, 256 ó 1024. Estos divisores se conocen como factores de prescaler. Observemos además que de usar un reloj proveniente del pin T0, entonces no será posible usar el prescaler.
  • 557. Registro TCCR0B TCCR0B FOC0A FOC0B --- --- WGM02 CS02 CS01 CS00 Tabla CS02 CS02 CS01 CS00 Fuente de reloj del Timer0 0 0 0 Sin fuente de reloj (Timer0 detenido) 0 0 1 F_CPU (Sin prescaler) 0 1 0 F_CPU/8 (con prescaler) 0 1 1 F_CPU/64 (con prescaler) 1 0 0 F_CPU/256 (con prescaler) 1 0 1 F_CPU/1024 (con prescaler) 1 1 0 Reloj externo en pin T0. El Timer0 avanza con el flanco de bajada. 1 1 1 Reloj externo en pin T0. El Timer0 avanza con el flanco de subida. Ahora revisemos el prescaler del Timer2. Este prescaler ofrece más factores de división, con lo que las temporizaciones podrán ser más flexibles. A diferencia del Timer0/1, si optamos por un reloj externo aplicado al pin TOSC1, dicha señal sí podrá pasar por el prescaler. Esta vez el prescaler se puede resetear con la señal PSRASY. Su nombre indica que se trata de naturaleza ASYncrona porque si el reloj del Timer2 viene del exterior, no habrá circuito que lo sincronice con el reloj del sistema F_CPU. Los bits AS2 y PSRASYse encuentran en el registro GTCCR.
  • 558. Prescaler y Reloj del Timer2. Registro TCCR2B TCCR2B FOC2A FOC2B --- --- WGM22 CS22 CS21 CS20 En la siguiente tabla la señal clkT2S puede ser F_CPU o el reloj proveniente del exterior. Tabla CS22 CS22 CS21 CS20 Fuente de reloj del Timer2 0 0 0 Sin fuente de reloj (Timer detenido). 0 0 1 clkT2S (Sin prescaler) 0 1 0 clkT2S/8 (Desde el prescaler) 0 1 1 clkT2S/32 (Desde el prescaler) 1 0 0 clkT2S/64 (Desde el prescaler) 1 0 1 clkT2S/128 (Desde el prescaler) 1 1 0 clkT2S/256 (Desde el prescaler) 1 1 1 clkT2S/1024 (Desde el prescaler) Registro GTCCR
  • 559. Tabla CS22 CS22 CS21 CS20 Fuente de reloj del Timer2 GTCCR TSM --- --- --- --- --- PSRASY PSRSYNC Modos de Operación del Timer0 y Timer2 En general existen 3 modos en que pueden trabajar los Timers: Modo Normal Modo CTC Modo PWM Cada modo tendrá sus variantes dependiendo del Timer. Por ejemplo, en el Timer1 existen hasta 12 modos PWM, pero bueno, de eso nos ocuparemos en su momento. Diagrama de bloques del Timer0. La figura nos resalta que esta vez vamos a trabajar con los bits WGM. Su nombre viene de Waveform Generation Mode porque estos bits pre-establecen el tipo de onda que podrá generar el Timer0 por los pines OC0A yOC0B. En la práctica es raro utilizar
  • 560. otras formas de onda que no sean de tipo PWM, así que el nombre no parece muy apropiado. En la figura también se aprecia que los GENERADORES DE ONDA también dependen de los bits COM (de Compare Output Mode). Estos bits establecen el modo en que finalmente saldrán las ondas por los pinesOC0A y OC0B, es decir, pueden salir normales, invertidas, o pueden simplemente no salir y dejar los pines OC0x libres para otras tareas. A lo que quiero llegar es que al menos en cursomicros los bits COM solo se usan en modo PWM. En los modos Normal y CTC nos olvidamos de ellos. Tabla Modos de Operación de Timer0 WGM02 WGM01 WGM00 Modo de Operación de Timer0 Inicio del Conteo Tope del Conteo 0 0 0 Normal 0x00 0xFF 0 0 1 PWM de Fase Correcta 0x00 0xFF 0 1 0 CTC 0x00 OCR0A 0 1 1 Fast PWM 0x00 0xFF 1 0 0 Reservado 0x00 --- 1 0 1 PWM de Fase Correcta 0x00 OCR0A 1 1 0 Reservado 0x00 --- 1 1 1 Fast PWM 0x00 OCR0A Los bits WGM están distribuidos en los dos registros de control del Timer0, TCCR0A yTCCR0B. El hecho de que el bit WFG02 esté en TCCR0B y no junto con sus pares enTCCR0A habiendo espacios vacíos allí se debe a cuestiones de compatibilidad con otros AVR. Registro TCCR0A TCCR0A COM0A1 COM0A0 COM0B1 COM0B0 --- --- WGM01 WGM00 Registro TCCR0B TCCR0B FOC0A FOC0B --- --- WGM02 CS02 CS01 CS00
  • 561. El Timer0 y el Timer2 en Modo Normal Este modo queda seleccionado cuando todos los bits WGM valen 0, es decir, es el modo por defecto del Timer0. De hecho, lo es en todos los Timers. Tabla WGM02 WGM02 WGM01 WGM00 Modo de Operación de Timer0 Inicio del Conteo Tope del Conteo 0 0 0 Normal 0x00 0xFF En modo Normal el Timer0, habilitado, avanza libre y cíclicamente en todo su rango, es decir, su registro TCNT0 cuenta desde 0x00 hasta 0xFF, luego se desborda para volver a iniciar desde 0x00. El desbordamiento del Timer activa el flag TOV0 del registro TIFR0 el cual puede programarse para disparar interrupciones. Como el registro TCNT0 es de lectura y escritura podemos en cualquier momento modificar su valor y así recortar los periodos de conteo para calibrar o ajustar las temporizaciones. Registro TCCR0A TCCR0A COM0A1 COM0A0 COM0B1 FOC0B --- COM0B0 --- --- WGM01 WGM00 Registro TCCR0B TCCR0B FOC0A --- WGM02 CS02 CS01 CS00 OCF0A TOV0 Registro TIFR0 TIFR0 --- --- --- --- --- OCF0B El Timer0 siempre inicia detenido, así que para que se cumpla todo lo descrito primero habrá echarlo a andar configurando los bits de reloj CS, según lo estudiado en El Reloj del Timer0 y del Timer2. Recordemos que los comparadores del Timer0 pueden sacar por los pines OC0A y OC0Bunas señales que se pueden configurar con los bits COM. En los modos Normal o CTC esta señal se forma poniendo a cero, a uno, o conmutando el valor de OC0A/OC0B. Todas las opciones posibles se muestran en la siguiente tabla. Para temas de temporización, que es normalmente el propósito del modo Normal o CTC, debemos escoger la primera opción, que es la predeterminada y que nos dejará los pines OC0A/OC0B libres para seguir usándolos como puertos de E/S generales. Tabla COM0A1 COM0A1 COM0A0 Descripción
  • 562. Tabla COM0A1 COM0A1 COM0A0 Descripción 0 0 Pin OC0A desconectado. Operación normal del pin 0 1 Pin OC0A conmuta en Coincidencia entre TCNT0 yOCR0A 1 0 Pin OC0A se limpia en Coincidencia entre TCNT0 yOCR0A 1 1 Pin OC0A se setea en Coincidencia entre TCNT0 yOCR0A La tabla solo muestra la configuración de la onda generada para el pin OC0A pero es la misma que se obtiene para el pin OC0B con los bits COM0B1 y COM0B0. Cálculo de la Temporización en Modo Normal Temporizar con el Timer0 implica cargar su registro TCNT0 con un valor adecuado y dejar que siga contando hasta que se desborde. Es el tiempo que demora en desbordarse lo que nos interesa conocer para aplicarlo a nuestras necesidades; y son el cálculo y la programación de ese tiempo el objetivo de esta sección. Bueno, asumo que en este momento ya sabemos cómo configurar el reloj del Timer0 (bits CS), que sabemos cómo programar el Timer0 en modo Normal (bits WGM) y que entendemos su operación en ese modo. Ahora aprenderemos a escoger la opción de reloj más adecuada para nuestras temporizaciones. Para empezar, debemos usar el reloj interno derivado de F_CPU (cuyo valor es teóricamente igual a la frecuencia del XTAL del megaAVR.), salvo que tengamos una señal externa periódica. Como sabemos, si la fuente de reloj es interna, el Timer0 y el Timer2 se programan igual. Lo único que cambiará serán los factores de prescaler. Tabla CS02 CS02 CS01 CS00 Fuente de reloj del Timer0 0 0 0 Sin fuente de reloj (Timer0 detenido) 0 0 1 F_CPU (Sin prescaler) 0 1 0 F_CPU/8 (con prescaler) 0 1 1 F_CPU/64 (con prescaler)
  • 563. Tabla CS02 CS02 CS01 CS00 Fuente de reloj del Timer0 1 0 0 F_CPU/256 (con prescaler) 1 0 1 F_CPU/1024 (con prescaler) 1 1 0 Reloj externo en pin T0. El Timer0 avanza con el flanco de bajada. 1 1 1 Reloj externo en pin T0. El Timer0 avanza con el flanco de subida. En primer lugar veamos cómo avanza el Timer0. Por ejemplo, si tenemos un XTAL de 8 MHz y no usamos prescaler, entonces el reloj del Timer0 será de 8 MHz y el registroTCNT0 se incrementará cada 1/8MHz = 128ns, lo mismo que un ciclo de instrucción básica. Pero si usamos el factor de prescaler 8, TCNT0 avanzará cada 1us. Si usamos el factor de prescaler de 256, TCNT0 avanzará cada 32us. Y si cambiamos de XTAL, los tiempos serán otros. Ahora entonces, suponiendo que seguimos con nuestro XTAL de 8MHz, el registro TCNT0avanzará desde 0 hasta 255 en 32us (sin prescaler). Pero si cargamos TCNT0 con 200, llegará al desbordamiento después de 7us; y si usamos prescaler de 8, lo hará después de 7×8 = 56us. Al inicio todos vemos en esto un enredo de números. Parece complejo pero solo es cuestión de encontrar el hilo de la madeja para suspirar diciendo ¡Ah…, era así de fácil! Sin embargo, hay quienes se rinden y prefieren usar fórmulas y cálculos directos como los descritos a continuación. Bueno, vamos al grano. El Tiempo que pasará el Timer0 contando desde un valor inicialTCNT0 hasta 255 y se produzca el desbordamiento está dado por la fórmula: Donde: Tabla de Temporización en Modo CTC con Timer0 Tiempo = Valor de la temporización. F_CPU = Frecuencia del XTAL del megaAVR. N = Factor de prescaler (1, 8, 64, 256 ó 1024).
  • 564. TCNT0 = Valor de inicio del registro TCNT0. Tabla de Temporización en Modo CTC con Timer0 Nota: los factores de prescaler N del Timer2 son 1, 8, 32, 64, 128, 256 y 1024. Eso podría dar otras soluciones para N y TCNT2. Como ves, ésta es una ecuación con dos incógnitas (N y TCNT0) y es posible encontrar más de una solución para ambas. Sin embargo, no todas serán igualmente adecuadas. Los valores más apropiados serán los que nos permitan realizar un mejor posterior ajuste de precisión. Si eres ducho resolviendo ecuaciones de Diofanto, puedes trabajar con esa fórmula. Pero si no quieres ir tanteando, puedes emplear las siguientes dos fórmulas: (He borrado todas sus deducciones para no alargar más esta sección.) Lo más probable es que el valor obtenido con esta fórmula no esté disponible como factor de prescaler válido (1, 8, 64, 256 ó 1024 para el Timer0 o 1, 8, 32, 64, 128, 256 y 1024 para el Timer2). En tal caso deberemos tomar el factor superior más cercano (“redondear” para arriba). La otra fórmula es: Como antes, si el resultado no fuera un número entero, habría que redondearlo para arriba. Si el factor de prescaler obtenido estuviera fuera del rango permitido (más alto que 1024), se puede optar por buscar otro camino, como fragmentar la temporización. Por otro lado, si la temporización es muy fina, puede que sea necesario subir un poquito el valor de inicio del TCNT0 para realizar una calibración añadiendo algunas instrucciones de relleno comonops. Estas dos situaciones las veremos en las prácticas; así que pierde cuidado si no las dejé muy claro. A modo de ejemplo, hallemos el factor de prescaler N y el valor de inicio de TCNT0 para generar una temporización de 5 ms si el megaAVR trabaja con un XTAL de 10 MHz. Y el valor de inicio del registro TCNT0 será:
  • 565. La secuencia de conteo resultaría así: Otro ejemplo. ¿Cuáles son la razón de prescaler y el valor inicial de TCNT0 para conseguir una temporización de 200 µs si nuestro megaAVR tiene un XTAL de 4 MHz? El factor de prescaler N sería: Y el valor inicial de TCNT0 será: Luego, la secuencia de conteo quedaría así: Finalmente, ¿cuáles son la razón de prescaler y el valor inicial de TCNT0 para conseguir una temporización de 50 ms si se tiene un megaAVR con un XTAL de 20 MHz? El factor de prescaler sería: ¿Y ahora de dónde vamos a sacar un factor de prescaler mayor que 3906.25 si el máximo es de 1024? ¿Buscamos otro Timer? Bueno, quizá podríamos temporizar 10 veces 5 ms. El Timer0 y el Timer2 en Modo CTC
  • 566. Tabla WGM02 WGM02 WGM01 WGM00 0 1 0 Modo de Operación de Timer0 Tope del Conteo 0x00 CTC Inicio del Conteo OCR0A Registro TCCR0A TCCR0A COM0A1 COM0A0 COM0B1 COM0B0 --- --- WGM01 WGM00 Registro TCCR0B TCCR0B FOC0A FOC0B --- --- WGM02 CS02 CS01 CS00 Su nombre es el acrónimo de Clear Timer onCompare Match y significa que el Timer se limpia cuando se produce una Coincidencia en la comparación de los registros TCNT0 yOCR0A. En este modo el Timer0 (su registro TCNT0) también empieza a contar desde 0x00 y se incrementa hasta que su valor sea igual al del registro OCR0A, en ese momento el registroTCNT0 se resetea y vuelve a contar desde 0x00. La Coincidencia también activa el flagOCF0A, el cual se puede usar para programar interrupciones. El registro OCR0A también es de lectura y escritura de modo que podemos establecer el tope hasta donde contará el Timer0. De cierta forma el auto-reseteo del Timer0 equivale a una auto-recarga. El diagrama indica la operación descrita. Diagrama simplificado del Timer0 para temporizaciones en modo CTC. En términos generales todo lo que puede hacer el registro OCR0A también lo puede hacer su gemelo OCR0B, pero en este caso en especial la regla se rompe: Aunque los comparadores trabajen en todo momento y hay una Coincidencia por cada uno de
  • 567. ellos, el modo CTC está reservado para operar únicamente con el registro OCR0A, es decir, aunque es posible que se activen los flags OCF0A y/o OCF0B, y se disparen inclusive sus interrupciones correspondientes, el Timer0 solo se resetea en su Coincidencia conOCR0A. Las aplicaciones del modo CTC son similares a las del modo Normal, o sea, las temporizaciones. Sin embargo, hay una diferencia que representa una ventaja o desventaja, según se vea. La auto-recarga del Timer0 en modo CTC es un suceso hardware que genera las más precisas temporizaciones independientemente de si el código está escrito en ensamblador, en un compilador o en otro. Pero la no necesidad de posteriores calibraciones software también implica que si la temporización obtenida no es la deseada, entonces no será posible ajustar su valor, a menos que volvamos a modificar el registro TCNT0, lo cual desnaturaliza este modo. Cálculo de la Temporización en Modo CTC La fórmula que nos da el tiempo entre coincidencia y coincidencia es la siguiente. Recuerda que en modo CTC está fórmula solo vale para el registro OCR0A y no paraOCR0B. Donde: Tabla de Temporización en Modo CTC con Timer0 Tiempo = Valor de la temporización. F_CPU = Frecuencia del XTAL del megaAVR. N = Factor de prescaler (1, 8, 64, 256 ó 1024). OCR0A = Valor de inicio del registro OCR0A. Tabla de Temporización en Modo CTC con Timer0 Nota: los factores de prescaler N del Timer2 son 1, 8, 32, 64, 128, 256 y 1024. Eso podría dar otras soluciones para N y TCNT2. De nuevo, esta ecuación tiene dos incógnitas (N y OCR0A). Como antes, es posible descomponerla en dos ecuaciones de una variable. La primera, para hallar N, es igual que en el modo Normal. De no resultar un valor exacto, también aquí debemos tomar el factor de prescaler superior más cercano.
  • 568. La segunda fórmula se obtiene despejando OCR0A de la fórmula inicial. Se presupone que para esto ya debimos haber hallado el valor de N. Si conseguimos para OCR0A una solución entera válida, será genial y no tendremos que recurrir a posteriores ajustes de precisión, sin importar en qué lenguaje o compilador se programe. De lo contrario, no habrá calibración en OCR0A que valga, de modo que será de poco alivio para el diseñador redondearlo a su valor más cercano, superior o inferior. En ese caso y si la precisión fuera realmente importante, se puede optar por cambiar de XTAL. Ya no vamos a poner ejemplos porque estas fórmulas se resuelven exactamente igual que en el Cálculo de las Temporizaciones en Modo Normal. Interrupciones del Timer0 y el Timer2 Esta vez el diagrama del Timer0 nos indica que debemos concentrarnos en los bits de flag que pueden disparar las interrupciones del Timer0.
  • 569. Flags de Desbordamiento y de Coincidencias del Timer0. La real potencia del Timer0 se deja apreciar al emplear su característica más notable: las interrupciones. El Timer0 tiene dos tipos de interrupciones: una por el desbordamiento de su registro TCNT0 y dos en las coincidencias de su registro TCNT0 con los registrosOCR0A y OCR0B. Estas interrupciones se controlan por los bits de los registros TIMSK0y TIFR0: TIMSK0 (Timer Interrupt Mask Register 0). Contiene los bits Enable de interrupciones. TIFR0 (Timer Interrupt Flags Register 0). Contiene los bits de Flag de interrupciones. Para quienes aún trabajan con los viejos megaAVR, ellos no tienen interrupción en coincidencia B, los bits de la coincidencia A son simplemente OCIE0 y OCF0, y los siguientes registros se llaman TIMSK y TIFR. No llevan el 0 porque también controlan las interrupciones del Timer1 y del Timer2. Registro TIMSK0 TIMSK0 --- --- --- --- --- OCIE0B OCIE0A TOIE0 --- --- --- --- --- OCF0B OCF0A TOV0 Registro TIFR0 TIFR0
  • 570. Interrupción por Desbordamiento del Timer0. El evento que puede disparar esta interrupción es el desbordamiento del registro TCNT0, o sea, la transición de 255 a 0. Esto implica la operación incremental del Timer0, sin importar si está contando en modo Normal, CTC o Fast PWM. En modo PWM de Fase Correcta el Timer0 cuenta en sube y baja sin pasar por la transición 255 a 0, así que en este modo no hay desbordamiento. El desbordamiento de Timer0 activará el flagTOV0 y si la interrupción está habilitada, se disparará. El bit TOV0 se limpia automáticamente al ejecutarse su función de interrupción ISR, pero también se puede limpiar por software, como de costumbre, escribiéndole un 1 y sin usar instrucciones de lectura-modificación-escritura como las generadas por las sentencias con el operador OR binario (|). Para habilitar la interrupción por Desbordamiento del Timer0 se setean los bitsTOIE0 y obviamente, el bit enable general de interrupciones I, del registro SREG. La instrucción del ensamblador dedicada a esta operación es SEI y que en lenguaje C se puede incrustar mediante la función macro del mismo nombre sei(). (No sé por qué repito estas cosas.) Interrupción en Coincidencia del Timer0. Como sabemos, los comparadores del Timer0 son circuitos que en todo momento están comparando los valores del registro TCNT0 con los registros OCR0A y OCR0B. Pues bien, el evento que puede disparar esta interrupción es la coincidencia entre los registros mencionados. Como puede haber dos coincidencias, aquí podemos tener hasta dos interrupciones. Especificando, cuando se detecte la igualdad entre TCNT0 y OCR0A se activará el flag OCF0A (Output Compare Flag 0 A), y cuando sean iguales TCNT0 y OCR0B se activará el flag OCF0B (Output Compare Flag 0 A). De nuevo, los flags se ponen a 1 independientemente de si sus interrupciones están habilitadas o no. Si lo están, se dispararán sus interrupciones, se ejecutarán las funciones ISR respectivas y los flagsOCF0A y/o OCF0B se limpiarán por hardware. Ya sobra decir que también se pueden limpiar por software escribiéndoles un uno. Ambas interrupciones son gemelas pero no son siamesas, es decir, funcionan exactamente igual pero no necesariamente se tienen que habilitar las dos al mismo tiempo. Se habilitan por separado seteando el bit OCIE0A para una y OCIE0B para la otra. Una observación: el circuito comparador (llamado Output Compare) trabaja siempre sin importar en qué modo está operando el Timer0 (Normal, CTC o PWM), aunque las implicancias no serán las mismas. Explico: una coincidencia en modo CTC resetea el registro TCNT0, mientras que en los otros modos el registro TCNT0 sigue su marchar sin hacer caso. Si captaste mi cháchara, habrás descubierto que es posible temporizar con la Interrupción en Coincidencia incluso si el Timer trabaja en modo PWM. El Timer0 y el Timer2 Como Contadores
  • 571. El modo Contador de los Timers es una variante de su operación en modo Normal. Se dice contador porque cuenta los pulsos (o flancos) de la señal aplicada en un pin del megaAVR. Esto nos lleva de regreso a la configuración del reloj del Timer y también a las diferencias entre el Timer0 y el Timer2 cuando sus relojes son externos. Antiguamente el Timer2 estaba más bien pensado para ser usado con un XTAL externo de reloj (de 32 kHz) conectado a los pinesTOSC1 y TOSC2 del AVR. De ahí sus características para trabajar asíncronamente con el reloj del sistema. Esa era su única forma de soportar un reloj externo, es decir, no podía usarse para contar pulsos o flancos sueltos. En los viejos AVR el Timer2 es de ese tipo. Por fortuna, el Timer2 de los nuevos AVR viene mejor equipado y ofrece la opción adicional de trabajar con una señal externa (sin XTAL) aplicada al pin TOSC1. De ese modo el Timer2 se pone a nivel del Timer0/1. En realidad tiene una limitación y una mejora al respecto. La limitación es que el Timer2 solo se incrementa con los flancos negativos de la señal externa, en tanto que el Timers0/1 puede programarse para que su avance sea con los flancos de bajada o de subida. La mejora es que el reloj externo del Timer2 sí pasa por el prescaler. En el Timer0/1 esto no es así por lo que el registro TCNT0 se incrementa o decrementa en uno por cada flanco de la señal externa. Se puede programar el factor de prescaler del Timer2 para que tenga un avance personalizado. Por ejemplo, si escogemos un factor de 8, entonces el registro TCNT0 podrá incrementarse cada 8 pulsos detectados en el pin TOSC1. Puedes ir a la sección El Prescaler del Timer0 y del Timer2 para revisar la estructura de los prescalers. En el Timer0 debemos configurar los bits de CS02:CS00 a 110 ó 111. Registro TCCR0B TCCR0B FOC0A FOC0B --- --- WGM02 CS02 CS01 Tabla CS02 CS02 CS01 CS00 Fuente de reloj del Timer0 1 1 0 Reloj externo en pin T0. El Timer0 avanza en el flanco de bajada. 1 1 1 Reloj externo en pin T0. El Timer0 avanza en el flanco de subida. CS00
  • 572. En el Timer2 los bits CS nos dan más opciones. Registro TCCR2B TCCR2B FOC2A FOC2B --- --- WGM22 CS22 CS21 CS20 Tabla CS22 CS22 CS21 CS20 Fuente de reloj del Timer2 0 0 1 clkT2S (Sin prescaler) 0 1 0 clkT2S/8 (Desde el prescaler) 0 1 1 clkT2S/32 (Desde el prescaler) 1 0 0 clkT2S/64 (Desde el prescaler) 1 0 1 clkT2S/128 (Desde el prescaler) 1 1 0 clkT2S/256 (Desde el prescaler) 1 1 1 clkT2S/1024 (Desde el prescaler) Tengamos en cuenta que, siendo el modo Contador una interpretación particular del modo Normal, siguen latentes todas las funciones de temporización del Timer0, incluyendo las interrupciones en el Desbordamiento y en las Coincidencias. De hecho, también es posible temporizar con la señal externa, siempre que sea una onda cuadrada periódica, claro está. La única consideración es que la señal de T0 o T1 jamás debería superar, o igualar siquiera, la performance del reloj del sistema F_CPU, para permitir la sincronización entre ellas. Por otro lado, el reloj externo del timer2 no está sincronizado y en ese caso la manipulación de sus registros de datos puede requerir el uso del registro ASSR. El Timer0 y el Timer2 en Modo PWM
  • 573. Inicialmente era el Timer1 el que fue plenamente equipado para generar ondas PWM. Si bien los otros Timers también ofrecen esa funcionalidad, aún tienen muchas limitaciones. El Timer0 de los AVR actuales puede producir hasta dos canales PWM, por los pines OC0Ay OC0B. El Timer2 hace lo propio por los pines OC2A y OC2B. Estos pines deberán estar configurados como salida para dar salida a las señales PWM. Esta es una característica que distingue a los módulos PWM de los demás periféricos del megaAVR donde la configuración y control de los pines involucrados quedan a cargo del módulo respectivo. Dada la paridad entre los dos Timers y entre los dos canales PWM, volvemos a aclarar que la siguiente exposición está relacionada solo al canal A del Timer0, dando por sentado que es también aplicable al canal B, tanto del Timer0 como del Timer2. Tabla WGM02 WGM02 WGM01 WGM00 Modo de Operación del Timer0 Inicio del Conteo Tope del Conteo 0 0 1 PWM de Fase Correcta 0x00 0xFF 0 1 1 Fast PWM 0x00 0xFF 1 0 1 PWM de Fase Correcta 0x00 OCR0A 1 1 1 Fast PWM 0x00 OCR0A La tabla de arriba nos muestra que el Timer0 puede generar dos tipos de ondas PWM, Fast PWM y PWM de Fase Correcta, según la forma como avanza el registro TCNT0. Cada uno de estos modos tiene a su vez una variante (aparecen sombreados en la tabla), donde el tope del conteo es el valor del registro OCR0A. Estos modos son especiales que suelen entorpecer la comprensión del modo PWM de los Timers. Así que los ignoraremos por el momento y en adelante asumiremos que el valor tope del Timer0 es siempre de 255. Con eso en mente podemos seguir. Timer0 en modo Fast PWM El modo Fast PWM se conoce también como PWM de pendiente única porque el registroTCNT0 avanza siempre hacia arriba, es decir, cuenta desde 0 hasta un valor TOPE que es255, después de lo cual se resetea y vuelve a empezar desde 0. Si graficamos este progreso, la curva resulta siendo efectivamente una escalera de pendiente única, como se ve abajo.
  • 574. Como se indica en la figura, la onda Fast PWM es generada por la conmutación del pinOC0A cada vez que el registro TCNT0 llega al tope y cada vez que TCNT0 coincide con el registro OCR0A. En la gráfica las coincidencias se señalan con pequeñas líneas rojas horizontales sobre la escalera. Según la configuración de los bits COM0A1 y COM0A0(del registro TCCR0A), la onda PWM puede ser invertida o no invertida. Si los bits COM0A1:0 valen 0b10 se establece una onda no-invertida, esto es, el pin OC0A se setea cuando TCNT0 llega al tope y se limpia cuando TCNT0 coincide con el registro OCR0A. Si los bits COM0A1:0 valen 0b11 se establece una onda invertida, esto es, el pinOC0A se limpia cuando TCNT0 llega al tope y se setea cuando TCNT0 coincide con el registro OCR0A. Puesto que el Timer0 siempre cuenta en todo su rango de 0 a 255, se deduce que el periodo, y por ende la frecuencia, de la onda PWM también serán constantes. Esa es la limitación del modo PWM de los Timers de 8 bits. Por otro lado, sí es posible modificar el valor del registro OCR0A, lo cual nos permitirá controlar el duty cycle de la onda PWM. En la siguiente imagen se indican las fórmulas para calcular el periodo y el duty cycle de una onda Fast PWM no-invertida. Como siempre, F_CPU es la frecuencia del procesador yN es el factor del prescaler. Recuerda que N tiene más posibles valores en
  • 575. el Timer 2 que en el Timer0. Para más detalles puedes revisar la sección relojes del Timer0 y del Timer2. Periodo y Duty cycle de una onda Fast PWM no-invertida. La frecuencia de la onda Fast PWM se obtiene invirtiendo la fórmula del periodo: Y la fórmula que nos da el duty cycle expresado en porcentaje la obtenemos dividiendo las fórmulas del duty cycle y del periodo: En esta fórmula podemos observar que si el registro OCR0A toma su valor máximo de 255, entonces el duty cycle será del 100%, lo cual significa que el estado del pin OC0Aserá de 1 lógico constante. En cambio, no hay ningún para el registro OCR0A que nos dé un duty cycle del 0%. Si el registro OCR0A vale 0 la salida será de unos pequeños picos que representan un duty cycle cercano al 0.4%. Ésa es la característica que diferencia al modo Fast PWM de los demás modos PWM. Timer0 en modo PWM de Fase Correcta El modo PWM de Fase Correcta se conoce también como PWM de doble pendiente porque el registro TCNT0 cuenta en sube y baja, es decir, primero avanza desde 0 hasta un valorTOPE (255) y después regresa de 255 hasta 0. Este proceso se repite cíclicamente y si lo graficamos, la curva resulta siendo efectivamente una escalera de doble pendiente, como se ve abajo.
  • 576. Como se indica en la figura, la onda PWM de Fase Correcta es generada por la conmutación del pin OC0A cada vez que el registro TCNT0 coincide con el registroOCR0A. En la gráfica las coincidencias se señalan con pequeñas líneas rojas horizontales sobre la escalera. Según la configuración de los bits COM0A1 y COM0A0 (del registroTCCR0A), la onda PWM puede ser invertida o no invertida. Si los bits COM0A1:0 valen 0b10 se establece una onda no-invertida, esto es, el pin OC0A se setea cuando TCNT0 coincide con el registro OCR0A en su conteo descendente, y se limpia cuando TCNT0 coincide con el registro OCR0A en su conteo ascendente. Si los bits COM0A1:0 valen 0b11 se establece una onda invertida, esto es, el pinOC0A se setea cuando TCNT0 coincide con el registro OCR0A en su conteo ascendente, y se limpia cuando TCNT0 coincide con el registro OCR0A en su conteo descendente. Puesto que el Timer0 siempre cuenta en todo su rango de 0 a 255, se deduce que el periodo, y por ende la frecuencia, de la onda PWM también serán constantes. Esa es la limitación del modo PWM de los Timers de 8 bits. Por otro lado, sí es posible modificar el valor del registro OCR0A, lo cual nos permitirá controlar el duty cycle de la onda PWM.
  • 577. En la siguiente imagen se indican las fórmulas para calcular el periodo y el duty cycle de una onda PWM de Fase Correcta no-invertida. Como siempre, F_CPU es la frecuencia del procesador y N es el factor del prescaler. Recuerda que N tiene más posibles valores en el Timer 2 que en el Timer0. Para más detalles puedes revisar la sección relojes del Timer0 y del Timer2. Periodo y Duty cycle de una onda PWM de Fase Correcta no-invertida. La frecuencia de la onda PWM de Fase Correcta se obtiene invirtiendo la fórmula del periodo. Observa que la doble pendiente de este modo hace que la máxima frecuencia obtenida sea la mitad de la frecuencia máxima que brinda el modo Fast PWM. Y la fórmula que nos da el duty cycle expresado en porcentaje la obtenemos dividiendo las fórmulas del duty cycle y del periodo: A diferencia del modo Fast PWM en esta fórmula podemos observar que el duty cycle puede abarcar todo su rango desde 0% (con OCR0A = 0) hasta el 100% (con OCR0A = 255). El 0% significa que la salida será un constante 0 lógico y un 100%, que la salida será un estado alto constante. Ésa es la razón por la que se denomina de fase correcta. Práctica: Temporización con Sondeo del Timer0 El programa genera una onda cuadrada conmuta cada 2.5 ms, pero como ya estamos en temas serios, la temporización debe ser lo más precisa posible, ni 1 µs más ni 1 µs menos. Visto de otro modo, el programa genera una señal de onda cuadrada de 200 Hz.
  • 578. Circuito para probar el Timer0 del microcontrolador AVR. El código fuente /****************************************************************************** * FileName: main.c * Purpose: Timer0 - Operación en modo Temporizador * Processor: ATmega164P * Compiler: AVR IAR C y AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. * * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta * licencia y la nota de autor de arriba. *****************************************************************************/
  • 579. #include "avr_compiler.h" voidPause(void);// Prototipo de función //************************************************************************ // Función principal //************************************************************************ intmain(void) { DDRA=0x01;// PA0 salida de señal /* Configuración del Timer0 * Modo de operación = Normal * Factor de prescaler = 256 */ TCCR0A=0x00; TCCR0B=(1<<CS02); while(1)// Loop forever { PINA=0x01;// Conmutar pin PA0 Pause();// Delay de 2499.55 us } }
  • 580. //************************************************************************ // Produce 2499.55 µs exactamente // Con el Prescaler de 256 y con con XTAL de 8 MHz el Timer0 se incrementa // cada 256/8 = 32 us. Por tanto, para alcanzar 2499.55 µs se requieren de // 2499.55/32 = 78.1 ticks. Ergo, TCNT0 se debe cargar con 256-78.1 = 178 //************************************************************************ voidPause(void) { GTCCR=(1<<PSRSYNC);// Resetaer prescaler TCNT0=178;// Cargar registro TCNT0 TIFR0=(1<<TOV0);// Limpiar flag de desbordamiento del Timer0 while((TIFR0&(1<<TOV0))==0);// Esperar hasta que ocurra el desbordamiento nop();nop(); nop();nop(); nop();nop(); nop();nop();// NOPs para ajustar la precisión nop();nop(); nop();nop(); nop();nop(); nop();nop(); nop(); } Descripción del programa
  • 581. El punto crucial del programa es el cálculo de la temporización. Según mi código, para que la señal cambie de nivel cada 2.5 ms, la funciónPause debería tomar 2499.55µs, ya que el bucle llega a dicha llamada cada 0.45 µs (lo vi en el simulador de Atmel Studio 6). Por supuesto, este valor puede variar de un compilador a otro porque cada cual compila a su modo. Inclusive varía en un mismo compilador según el nivel de optimización establecido o según el microcontrolador usado. Esta exposición la hago habiendo compilado el código con AVR GCC con nivel de optimización –Os. while(1)// Loop forever { PINA=0x01;// Conmutar pin PA0 Pause();// Delay de 2499.55 us } El factor de prescaler sería: Y el valor inicial del TCNT0 es: El ajuste de la temporización se ha conseguido añadiendo algunos nops en Pause. Esto es tiempo muerto pero son solo 2 us, nada comparado con los 2500 us del total. Para calibrar estas precisiones es aconsejable recurrir al Cronómetro de Proteus o alStopwatch de Atmel Studio 6. Quizá te puedas preguntar ¿qué gracia tiene realizar temporizaciones de este modo, si bien se pueden usar los delays? En primer lugar, los conocidos delays nunca son precisos de por sí y, en segundo lugar, son muy susceptibles de sufrir dilataciones debido a las interrupciones. Además, significan tiempo muerto que impiden que el CPU ejecute otras tareas. Por ejemplo, ¿cómo harías si quisieras revisar el estado del puerto serie continuamente pero solo por 1 segundo?, ¿acaso le pondrías un delay de 1s? No, ¿verdad? Bueno, puede haber varias soluciones pero la más recomendada suele ser usando delays a base de Timers, en especial con sus interrupciones. Práctica: Interrupción del Timer0 Se genera una onda cuadrada de frecuencia 500 Hz. Quizá creas que no es una práctica muy provechosa pero en realidad en la gran mayoría de aplicaciones el Timer0 funciona como se verá aquí. Dependerá del diseñador saber qué hacer con esta señal.
  • 582. Por ejemplo, aquí generaremos una onda PWM y al mismo tiempo una señal para bascular un LED en intervalos largos de tiempo, más allá de lo que permite el prescaler. Esta onda PWM se genera a nivel software y no tiene nada que ver con el modo PWM de los Timer0 (generado por hardware y con muchísima mejor performance). Esto es solo una demostración y hasta parece que el mismo datasheet recomienda no hacerlo. Pero como decía, se podrán encontrar otras aplicaciones donde la señal generada sea más útil, como las ondas PRM para controlar la velocidad de los motores de sus primeros robots que utilizaba Dale Heatherington. La onda PWM será de 500 Hz y con duty cycle variable mediante las teclas + y – del teclado. A la salida se puede conectar un pequeño motor DC o simplemente un LED, cuya intensidad de brillo variará. Por otro lado, el programa también hará parpadear un LED cada 500 ms, un tiempo que a priori no se puede conseguir así nada más. Esto para demostrar que las temporizaciones no tienen que ser tareas exclusivas. En el circuito el IRF730 puede ser sustituido por un IRF720, un IRF540 o cualquier otro similar. Circuito para probar el Timer0 del microcontrolador AVR.
  • 583. El código fuente /****************************************************************************** * FileName: main.c * Purpose: Interrupción del Timer0 en Modo Normal * Processor: ATmega164P * Compiler: AVR IAR C y AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. * * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta * licencia y la nota de autor de arriba. *****************************************************************************/ #include "avr_compiler.h" #include "usart.h" #define MaxDuty 20 // Máximo duty cycle volatileunsignedcharDuty;// Duty cycle actual /****************************************************************************** * Gestor de Interrupción por Desbordamoento del Timer0. * Esta interrupción se dispara cada 100 µs exactamente. * Genera una señal PWM con una frecuencia de 500 Hz *****************************************************************************/
  • 584. ISR(TIMER0_OVF_vect) { staticunsignedcharDgen=1;// Duty generator. Rango = [1:MaxDuty] staticunsignedintticks=0; TCNT0+=157;// Reponer registro TCNT0 if(Dgen<=Duty) PORTA|=(1<<0);// 1-> Salida de señal PWM else PORTA&=~(1<<0);// 0-> Salida de señal PWM if(++Dgen>MaxDuty)// Dgen=1;// Rango de Dgen es [1:MaxDuty] if(++ticks==5000)// Sig. bloque se ejecuta cada 5000×100u = 500ms { PINA=(1<<1);// Conmutar pin PA0 (LED) ticks=0; } } /****************************************************************************** * Main Function *****************************************************************************/ intmain(void)
  • 585. { DDRA=(1<<1)|(1<<0);// PA0 -> Salida de señal PWM // PA1 -> Salida LED Duty=0;// Rango = [0 : MaxDuty] usart_init(); puts("rn Interrupción del Timer0 en Modo Normal r"); puts("rn Control de Motor DC "); puts("rn (+) Aumentar velocidad"); puts("rn (-) Disminuir velocidad"); /* Configuración del Timer0 * - Modo de operación = Normal * - Factor de prescaler = 8 */ TCCR0A=0x00; TCCR0B=(1<<CS01); /* Habilitar Interrupción por Desbordamiento del Timer0 */ TIMSK0=(1<<TOIE0); sei(); for(;;) { if(kbhit())
  • 586. { chark=getchar(); // El valor de Duty está limitado al rango [0 : MaxDuty] if((k=='+')&&(Duty<MaxDuty)){ Duty++; } elseif((k=='-')&&(Duty>0)){ Duty--; } } } } Descripción del programa El principio el Timer0 trabaja en modo Normal como en el programa anterior, solo que se le configura para que dispare interrupciones cada 100 µs. Ahora no interesa por qué 100 µs. El hecho es que para tal cometido los cálculos indicaban que el factor de prescaler Ndebía ser de 8 y el valor a recargar en TCNT0 debía ser de 156, tal como se ve abajo. Y el valor inicial del TCNT0 será: A pesar de que el cálculo para TCNT0 resultó exacto, en el programa tuve que utilizar 157para conseguir los 100µs buscados. Como se predijo en la teoría, esto se debe a que la temporización es muy fina. Pero no solo se usa 157 en vez de 156 sino
  • 587. que en vez de una recarga directa como TCNT0 = 157 se utiliza una suma, TCNT0 += 157. Para las temporizaciones periódicas por interrupciones esta suma ayuda a disminuir las instrucciones de relleno que se deben colocar la ajustar la precisión. De hecho, como se ve, no tuve que añadir ni siquiera un nop como en la anterior práctica. Las interrupciones se disparan con total precisión cada 100us. El hecho de tener que calibrar manualmente estás temporizaciones a pesar de que los cálculos eran precisos se debe a que la sentencia de recarga del registro TCNT0 no se produce en el preciso instante en que se desborda el Timer0, puesto que la ejecución de lafunción de interrupción ISR implica la ejecución de cierto código como el salto a dicha función y el almacenamiento temporal que se hace de algunos datos del programa. Todo esto es código oculto y se ejecuta muy rápido, pero no tanto como para pasar desapercibido ante temporizaciones pequeñas del orden de los microsegundos. Mientras se ejecuta ese pequeño código el Timer sigue avanzando de modo que el cálculo teórico no siempre se reflejará en la práctica. Pero no todo es mala noticia. Aún nos falta experimentar con el modo CTC. En las siguientes prácticas veremos que el Timer en modo CTC utiliza su “recarga automática” para temporizar mejor sin necesidad de este tipo de ajustes. Ahora toca pensar en la temporización de 500 ms. Con nuestro XTAL de 8MHz y el máximo factor de prescaler para el Timer0 solo llegaríamos a 32.768 ms, que está bastante lejos de lo que buscamos. Aunque en las mismas condiciones el Timer1 puede temporizar hasta 8 388 608 ms, aquí vemos que no es necesario recurrir a él y así lo reservamos para su trabajo por excelencia que son las ondas PWM. Bueno, lo que hice en el programa es usar la anterior temporización de 100 µs para incrementar el contador ticks. Entonces deducimos que para alcanzar 500 ms, ticksdeberá llegar a 5000. Y allí lo tenemos. if(++ticks==5000)// Sig. bloque se ejecuta cada 5000×100u = 500ms { PINA=(1<<1);// Conmutar pin PA0 (LED) ticks=0; } Lo hecho equivale a hacer una temporización grande a base de varias temporizaciones menores. Otro ejemplo podría ser temporizar 50 segundos partiéndolos en 50000. Habíamos hablado de esto en las secciones teóricas, cuando veíamos los ejemplos cuyos cálculos no daban soluciones. También se pueden añadir más contadores para multiplicar más temporizaciones, o emplear más variables como Duty y Dgen para sacar otros canales PWM y controlar varios motores... Las ideas sobran.
  • 588. No voy a explicar el algoritmo de generación de la onda PWM porque creo que no viene al caso y porque es fácil de deducir. Posteriormente estudiaremos los Timers en modos PWM para generar ondas PWM de alta frecuencia y a nivel hardware. Una observación final: en la función principal el programa sondea el puerto serie para ver si llegaron datos. También se pudo utilizar la interrupción de recepción del USART para esta tarea y así poner al AVR en algún modo Sleep para ahorrar energía. Quizá se deba hacer en una aplicación final, pero como estas prácticas son de ejemplo,… Práctica: El Timer0 en Modo CTC Se utiliza el Timer0 para incrementar dos contadores, uno cada 1ms y el otro cada 100ms. Los contadores se pueden usar para establecer el tiempo de ejecución de otras rutinas. A manera de simple ejemplo, aunque no le encuentro mucha gracia, aquí se espera por 5 segundos a que el usuario ingrese una contraseña por el puerto serie para ahorrar en el circuito, pero que bien podría ser el prototipo de acceso a un sistema digitando la clave en un teclado matricial por un tiempo establecido. El tiempo se mide a partir del momento en que se presiona el primer dígito. Recuerdo haber hecho la pregunta ¿cómo haríamos para ejecutar una tarea durante cierto tiempo?, ya que, obviamente, para esto no se pueden recurrir a los típicos delays porque son tiempo muerto y poco precisos. Bueno, pues, en esta práctica veremos una solución. El circuito. El código fuente
  • 589. /****************************************************************************** * FileName: main.c * Purpose: Temporización con el Timer0 en Modo CTC usando Interrupciones * Processor: ATmega164P * Compiler: AVR IAR C y AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. * * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta * licencia y la nota de autor de arriba. *****************************************************************************/ #include "avr_compiler.h" #include "usart.h" charGetNumStr(char*,unsignedchar); volatileunsignedcharticks_ms; volatileunsignedcharticks_100ms; /****************************************************************************** * Gestor de Interrupción en Coincidencia del Timer0. * Esta función se ejecuta cada 1 ms exactamente. *****************************************************************************/
  • 590. ISR(TIMER0_COMPA_vect) { staticunsignedchari; ticks_ms++; if(++i>=100) { // Este bloque se ejecuta cada 100 ms ticks_100ms++; i=0; } } /****************************************************************************** * Main Function *****************************************************************************/ intmain(void) { constcharpsw[11]="1234";// password actual charbuffer[11]; DDRA=(1<<0);// PA0 -> Salida LED usart_init(); puts("rn Temporización con el Timer0 en Modo CTC usando Interrupciones");
  • 591. /* Configuración del Timer0 * - Modo de operación = CTC * - Factor de prescaler = 64 * - Periodo de auto-reset = 1 ms */ TCCR0A=(1<<WGM01); TCCR0B=(1<<CS00)|(1<<CS01); OCR0A=124;// Límite de TCNT0 /* Habilitar Interrupción en Coincidencia del Timer0 */ TIMSK0=(1<<OCIE0A); sei(); for(;;) { start: GetNumStr(buffer,0);// Reset internal counter puts("rr Ingrese su password r "); while(kbhit()==0);// Esperar a que lleguen datos al puerto serie GTCCR=(1<<PSRSYNC);// Resetaer prescaler TCNT0=0;// Resetear registro TCNT0 ticks_100ms=0; do{ // Estas rutinas se ejecutan durante 5 segundos. // Hasta que ticks_100ms = 50 (50×100ms = 5 segundos).
  • 592. if(GetNumStr(buffer,10))// Si se leyeron hasta 10 números { if(strcmp(buffer,psw)==0){ puts(" Password OK"); PORTA|=(1<<0);// Prender LED de PA0 puts("r Presione una tecla para salir"); getchar(); PORTA&=(~1<<0);// Apagar LED de PA0 gotostart; } else{ puts(" Error"); gotostart; } } }while(ticks_100ms<50); puts("r Timer out "); } } //**************************************************************************** // Lee una cadena de texto de 'len' números // El tamaño de 'buffer' debe ser mayor que 'len'. //**************************************************************************** charGetNumStr(char*buffer,unsignedcharlen) {
  • 593. charc;staticunsignedchari=0; if(len==0)i=0; if(kbhit()){ c=getchar(); if((c<='9'&&c>='0')&&(i<len)){// Si c está entre 0 y 9 buffer[i++]=c;// Guardar en buffer putchar(c);// Eco } elseif((c=='b')&&(i)){// Si c es RETROCESO y si i>0 i--;// putchar(c);// Eco } elseif((c=='r')&&(i)){// Si c es ENTER y si i>0 buffer[i]='0';// Poner un 0x00 (fin de cadena) putchar(c);// Eco i=0;// Resetear contador return1;// Retornar con 1 } } return0;// Retornar con 0 } Descripción del programa La Interrupción CTC o en Coincidencia del Timer0 incrementa los contadores ticks_ms yticks_100ms. Creo que es obvio cada cuánto tiempo se incrementan. Para generar el tiempo base de 1ms los valores de prescaler N del
  • 594. registro OCR0A los obtuve con las formulas presentadas en la sección Cálculo de la Temporización en Modo CTC. Observa que el cálculo salió preciso y esos mismos valores son los que aparecen en el programa. La temporización es precisa y no hay que preocuparse por recargas ni ajustes, ni nada. Genial, ¿verdad? Para terminar esto quiero mencionar que el factor de prescaler usado es bastante grande como para que el prescaler avance unos varios ticks que pueden adelantar la temporización. En la simulación vi que en vez de la espera de 5 segundos solo se cronometraban 4.973 segundos, así que tuve que resetear el prescaler y con eso el problema se arregló. GTCCR=(1<<PSRSYNC);// Resetaer prescaler Por supuesto que he dramatizado el tema, pero quise destacarlo para recordar que si bien puede haber otras aplicaciones donde sí resulte realmente serio, se debe pensar bien antes de hacerlo porque el prescaler es también compartido por el Timer1. Por ejemplo, si tuviera el Timer1 trabajando en modo PWM, yo no me atrevería a resetear el prescaler. Práctica: Control de Potencia Un poco más adelante nos dedicaremos a los motores DC. Pero antes me parece interesante controlar la velocidad de un pequeño motor AC monofásico o de algún dispositivo de potencia que no genere mucho ruido como por ejemplo una típica bombilla de 100W. Bueno yo sé que en estos tiempos eso ya no es nada típico, pero la idea es variar la alimentación de 220V o 110V de la red doméstica para controlar un dispositivo de ese calibre. El circuito no está blindado para filtrar los ruidos que pueden generar los motores AC de cierta magnitud, así que es recomendable que no sea muy grande. Aunque el programe funcione bien, los ruidos grandes “sacuden” al microcontrolador y perturban su operación.
  • 595. El circuito. El código fuente /****************************************************************************** * FileName: main.c * Purpose: Timer0 en Modo CTC * Processor: ATmega164P * Compiler: AVR IAR C y AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. *
  • 596. * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta * licencia y la nota de autor de arriba. *****************************************************************************/ #include "avr_compiler.h" #include "usart.h" #define MOC_OFF() (PORTA |= (1<<0)) #define MOC_ON() (PORTA &= ~(1<<0)) volatileunsignedchartop=0; volatileunsignedcharduty=0; volatileunsignedcharticks=0; //***************************************************************************** // Gestor de Interrupción INT0. // Esta función se ejecuta cuando se detectan flancos de subida en el pin INT0. //***************************************************************************** ISR(INT0_vect) { staticunsignedchari=0; staticunsignedintavr=0; if(duty) {
  • 597. MOC_ON(); } avr+=ticks; if(++i>=4){ i=0; avr=avr/4; top=(unsignedchar)avr+1; avr=0; } ticks=0; } //***************************************************************************** // Gestor de Interrupción en Coincidencia del Timer0. // Esta función se ejecuta cada 200 us exactamente. //***************************************************************************** ISR(TIMER0_COMPA_vect) { if(++ticks>=duty) { if(duty<top) { MOC_OFF(); } } }
  • 598. /****************************************************************************** * Main Function *****************************************************************************/ intmain(void) { DDRA=(1<<0);// PA0 -> Salida MOC usart_init(); printf("rn Control de Potencia "); printf("rn (+) Subir duty cycle "); printf("rn (-) Bajar duty cycle r"); /* Configuración del Timer0 * - Modo de operación = CTC * - Factor de prescaler = 8 * - Periodo de auto-reset = 200 us */ TCCR0A=(1<<WGM01); TCCR0B=(1<<CS01); OCR0A=199;// Límite de TCNT0 /* Habilitar parcialmente la Interrupción en Coincidencia del Timer0 */ TIMSK0=(1<<OCIE0A); /* Configurar y habilitar parcialmnete la interrupción INT0 para que se
  • 599. * dispare con cualquier flanco (de subida y/o de bajada) detectado en el * pin INTx */ EIMSK=(1<<INT0);// Habilitar INT0 EICRA=(1<<INT0*2);// Elegir flanco de bajada/subida (modo 1) sei();// Habilitación general de interrupciones for(;;) { if(kbhit()) { chark=getchar(); if(top==0){ printf("r No AC signal detected"); } elseif((k=='+')&&(duty<top)) { printf("r duty = %d",++duty); } elseif((k=='-')&&(duty)) { printf("r duty = %d",--duty); } }
  • 600. } } Descripción del programa No hay mucho que explicar. Los cálculos de la temporización se realizaron exactamente como en la anterior práctica. Salieron precisos y no fue necesario realizar calibraciones ni nada. La Interrupción INT0 se estudió tan ampliamente como lo estamos haciendo con los Timers. Su función en este programa es detectar los cruces por cero de la señal alterna AC. En ese momento se activa el opto-acoplador y también se reinicia el contador ticks, el cual tiene la función de medir el tiempo que dura cada semiperiodo de la señal alterna. En el código se promedian cuatro de estos tiempos, aunque creo que hubiera sido mejor añadirle un filtro software para los picos. La Interrupción en Coincidencia del Timer0pone fin al tiempo que el opto-acoplador permanece activo. Este tiempo está determinado por el valor de la variable ticks, el cual a su vez se incrementa desde 0 hasta duty, que es el duty cycle establecido por el usuario mediante la consola del puerto serie. No sé si seguir explicando o sugerirte que mejor vieras el resultado en un osciloscopio, aunque sea en Proteus. Se ve impresionante. La siguiente figura es una captura de la onda alterna de 220V solo que recortada con un duty cycle de 50%. Como siempre la práctica lleva su archivo de simulación de Proteus y la puedes descargar haciendo clic en la imagen del circuito.
  • 601. Práctica: El Timer0/2 Como Contador La práctica no puede ser más sencilla: el Timer0 contará los flancos de bajada generados por un pulsador conectado al pin T0 del AVR. El valor del registro TCNT0 será enviado al terminal serial. No se pondrá ningún mecanismo antirebote. Así que el Timer0 se incrementará con todo y los rebotes.
  • 602. Circuito para probar el Timer0 del microcontrolador AVR. El código fuente /****************************************************************************** * FileName: main.c * Purpose: Timer0 en Modo Normal como Contador * Processor: ATmega164P * Compiler: AVR IAR C y AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. * * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta * licencia y la nota de autor de arriba. *****************************************************************************/
  • 603. #include "avr_compiler.h" #include "usart.h" /****************************************************************************** * Main Function *****************************************************************************/ intmain(void) { unsignedchartcnt0; PORTB|=(1<<0);// Activar pull-up de pin PB0/T0 usart_init(); printf("rn Timer0 como Contador de Pulsos r"); /* Configuración del Timer0 * - Modo de operación = Normal * - Fuente de reloj = Pin T0 * El registro TCNT0 se incrementa con los flancos de bajada del pin T0 */ TCCR0A=0X00; TCCR0B=(1<<CS02)|(1<<CS01); /* Resetear TCNT0 */ TCNT0=0x00;
  • 604. tcnt0=~TCNT0; for(;;) { if(tcnt0!=TCNT0) { tcnt0=TCNT0; printf("r Valor del registro TCNT0 = %d",tcnt0); } } } Práctica: Teclado Alfanumérico basado en teclado matricial de 4x4 El ejemplo más notorio de este tipo de teclados lo tenemos en los teléfonos celulares. Allí vemos cómo unas pocas teclas se multiplican para permitir el ingreso de un conjunto amplio de caracteres entre letras, números y símbolos ortográficos o matemáticos. En esta práctica aprenderemos una forma de implementarlo. Había intentado más de una vez escribir este programa prescindiendo de las interrupciones del Timer y así incluirlo en un capítulo previo, pero había fracasado. Fue recién después de terminarlo como está ahora que hallé el algoritmo que buscaba. Pero bueno, aquí va de todas formas (con interrupción del Timer0). Emularemos el modo de ingreso de texto en un celular donde cada tecla vale por varios dígitos. Cuando pulsamos seguidamente una tecla se nos van mostrando los caracteres que representa mientras no pasemos un tiempo de ventana entre pulsada y pulsada. El carácter escogido será el que dejemos permanecer por más de ese tiempo. El teclado aceptará todos los caracteres ASCII visibles. Los caracteres validados se irán almacenando en un buffer. Por defecto las letras se presentan en minúsculas. Para pasar a mayúsculas debemos presionar la tecla MAY. Eso además encenderá un LED. Para regresar al modo de minúsculas presionamos MAY otra vez. Las otras teclas de función son: DEL: permite borrar el carácter actual del buffer.
  • 605. RST: Borra todas los caracteres del buffer. ENT: Reproduce todo el contenido del buffer. El enunciado suena interesante pero no se deja apreciar a plenitud en el circuito que armaremos donde como siempre y para facilitar su implementación usaremos la interface con la computadora para visualizar los datos en ella. Ahora que si alguien tiene un poco más de paciencia para añadirle un LCD puede descargar esta práctica. Los resultados se ven mucho mejor en un LCD. El pequeño problema es que en ese programa la rutina de ingreso de símbolos me quedó incompleta. Será para una próxima actualización. Circuito para teclado alfanumérico basado en teclado matricial de 4x4. Los códigos fuente El programa utiliza la interrupción del Timer0 para incrementar un contador que a su vez usaremos para medir el tiempo entre las pulsadas de una tecla. La temporización se reinicia si se presiona otra tecla a la vez que se valida el carácter de la tecla previa. La temporización se detiene mientras una tecla permanezca presionada. El código del teclado ha sido encerrado en una librería de archivos keypad.h y keypad.c. Con eso el código fuente principal queda así:
  • 606. /****************************************************************************** * FileName: main.c * Purpose: Teclado alfanumérico basado en teclado matricial de 4x4 * Processor: AVR ATmegaXX4 * Compiler: AVR IAR C & AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. * * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta * licencia y las notas de autor y copyright de arriba. *****************************************************************************/ #include "avr_compiler.h" #include "usart.h" #include "keypad.h" /****************************************************************************** * Mapa de teclas *****************************************************************************/ PROGMEMconstcharsymbol[]={'*','!','"','#','$','%','&',''','(', ')','+','-','/',':',';','<','=','>','?', '@','[','',']','^','_','{','|','}','~'}; PROGMEMconstcharkeys[16][5]={
  • 607. {'1','a','b','c',4},{'2','d','e','f',4},{'3','g','h','i',4},{'R',0,0,0,1}, {'4','j','k','l',4},{'5','m','n','o',4},{'6','p','q','r',4},{'D',0,0,0,1}, {'7','s','t','u',4},{'8','v','w','x',4},{'9','y','z',' ',3},{'M',0,0,0,1}, {'*',0,0,0,sizeof(symbol)},{'0','.',',',0,3},{' ',0,0,0,1},{'E',0,0,0,1}}; volatileunsignedcharticks_4ms; /****************************************************************************** * Gestor de 'Interrupción en Coincidencia del Timer0'. * Esta función se ejecuta cada 4 ms exactamente. *****************************************************************************/ ISR(TIMER0_COMPA_vect) { if(ticks_4ms<255) ticks_4ms++; } /****************************************************************************** * Función principal *****************************************************************************/ intmain(void) { unsignedcharkey,old_key;// Desplazamientos de teclas charckey;// valor ASCCI de tecla unsignedcharold_ticks_4ms; unsignedcharidx,index;
  • 608. staticcharmessage[64];// El mensaje a recibir puede ser de hasta 64 caracteres DDRD|=(1<<PD5);// Pin PA0 = salida para LED indicador de mayúsculas PORTD&=~(1<<PD5);// Iniciar con LED apagado (minúsculas) usart_init();// Inicializar USART0 @ 9600 N 1 puts("rn Teclado Alfanumérico rn"); /****************************************************************** * Configuración del Timer0 * - Bits WGM: Modo de operación = CTC * - Bits COM: Operación normal de pines OC0A y OC0B * - Bits CS: Fuente de reloj = F_CPU/256 (Prescaler = N = 256) *****************************************************************/ TCCR0A=(1<<WGM01); TCCR0B=(1<<CS02); OCR0A=124;// Límite de TCNT0 /* Habilitar 'Interrupción en Coincidencia del Timer0' */ TIMSK0=(1<<OCIE0A); sei(); index=0; message[index]=0x00;
  • 609. while(1) { key=keypad_read();// Leer teclado if(key<16)// Sí hubo Tecla pulsada { /*****************************************************************/ switch(pgm_read_byte(&(keys[key][0]))) { case'E':// Si es tecla ENT message[index]=0x00;// Poner fin de cadena puts(message);// Mostrar el mensaje putchar('r'); keypad_released(); break; case'R':// Si es tecla RST while(index) message[--index]=0x00;// Borrar todo lo escrito keypad_released(); break; case'D':// Si es tecla DEL if(index) message[--index]=0x00;// Borrar última tecla keypad_released(); break;
  • 610. case'M':// Si es tecla MAY PIND|=(1<<PD5);// Conmutar LED = flag de mayúsculas keypad_released(); break; default:// Las demás teclas son multiplexadas /*********************************************************/ ticks_4ms=0;// Iniciar temporización idx=0;// Iniciar índice idx do{ if(key<16) { /************************************************ * Obtener valor ASCCI de la tecla desde el array * symbol o desde el array keys según sea el caso. ************************************************/ if(pgm_read_byte(&(keys[key][0]))=='*') ckey=pgm_read_byte(&(symbol[idx])); else ckey=pgm_read_byte(&(keys[key][idx])); /************************************************ * Si está encendido el LED de mayúsculas y si es * una letra minúscula, convertirla en mayúscula. ************************************************/ if((PIND&(1<<5))&&('a'<=ckey)&&(ckey<='z'))
  • 611. ckey=ckey-32; /************************************************ * Visualizar el carácter, actualizar old_key y * validar key. ************************************************/ putchar(ckey); old_key=key; key=16; /************************************************ * Detener la temporización mientras la tecla * esté pulsada. ************************************************/ old_ticks_4ms=ticks_4ms; keypad_released(); ticks_4ms=old_ticks_4ms; } /************************************************ * Volver a leer el teclado. ************************************************/ key=keypad_read(); if(key<16)// Si hubo tecla pulsada { if(key==old_key) {
  • 612. /********************************************** * Si es la misma tecla, avanzamos el índice idx * cíclicamente y reniciamos la temporización **********************************************/ if(idx<(pgm_read_byte(&(keys[key][4]))-1)) idx++; else idx=0; ticks_4ms=0; } else { /********************************************** * Si es otra tecla, forzamos a que termine * la temporización. **********************************************/ ticks_4ms=255; } if(pgm_read_byte(&(keys[key][4]))==1) { /********************************************** * Si es una tecla mono-función, forzamos a que * termine la temporización para salir. **********************************************/ ticks_4ms=255; }
  • 613. } }while(ticks_4ms<150);// Mientras no pasen 150*4 = 0.6 segundos if(index<(sizeof(message)-1))// Si queda espacio en buffer { message[index++]=ckey;// Almacenar tecla putchar('r');// Salto de línea } /*********************************************************/ } } } } En seguida tenemos los archivos de la librería del teclado. Las funciones proveídas devuelven un código entre 0 y 15 si se encuentra alguna tecla pulsada; es el programa principal el que se encarga de interpretar qué dígito de la tecla se valida. /****************************************************************************** * FileName: keypad.h * Purpose: Librería para Teclado matricial de 4x4 * Processor: AVR ATmegaXX4 * Compiler: AVR IAR C & AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. * * License: Se permiten el uso y la redistribución de este código con
  • 614. * modificaciones o sin ellas, siempre que se mantengan esta * licencia y las notas de autor y copyright de arriba. *****************************************************************************/ #include "avr_compiler.h" #include "avr_compiler.h" //**************************************************************************** // Configuración del puerto de interface //**************************************************************************** #define kpd_PORT PORTB // Port write #define kpd_PIN PINB // Port read #define kpd_DDR DDRB // Dirección de puerto //**************************************************************************** // Prototipos de funciones //**************************************************************************** charkeypad_read(void);// Retorna el valor ASCII de la tecla presionada // o retorna 0x00 si no se presionó nada voidkeypad_released(void);// Espera hasta que el teclado esté libre charkeypad_scan(void);// Escanea el teclado /******************************************************************************
  • 615. * FileName: keypad.c * Purpose: Librería para Teclado matricial de 4x4 * Processor: AVR ATmegaXX4 * Compiler: AVR IAR C & AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. * * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta * licencia y las notas de autor y copyright de arriba. *****************************************************************************/ #include "avr_compiler.h" #include "keypad.h" /***************************************************************************** * Mapa de desplazamientos de las teclas * =================== * | 0|1 |2 |3 | * ------------------- * | 4|5 |6 |7 | * ------------------- * | 8 | 9 | 10 | 11 | * ------------------- * | 12 | 13 | 14 | 15 |
  • 616. * =================== *****************************************************************************/ //**************************************************************************** // Escanea el teclado y retorna desplazamiento de la tecla presionada por // al menos 25ms. En otro caso retorna 0xFF. //**************************************************************************** charkeypad_read(void) { unsignedchari,k; k=keypad_scan();// Escanear teclado if(k<16)// Si hubo alguna tecla pulsada { for(i=0;i<25;i++) { if(k!=keypad_scan()) return0xFF; delay_us(1000); } } returnk; } //**************************************************************************** // Espera hasta que el teclado quede libre. //****************************************************************************
  • 617. voidkeypad_released(void) { delay_us(10);// while(keypad_scan()!=0xFF)// Mientras se detecte alguna tecla pulsada continue;// seguir escaneando. } //**************************************************************************** // Escanea el teclado y retorna el desplazamiento de la primera tecla que // encuentre pulsada. De otro modo retorna 0xFF //**************************************************************************** charkeypad_scan(void) { unsignedcharCol,Row; charRowMask,ColMask; kpd_DDR=0x0F;// Nibble alto entrada, nibble bajo salida kpd_PORT=0xF0;// Habilitar pull-ups del nibble alto RowMask=0xFE;// Inicializar RowMask a 11111110 for(Row=0;Row<4;Row++) { kpd_PORT=RowMask;// delay_us(10);// Para que se estabilice la señal ColMask=0x10;// Inicializar ColMask a 00010000
  • 618. for(Col=0;Col<4;Col++) { if((kpd_PIN&ColMask)==0)// Si hubo tecla pulsada { kpd_DDR=0x00;// Todo puerto entrada otra vez return4*Row+Col;// Retornar desplazamiento de tecla pulsada } ColMask<<=1;// Desplazar ColMask para escanear }// siguiente columna RowMask<<=1;// Desplazar RowMask para escanear RowMask|=0x01;// siguiente fila } // Se llega aquí si no se halló ninguna tecla pulsada kpd_DDR=0x00;// Todo puerto entrada otra vez return0xFF;// Retornar Código de no tecla pulsada } Registros del Timer0 Registro TCCR0A TCCR0A COM0A1 Registro TCCR0B COM0A0 COM0B1 COM0B0 --- --- WGM01 WGM00
  • 619. TCCR0B FOC0A FOC0B --- --- --- --- --- --- --- --- --- TSM --- --- WGM02 CS02 CS01 CS00 --- OCIE0B OCIE0A TOIE0 --- --- OCF0B OCF0A TOV0 --- --- --- PSRASY PSRSYNC Registro TCNT0 TCNT0 Registro OCR0A OCR0A Registro OCR0B OCR0B Registro TIMSK0 TIMSK0 Registro TIFR0 TIFR0 Registro GTCCR GTCCR TCCR0A – Timer/Counter Control Register 0 A Registro TCCR0A TCCR0A COM0A1 COM0A0 COM0B1 COM0B0 --- --- WGM01 WGM00 Registro de Microcontrolador COM0A1: 0 Compare Match Output A Mode Estos bits controlan el comportamiento del pin Output Compare (OC0A). Si uno o los dos bits COM0A1:0 están seteados, la salida OC0A tiene prioridad sobre la funcionalidad normal del pin al que está conectado. Sin embargo, note que el bit del registro DDR correspondiente al pin OC0A debe estar configurado como salida para habilitar el driver de salida. Cuando OC0A está conectado al pin la función de los bits COM0A1:0 depende de la configuración de los bits WGM02:0. La siguiente tabla muestra la funcionalidad de los bits COM0A1:0 cuando los bits WGM02:0 están configurados en modo Normal oCTC (no PWM). Tabla COM0A1
  • 620. COM0A1 COM0A0 Descripción 0 0 OC0A desconectado. Operación normal del pin 0 1 OC0A conmuta en Coincidencia de TCNT0 y OCR0A 1 0 OC0A se limpia en Coincidencia de TCNT0 y OCR0A 1 1 OC0A se setea en Coincidencia de TCNT0 y OCR0A La siguiente tabla muestra la funcionalidad de los bits COM0A1:0 cuando los bits WGM02:0 están configurados en modo Fast PWM. Nota: ocurre un caso especial cuando OCR0A es el tope del conteo y el bit COM0A1 vale uno. En este caso se ignora la Coincidencia, pero la puesta a cero o a uno de OC0A se produce al llegar a 0x00. Tabla COM0A1 COM0A1 COM0A0 Descripción 0 0 OC0A desconectado. Operación normal del pin 0 1 WGM02 = 0: OC0A Desconectado, Operación normal del pin. WGM02 = 1: OC0A conmuta en Coincidencia. 1 0 OC0A se limpia en la Coincidencia, y se setea al llegar a 0x00 (modo no-invertido) 1 1 OC0A se setea en la Coincidencia, y se limpia al llegar a 0x00 (modo invertido). La siguiente tabla muestra la funcionalidad de los bits COM0A1:0 cuando los bits WGM02:0 están configurados en modo PWM de Fase Correcta. Nota: ocurre un caso especial cuando OCR0A es el tope del conteo y el bit COM0A1 vale uno. En este caso se ignora la Coincidencia, pero la puesta a cero o a uno de OC0A se produce al llegar al tope. Tabla COM0A1 COM0A1 COM0A0 Descripción
  • 621. 0 0 1 1 COM0B1: 0 0 OC0A desconectado. Operación normal del pin 1 WGM02 = 0: OC0A Desconectado, Operación normal del pin. WGM02 = 1: OC0A conmuta en Coincidencia. 0 OC0A se limpia en la Coincidencia cuando se cuenta hacia arriba, y se setea en la Coincidencia cuando se cuenta hacia abajo. 1 OC0A se setea en la Coincidencia cuando se cuenta hacia arriba, y se limpia en la Coincidencia cuando se cuenta hacia abajo. Compare Match Output B Mode Estos bits controlan el comportamiento del pin Output Compare (OC0B). si uno o los dos bits COM0B1:0 están seteados, la salida OC0B tiene prioridad sobre la funcionalidad normal del pin al que está conectado. Sin embargo, note que el bit del registro DDR correspondiente al pin OC0B debe estar configurado como salida para habilitar el driver de salida. Cuando OC0B está conectado al pin la función de los bits COM0B1:0 depende de la configuración de los bits WGM02:0. La siguiente tabla muestra la funcionalidad de los bits COM0B1:0 cuando los bits WGM02:0 están configurados en modo Normal oCTC (no PWM). Tabla COM0B1 COM0B1 COM0B0 Descripción 0 0 OC0B desconectado. Operación normal del pin 0 1 OC0B conmuta en Coincidencia de TCNT0 y OCR0B 1 0 OC0B se limpia en Coincidencia de TCNT0 y OCR0B 1 1 OC0B se setea en Coincidencia de TCNT0 y OCR0B La siguiente tabla muestra la funcionalidad de los bits COM0B1:0 cuando los bits WGM02:0 están configurados en modo Fast PWM. Nota: ocurre un caso especial cuando OCR0B es el tope del conteo y el bit
  • 622. COM0B1 vale uno. En este caso se ignora la Coincidencia, pero la puesta a cero o a uno de OC0B se produce al llegar a 0x00. Tabla COM0B1 COM0B1 COM0B0 Descripción 0 0 OC0B desconectado. Operación normal del pin 0 1 Reservado 1 0 OC0A se limpia en la Coincidencia, y se setea al llegar a 0x00 (modo no-invertido) 1 1 OC0A se setea en la Coincidencia, y se limpia al llegar a 0x00 (modo invertido). La siguiente tabla muestra la funcionalidad de los bits COM0B1:0 cuando los bits WGM02:0 están configurados en modo PWM de Fase Correcta. Nota: ocurre un caso especial cuando OCR0B es el tope del conteo y el bit COM0B1 vale uno. En este caso se ignora la Coincidencia, pero la puesta a cero o a uno de OC0B se produce al llegar al tope. Tabla COM0B1 COM0B1 COM0B0 Descripción 0 0 OC0B desconectado. Operación normal del pin 0 1 Reservado 0 OC0B se limpia en la Coincidencia cuando se cuenta hacia arriba, y se setea en la Coincidencia cuando se cuenta hacia abajo. 1 OC0B se setea en la Coincidencia cuando se cuenta hacia arriba, y se limpia en la Coincidencia cuando se cuenta hacia abajo. 1 1 Bits 3:2 Reservados Estos bits están reservados en los ATmega164A/PA, ATmega324A/PA,
  • 623. ATmega644A/PA y ATmega1284/P, y siempre se leerán como cero. WGM01:0 Waveform Generation Mode Estos bits se combinan con el bit WFGM02 del registro TCCR0B. Juntos controlan la secuencia de conteo del contador, el valor máximo del conteo (el tope) y el tipo de la forma de onda que se generará. Los modos operación que soporta la unidad del Timer/Counter son: modo Normal (contador), modo Clear Timer on Compare Match (CTC), y dos tipos de Modulación de Ancho de Pulso (PWM). Tabla WGM02 WGM02 WGM01 WGM00 Modo de Operación del Timer0 Tope del Conteo 0 0 0 Normal 0xFF 0 0 1 PWM de Fase Correcta 0xFF 0 1 0 CTC OCR0A 0 1 1 Fast PWM 0xFF 1 0 0 Reservado --- 1 0 1 PWM de Fase Correcta OCR0A 1 1 0 Reservado --- 1 1 1 Fast PWM OCR0A TCCR0B – Timer/Counter Control Register 0 B Registro TCCR0B TCCR0B FOC0A FOC0B --- --- WGM02 CS02 CS01 CS00 Registro de Microcontrolador FOC0A Force Output Compare A El bit FOC0A solo está activo cuando los bits WGM establecen un modo no PWM. Sin embargo, para asegurar la compatibilidad con futuros dispositivos, este bit se debe mantener en cero al escribir en TCCRB cuando el Timer está operando en
  • 624. uno de los modos PWM. Si se escribe un uno en el bit FOCA, se fuerza una Coincidencia inmediata en la Unidad Generadora de Forma de Onda. La salida de OC0A cambia de acuerdo con la configuración de los bits COM0A1:0. Note que el bit FOC0A se implementa como strobe. Así que es el valor presente en los COM0A1:0 los que determinan el efecto de la Coincidencia forzada. Un strobe de FOC0A no generará ninguna interrupción ni tampoco reseteará el Timer en modo CTC si se usa OCR0A como tope del conteo. El bit FOC0A siempre se lee como cero. FOC0B Force Output Compare B El bit FOC0B solo está activo cuando los bits WGM establecen un modo no PWM. Sin embargo, para asegurar la compatibilidad con futuros dispositivos, este bit se debe mantener en cero al escribir en TCCRB cuando está operando en modo PWM. Si se escribe un uno en el bit FOC0B, se fuerza una Coincidencia inmediata en la Unidad Generadora de Forma de Onda. La salida de FOC0B cambia de acuerdo con la configuración de los bits COM0B1:0. Note que el bit FOC0B se implementa como strobe. Así que es el valor presente en los COM0B1:0 los que determinan el efecto de la Coincidencia forzada. Un strobe de FOC0B no generará ninguna interrupción ni tampoco reseteará el Timer en modo CTC si se usa OCR0B como tope del conteo. El bit FOC0B siempre se lee como cero. Bits 5:4 Reservados Estos bits están reservados y siempre se leerán como cero. WGM02 Waveform Generation Mode Ver la descripción de los bits WGM01:0 del registro TCCR0A. CS02:0 Clock Select Los tres bits de Clock Select seleccionan la fuente de reloj que usará el Timer/Counter. Si se usan los modos de pin externo para el Timer/Counter0, las transiciones en el pin T0 harán el contador incluso si el pin está configurado como salida. Esta característica permite el control software del contador. Tabla CS02
  • 625. CS02 CS01 CS00 Fuente de reloj del Timer0 0 0 0 Sin fuente de reloj (el Timer0 está detenido) 0 0 1 F_CPU (Sin prescaler) 0 1 0 F_CPU/8 (con prescaler) 0 1 1 F_CPU/64 (con prescaler) 1 0 0 F_CPU/256 (con prescaler) 1 0 1 F_CPU/1024 (con prescaler) 1 1 0 Reloj externo en pin T0. El Timer0 avanza con el flanco de bajada. 1 1 1 Reloj externo en pin T0. El Timer0 avanza con el flanco de subida. TCNT0 – Timer/Counter 0 Register Registro TCNT0 TCNT0 Registro de Microcontrolador Bits 7:0 TCNT0[7:0] El Registro Timer/Counter da acceso directo al contador de 8 bits del Timer/Counter para las operaciones de lectura y escritura. La escritura en el registro TCNT0 bloquea (quita) la Coincidencia en el siguiente ciclo de reloj del Timer. La modificación de TCNT0 cuando el contador está corriendo conlleva un riesgo de perder una Coincidencia entre los registros TCNT0 y OCR0x. OCR0A – Output Compare Register 0 A Registro OCR0A OCR0A Registro de Microcontrolador Bits 7:0 OCR0A[7:0]
  • 626. El registro Output Compare A contiene un valor de 8 bits que es continuamente comparado con el valor del contador (TCNT0). Se puede usar una Coincidencia para disparar una Interrupción en Coincidencia, o para generar una onda por el pin OC0A. OCR0B – Output Compare Register 0 B Registro OCR0B OCR0B Registro de Microcontrolador Bits 7:0 OCR0B[7:0] El registro Output Compare B contiene un valor de 8 bits que es continuamente comparado con el valor del contador (TCNT0). Se puede usar una Coincidencia para disparar una Interrupción en Coincidencia, o para generar una onda por el pin OC0B. TIMSK0 – Timer/Counter Interrupt Mask 0 Register Registro TIMSK0 TIMSK0 --- --- --- --- --- OCIE0B OCIE0A TOIE0 Registro de Microcontrolador Bits 7:3 Reserved Estos bits están reservados y siempre se leerán como cero. OCIE0B Timer/Counter Output Compare Match B Interrupt Enable Cuando se escribe uno en el bit OCIE0B y el bit I del registro SREG vale uno, se habilita la Interrupción en Coincidencia B del Timer/Counter0. Si ocurre una Coincidencia entre TCNT0 y OCR0B se ejecutará la función de interrupción correspondiente, esto es, cuando se active el flag OCF0B del registro TIFR0. OCIE0A Timer/Counter Output Compare Match A Interrupt Enable Cuando se escribe uno en el bit OCIE0A, y el bit I del registro SREG vale uno, se habilita la Interrupción en Coincidencia A del Timer/Counter0. Si ocurre una Coincidencia entre TCNT0 y OCR0A, se ejecutará la función de interrupción correspondiente, esto es, cuando se active el flag OCF0A del registro TIFR0. TOIE0 Timer/Counter0 Overflow Interrupt Enable Cuando se escribe uno en el bit TOIE0, y el bit I del registro SREG vale uno, se habilita la Interrupción por Desbordamiento del Timer/Counter0. Si ocurre un
  • 627. Desbordamiento en el registro TCNT0, se ejecutará la función de Interrupción correspondiente, esto es, cuando se active al flag TOV0 del registro TIFR0. TIFR0 – Timer/Counter Interrupt Flag 0 Register Registro TIFR0 TIFR0 --- --- --- --- --- OCF0B OCF0A TOV0 Registro de Microcontrolador Bits 7:3 Reserved OCF0B Timer/Counter 0 Output Compare B Match Flag Estos bits están reservados y siempre se leerán como cero. El bit OCF0B se setea cuando ocurre una Coincidencia entre los datos de los registros TCNT0 y OCR0B. El flag OCF0B se limpia por hardware al ejecutarse su correspondiente función de interrupción. Alternativamente se puede limpiar escribiendo en él un uno lógico. Cuando valgan uno el bit I de SREG, el bit de enable OCIE0B y el bit de flag OCF0B, entonces se ejecutará la Interrupción en Coincidencia B del Timer/Counter0. OCF0A Timer/Counter 0 Output Compare A Match Flag El bit OCF0A se setea cuando ocurre una Coincidencia entre los datos de los registros TCNT0 y OCR0A. El flag OCF0A se limpia por hardware al ejecutarse su correspondiente función de interrupción. Alternativamente se puede limpiar escribiendo en él un uno lógico. Cuando valgan uno el bit I de SREG, el bit de enable OCIE0A y el bit de flag OCF0A, entonces se ejecutará la Interrupción en Coincidencia A del Timer/Counter0. TOV0 Timer/Counter0 Overflow Flag El bit TOV0 se setea cuando ocurre un Desbordamiento del registro TCNT0. El flag TOV0 se limpia por hardware al ejecutarse su correspondiente función de interrupción. Alternativamente se puede limpiar escribiendo en él un uno lógico. Cuando valgan uno el bit I de SREG, el bit de enable OCIE0A y el bit de flag OCF0A, entonces se ejecutará la Interrupción por Desbordamiento del Timer/Counter0. La activación de este flag depende de la configuración de los bits WGM02:0. GTCCR – General Timer/Counter Control Register Registro GTCCR
  • 628. GTCCR TSM --- --- --- --- --- PSRASY PSRSYNC Registro de Microcontrolador TSM Timer/Counter Synchronization Mode Al escribir uno en el bit TSM se activa el modo de Sincronización del Timer/Counter. En este modo, se mantendrán los valores que se escriban en los bits PSRASY y PSRSYNC, para mantener activadas las señales de reset del prescaler correspondiente. Esto asegura que los correspondientes Timers/Counters estén detenidos y se puedan configurar al mismo valor sin correr el riesgo de que uno de ellos avance durante la configuración. Si se escribe un cero en el bit TSM, los bits PSRASY y PSRSYNC se limpian por hardware y los Timers/Counters empiezan a contar simultáneamente. PSRASY Prescaler Reset Timer/Counter2 Cuando este bit vale uno, el prescaler del Timer/Counter2 se reseteará. Normalmente este bit se limpia de inmediato por hardware. Si se escribe en este bit cuando el Timer/Counter2 está trabajando en modo asíncrono, el bit permanecerá en uno hasta que se resetee el prescaler. El bit no se limpiará por hardware si el bit TSM vale uno. PSRSYNC Prescaler Reset Si este bit vale uno, el prescaler del Timer/Counter0 y el Timer/Counter1 se reseteará. Normalmente este bit se limpia de inmediato por hardware, excepto cuando el bit TSM valga uno. Note que el Timer/Counter0 y el Timer/Counter1 comparten el mismo prescaler y el reset de este prescaler afecta a ambos Timers. Registros del Timer2 Registro TCCR2A TCCR2A COM2A1 COM2A0 COM2B1 FOC2B --- COM2B0 --- --- WGM21 WGM20 Registro TCCR2B TCCR2B Registro TCNT2 TCNT2 FOC2A --- WGM22 CS22 CS21 CS20
  • 629. Registro OCR2A OCR2A Registro OCR2B OCR2B Registro TIMSK2 TIMSK2 --- --- --- --- --- OCIE2B OCIE2A TOIE2 --- --- --- --- --- OCF2B OCF2A TOV2 TSM --- --- --- --- --- Registro TIFR2 TIFR2 Registro GTCCR GTCCR PSRASY PSRSYNC Registro de Microcontrolador CS22:0 Clock Select Los tres bits de Clock Select seleccionan la fuente de reloj que usará el Timer/Counter2. Tabla CS22 CS22 CS21 CS20 Fuente de reloj del Timer2 0 0 0 Sin fuente de reloj (Timer/Counter2 detenido). 0 0 1 clkT2S (Sin prescaler) 0 1 0 clkT2S/8 (Desde el prescaler) 0 1 1 clkT2S/32 (Desde el prescaler) 1 0 0 clkT2S/64 (Desde el prescaler) 1 0 1 clkT2S/128 (Desde el prescaler) 1 1 0 clkT2S/256 (Desde el prescaler) 1 1 1 clkT2S/1024 (Desde el prescaler)
  • 630. ASSR – Asynchronous Status Register Si se efectúa una escritura en cualquiera de los cinco registros del Timer/Counter2 cuando su correspondiente flag de busy (ocupado) vale uno, el valor actualizado puede resultar corrompido y se podría disparar una interrupción inintencionada. El mecanismo para leer los registros TCNT2, OCR2A, OCR2B, TCCR2A y TCCR2B es diferente. Al leer TCNT2 se obtiene el valor actual del Timer. Al leer OCR2A, OCR2B, TCCR2A y TCCR2Bse obtiene el valor del registro de almacenamiento temporal. Registro ASSR ASSR --- EXCLK AS2 TCN2UB OCR2AUB OCR2BUB TCR2AUB TCR2BUB Registro de Microcontrolador EXCLK Enable External Clock Input Al escribir uno en el bit EXCLK, y se tiene seleccionado el reloj asíncrono, se habilita el buffer de entrada de reloj externo y se puede poner un reloj externo al pin Timer Oscillator 1 (TOSC1) en vez del XTAL de 32kHz. La escritura en EXCLK se debería realizar antes de seleccionar la operación asíncrona. Note que el oscilador de XTAL solo trabajará cuando este bit vale cero. AS2 Asynchronous Timer/Counter2 Al escribir cero en este bit, el reloj del Timer/Counter2 provendrá del reloj del sistema F_CPU. Al escribir uno en AS2, el reloj del Timer/Counter2 derivará de un XTAL externo conectado al pin Timer Oscillator 1 (TOSC1). Si se cambia el valor de AS2, se podrían corromper los contenidos de los registros TCNT2, OCR2A, OCR2B, TCCR2A y TCCR2B. TCN2UB Timer/Counter2 Update Busy Cuando el Timer/Counter2 opera en modo asíncrono y se escribe en el registro TCNT2, este bit se activa a uno. Este bit se limpiará por hardware cuando TCNT2 se actualice desde el registro de almacenamiento temporal. Un cero lógico en este bit indica que el registro TCNT2 está listo para ser actualizado con un nuevo valor. OCR2AUB Output Compare Register2 A Update Busy Cuando el Timer/Counter2 opera en modo asíncrono y se escribe en el registro OCR2A, este bit se activa a uno. Este bit se limpiará por hardware cuando OCR2A se actualice desde el registro de almacenamiento temporal. Un cero lógico en este bit indica que el registro OCR2A está listo para ser actualizado con un nuevo valor.
  • 631. OCR2BUB Output Compare Register2 B Update Busy Cuando el Timer/Counter2 opera en modo asíncrono y se escribe en el registro OCR2B, este bit se activa a uno. Este bit se limpiará por hardware cuando OCR2B se actualice desde el registro de almacenamiento temporal. Un cero lógico en este bit indica que el registro OCR2B está listo para ser actualizado con un nuevo valor. TCR2AUB Timer/Counter Control Register2 A Update Busy Cuando el Timer/Counter2 opera en modo asíncrono y se escribe en el registro TCCR2A, este bit se activa a uno. Este bit se limpiará por hardware cuando TCCR2A se actualice desde el registro de almacenamiento temporal. Un cero lógico en este bit indica que el registro TCCR2A está listo para ser actualizado con un nuevo valor. TCR2BUB Timer/Counter Control Register2 B Update Busy Cuando el Timer/Counter2 opera en modo asíncrono y se escribe en el registro TCCR2B, este bit se activa a uno. Este bit se limpiará por hardware cuando TCCR2B se actualice desde el registro de almacenamiento temporal. Un cero lógico en este bit indica que el registro TCCR2B está listo para ser actualizado con un nuevo valor. El Timer1 y el Timer3 Si antes habíamos estudiado a la par el Timer0 y el Timer2 por ser muy simulares, esta vez la referencia al Timer3 se reduce solo a su mención en los títulos. Sucede que el Timer3 es completamente idéntico al Timer1. La mala noticia es que solo está disponible en los ATmega128xy :-( El Timer/Counter1, o simplemente Timer1, es de 16 bits. En principio, opera de la misma forma en que lo hace el Timer0, solo que utilizando sus registros de datos de 16 bits. Esto lo podremos comprobar al notar la gran similitud que tienen sus diagramas de bloques. Como en los megaAVR cada registro de E/S es de 8 bits, en realidad los registros de datos del Timer1 están compuestos por la unión de dos registros de 8 bits. Por ejemplo,TCNT1 se forma uniendo los registrosTCNT1H y TCNT1L. Lo mismo sucede con los siguientes registros OCR1A = unión de OCR1AH y OCR1AL OCR1B = unión de OCR1BH y OCR1BL ICR1 = unión de ICR1H e ICR1L Los registros de control del Timer1 siguen siendo de 8 bits: TCCR1A, TCCR1B, TCCR1C,TIMSK1, TIFR1 y GTCCR.
  • 632. Diagrama de bloques del Timer1. Este diagrama del Timer2 es una adaptación que nos facilitará su descripción funcional. Es una descripción corta para no redundar demasiado sobre lo explicado para el Timer0. Bueno, empecemos: Los bits WGM configuran en gran medida el modo de operación del Timer2. Con ellos podemos escoger entre los modos Normal, CTC o PWM. Notemos que a diferencia del Timer0, ahora son 4 bits WGM. Esto es porque el Timer2 ofrece muchas variantes de los modos CTC y sobre todo PWM. Hay 16 modos en total, pero muchos de ellos son redundantes y de poca utilidad. Tabla WGM13 WGM13 WGM12 WGM11 WGM10 Modo de Operación del Timer1 Inicio de Conteo Tope de Conteo 0 0 0 0 Normal 0x00 0xFFFF 0 0 0 1 PWM de Fase Correcta, 8 bits 0x00 0x00FF
  • 633. Tabla WGM13 WGM13 WGM12 WGM11 WGM10 Modo de Operación del Timer1 Inicio de Conteo Tope de Conteo 0 0 1 0 PWM de Fase Correcta, 9 bits 0x00 0x01FF 0 0 1 1 PWM de Fase Correcta 10 bits 0x00 0x03FF 0 1 0 0 CTC 0x00 OCR1A 0 1 0 1 Fast PWM, 8 bits 0x00 0x00FF 0 1 1 0 Fast PWM, 9 bits 0x00 0x01FF 0 1 1 1 Fast PWM, 10 bits 0x00 0x03FF 1 0 0 0 PWM de Fase y Frecuencia Correctas 0x00 ICR1 1 0 0 1 PWM de Fase y Frecuencia Correctas 0x00 OCR1A 1 0 1 0 PWM de Fase Correcta 0x00 ICR1 1 0 1 1 PWM de Fase Correcta 0x00 OCR1A 1 1 0 0 CTC 0x00 ICR1 1 1 0 1 Reservado 0x00 – 1 1 1 0 Fast PWM 0x00 ICR1 1 1 1 1 Fast PWM 0x00 OCR1A Los bits CS (Clock Select), como indica su nombre, son para configurar la fuente de reloj del Timer2. El reloj del Timer2 es idéntico al del Timer0. Recordemos que comparten el mismo prescaler y aunque también trabajan con los mismos divisores o factores de prescaler, su configuración no tiene que ser la misma, es decir, podemos
  • 634. emplear nuestro Timer0 con un factor de 8 y el Timer2 con el factor 1024. Para mayor información puedes revisar la sección los prescalers del Timer0 y del Timer2. Tabla CS12 CS12 CS11 CS10 Fuente de reloj del Timer1 0 0 0 Sin fuente de reloj (el Timer1 está detenido) 0 0 1 F_CPU (Sin prescaler) 0 1 0 F_CPU/8 (con prescaler) 0 1 1 F_CPU/64 (con prescaler) 1 0 0 F_CPU/256 (con prescaler) 1 0 1 F_CPU/1024 (con prescaler) 1 1 0 Reloj externo en pin T1. El Timer1 avanza en el flanco de bajada. 1 1 1 Reloj externo en pin T1. El Timer1 avanza en el flanco de subida. Los bits COM. Hasta ahora los habíamos estado dejando de lado. Como su nombre lo indica, estos bits controlan en modo de salida de los comparadores (Compare Output Mode). Los habíamos ignorado porque no tiene mucho sentido usarlos en los modos Normal o CTC. En cambio ahora que entraremos de lleno en el modo PWM, serán más que necesarios. Desde el momento en que configuremos estos bits para sacar las ondas (PWM o no) por los pines OC1A y/o OC1B el Timer2 asumirá el control sobre ellos y ya no podremos usarlos como puertos de E/S generales (mediante el registro PORTx respectivo). Sin embargo, todavía será necesario configurarlos como pines de salida en sus correspondientes bits del registro DDRx. Los bits COM son detestables porque tienen diferente efecto dependiendo de si el Timer opera en modo Normal y CTC, en modo Fast PWM o en modo PWM de Fase correcta y PWM de Fase y Frecuencia correctas. Tan solo mencionar estos términos marea un poco, ¿verdad? Por eso voy a reservar las tablas correspondientes para otro momento. El Registro ICR1. Como te habrás dado cuenta, este registro es nuevo; es propio del Timer1. Su nombre ICR1 es el acrónimo de Input Capture Register 1 y tiene dos funciones. En primer lugar sirve para capturar el valor del registro TCNT1 justo en el momento en que el bloque Fuente de Captura (ver la figura) le dé la señal. Esta función es útil en aplicaciones que necesitan medir la frecuencia de alguna señal externa, pero no tiene nada que ver con el modo PWM así que la ignoraremos por el momento. Sin embargo, el registro ICR1 también puede trabajar como el tope del conteo del Timer1 en los modos PWM. Ésta es su faceta en que más se le emplea.
  • 635. El bloque FUENTE DE CAPTURA. Se trata de un circuito que selecciona la señal de captura. Según su diagrama mostrado abajo, se disponen de dos opciones: o es el pin ICPdel megaAVR o es la salida del Comparador Analógico. La elección se hace mediante el bit ACIC, del registro ACRS. El bit ICNC1 (del registro TCCR1B) puede activar el eliminador de ruido para evitar falsos disparos causados por los picos del ruido. En la etapa final, el bit ICES1 (también del registro TCCR1B) establece si el disparo se dará en el flanco de subida o de bajada de la fuente seleccionada. Una vez detectada la señal elegida, se activará el flag ICF1 y el registro ICR1 le tomará una fotografía al registroTCNT1. Este evento se puede aprovechar para programar y disparar su interrupción. Diagrama de bloques de la Entrada de Captura. El Timer1 y el Timer3 en Modos Normal y CTC Con su registro TCNT1 de 16 bits, el Timer1 puede contar desde 0x0000 hasta 0xFFFF. En modo Normal y en modo CTC avanza siempre en modo ascendente. En modo Normal llega hasta 0xFFFF y en modo CTC avanza hasta que coincida con el valor del registroOCR1A o ICR1 que también son de 16 bits. El conteo es siempre cíclico, o sea que después de llegar a su valor tope, la cuenta se reinicia desde 0. Notamos que el modo CTC con tope de conteo establecido por registro ICR1 no era una opción disponible en los Timers de 8 bits. Este modo se selecciona con los bits WGM13:0 = 1100b. Cualquiera que sea su modo de operación, el registro de conteo TCNT1 siempre es comparado con los registros OCR1A yOCR1B. Las Coincidencias detectadas activarán los flags OCF1A y OCF1B, respectivamente (ver el diagrama de bloques) y se pueden usar estos eventos para programar las interrupciones de Coincidencias setenado los bits del registroTIMSK1 OCIE1A para la coincidencia entreTCNT1 y OCR1A, y OCIE1B para la coincidencia entre TCNT1 y OCR1B. Si bien pueden usarse las Coincidencias como señales para setear, limpiar o conmutar el estado de los pines OC1A y/o OC1B, según la configuración de los bits COM, nunca he visto una aplicación que saque provecho de esa característica. El Timer1 se reserva
  • 636. de forma casi exclusiva para el modo PWM y es raro verlo trabajar incluso en los modos Normal o CTC. Con todo, si hubiera que hacerlo, no hay mucho que añadir sobre la operación de los Timers0 o 2 en ese modo. Obviamente, las fórmulas de temporización adoptarán nuevas formas, teniendo en cuenta los 16 bits del Timer1. El Tiempo que pasará entre la carga del Timer1 con un valor inicial TCNT1 hasta su desbordamiento está dado por: Donde: Tabla de Temporización en Modo CTC con Timer0 Tiempo = Valor de la temporización. F_CPU = Frecuencia del XTAL del megaAVR. N = Factor de prescaler (1, 8, 64, 256 ó 1024). TCNT1 = Valor de inicio del registro TCNT1. Las siguientes formulas nos dan un camino para hallar por partes las dos incógnitas de la primera fórmula. Si el Timer1 está programado en modo CTC, el tiempo entre coincidencia y coincidencia de TCNT1 con TOPE podemos calcular utilizando la fórmula. Donde: Tabla de Temporización en Modo CTC con Timer0 Tiempo = Valor de la temporización. F_CPU = Frecuencia del XTAL del megaAVR. N = Factor de prescaler (1, 8, 64, 256 ó 1024).
  • 637. TOPE = Valor del registro OCR1A o del registro ICR1, según el modo CTC En este caso la fórmula se puede descomponer en las siguientes dos fórmulas de una sola variable. Si no tienes idea de cómo usarlas o de cuál modo (Normal o CTC) es mejor para temporizar, puedes revisar la teoría dedicada al Timer0 y al Timer2. El Timer1 y el Timer3 en Modo PWM Los Timers 1 y 3 generan mejores ondas PWM que los Timers 0 y 2. Además de obtener señales con resolución de hasta 16 bits, la frecuencia de la señal PWM es completamente controlable. El Timer1 de los AVR puede producir hasta dos canales PWM, por los pines OC1A yOC1B. El Timer3 hace lo propio por los pinesOC3A y OC3B. Estos pines deberán estar configurados como salida para dar salida a las señales PWM. Esta es una caractrística que distingue a los módulos PWM de los otros periféricos del megaAVR donde la configuración y control de los pines involucrados quedan a cargo del módulo respectivo. El mecanismo como los Timers de 16 bits generan ondas PWM es similar a como lo hacen los Timers de 8 bits, de manera que nos debe ser familiar trabajar con los registros de control. La única novedad es la aparición en escena del nuevo registro de 16 bits ICR1, cuya función en modo PWM es establecer el tope de conteo opcional. El registro ICR1 es en realidad la unión de los registros de 8 bits ICR1H e ICR1L. Dada la semejanza entre los dos Timers y entre los dos canales PWM, volvemos a aclarar que la siguiente exposición está relacionada solo al canal A del Timer1, dando por sentado que es también aplicable al canal B, tanto del Timer1 como del Timer3. El Timer3 no está disponible en muchos AVR. Tabla WGM13 WGM13 WGM12 WGM11 WGM10 Modo de Operación del Timer1 Inicio de Conteo Tope de Conteo 0 0 0 1 PWM de Fase Correcta, 8 bits 0x00 0x00FF 0 0 1 0 PWM de Fase Correcta, 9 bits 0x00 0x01FF
  • 638. Tabla WGM13 WGM13 WGM12 WGM11 WGM10 Modo de Operación del Timer1 Inicio de Conteo Tope de Conteo 0 0 1 1 PWM de Fase Correcta, 10 bits 0x00 0x03FF 0 1 0 1 Fast PWM, 8 bits 0x00 0x00FF 0 1 1 0 Fast PWM, 9 bits 0x00 0x01FF 0 1 1 1 Fast PWM, 10 bits 0x00 0x03FF 1 0 0 0 PWM de Fase y Frecuencia Correctas 0x00 ICR1 1 0 0 1 PWM de Fase y Frecuencia Correctas 0x00 OCR1A 1 0 1 0 PWM de Fase Correcta 0x00 ICR1 1 0 1 1 PWM de Fase Correcta 0x00 OCR1A 1 1 1 0 Fast PWM 0x00 ICR1 1 1 1 1 Fast PWM 0x00 OCR1A La tabla de arriba nos muestra que el Timer1 puede generar tres tipos de ondas PWM, según la forma como avanza el registro TCNT1: Fast PWM PWM de Fase Correcta y PWM de Fase y Frecuencia Correctas Cada uno de estos modos tiene a su vez otras variantes dependiendo del valor tope hasta donde puede contar el Timer1. Son especiales los modos que en la tabla aparecen sombreados (donde el tope del conteo es el valor del registro OCR1A) porque suelen entorpecer la comprensión del modo PWM de los Timers. Así que los ignoraremos por el momento y en adelante asumiremos que tope del Timer1 puede ser de 0x00FF, 0x01FF, 0x03FF o el valor del registro ICR1. Con eso aclarado podemos seguir. Timer1 en modo Fast PWM
  • 639. El modo Fast PWM se conoce también como PWM de pendiente única porque el registroTCNT1 avanza siempre hacia arriba, es decir, cuenta desde 0 hasta un valor TOPE(0x00FF, 0x01FF, 0x03FF o el valor del registro ICR1), después de lo cual se resetea y vuelve a empezar desde 0. Si graficamos este progreso, la curva resulta siendo efectivamente una escalera de pendiente única, como se ve abajo. La figura mostrada corresponde al modo Fast PWM que tiene al registro ICR1 como tope de conteo por ser el caso más usual. Según la figura, la onda Fast PWM es generada por la conmutación del pin OC1A cada vez que el registro TCNT1 llega al tope y cada vez queTCNT1 coincide con el registro OCR1A. En la gráfica las coincidencias se señalan con pequeñas líneas rojas horizontales sobre la escalera. Según la configuración de los bitsCOM1A1 y COM1A0 (del registro TCCR1A), la onda PWM puede ser invertida o no invertida. Si los bits COM1A1:0 valen 0b10 se establece una onda no-invertida, esto es, el pin OC1A se setea cuando TCNT1 llega al tope y se limpia cuando TCNT1 coincide con el registro OCR1A. Si los bits COM1A1:0 valen 0b11 se establece una onda invertida, esto es, el pinOC1A se limpia cuando TCNT1 llega al tope y se setea cuando TCNT1 coincide con el registro OCR1A. Puesto que el Timer1 puede contar hasta varios valores TOPE (0x00FF, 0x01FF, 0x03FFo el valor del registro ICR1), se deduce que el periodo, y por ende la frecuencia,
  • 640. de la onda PWM también serán variables. Esta flexibilidad lo distingue del modo PWM de los Timers de 8 bits, donde el periodo es siempre constante. También es posible modificar el valor del registro OCR1A, lo cual nos permitirá controlar el duty cycle de la onda PWM. En la siguiente imagen se indican las fórmulas para calcular el periodo y el duty cycle de una onda Fast PWM no-invertida. Como siempre, F_CPU es la frecuencia del procesador yN es el factor del prescaler. Recuerda que el Timer1 comparte el mismo prescaler con el Timer0, aunque ello no significa que tengan que trabajar con el mismo factor de prescaler. Para más detalles puedes revisar la sección relojes del Timer0 y del Timer2. Periodo y Duty cycle de una onda Fast PWM no-invertida. La frecuencia de la onda Fast PWM se obtiene invirtiendo la fórmula del periodo. Recordemos que TOPE puede valer 0x00FF, 0x01FF, 0x03FF o el valor del registro ICR1. En el caso de que escojamos la última opción podemos cargar en ICR1 cualesquiera valores siempre que sean mayores que 2. El máximo valor es desde luego 0xFFFF = 65535. Luego estudiaremos los otros modos PWM y al hacer comparaciones veremos que esta fórmula nos permite obtener frecuencias más altas (hasta el doble de lo que se puede conseguir con los otros PWM). Eso explica la razón de su nombre: Fast PWM = PWM rápido. Y la fórmula que nos da el duty cycle expresado en porcentaje la obtenemos dividiendo las fórmulas del duty cycle y del periodo.
  • 641. Puesto que el registro OCR1A tiene el mismo rango de valores que TOPE, esta fórmula nos permite observar que el duty cycle puede ser del 100% (si OCR1A = TOPE), lo cual significa que el estado del pin OC0A será de 1 lógico constante. En cambio, no hay ningún valor para el registro OCR1A que nos dé un duty cycle del 0%. Si el registro OCR1A vale 0 la salida será de unos pequeños picos que representan un duty cycle cercano al 0.0015%. Ésa es la característica que diferencia al modo Fast PWM de los demás modos PWM. No pierdas de vista que que este duty cycle corresponde a una onda PWM no invertida. Si la onda es invertida, el duty cycle será el complemento a 100, por ejemplo, un duty cycle de 30% de un PWM invertido equivale a un duty cycle de 70% de un PWM no invertido. Timer1 en modo PWM de Fase Correcta El modo PWM de Fase Correcta se conoce también como PWM de doble pendiente porque el registro TCNT1 cuenta en sube y baja, es decir, primero avanza desde 0 hasta el valorTOPE (0x00FF, 0x01FF, 0x03FF o el valor del registro ICR1), y después regresa desdeTOPE hasta 0. Este proceso se repite cíclicamente y si lo graficamos, la curva resulta siendo efectivamente una escalera de doble pendiente, como se ve abajo. Como se indica en la figura, la onda PWM de Fase Correcta es generada por la conmutación del pin OC1A cada vez que el registro TCNT1 coincide con el registroOCR1A. En la gráfica las coincidencias se señalan con pequeñas líneas rojas horizontales sobre la escalera. Según la configuración de los
  • 642. bits COM1A1 y COM1A0 (del registroTCCR1A), la onda PWM puede ser invertida o no invertida. Si los bits COM1A1:0 valen 0b10 se establece una onda no-invertida, esto es, el pin OC1A se setea cuando TCNT1 coincide con el registro OCR1A en su conteo descendente, y se limpia cuando TCNT1 coincide con el registro OCR1A en su conteo ascendente. Si los bits COM1A1:0 valen 0b11 se establece una onda invertida, esto es, el pinOC1A se setea cuando TCNT1 coincide con el registro OCR1A en su conteo ascendente, y se limpia cuando TCNT1 coincide con el registro OCR1A en su conteo descendente. Puesto que el Timer1 puede contar hasta varios valores TOPE (0x00FF, 0x01FF, 0x03FFo el valor del registro ICR1), se deduce que el periodo, y por ende la frecuencia, de la onda PWM también serán variables. Esta flexibilidad lo distingue del modo PWM de los Timers de 8 bits, donde el periodo es siempre constante. También es posible modificar el valor del registro OCR1A, lo cual nos permitirá controlar el duty cycle de la onda PWM. En la siguiente imagen se indican las fórmulas para calcular el periodo y el duty cycle de una onda PWM de Fase Correcta no-invertida. Como siempre, F_CPU es la frecuencia del procesador y N es el factor del prescaler. Recuerda que el Timer1 comparte el mismo prescaler con el Timer0, aunque ello no significa que tengan que trabajar con el mismo factor de prescaler. Para más detalles puedes revisar la sección relojes del Timer0 y del Timer2. Periodo y Duty cycle de una onda PWM de Fase Correcta no-invertida. La frecuencia de la onda PWM de Fase Correcta se obtiene invirtiendo la fórmula del periodo. Recordemos que TOPE puede valer 0x00FF, 0x01FF, 0x03FF o el valor del registro ICR1. En el caso de que escojamos la última opción podemos cargar en el registro ICR1 cualesquiera valores siempre que sean mayores que 2. El máximo valor es desde luego 0xFFFF = 65535.
  • 643. Y la fórmula que nos da el duty cycle expresado en porcentaje la obtenemos dividiendo las fórmulas del duty cycle y del periodo: A diferencia del modo Fast PWM en esta fórmula podemos observar que el duty cycle puede abarcar todo su rango desde 0% (con OCR1A = 0) hasta el 100% (con OCR1A =TOPE). El 0% significa que la salida será un constante 0 lógico y un 100%, que la salida será un estado alto constante. Ésa es la razón por la que se denomina de fase correcta. No pierdas de vista que que este duty cycle corresponde a una onda PWM no invertida. Si la onda es invertida, el duty cycle será el complemento a 100, por ejemplo, un duty cycle de 30% de un PWM invertido equivale a un duty cycle de 70% de un PWM no invertido. PWM de Fase y Frecuencia Correctas Este modo es igual al modo PWM de Fase Correcta en cuanto al mecanismo de generación de la onda PWM y al cálculo de su frecuencia y duty cycle. Todas las fórmulas presentadas en esa sección són también válidas en este modo, así que se recomienda revisarla para no seguir redundando. En su lugar vamos a aprovechar este apartado para conocer algunos puntos que no fueron tratados antes por requerir precisamente de una visión más general de todos los modos PWM. El modo PWM de Fase y Frecuencia Correctas es idéntico al modo PWM de fase Correctaen todos los aspectos, excepto por el momento en que se actualiza el registro OCR1A. Si no le diste importancia a los recuadros amarillos que aparecen encima de cada una de las gráficas de pendiente mostradas anteriormente, éste es el momento de echar la mirada atrás porque las vamos a comparar con la gráfica de doble pendiente correspondiente a la onda PWM de Fase y Frecuencia Correctas que se muestra abajo.
  • 644. Para comprender el concepto de actualización del registro OCR1A primero debemos saber que en cualquiera de los modos PWM este registro trabaja con doble buffer, es decir, cuando escribimos un dato en OCR1A en realidad estamos enviando el dato a su buffer. Luego el dato será transferido al verdadero registro OCR1A, justo cuando el registroTCNT1 llegue al valor 0x0000 o el valor de TOPE, según el modo PWM. A dicha transferencia es que nos referimos cuando hablamos de actualización del registro OCR1A. Recuerda que este registro lo usamos para controlar el duty cycle de la onda PWM. En el modo PWM de Fase y Frecuencia Correctas el registro OCR1A se actualiza cuando el registro TCNT1 llega a 0x0000. Si observas bien la gráfica mostrada arriba, verás que este hecho permite que los pulsos de la onda PWM sean simétricos en todos sus periodos, sin importar el momento en que cambia su duty cycle. Por esa razón se llama de Frecuencia Correcta, o sea, si todo anda bien en el periodo, también anda bien en la frecuencia. En todos los otros modos PWM el registro OCR1A se actualiza cuando el registroTCNT1 alcanza el valor de TOPE (0x00FF, 0x01FF, 0x03FF o el valor del registroICR1). Si observas las gráficas de pendiente de los modos Fast PWM y PWM de Fase Correcta, notarás que esto producirá una onda cuyo duty cycle cambia brúscamente al modificar el registro OCR1A. Piensa en esto: Si deseamos modificar el duty cycle de nuestro PWM, lo ideal sería que ese cambio tenga efecto a partir del siguiente periodo de la onda como en el anterior caso, ¿verdad? Pues ésa es la única y acaso irrelevante característica que diferencia al modo PWM de Fase y Frecuencia Correctas del resto de modos PWM.
  • 645. Registros del Timer1 Registro TCCR1A TCCR1A COM1A1 COM1A0 COM1B1 COM1B0 --- --- WGM11 WGM10 Registro TCCR1B TCCR1B ICNC1 ICES1 FOC1A FOC1B --- WGM13 WGM12 CS12 CS11 CS10 --- --- --- Registro TCCR1C TCCR1C Registro TCNT1H TCNT1H Registro TCNT1L TCNT1L Registro OCR1AH OCR1AH Registro OCR1AL OCR1AL Registro OCR1BH OCR1BH Registro OCR1BL OCR1BL Registro ICR1H ICR1H Registro ICR1L ICR1L Registro TIMSK1 --- --- ---
  • 646. TIMSK1 --- --- ICIE1 --- --- OCIE1B OCIE1A TOIE1 --- --- ICF1 --- --- OCF1B OCF1A TOV1 TSM --- --- --- --- --- Registro TIFR1 TIFR1 Registro GTCCR GTCCR PSRASY PSRSYNC WGM11 WGM10 TCCR1A – Timer/Counter Control Register 1 A Registro TCCR1A TCCR1A COM1A1 COM1A0 COM1B1 COM1B0 --- --- Registro de Microcontrolador COM1A1: 0 Compare Output Mode for Channel A COM1B1: 0 Compare Output Mode for Channel B Los bits COM1A1:0 y COM1A1:0 controlan el comportamiento de los pines Output Compare (OC1A y OC1B, respectivamente). Si uno o los dos bits COM1A1:0 están seteados, la salida OC0A tiene prioridad sobre la funcionalidad normal del pin al que está conectado. Si uno o los dos bits COM1B1:0 están seteados, la salida OC0B tiene prioridad sobre la funcionalidad normal del pin al que está conectado. Sin embargo, note que el bit del registro DDR correspondiente al pin OC0A o OC0B debe estar configurado como salida para habilitar el driver de salida. Cuando OC0A o OC0B está conectado al pin la función de los bits COM1x1:0 depende de la configuración de los bits WGM13:0. La siguiente tabla muestra la funcionalidad de los bits COM1x1:0 cuando los bits WGM13:0 están configurados en modo Normal oCTC (no PWM). Tabla COM1A1/COM1B1 COM1A1/ COM1A0/ Descripción COM1B1 COM1B0 0 0 OC0A/OC0B desconectado. Operación normal de pin 0 1 OC0A/OC0B conmuta en Coincidencia
  • 647. 1 0 OC0A/OC0B se limpia en Coincidencia 1 1 OC0A/OC0B se setea en Coincidencia La siguiente tabla muestra la funcionalidad de los bits COM1A1:0 cuando los bits WGM13:0 están configurados en modo Fast PWM. Nota: ocurre un caso especial cuando OCR1A/OCR1B es el tope del conteo y el bit COM1A1/COM1B1 vale uno. En este caso se ignora la Coincidencia, pero la puesta a cero o a uno de OC0A/OC0B se produce al llegar a 0x0000. Tabla COM1A1/ COM1B1 COM1A1/ COM1B1 COM1A0/ COM1B0 0 0 OC1A/OC1B desconectado. Operación normal del pin 1 WGM13:0 = 14 o 15: OC1A conmuta en Coincidencia, OC1B desconectado (operación normal de pin). Para las demás configuraciones de WGM1, OC1A/OC1B desconectado (operación normal de pin). 1 0 OC1A/OC1B se limpia en la Coincidencia, y se setea al llegar a 0x0000 (modo noinvertido) 1 1 OC1A/OC1B se setea en la Coincidencia, y se limpia al llegar a 0x00 (modo invertido). 0 Descripción La siguiente tabla muestra la funcionalidad de los bits COM1A1:0 cuando los bits WGM13:0 están configurados en modo PWM de Fase Correcta o PWM de Fase y Frecuencia Correctas. Nota: ocurre un caso especial cuando OCR1A es el tope del conteo y el bit COM1A1/COM1B1 vale uno... Tabla COM1A1/ COM1B1
  • 648. COM1A1/ COM1B1 COM1A0/ COM1B0 0 0 OC1A/OC1B desconectado. Operación normal del pin 1 WGM13:0 = 9 u 11: OC1A conmuta en Coincidencia, OC1B desconectado (operación normal de pin). Para las demás configuraciones de WGM1, OC1A/OC1B desconectado (operación normal de pin). 0 OC1A/OC1B se limpia en la Coincidencia cuando se cuenta hacia arriba, y se setea en la Coincidencia cuando se cuenta hacia abajo. 1 OC1A/OC1B se setea en la Coincidencia cuando se cuenta hacia arriba, y se limpia en la Coincidencia cuando se cuenta hacia abajo. 0 1 1 Bits 3:2 Descripción Reservados Estos bits están reservados en los ATmega164A/PA, ATmega324A/PA, ATmega644A/PA y ATmega1284/P, y siempre se leerán como cero. WGM11:0 Waveform Generation Mode Estos bits se combinan con los bits WFGM13:2 del registro TCCR1B. Juntos controlan la secuencia de conteo del contador, el valor máximo del conteo (el tope) y el tipo de la forma de onda que se generará. Los modos operación que soporta la unidad del Timer/Counter son: modo Normal (contador), modo Clear Timer on Compare Match (CTC), y tres tipos de Modulación de Ancho de Pulso (PWM). Tabla WGM13 WGM13 WGM12 WGM11 WGM10 0 0 0 0 Modo de Operación del Timer1 Normal Tope del Conteo 0xFFFF
  • 649. 0 0 0 1 PWM de Fase Correcta, 8 bits 0x00FF 0 0 1 0 PWM de Fase Correcta, 9 bits 0x01FF 0 0 1 1 PWM de Fase Correcta, 10 bits 0x03FF 0 1 0 0 CTC OCR1A 0 1 0 1 Fast PWM, 8 bits 0x00FF 0 1 1 0 Fast PWM, 9 bits 0x01FF 0 1 1 1 Fast PWM, 10 bits 0x03FF 1 0 0 0 PWM de Fase y Frecuencia Correctas ICR1 1 0 0 1 PWM de Fase y Frecuencia Correctas OCR1A 1 0 1 0 PWM de Fase Correcta ICR1 1 0 1 1 PWM de Fase Correcta OCR1A 1 1 0 0 CTC ICR1 1 1 0 1 Reservado – 1 1 1 0 Fast PWM ICR1 1 1 1 1 Fast PWM OCR1A TCCR1B – Timer/Counter Control Register 1 B Registro TCCR1B TCCR1B ICNC1 ICES1 Registro de Microcontrolador --- WGM13 WGM12 CS12 CS11 CS10
  • 650. ICNC1 Input Capture Noise Canceler La escritura de uno en este bit activa el circuito eliminador de ruido Input Capture Noise Canceler del Timer1. Cuando el eliminador de ruido está activado, la entrada del pin Input Capture (ICP1) será filtrada. La función del filtro requiere de la igualdad de cuatro muestreos del pin ICP1 para cambiar su estado. Debido a esto, cuando el eliminador de ruido está habilitado, la unidad de Input Capture se retrasa por cuatro ciclos del oscilador. ICES1 Input Capture Edge Select Este bit selecciona el flanco en el pin Input Capture (ICP1) que se usará para disparar el evento de captura. Cuando el bit ICES1 vale uno, el disparo se dará en el flanco de bajada, y si ICES1 vale cero, el disparo se dará en el flanco de subida. Cuando se dispara una captura de acuerdo con la configuración del bit ICES1, el valor del contador se copiará al registro Input Capture Register (ICR1). El evento también seteará el flag Input Capture (ICF1), y se puede usar para generar una Interrupción de Input Capture, si esta interrupción está habilitada. Cuando se usa como el tope de conteo el registro ICR1 (ver la descripción de los bits WGM13:0 ubicados en los registros TCCR1A y TCCR1B), ICP1 queda desconectado y por tanto se deshabilita la función de Input Capture. Bits 5 Reservado Este bit está reservado para usos futuros. Al escribir en TCCR1B, este bit se debe mantener en cero para asegurar la compatibilidad con futuros dispositivos. WGM13: 2 Waveform Generation Mode CS12:0 Clock Select Ver la descripción de los bits WGM11:0 del registro TCCR1A. Los tres bits de Clock Select seleccionan la fuente de reloj que usará el Timer/Counter. Si se usan los modos de pin externo para el Timer/Counter1, las transiciones en el pin T1 harán el contador incluso si el pin está configurado como salida. Esta característica permite el control software del contador. Tabla CS12 CS12 CS11 CS10 Fuente de reloj del Timer1
  • 651. 0 0 0 Sin fuente de reloj (el Timer1 está detenido) 0 0 1 F_CPU (Sin prescaler) 0 1 0 F_CPU/8 (con prescaler) 0 1 1 F_CPU/64 (con prescaler) 1 0 0 F_CPU/256 (con prescaler) 1 0 1 F_CPU/1024 (con prescaler) 1 1 0 Reloj externo en pin T1. El Timer1 avanza en el flanco de bajada. 1 1 1 Reloj externo en pin T1. El Timer1 avanza en el flanco de subida. TCCR1C – Timer/Counter Control Register 1 C Registro TCCR1C TCCR1C FOC1A FOC1B --- --- --- --- --- --- Registro de Microcontrolador FOC1A Force Output Compare A FOC1B Force Output Compare B Los bits FOC1A/FOC1B solo están activos cuando los bits WGM13:0 establecen un modo no PWM. Sin embargo, para asegurar la compatibilidad con futuros dispositivos, estos bits se deben mantener en cero al escribir en TCCR1C cuando el Timer está operando en uno de los modos PWM. Si se escribe un uno en el bit FOC1A/FOC1B, se fuerza una Coincidencia inmediata en la Unidad Generadora de Forma de Onda. La salida de OC1A/OC1B cambia de acuerdo con la configuración de los bits COM1A1:0/COM1B1:0. Note que los bits FOC1A/FOC1B se implementan como strobes. Así que es el valor presente en los bits COM1A1:0/COM1B1:0 los que determinan el efecto de la Coincidencia forzada. Un strobe de FOC1A/FOC1B no generará ninguna interrupción ni tampoco reseteará el Timer en modo CTC si se usa OCR1A como tope del conteo.
  • 652. Los bits FOC1A/FOC1B siempre se leen como cero. Bits 5:0 Reservados Estos bits están reservados y siempre se leerán como cero. TCNT1H y TCNT1L – Timer/Counter 1 Register Registro TCNT1H TCNT1H Registro TCNT1L TCNT1L Registro de Microcontrolador Bits 15:0 TCNT1H[7:0] y TCNT1L[7:0] Los dos registros TCNT1H y TCNT1L combinados, dan acceso directo al contador de 16 bits del Timer/Counter para las operaciones de lectura y escritura. Para asegurar que los dos registros se lean y escriban al mismo tiempo, el acceso se realiza utilizando un registro temporal de 8 bits para almacenar el byte alto (TCNT1H). Este registro temporal se comparte con todos los otros registros de 16 bits. La modificación de TCNT1 cuando el contador está corriendo conlleva un riesgo de perder una Coincidencia entre los registros TCNT1 y OCR1A/ OCR1B. La escritura en el registro TCNT0 bloquea (quita) la Coincidencia en el siguiente ciclo de reloj del Timer para todas las unidades de comparación. OCR1AH y OCR1AL – Output Compare Register 1 A Registro OCR1AH OCR1AH Registro OCR1AL OCR1AL Registro de Microcontrolador Bits 15:0 OCR1AH[7:0] y OCR1AL[7:0] Los dos registros Output Compare A contienen un valor de 16 bits que es continuamente comparado con el valor del contador (TCNT1). Se puede usar una Coincidencia para disparar una Interrupción en Coincidencia, o para generar una
  • 653. onda por el pin OC1A. Para asegurar que los dos registros Output Compare A se escriban simultáneamente cuando el CPU escribe en estos registros, el acceso se realiza utilizando un registro temporal de 8 bits para almacenar el byte alto (OCR1AH). Este registro temporal es compartido por todos los demás registros de 16 bits. OCR1BH y OCR1BL – Output Compare Register 1 B Registro OCR1BH OCR1BH Registro OCR1BL OCR1BL Registro de Microcontrolador Bits 15:0 OCR1BH[7:0] y OCR1BL[7:0] Los dos registros Output Compare B contienen un valor de 16 bits que es continuamente comparado con el valor del contador (TCNT1). Se puede usar una Coincidencia para disparar una Interrupción en Coincidencia, o para generar una onda por el pin OC1B. Para asegurar que los dos registros Output Compare B se escriban simultáneamente cuando el CPU escribe en estos registros, el acceso se realiza utilizando un registro temporal de 8 bits para almacenar el byte alto (OCR1BH). Este registro temporal es compartido por todos los demás registros de 16 bits. ICR1H y ICR1L – Input Capture Register 1 Registro ICR1H ICR1H Registro ICR1L ICR1L Registro de Microcontrolador Bits 15:0 ICR1H [7:0] y ICR1L [7:0] El registro Input Capture se actualiza con el valor del contador (TCNT1) cada vez que ocurre un evento en el pin ICP1 (u opcionalmente en la salida del Comparador Analógico que va al Timer/Counter1). Se puede usar el registro Input Capture para
  • 654. definir el valor tope del conteo. El registro Input Capture es de 16 bits. Para asegurar que sus dos bytes se escriban simultáneamente cuando el CPU escribe en estos registros, el acceso se realiza utilizando un registro temporal de 8 bits para almacenar el byte alto (ICR1H). Este registro temporal es compartido por todos los demás registros de 16 bits. TIMSK1 – Timer/Counter Interrupt Mask 1 Register Registro TIMSK1 TIMSK1 --- --- ICIE1 --- --- OCIE1B OCIE1A TOIE1 Registro de Microcontrolador Bits 7:6 Reserved Bits 4:3 Reserved Estos bits están reservados y siempre se leerán como cero. ICIE1 Timer/Counter1, Input Capture Interrupt Enable Al escribir uno en este bit, y el bit enable general I del registro SREG vale uno, se habilita la Interrupción Input Capture del Timer/Counter1. Al activarse el flag ICF1, del registro TIFR1, se ejecutará su respectiva función de interrupción ISR. OCIE1B Timer/Counter1, Output Compare B Match Interrupt Enable Al escribir uno en este bit, y el bit enable general I del registro SREG vale uno, se habilita la Interrupción en Coincidencia B del Timer/Counter1. Al activarse el flag OCF1B, del registro TIFR1, se ejecutará su respectiva función de interrupción ISR. OCIE1A Timer/Counter1, Output Compare A Match Interrupt Enable Al escribir uno en este bit, y el bit enable general I del registro SREG vale uno, se habilita la Interrupción en Coincidencia A del Timer/Counter1. Al activarse el flag OCF1A, del registro TIFR1, se ejecutará su respectiva función de interrupción ISR. TOIE1 Timer/Counter1 Overflow Interrupt Enable Cuando se escribe uno en el bit TOIE1, y el bit I del registro SREG vale uno, se habilita la Interrupción por Desbordamiento del Timer/Counter1. Si ocurre un Desbordamiento en el registro TCNT1, se ejecutará la función de Interrupción correspondiente, esto es, cuando se active al flag TOV1 del registro TIFR1. TIFR1 – Timer/Counter Interrupt Flag 1 Register
  • 655. Registro TIFR1 TIFR1 --- --- ICF1 --- --- OCF1B OCF1A TOV1 Registro de Microcontrolador Bits 7:6 Reserved Bits 4:3 Reserved Estos bits están reservados y siempre se leerán como cero. ICF1 Timer/Counter1, Input Capture Flag Este flag se activa cuando ocurre un evento en el pin ICP1. Si la configuración de los bits WGM13:0 establecen el registro Input Capture como tope del conteo, el flag ICF1 se activará cuando el contador alcance el valor tope. El bit ICF1 se limpia automáticamente al ejecutarse la función de Interrupción respectiva. Alternativamente, ICF1 se puede limpiar por software escribiendo sobre él un uno lógico. OCF1B Timer/Counter1, Output Compare B Match Flag Este flag se activa en el ciclo de reloj del Timer después de que el valor del contador (TCNT1) coincida con el registro Output Compare B (OCR1B). Note que una Coincidencia forzada con el bit FOC1B no activará el flag OCF1B. El flag OCF1B se limpia por hardware al ejecutarse su correspondiente función de interrupción. Alternativamente se puede limpiar escribiendo en él un uno lógico. OCF1A Timer/Counter1, Output Compare A Match Flag Este flag se activa en el ciclo de reloj del Timer después de que el valor del contador (TCNT1) coincida con el registro Output Compare A (OCR1A). Note que una Coincidencia forzada con el bit FOC1A no activará el flag OCF1A. El flag OCF1A se limpia por hardware al ejecutarse su correspondiente función de interrupción. Alternativamente se puede limpiar escribiendo en él un uno lógico. TOV1 Timer/Counter1, Overflow Flag La activación de este flag depende de la configuración de los bits WGM13:0. En los modos Normal y CTC el flag TOV1 se activa cuando se desborda el Timer1. El flag TOV1 se limpia por hardware al ejecutarse su correspondiente función de
  • 656. interrupción. Alternativamente se puede limpiar escribiendo en él un uno lógico. GTCCR – General Timer/Counter Control Register Registro GTCCR GTCCR TSM --- --- --- --- --- PSRASY PSRSYNC Registro de Microcontrolador TSM Timer/Counter Synchronization Mode Al escribir uno en el bit TSM se activa el modo de Sincronización del Timer/Counter. En este modo, se mantendrán los valores que se escriban en los bits PSRASY y PSRSYNC, para mantener activadas las señales de reset del prescaler correspondiente. Esto asegura que los correspondientes Timers estén detenidos y se puedan configurar al mismo valor sin correr el riesgo de que uno de ellos avance durante la configuración. Si se escribe un cero en el bit TSM, los bits PSRASY y PSRSYNC se limpian por hardware y los Timers/Counters empiezan a contar simultáneamente. PSRASY Prescaler Reset Timer/Counter2 Cuando este bit vale uno, el prescaler del Timer/Counter2 se reseteará. Normalmente este bit se limpia de inmediato por hardware. Si se escribe en este bit cuando el Timer/Counter2 está trabajando en modo asíncrono, el bit permanecerá en uno hasta que se resetee el prescaler. El bit no se limpiará por hardware si el bit TSM vale uno. PSRSYNC Prescaler Reset Si este bit vale uno, el prescaler del Timer/Counter0 y el Timer/Counter1 se reseteará. Normalmente este bit se limpia de inmediato por hardware, excepto cuando el bit TSM valga uno. Note que el Timer/Counter0 y el Timer/Counter1 comparten el mismo prescaler y el reset de este prescaler afecta a ambos Timers. Registros del Timer3 Registro TCCR3A TCCR3A COM3A1 COM3A0 COM3B1 COM3B0 --- --- WGM31 WGM30 Registro TCCR3B TCCR3B ICNC3 ICES3 --- WGM33 WGM32 CS32 CS31 CS30
  • 657. Registro TCCR3C TCCR3C FOC3A FOC3B --- --- --- --- --- --- Registro TCNT3H TCNT3H Registro TCNT3L TCNT3L Registro OCR3H OCR3H Registro OCR3AL OCR3AL Registro OCR3BH OCR3BH Registro OCR3BL OCR3BL Registro ICR3H ICR3H Registro ICR3L ICR3L Registro TIMSK3 TIMSK3 --- --- ICIE3 --- --- OCIE3B OCIE3A TOIE3 --- --- ICF3 --- --- OCF3B OCF3A TOV3 TSM --- --- --- --- --- Registro TIFR3 TIFR3 Registro GTCCR GTCCR PSRASY PSRSYNC
  • 658. Práctica: El Timer1 en Modo PWM Se generan ondas PWM de Fase y Frecuencia correctas por los dos canales, A y B. Se pueden configurar la Frecuencia y el Duty cycle de la dos señales desde la consola serial. Circuito para probar el Timer1 del microcontrolador AVR. El código fuente /****************************************************************************** * FileName: main.c * Purpose: Timer1 - Operación en modo PWM * Processor: ATmega164P * Compiler: AVR IAR C y AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. * * License: Se permiten el uso y la redistribución de este código con
  • 659. * modificaciones o sin ellas, siempre que se mantengan esta * licencia y la nota de autor de arriba. *****************************************************************************/ #include "avr_compiler.h" #include "usart.h" charGetNumStr(char*buffer,unsignedcharlen); intmain(void) { unsignedintreg; chark,buffer[10]; usart_init(); puts("rn Timer1 en Modo PWM r"); /* Configuración del Timer1 * - Bits WGM: PWM de Fase y Frecuencia Correctas con tope de conteo = ICR1 * - Bits COM: PWM no-invertida en los dos canales A y B * - Bits CS: Fuente de reloj = F_CPU/8 */ TCCR1A=(1<<COM1A1)|(1<<COM1B1); TCCR1B=(1<<WGM13)|(1<<CS11); DDRD=(1<<4)|(1<<5);// Pines OC1A y OC1B salidas PWM
  • 660. /* Poner algunos valores iniciales */ ICR1=200;// frecuencia PWM = 1/(2*200 ) = 2.5 kHz OCR1A=100;// Duty cycle canal A = 50% OCR1B=50;// Duty cycle canal B = 25% puts("r Escoja el registro a modificar "); puts("r [1] ICR1 -> Frecuencia"); puts("r [2] OCR1A -> Duty cycle A"); puts("r [3] OCR1B -> Duty cycle Br"); while(1) { do{ k=getchar(); }while(!((1<='k')&&(k<='3'))); switch(k) { case'1':puts("r ICR1 = ");break; case'2':puts("r OCR1A = ");break; case'3':puts("r OCR1B = ");break; } while(GetNumStr(buffer,9)==0); reg=atoi(buffer); switch(k) { case'1':ICR1=reg;break;
  • 661. case'2':OCR1A=reg;break; case'3':OCR1B=reg;break; } } } //**************************************************************************** // Lee una cadena de texto de 'len' números // El tamaño de 'buffer' debe ser mayor que 'len'. //**************************************************************************** charGetNumStr(char*buffer,unsignedcharlen) { charc;staticunsignedchari=0; if(kbhit()){ c=getchar(); if((c<='9'&&c>='0')&&(i<len)){// Si c está entre 0 y 9 buffer[i++]=c;// Guardar en buffer putchar(c);// Eco } elseif((c=='b')&&(i)){// Si c es RETROCESO y si i>0 i--;// putchar(c);// Eco } elseif((c=='r')&&(i)){// Si c es ENTER y si i>0 buffer[i]='0';// Poner un 0x00 (fin de cadena) putchar(c);// Eco
  • 662. i=0;// Resetear contador return1;// Retornar con 1 } } return0;// Retornar con 0 } Reproductor de Ringtones en formato RTTTL De todos los proyectos con microcontroladores creo que los relacionados con las luces y el sonido son las más atractivas. El procesamiento del sonido dependerá de la calidad de como lo queramos oír. Y si deseamos música, un simple microcontrolador de 8 bits nos puede resultar corto. Un proyecto más a nuestro alcance sería un reproductor de ringtones, por tratarse de la música que viene en los formatos más sencillos. El conocido formato RTTTL (RingTone Text Transfer Language) fue desarrollado por Nokia hace muchísimos años para sus productos celulares y por la popularidad alcanzada se ha convertido en el más usado para los juguetes, tarjetas navideñas, cajitas de música, etc. Como su nombre indica, el formato RTTTL se basa en un archivo de texto y su estructura luce más o menos así: estructura de un ringtone en formato RTTTL Newyear:d=4,o=5,b=125:a4,d.,8d,d,f#,e.,8d,e,8f#,8e,d.,8d,f#,a,2b.,b,a.,8f#,f#,d,e.,8d,e,8f#,8e,d., 8b4,b4,a4,2d,16p Se trata de una simple línea de texto que se divide en tres secciones. Cada sección se separa de la otra por el carácter dos puntos (:). Eso de los colores lo puse yo para su mejor identificación. El título del ringtone. Es un texto cualquiera que no debería superar los 10 caracteres. Los parámetros por defecto. Establecen los valores para d, o, b, s y l que serán tomados en cuenta en la sección de notas. Al mismo tiempo si aquí no se especificand (duration), o (octava) y b (beats por minuto), se deben tomar los valores por defecto d=4, o=6 y b=63. Describimos estos parámetros más abajo. s y l son raramente usados (de hecho, yo nunca los vi). s. Indica el estilo en que se toca el ringtone.
  • 663. l. Indica las veces que se reproducirá el ringtone actual. Puede valer entre 0 y 15. El 15 significa que el ringtone se reproduce indefinidamente. Los comandos de las notas. Los comandos deben estar separados por comas. Cada comando representa un sonido diferente y debe presentar el siguiente esquema: <duración><nota><escala><duración-especial> donde solo nota es necesaria. Los otros parámetros son opcionales. La nota y su escala 'o' Es el único campo necesario. Las 12 notas se identifican por C, C#, D, D#, E, F, F#, G, G#,A y A#. Además está la pausa identificada por P. En los ringtones se suelen escribir en minúsculas. El formato RTTTL solo soporta 4 escalas, que corresponden a las octavas 4, 5, 6 y 7. Es fácil notar en la siguiente tabla que la frecuencia de cada nota en una escala es el doble de su valor en la escala previa. Esta tabla se obtiene teniendo como base A4 = 440 Hz, es decir, cuando la nota A en la octava 4 vale 440 Hz (fuente: www.music.sc.edu). Sobra decir que la frecuencia de la pausa es 0. Tabla Nota Nota Frecuencia en Octava 4 Frecuencia en Octava 5 Frecuencia en Octava 6 Frecuencia en Octava 7 C 261.63 523.25 1046.50 2093.00 C# 277.18 554.37 1108.73 2217.46 D 293.66 587.33 1174.66 2349.32 D# 311.13 622.25 1244.51 2489.02 E 329.63 659.26 1318.51 2637.02 F 349.23 698.46 1396.91 2793.83 F# 369.99 739.99 1479.98 2959.96 G 392.00 783.99 1567.98 3135.96 G# 415.30 830.61 1661.22 3322.44 A 880.00 1760.00 3520.00 440.00
  • 664. Tabla Nota Nota Frecuencia en Octava 4 Frecuencia en Octava 5 Frecuencia en Octava 6 Frecuencia en Octava 7 A# 466.16 932.33 1864.66 3729.31 B 987.77 1975.53 3951.07 493.88 La duración 'd' y la duración-especial '.' Representa la fracción de tiempo que debe sonar una nota. Su valor por defecto es 4, es decir, si no se indica otro valor, la nota debería durar la cuarta parte de una nota entera. Los otros valores para la duración son 1, 2, 4, 8, 16 y 32. La duración-especial se representa por el carácter punto (.) y significa que el tiempo establecido por la duración d se debe prolongar en un 50%. El estándar RTTTL también contempla los caracteres ; y & como duraciones especiales pero no se suelen usar en la práctica. Los beats por minuto 'b' Habíamos dicho previamente que la duración d divide el tiempo que suena una nota entera. Pues bien, una nota entera debe durar 4 beats. El valor de cada beat se calcula por medio del parámetro b, que indica los beats por minuto a que se toca el ringtone. Por ejemplo, si b=125, cada beat dura 60/125 = 0.48 segundos. Los valores posibles de b son 25, 28, 31, 35, 40, 45, 50, 56, 63, 70, 80, 90, 100, 112, 125, 140, 160, 180, 200, 225, 250, 285, 320, 355, 400, 450, 500, 565, 635, 715, 800 y 900. Si no se especifica se debe tomar su valor por defecto b=63. La práctica Por más que el formato RTTTL sea muy sencillo, al principio cuesta un poco asimilarlo. Por eso en esta práctica no solo reproduciremos ringtones con nuestro AVR, sino que podremos visualizar las notas que se están tocando en tiempo real. El programa nos permitirá inclusive ver los parámetros decodificados de cada nota. Desde el teclado se puede avanzar o retroceder por los 63 ringtones disponibles, mediante las teclas (+) y (-). Si presionamos la tecla (*) también podremos ver las notas que están sonando. Y si presionamos la tecla (.) descubriremos la diferencia que hay entre un ringtone cuyas notas suenan todo su periodo y cuando suena solo las 7/8 partes de su periodo. Esto último se describe mejor al final.
  • 665. Circuito para el reproductor de Ringtones en formato RTTTL Este programa lo compilé para un ATmega324P para albergar todos los ringtones que quize probar. Si tienes un megaAVR con memoria FLASH inferior a 16K puedes puedes quitar algunos ringtones y recompilar el programa. /****************************************************************************** * FileName: main.c * Purpose: Reproductor de Ringtones en formato RTTTL de Nokia. * Processor: ATmel AVR * Compiler: AVR IAR C y AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved.
  • 666. * * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta * licencia y las notas de autor y copyright de arriba. *****************************************************************************/ #include "avr_compiler.h" #include "usart.h" #include <ctype.h> voidSetupTimers(void); unsignedintGetDefault(PGM_Pr,charpar); unsignedintGetFreq(char*n,unsignedcharoctave); unsignedintGetLeng(char*n,unsignedintduration,unsignedintbpm); voidPlayNote(unsignedintf_pwm,unsignedintt); /* Fuentes de ringtones * http://ez4mobile.com/nokiatone/rtttf.htm * http://www.free-nokia-ring-tones-4u.com/ * http://www.free-ringtones.eu.com/ */ /* Films */ PROGMEMconstcharrt01[]="Stars Wars:d=4,o=5,b=180:8f,8f,8f,2a#.,2f.6,8d#6,8d6,8c6,2a#.6,f.6,8d#6,8d6,8c6,2a#.6,f.6,8d#6,8d6,8d #6,2c6,p,8f,8f,8f,2a#.,2f.6,8d#6,8d6,8c6,2a#.6,f.6,8d#6,8d6,8c6,2a#.6,f.6,8d#6,8d6,8d#6,2c6,p";
  • 667. PROGMEMconstcharrt02[]="Star Wars End:d=4,o=5,b=225:2c,1f,2g.,8g#,8a#,1g#,2c.,c,2f.,g,g#,c,8g#.,8c.,8c6,1a#.,2c,2f.,g,g#.,8f,c.6,8g#,1f 6,2f,8g#.,8g.,8f,2c6,8c.6,8g#.,8f,2c,8c.,8c.,8c,2f,8f.,8f.,8f,2f"; PROGMEMconstcharrt03[]="Star Wars Imp:d=4,o=5,b=112:8d.,16p,8d.,16p,8d.,16p,8a#4,16p,16f,8d.,16p,8a#4,16p,16f,d.,8p,8a.,16p,8a., 16p,8a.,16p,8a#,16p,16f,8c#.,16p,8a#4,16p,16f,d.,8p,8d.6,16p,8d,16p,16d,8d6,8p,8c#6,16p,16c6, 16b,16a#,8b,8p,16d#,16p,8g#,8p,8g,16p,16f#,16f,16e,8f,8p,16a#4,16p,2c#";// Dude PROGMEMconstcharrt04[]="Exorcist: d=4,o=5,b=130:8e6,8a6,8e6,8b6,8e6,8g6,8a6,8e6,8c7,8e6,8d7,8e6,8b6,8c7,8e6,8a6,8e6,8b6,8e6, 8g6,8a6,8e6,8c7,8e6,8d7,8e6,8b6,8c7,8e6,8b6,8e6,8a6,8e6,8b6,8e6,8g6,8a6,8e6,8c7,8e6,8d7,8e6 ,8b6,8c7"; PROGMEMconstcharrt05[]="The Godfather:d=8,o=5,b=56:g,c6,d#6,d6,c6,d#6,c6,d6,c6,g#,a#,2g,g,c6,d#6,d6,c6,d#6,c6,d6,c6,g,f#,2f, f,g#,b,2d6,f,g#,b,2c6,c,d#,a#,g#,g,a#,g#,g#,g,g,b,2c6"; PROGMEMconstcharrt06[]="Indiana Jones:d=4,o=5,b=250:e,8p,8f,8g,8p,2c.6,8p.,d,8p,8e,1f,p.,g,8p,8a,8b,8p,2f.6,p,a,8p,8b,2c6,2d6,2e6 ,e,8p,8f,8g,8p,1c6,p,d6,8p,8e6,1f6,g,8p,8g,e.6,8p,d6,8p,8g,e.6,8p,d6,8p,8g,f.6,8p,e6,8p,8d6,1c6"; PROGMEMconstcharrt07[]="James Bond: d=4,o=5,b=140:8e,16f#,16f#,8f#,f#,8e,8e,8e,8e,16g,16g,8g,g,8f#,8f#,8f#,8e,16f#,16f#,8f#,f#,8e,8e, 8e,8e,16g,16g,8g,g,8f#,8f,8e,8d#6,2d.6,8b,8a,1b"; PROGMEMconstcharrt08[]="Mission Impossible:d=4,o=6,b=100:32d,32d#,32d,32d#,32d,32d#,32d,32d#,32d,32d,32d#,32e,32f,32f#,32g ,16g,8p,16g,8p,16a#,16p,16c,16p,16g,8p,16g,8p,16f,16p,16f#,16p,16g,8p,16g,8p,16a#,16p,16c,16 p,16g,8p,16g,8p,16f,16p,16f#,16p,16a#,16g,2d,32p,16a#,16g,2c#,32p,16a#,16g,2c,16p,16a#5,16c" ; PROGMEMconstcharrt09[]="20th Century Fox:d=16,o=5,b=140:b,8p,b,b,2b,p,c6,32p,b,32p,c6,32p,b,32p,c6,32p,b,8p,b,b,b,32p,b,32p,b,32p,b ,32p,b,32p,b,32p,b,32p,g#,32p,a,32p,b,8p,b,b,2b,4p,8e,8g#,8b,1c#6,8f#,8a,8c#6,1e6,8a,8c#6,8e6, 1e6,8b,8g#,8a,2b"; PROGMEMconstcharrt10[]="Jurasic Park:d=4,o=6,b=180:a5,2g5,d5,2a5,8a5,8b5,c.,8c,b5,g5,2a.5,2p,c,b5,g5,g_.5,8e5,8a5,8b5,c,2c.5,d5 ,2e5,d.5,p,c5,2g5,d5,2a5,8a5,8b5,c.,8c,b5,g5,2a.5,2p,c,b5,g5,g_.5,8e5,8a5,8b5,c,2c.5,d5,2e5,d.5"; PROGMEMconstcharrt11[]="Lord of the rings:d=4,o=6,b=140:c5,d5,2e5,2g5,2e.5,d5,1c.5,e5,g5,2a5,2c,2b.5,g5,2e.5,8f5,8e5,2d5,c5,d5,2e5, 2g5,2e.5,d5,1c.5,e5,g5,1a5,a5,g5,e5,1d.5,2p,1c5";
  • 668. PROGMEMconstcharrt12[]="Terminator:d=4,o=6,b=40:32d#6,16f6,4f#6,16f.6,16c#6,4f#5,32d#6,1 6f6,4f#6,16f.6,16c#6,4a#6,4g#6,16d#6,16f6,4f#6,16f.6,16c#6,4g#5,4f#5,16d#5,4f#5,4f5,32d#6,16f 6,4,f#6,2f#.5"; PROGMEMconstcharrt13[]="Robocop:d=8,o=5,b=140:f,g#,f,4g,4a#.,32p,f,g#,f,2c#,p,f,g#,f,4g,4a#,2 d#.,4p,f,g#,f,4g,4a#.,32p,a#,c#6,a#,2f6,p,f6,g#6,f6,4c7,4g#6,2a#6.,p,16a#6,16a#6,2c7."; PROGMEMconstcharrt14[]="Rocky:d=4,o=6,b=125:16e5,8g5,16p,2a5,8p,16a5,8b5,16p,2e5,16p,32 p,16e5,8g5,16p,2a5,16p,32p,16a5,8b5,16p,1e5,8p,16p,16d,16c,8d,16p,16c,16d,e,p,16c,16c,8b5,1 6b5,8a5,16a5,g5,8f,1e"; PROGMEMconstcharrt15[]="The Good The Bad And The Ugly:d=8,o=5,b=125:16a,16d6,16a,16d6,2a,4f,4g,2d,4p,16a,16d6,16a,16d6,2a,4f,4g,2c6,4p,16a,16 d6,16a,2d6,4f6,e6,d6,2c6,4p,16a,16d6,16a,16d6,4a.,4g,d,2d"; PROGMEMconstcharrt16[]="Top Gun:d=4,o=5,b=100:32p,8g#,8g#,8g#,8g#,g#,8d#,g,8g,8g,8g,g,8d#,8f.,16d#,c,p,8g#,8g#,8g#,8g#,g#, 8d#,g,8g,8g,8g,g,8g#,8f.,16d#,c,p,8a#,8a#,8a#,8a#,a#,8f,g#,8g#,8g#,8g#,g#,8a#,8g.,16f,d#,p,8g#,8g #,8g#,8g#,g#,8d#,g,8g,8g,8g,g,8g#,8f,16d#,c#.,p,8f,2d#,8d#,8f,8g#,8a#,2g#"; /* Tv */ PROGMEMconstcharrt17[]="Knight Rider:d=4,o=5,b=63:16e,32f,32e,8b,16e6,32f6,32e6,8b,16e,32f,32e,16b,16e6,d6,8p,p,16e,32f,32e, 8b,16e6,32f6,32e6,8b,16e,32f,32e,16b,16e6,f6,p"; PROGMEMconstcharrt18[]="Knight Rider 2:d=4,o=5,b=125:16e,16p,16f,16e,16e,16p,16e,16e,16f,16e,16e,16e,16d#,16e,16e,16e,16e,16p,16 f,16e,16e,16p,16f,16e,16f,16e,16e,16e,16d#,16e,16e,16e,16d,16p,16e,16d,16d,16p,16e,16d,16e,1 6d,16d,16d,16c,16d,16d,16d,16d,16p,16e,16d,16d,16p,16e,16d,16e,16d,16d,16d,16c,16d,16d,16d "; PROGMEMconstcharrt19[]="Hawaii 50:d=4,o=6,b=80:16a5,16a5,16c,16e.,8d.,a5,16a5,16a5,16g5,16c.,a.5,16a5,16a5,16c,16e.,8d.,a,16 g,16g,16e,16c.,2a,16c7,16a#,16a,16g,16f,16e,16d,16e,16c,d,8d,16a#,16g#,16g,16e,16d,16c,16d,16 e.,16d,16c.,8d,a,16g,16g,16e,16c.,2d"; PROGMEMconstcharrt20[]="Charlie's Angels:d=4,o=5,b=125:32p,c#,8d,2c#,b4,e,d,c#,8d,2c#,8d,8b4,8c#,8d,8e,f#,8g,2f#,e,a,g,2f#,16a4,1 6b4,16c,16d,16e,16f#,16g,16a,b,8c6,2b,8a,8f#,g,8a,b,8c6,2b,a,d6,c6,2b"; PROGMEMconstcharrt21[]="The Adams Family:d=4,o=5,b=160:8c,f,8a,f,8c,b4,2g,8f,e,8g,e,8e4,a4,2f,8c,f,8a,f,8c,b4,2g,8f,e,8c,d,8e,1f,8c,8d, 8e,8f,1p,8d,8e,8f#,8g,1p,8d,8e,8f#,8g,p,8d,8e,8f#,8g,p,8c,8d,8e,8f";
  • 669. PROGMEMconstcharrt22[]="The Xfiles:d=4,o=5,b=125:e,b,a,b,d6,2b.,1p,e,b,a,b,e6,2b.,1p,g6,f#6,e6,d6,e6,2b.,1p,g6,f#6,e6,d6,f#6,2b. ,1p,e,b,a,b,d6,2b.,1p,e,b,a,b,e6,2b.,1p,e6,2b."; PROGMEMconstcharrt23[]="I Dream Of Jeanie:d=8,o=6,b=250:4g.5,p,d,4p,d,p,c,p,e,d,p,c,p,4f.5,p,d,4p,d,p,c,p,e,d,p,c,p,4g.5,p,d,4p,d,p,c,p, e,d,p,c,p,2f.5,f,f,f,1p,4f.,p,f,4p,f,p,f,p,f,d#,p,c#,p,4d#.,p,c,4p,d#,p,d#,p,d#,c#,p,c,p,4c#.,p,a#5,4p,c#, p,c#,p,c#,c,p,a#5,p,1c,p,d,p,c,a#5,p,a5"; /* Cartoons */ PROGMEMconstcharrt24[]="Dragon ball:d=4,o=4,b=56:16f5,16f5,16d#5,32c5,4f5,32f5,32f5,16f5,16d#5,32c5,4f5,32a#4,32a#4,32a#4,3 2a#4,16a#4,16g#4,32a#4,32a#4,32a#4,32a#4,16a#4,16g#4,2c5,16c5,16d#5,8f5,8f5,8f5,16d#5,16c 5,16d#5,16c5,16d#5,32d#5,8f.5,16c5,16d#5,4f5,16g#5,16g5,16f5,8d#.5,16g#5,16g5,8f5,8d#5,2f5"; PROGMEMconstcharrt25[]="Transformers:d=32,o=6,b=45:p,f5,16a#5,c,4c#,16a#5,c,16c#,f#5,4f#5, a#5,c,16c#,16c#,16d#,16f,f#,16d#,c#,16d#,f,16c#,c,16c#,d#,16c.,a#5,a5,c,a#5,8a#5"; PROGMEMconstcharrt26[]="Looney Toons:d=4,o=5,b=140:32p,c6,8f6,8e6,8d6,8c6,a.,8c6,8f6,8e6,8d6,8d#6,e.6,8e6,8e6,8c6,8d6,8c6,8e 6,8c6,8d6,8a,8c6,8g,8a#,8a,8f"; PROGMEMconstcharrt27[]="De Smurfe:d=4,o=5,b=112:g,8c.6,16g,8a,8f,d,8g.,16c,8d,8e,2d,g,8c.6,16g,8a,8f,d,8g.,16e,8f,8d,c,p,g,8c .6,16g,8a,8f,d,8g.,16c,8d,8e,2d,g,8c.6,16g,8a,8f,d,8g.,16e,8f,8d,c,p,g,8c.6,16g,8a,8f,d,8g.,16c,8d,8 e,2d,g,8c.6,16g,8a,8f,d,8g.,16e,8f,8d,c"; PROGMEMconstcharrt28[]="Animaniacs:d=4,o=5,b=160:d#6,d6,d#6,f.6,8d#6,d.6,8d#6,c6,2p,8d6,8 d#6,f.6,8d#6,d.6,8d#6,a#,2p,8c6,8a#,8g#,8g#,8c6,8d#6,g#6,8p,8g#6,8a#.6,16g#6,8g6,8g#6,f6,8p,8f 6,d#6,c7,a#.6,8g#6,g#6"; PROGMEMconstcharrt29[]="Inspector Gadget:d=4,o=5,b=320:c#,8d#,e,8f#,g#,8p,8e,p,g,8p,8d#,p,f#.,8e,p,c#,8d#,e,8f#,g#,8p,8c#6,p,2c.6, p,2p,c#,8d#,8e,8p,8f#,g#,8p,8e,p,g,8p,8d#,p,f#.,e.,c#,1p,2p,8p,c.,c#,2p,c#,8d#,e,8f#,g#,8p,8e,p,g,8 p,8d#,p,f#.,8e,p,c#,8d#,e,8f#,g#,8p,8c#6,p,2c.6,p,2p,c#,8d#,8e,8p,8f#,g#,8p,8e,p,g,8p,8d#,p,f#.,e., c#,1p,2p,8p,c.,c#"; PROGMEMconstcharrt30[]="The Simpsons:d=4,o=5,b=160:c.6,e6,f#6,8a6,g.6,e6,c6,8a,8f#,8f#,8f#,2g,8p,8p,8f#,8f#,8f#,8g,a#.,8c6,8c 6,8c6,c6";// Check PROGMEMconstcharrt31[]="Barbie girl:d=4,o=5,b=125:8g#,8e,8g#,8c#6,a,p,8f#,8d#,8f#,8b,g#,8f#,8e,p,8e,8c#,f#,c#,p,8f#,8e,g#,f#,8g#, 8e,8g#,8c#6,a,p,8f#,8d#,8f#,8b,g#,8f#,8e,p,8e,8c#,f#,c#,p,8f#,8e,g#,f#";
  • 670. PROGMEMconstcharrt32[]="Superman:d=4,o=6,b=200:8d5,8d5,8d5,8g.5,16p,8g5,2d,8p,8d,8e,8d, 8c,1d,8p,8d5,8d5,8d5,8g.5,16p,8g5,2d,8d,8d,8e,8c,8g5,8e,2d.,p,8g5,8g5,8g5,2f#.,d.,8g5,8g5,8g5,2 f#.,d.,8g5,8g5,8g5,8f#,8e,8f#,2g.,8g5,8g5,8g5,2g.5"; PROGMEMconstcharrt33[]="Popeye:d=4,o=5,b=225:8a#,8a#,8a#,8a#,8g#,8p,8g,2a#,8p,8a#,8c6,8g #,8c6,8d#6,8p,8c6,2a#,8p,8a#,8c6,8g#,8c6,8d#6,8d6,8c6,8a#,8c6,8a#,8g,8d#,8a#,8a#,8a#,8a#,8c6, 8p,8d6,2d#6"; PROGMEMconstcharrt34[]="The Pink Panther:d=4,o=6,b=70:32f#.5,g5,32a.5,a#5,32f#.5,16g.5,32a.5,16a#.5,32d#.,16d.,32g.5,16a#.5,32d .,2c#,32c.,32a#.5,32g.5,32f.5,g.5,32p,16g.,32f.,16d.,32c.,16a#.5,32g.5,32c#,8c,32c#,8c,32c#,8c,32c #,8c,32a#.5,32g.5,32f.5,16g.5,g.5"; PROGMEMconstcharrt35[]="Cantina:d=4,o=5,b=250:8a,8p,8d6,8p,8a,8p,8d6,8p,8a,8d6,8p,8a,8p,8 g#,a,8a,8g#,8a,g,8f#,8g,8f#,f.,8d.,16p,p.,8a,8p,8d6,8p,8a,8p,8d6,8p,8a,8d6,8p,8a,8p,8g#,8a,8p,8g, 8p,g.,8f#,8g,8p,8c6,a#,a,g"; PROGMEMconstcharrt36[]="Flinstones:d=4,o=5,b=40:32p,16f6,16a#,16a#6,32g6,16f6,16a#.,16f6, 32d#6,32d6,32d6,32d#6,32f6,16a#,16c6,d6,16f6,16a#.,16a#6,32g6,16f6,16a#.,32f6,32f6,32d#6,32 d6,32d6,32d#6,32f6,16a#,16c6,a#,16a6,16d.6,16a#6,32a6,32a6,32g6,32f#6,32a6,8g6,16g6,16c.6,3 2a6,32a6,32g6,32g6,32f6,32e6,32g6,8f6,16f6,16a#.,16a#6,32g6,16f6,16a#.,16f6,32d#6,32d6,32d6 ,32d#6,32f6,16a#,16c.6,32d6,32d#6,32f6,16a#,16c.6,32d6,32d#6,32f6,16a#6,16c7,8a#.6"; PROGMEMconstcharrt37[]="Woody Woodpecker:d=16,o=6,b=125:c,f,a,8c7,8a,4p,p,c,f,a,8c7,8a,4p,c7,p,a,8a#,c7,8a#.,8a.,8g,4f,4p,8p,c, f,a,8c7,8a,4p,p,c,f,a,8c7,8a,4p,c7,p,a,8a#,c7,8a#.,8a.,8g,4c7"; PROGMEMconstcharrt38[]="Batman:d=4,o=5,b=200:8f,8f,8e,8e,8d#,8d#,8e,8e,8f,8f,8e,8e,8d#,8d #,8e,8e,8f,8f,8e,8e,8d#,8d#,8e,8e,g#,2g#,p,8f,8f,8e,8e,8d#,8d#,8e,8e,8f,8f,8e,8e,8d#,8d#,8e,8e,8f, 8f,8e,8e,8d#,8d#,8e,8e,g#,2g#,p,8f,8f,8e,8e,8d#,8d#,8e,8e,8f,8f,8e,8e,8d#,8d#,8e,8e,8f,8f,8e,8e,8 d#,8d#,8e,8e,g#,2g#,p,f"; /* Classics */ PROGMEMconstcharrt39[]="Beethoven - Fur Elise: d=4,o=5,b=140:8e6,8d#6,8e6,8d#6,8e6,8b,8d6,8c6,a,8p,8c,8e,8a,b,8p,8e,8g#,8b,c6,p,8e,8e6,8d#6 ,8e6,8d#6,8e6,8b,8d6,8c6,a,8p,8c,8e,8a,b,8p,8e,8c6,8b,2a"; PROGMEMconstcharrt40[]="Beethoven - 5th Symphony:d=4,o=5,b=180:8f,8f,8f,1c#,8p,8d#,8d#,8d#,1c,8p,8f,8f,8f,8c#,8f#,8f#,8f#,8f,8c#6,8c#6, 8c#6,2a#,8p,8f,8f,8f,8c,8f#,8f#,8f#,8f,8d#6,8d#6,8d#6,1c6,8f6,8f6,8d#6,8c#6,8c#,8c#,8d#,8f,8f6,8f 6,8d#6,8c#6,8c#,8c#,8d#,8f,8f6,8f6,8d#6,c#6,p,a#,p,2f6";
  • 671. PROGMEMconstcharrt41[]="Vivaldi - 4 Seasons Summer:d=16,o=6,b=120:8e,8g#,8g#,8g#,f#,e,4b.,b,a,8g#,8g#,8g#,f#,e,4b.,b,a,8g#,a,b,8a,8g#,8f#,8 d#,4b.,8e,8g#,8g#,8g#,f#,e,4b.,b,a,8g#,8g#,8g#,f#,e,4b.,b,a,8g#,a,b,8a,8g#,8f#,8d#,4b."; PROGMEMconstcharrt42[]="Mozart:d=4,o=6,b=120:16f#5,16e5,16d#5,16e5,g5,16a5,16g5,16f#5,1 6g5,b5,16c,16b5,16a#5,16b5,16f#,16e,16d#,16e,16f#,16e,16d#,16e,g,8e,8g,32d,32e,16f#,8e,8d,8e ,32d,32e,16f#,8e,8d,8e,32d,32e,16f#,8e,8d,8c#,b5"; PROGMEMconstcharrt43[]="Mozart - The Nutcracker Suite:d=16,o=5,b=225:8d6,4p,d6,p,d6,p,d6,p,8e6,4p,8e6,4p,8f#6,4p,8d6,4p,2e.6,8p,8d6,4p,d6,p,d 6,p,d6,p,8e6,4p,8e6,4p,8f#6,4p,8d6,4p,2e.6,8e,c6,8p,8d6,c6,8p,8b,a,8p,8g,f#,8p,8a,d6,8p,8e6,d6, 8p,8c6,b,8p,8a,g,8p,8b,4e6,8d6,4c6,8e6,4f#6,8e6,4d6,8f#6,4g6,8f#6,4e6,8f#6,8g6,4p,4g.6"; PROGMEMconstcharrt44[]="Beethoven - Ode To Joy:d=4,o=6,b=100:a5,a5,a#5,c,c,a#5,a5,g5,f5,f5,g5,a5,a.5,8g5,2g5,a5,a5,a#5,c,c,a#5,a5,g5,f5,f5,g5 ,a5,g.5,8f5,2f5,g5,g5,a5,f5,g5,8a5,8a#5,a5,f5,g5,8a5,8a#5,a5,g5,f5,g5,c5,2a5,a5,a#5,c,c,a#5,a5,g5,f 5,f5,g5,a5,g.5,8f5,2f5"; PROGMEMconstcharrt45[]="Bach - Fugue In D Minor:d=32,o=6,b=45:d#,c#,d#,b5,d#,a#5,d#,g#5,d#,g5,d#,g#5,d#,a#5,d#,b5,d#,d#5,d#,f5,d#,g5,d# ,g#5,d#,g5,d#,g#5,d#,a#5,d#,8b.5,16c,8c#.,16d,8d#.,16d#,8f.,16f,16f#,16a#5,16b5,16d#,8g#.5,8b5, 16a#5,16g#5,16c#,8f#5"; PROGMEMconstcharrt46[]="Bach Badinerie:d=16,o=6,b=125:8b,d7,b,8f#,b,f#,8d,f#,d,4b5,f#5,b5,d,b5,c#,b5,c#,b5,a#5,c#,e,c#,8d,8b5 ,8b,d7,b,8f#,b,f#,8d,f#,d,4b5,8d,8d,8d,8d,8b,8d,32d,32c#,32d,32c#,8c#,8f#,8f#,8f#,8f#,8d7,8f#,32f #,32f,32f#,32f,8f,c#,f#,a,f#,g#,f#,g#,f#,f,g#,b,g#,a,g#,a,g#,f#,a,f#,f,f#,b,f#,f,f#,c#7,f#,f,f#,d7,f#,f,f#,d 7,c#7,b,c#7,a,g#,f#,8a,8g#,4f#";// Dude /* Christmas */ PROGMEMconstcharrt47[]="Silent Night:d=4,o=5,b=112:g.,8a,g,2e.,g.,8a,g,2e.,2d6,d6,2b.,2c6,c6,2g.,2a,a,c6.,8b,a,g.,8a,g,2e.,2a,a,c6., 8b,a,g.,8a,g,2e.,2d6,d6,f6.,8d6,b,2c6.,2e6.,c6,g,e,g.,8f,d,2c."; PROGMEMconstcharrt48[]="Jingle Bells:d=8,o=6,b=125:g5,e,d,c,2g5,g5,e,d,c,2a5,a5,f,e,d,b5,g5,b5,d,g.,16g,f,d,2e,g5,e,d,c,2g5,16f#5, g5,e,d,c,2a5,a5,f,e,d,g,16g,16f#,16g,16f#,16g,16g#,a.,16g,e,d,4c,4g,e,e,e.,16d#,e,e,e.,16d#,e,g,c.,1 6d,2e,f,f,f.,16f,f,e,e,16e,16e,e,d,d,e,2d"; PROGMEMconstcharrt49[]="We Wish You:d=4,o=5,b=170:d,g,8g,8a,8g,8f#,e,e,e,a,8a,8b,8a,8g,f#,d,d,b,8b,8c6,8b,8a,g,e,d,e,a,f#,2g,d,g,8 g,8a,8g,8f#,e,e,e,a,8a,8b,8a,8g,f#,d,d,b,8b,8c6,8b,8a,g,e,d,e,a,f#,1g,d,g,g,g,2f#,f#,g,f#,e,2d,a,b,8a,8 a,8g,8g,d6,d,d,e,a,f#,2g";
  • 672. PROGMEMconstcharrt50[]="Santa Clauss:d=4,o=5,b=160:16c,8e.,16f,g,2g,16g,8a.,16b,c6,2c6,8e.,16f,g,g,g,8a.,16g,f,2f,e,g,c,e,d,2f,b4, 1c.,16c,8e.,16f,g,2g,16g,8a.,16b,c6,2c6,8e.,16f,g,g,g,8a.,16g,f,2f,e,g,c,e,d,2f,b4,1c.6"; PROGMEMconstcharrt51[]="Rudolph:d=16,o=6,b=100:32p,g#5,8a#5,g#5,8f5,8c#,8a#5,4g#.5,g#5,a #5,g#5,a#5,8g#5,8c#,2c,f#5,8g#5,f#5,8d#5,8c,8a#5,4g#.5,g#5,a#5,g#5,a#5,8g#5,8a#5,2f5,g#5,8a#5 ,a#5,8f5,8c#,8a#5,4g#.5,g#5,a#5,g#5,a#5,8g#5,8c#,2c,f#5,8g#5,f#5,8d#5,8c,8a#5,4g#.5,g#5,a#5,g# 5,a#5,8g#5,8d#,2c#"; PROGMEMconstcharrt52[]="Deck The Halls:d=8,o=6,b=140:4g.,f,4e,4d,4c,4d,4e,4c,d,e,f,d,4e.,d,4c,4b5,2c,4d.,e,4f,4d,4e.,f,4g,4d,e,f#,4g,a ,b,4c7,4b,4a,4g"; PROGMEMconstcharrt53[]="So this is christmass:d=4,o=6,b=160:c5,f5,g5,a5,f5,1c5,p,2p,c5,f5,g5,a5,8g5,8f5,1d5,p,2p,d5,g5,a5,a#5,a5,2g .5,1p,c5,a5,c,a5,8a5,8g5,1f5,p,2p,f5,d,d,d,c,1a#5,p,2p,f5,a#5,c,d,1c,1p,f5,c,d,d#,d,1c,p,2p,f5,d,f,d, 8a#5,8g5,2f.5"; /* Traditional */ PROGMEMconstcharrt54[]="Lord of the rings:d=4,o=6,b=125:8c5,8p,d#,d#,d,d,p,8c5,8p,d#,d#,d,d,p,8c5,8p,d#,d#,d,2d#,2f,d#,2d.,8c5,8p,d #,d#,d,d,p,8c5,8p,d#,d#,d,d,p,8c5,8p,d#,d#,d,2d#,2f,d#,2d.,c5"; PROGMEMconstcharrt55[]="Camptown Races:d=4,o=5,b=180:g,g,e,g,a,g,2e,e,2d.,e,2d.,g,g,e,g,a,g,2e,2d,e,d,1c,c.,8c,e,g,1c6,a.,8a,c6,a,1g,g, g,8e,8e,8g,8g,a,g,2e,d,8e,8f,e,8e,8d,2c,2p"; PROGMEMconstcharrt56[]="Circus:d=32,o=6,b=45:16c#,16c,b5,c,b5,a#5,16a5,16g#5,16g5,16g#5,1 6a#5,16a5,g#5,a5,g#5,g5,16f#5,16f5,16e5,16f5,16g#5,f#5,f#5,16d5,16d#5,16g#5,f#5,f#5,16d5,16d #5,c,c#,d,d#,e,f,f#,g,g#,a,a#,b,16a#,16g#,16c#,16c,b5,c,b5,a#5,16a5,16g#5,16g5,16g#5,16a#5,16a 5,g#5,a5,g#5,g5,16f#5,16f5,16e5,16f5,16e5,e5,e5,8g5,g#5,a#5,g#5,g5,16f5,16g#5,16c,c,c,16c,16c, c,c,c,c,c,c,c"; PROGMEMconstcharrt57[]="Pop Goes The Weasel:d=4,o=6,b=200:8g5,c,8c,d,8d,8e,8g,8e,c,8g5,c,8c,d,8d,e.,c,8g5,c,8c,d,8d,8e,8g,8e,c.,a.,d,8f, e.,c.,c,8c,a5,8c,8b5,8d,8b5,g.5,c,8c,a5,8c,b.5,g.5,f5,8e5,f5,8g5,a5,8b5,c,8d,g.,d,8f,e.,c."; PROGMEMconstcharrt58[]="Rock A Bye Baby (Lullaby):d=4,o=5,b=90:c,8d#,c6,2a#,g#,c,d#,g#,2g.,c#.,8d#,8c#.6,2c6,a#,a#,g#,f,2d#,8p,c.,8d#,c6,2 a#,g#,c,d#,g#,2g,f,d#,g#,c#6,c6,g#.,a#,f,g#,2g#"; PROGMEMconstcharrt59[]="Sailors Hornpipe:d=8,o=5,b=200:f6,e6,4f6,f.,32p,4f,c6,a#,a,c6,f6.,32p,4f6,g6,f#6,4g6,g.,32p,4g,g6,f6,e6,d6 ,c6.,32p,4c6,d6,e6,f6,e6,d6,c6,d6,c6,a#,a,a#,a,g,f,g,f,e,d,c,d,e,f,g,a#,a,g,4a,4f,4f.";
  • 673. PROGMEMconstcharrt60[]="Yankee Doodle:d=16,o=6,b=50:d.,g,g,a,b,g,b,a,d,g,g,a,b,8g,f#,d,g,g,a,b,c7,b,a,g,f#,d,e,f#,8g,8g,e.,32f#.,e,d, e,f#,8g,d.,32e.,d,c,8b5,8d,e.,32f#.,e,d,e,f#,g,e,d,g,f#,a,8g,8g"; PROGMEMconstcharrt61[]="Halloween:d=4,o=5,b=180:32p,8d6,8g,8g,8d6,8g,8g,8d6,8g,8d#6,8g,8 d6,8g,8g,8d6,8g,8g,8d6,8g,8d#6,8g,8c#6,8f#,8f#,8c#6,8f#,8f#,8c#6,8f#,8d6,8f#,8c#6,8f#,8f#,8c#6, 8f#,8f#,8c#6,8f#,8d6,8f#"; PROGMEMconstcharrt62[]="Happy Birthday:d=4,o=5,b=125:8g.,16g,a,g,c6,2b,8g.,16g,a,g,d6,2c6,8g.,16g,g6,e6,c6,b,a,8f6.,16f6,e6,c6,d 6,2c6,8g.,16g,a,g,c6,2b,8g.,16g,a,g,d6,2c6,8g.,16g,g6,e6,c6,b,a,8f6.,16f6,e6,c6,d6,2c6"; PROGMEMconstcharrt63[]="Newyear:d=4,o=5,b=125:a4,d.,8d,d,f#,e.,8d,e,8f#,8e,d.,8d,f#,a,2b.,b,a .,8f#,f#,d,e.,8d,e,8f#,8e,d.,8b4,b4,a4,2d,16p"; PROGMEMPGM_Pconstringtones[]={ rt01,rt02,rt03,rt04,rt05,rt06,rt07,rt08,rt09,rt10, rt11,rt12,rt13,rt14,rt15,rt16,rt17,rt18,rt19,rt20, rt21,rt22,rt23,rt24,rt25,rt26,rt27,rt28,rt29,rt30, rt31,rt32,rt33,rt34,rt35,rt36,rt37,rt38,rt39,rt40, rt41,rt42,rt43,rt44,rt45,rt46,rt47,rt48,rt49,rt50, rt51,rt52,rt53,rt54,rt55,rt56,rt57,rt58,rt59,rt60, rt61,rt62,rt63 }; constunsignedintNUM_RT=sizeof(ringtones)/2;// Número de ringtones disponibles volatileunsignedintticks_ms;// Contador de milisegundos volatileunsignedintsilence_ms;// Número de milisegundos de los silencios volatileunsignedcharsilence;// Flag para habilitar los silensios volatileunsignedcharskip;// Flag para detener y saltear el ringtone volatileunsignedcharrIndex;// Indice de ringtone
  • 674. volatileunsignedchardisplay_notes;// Flag para mostrar notas /****************************************************************************** * Gestor de Interrupción en Coincidencia del Timer0. * Esta función se ejecuta cada 1 ms exactamente. *****************************************************************************/ ISR(TIMER0_COMPA_vect) { if(ticks_ms) { ticks_ms--; if((ticks_ms<silence_ms)&&(silence==1)) OCR1A=0;// Poner Duty cycle = 0 para apagar el buzzer } } /***************************************************************************** * Gestor de Interrupción de Recepción Completada. * Esta función se dispara ante la presencia de algún dato en el USART. * Nota: * Para el ATmega324P (entre algunos otros) el nombre del Vector de esta * Interrupción debería ser USART0_RX_vect (con guiones bajo simples) en lugar * de USART0__RX_vect. Debe ser un error del archivo "iom324p.h" que * probablemenete se corrija en el futuro. Entre tanto se debe tener cuidado * porque AVR GCC no da error. Para IAR C se cambia por USART0_RX_vect. *****************************************************************************/
  • 675. #ifdef USART0__RX_vect ISR(USART0__RX_vect) #else ISR(USART0_RX_vect) #endif { chark=UDR0;// Leer dato llegado if(k=='-')// Tocar el ringtone anterior { if(rIndex>0){ rIndex--; ticks_ms=0;// Detener la nota actual skip=1;// Detener y saltear el ringtone actual } } elseif(k=='+')// Tocar el ringtone siguiente { if(rIndex<(NUM_RT-1)){ rIndex++; ticks_ms=0;// Detener la nota actual skip=1;// Detener y saltear el ringtone actual } } elseif(k=='*')// Visualizar u ocultar las notas { if(display_notes==0)display_notes=1;
  • 676. elsedisplay_notes=0; } elseif(k=='.')// Habilitar o deshabilitar silencios { if(silence==0)silence=1; elsesilence=0; } } /****************************************************************************** * Main function *****************************************************************************/ intmain(void) { unsignedintFrecuencia,Longitud; unsignedintb,d,o; charc,cad[50]; unsignedcharcounter,i,index_bk; PGM_PpRingtone; usart_init();// 9600 - 8N1 /* Habilitar Interrupción de 'Recepción Completada' del USART */ UCSR0B|=(1<<RXCIE0); printf("r Ringtones player ");
  • 677. printf("r ================ r"); printf("r %d ringtones available",NUM_RT); printf("r Press (+) for next ringtone"); printf("r Press (-) for previous ringtone"); printf("r Press (*) to show/hide playing notes"); printf("r Press (.) to enable/disable silence r"); SetupTimers();// Configurar los Timers 0 y 1 rIndex=0;// Iniciar con el primer ringtone display_notes=0;// No visualizar las notas silence=1;// No tocar silencios while(1) { /* Apuntar al ringtone número rIndex */ index_bk=rIndex; pRingtone=(PGM_P)pgm_read_word(&ringtones[index_bk]); /* Extraer el título del ringtone. * El bucle copia los primeros caracteres hasta llegar al primer ':' */ i=0; while((cad[i++]=pgm_read_byte(pRingtone++))!=':'); cad[i-1]='0';// Poner final
  • 678. /* Obtener parámetros de las notas por defecto */ b=GetDefault(pRingtone,'b');// Beats per minute d=GetDefault(pRingtone,'d');// Duration o=GetDefault(pRingtone,'o');// Octave /* Mostrar el título y los parámetros de control obtenidos */ printf("r [%d] %s",rIndex+1,cad); //printf("r b = %d d = %d o = %d ", b, d, o); /* Avanzar el puntero hasta la primera nota (pasar el segundo ':') */ while(pgm_read_byte(pRingtone++)!=':'); counter=0; if(index_bk==rIndex) skip=0;// Activar flag para ejecutar ringtone /* Bucle while para leer y ejecutar todas las notas del ringtone actual */ do{ /* Leer próxima nota del ringtone * Los caracteres se copian en el array cad hasta encontrar ',' o hasta * llegar al final de la ringtone. */ i=0; do{ c=pgm_read_byte(pRingtone++); cad[i++]=c;
  • 679. }while((c!=',')&&(c!='0')); cad[i-1]='0';// Poner final /* Decodificar la nota. Esto es obtener su Frecuencia y su Longitud */ Frecuencia=GetFreq(cad,o); Longitud=GetLeng(cad,d,b); /* Tocar la nota con los parámetros de Frecuencia y Longitud enviados */ PlayNote(Frecuencia,Longitud); /* Visualizar la nota a tocar si está activo el flag display_notes */ if(display_notes==1){ //printf("r N =%5s F =%5d D =%5d S =%3d", cad, Frecuencia, Longitud, silence_ms); if(((counter++)%16)==0)printf("r %s",cad); elseprintf(" %s",cad); } }while((c!=0)&&(skip==0)); PlayNote(0,3000);// Esto dará una pausa de 3000 ms PlayNote(0,0);// Esperar a que termine la pausa } } /***************************************************************************** * Devuelve el valor numérico que sigue al parámetro par. * par es un carácter que puede valer: * 'b': Beats Per Minute del ringtone.
  • 680. * 'o': Octava del ringtone. * 'd': Duración del ringtone. * 'l': Repeticiones de la nota. ****************************************************************************/ unsignedintGetDefault(PGM_Pr,charpar) { charc,num[5];unsignedchari=0; /* Avanzar el puntero hasta llegar al parámetro par * Se asume que r ya está apuntando al primer carácter de la sección que * contiene los parámetros a usar por defecto */ while(pgm_read_byte(r++)!=par); /* Copiar número subsiguiente hasta encontrar ',' o el siguiente ':' */ do{ c=pgm_read_byte(r); if(isdigit(c))// Filtar los números num[i++]=c; r++; }while((c!=':')&&(c!=',')); num[i]='0';// Poner final returnatoi(num);// Retornar cadena convertida en número }
  • 681. /***************************************************************************** * Devuelve el valor de la frecuencia de la nota en Hz. * La nota se pasa en n como una cadena terminada en nulo. ****************************************************************************/ unsignedintGetFreq(char*n,unsignedcharoctave) { // C C# D D# E F F# G G# A A# B Pausa unsignedintNoteFreqs[]={262,277,294,311,330,349,370,392,415,440,466,494,000}; unsignedintFrecuencia; unsignedchari=0; /* Avanzar el puntero salteando los caracteres numéricos de la duración */ while(isdigit(*n)) n++; /* Hallar la frecuencia base de la nota * Para esto buscamos el índice del array NoteFreqs que la contiene */ switch(*n++) { case'c':i=0;break; case'd':i=2;break; case'e':i=4;break; case'f':i=5;break; case'g':i=7;break;
  • 682. case'a':i=9;break; case'b':i=11;break; case'p':i=12;break; } // Ver si la nota es en realidad de la forma 'x#' if(*n=='#'){ i++; n++; } Frecuencia=NoteFreqs[i];// Obtener la frecuencia del array NoteFreqs /* Avanzar el puntero hasta el final buscando un carácter numérico que debe * ser la octava. Si se le encuentra se le convierte en número (asumiendo * que vale entre 4 y 7) y se actualiza la variable octave. */ while(*n!='0'){ if(isdigit(*n)) octave=*n-'0';// Ascii a integer n++; } Frecuencia=Frecuencia<<(octave-4);// Aplicar la octava returnFrecuencia; }
  • 683. /***************************************************************************** * Devuelve el valor de la Longitud de la nota en ms. * La nota se pasa en n como una cadena terminada en nulo. ****************************************************************************/ unsignedintGetLeng(char*n,unsignedintduration,unsignedintbpm) { unsignedintLongitud; /* Procesar la duración de la nota actual, si es que hay. De no haber, luego * se utilizará la duración por defecto recibida como parámetro. */ if(isdigit(*n)) duration=*n++-'0'; if(isdigit(*n)) duration=duration*10+*n++-'0'; /* Calcular la longitud de la nota en milisegundos, en base a su * duración y bpm. */ Longitud=60000/bpm;// Duración de un beat Longitud*=4;// Duración de una nota entera Longitud/=duration;// Duración de la nota actual /* Avanzamos el puntero hasta el final buscando el caracter '.' * Si se le encuentra, la longitud de la nota se debe extender en 50%.
  • 684. */ while(*n!='0'){ if(*n++=='.') Longitud+=Longitud/2; } returnLongitud; } /***************************************************************************** * Para tocar la nota en el formato RTTTL esta función solo necesita su * frecuencia en Hz y el tiempo de su duración en ms. * La señal es generada por el Timer1 trabajando en modo PWM. La frecuencia * de la onda PWM está dada por la fórmula. F_PWM = F_CPU/(2*ICR1). * El volumen de la nota no es considerado en el formato RTTTL de Nokia. ****************************************************************************/ voidPlayNote(unsignedintf_pwm,unsignedintt) { unsignedinttime; do{ cli(); time=ticks_ms; sei(); }while(time>0);// Esperar a que termine el tiempo de la nota previa
  • 685. if(skip==0){ cli(); ticks_ms=t;// Poner el tiempo que se tocará la nueva nota silence_ms=t/8;// Poner el tiempo que se tocará el silencio sei(); } if((f_pwm==0)||(skip==1)) OCR1A=0;// Poner Duty cycle = 0 para apagar el buzzer else{ ICR1=(unsignedint)(F_CPU/(2*f_pwm)); OCR1A=ICR1/2;// Poner Duty cycle = 50% } } /***************************************************************************** * Configuración de los Timers 0 y 1. * El Timer0 trabajará en modo CTC para generar interrupciones cada 1ms. Esto * servirá para medir el tiempo que debe durar la ejecución de cada nota. * El Timer1 funcionará en modo PWM para generar por el pin OC1A/PD5 del * megaAVR la señal que se aplicará al buzzer. ****************************************************************************/ voidSetupTimers(void) { /* Configuración del Timer1
  • 686. * - Bits WGM: PWM de Fase y Frecuencia Correctas con tope de conteo = ICR1 * - Bits COM: PWM no-invertida en el canal A * - Bits CS: Fuente de reloj = F_CPU (sin prescaler) */ TCCR1A=(1<<COM1A1); TCCR1B=(1<<WGM13)|(1<<CS10); DDRD=(1<<PD5);// Pin OC1A/PD5 salida PWM /* Configuración del Timer0 * - Bits WGM: Modo de operación = CTC * - Bits CS: Fuente de reloj = F_CPU/64 (Factor de prescaler N = 64) * - Periodo de auto-reset = 1 ms */ TCCR0A=(1<<WGM01); TCCR0B=(1<<CS00)|(1<<CS01); OCR0A=124;// Límite de TCNT0 /* Habilitar Interrupción en Coincidencia del Timer0 */ TIMSK0=(1<<OCIE0A); sei(); } Descripción del programa Este programa lo creé hace varios años intrigado por los malos resultados que obtuvo Craig Peacok en su reproductor de ringtones RTTTL. Es decir, cuando vi el proyecto de Craig Peacok, que era uno de mis autores referentes en programación de PICs de ese entonces, de inmediato quize escuchar cómo sonaban los famosos rongtones en un
  • 687. microcontrolador. Grabé el PIC con el mismo archivo HEX de su web y, para mi gran decepción, sonaba pésimo. Yo había escuchado mejores melodías reproducidas por códigos muy sencillos como por ejemplo la función tune del PICAXE y no podía creer que el programa aparentemente tan sofisticado de Craig funcionara tan mal. Así que antes de averiguar qué andaba mal en el proyecto de Craig, decidí escribir un reproductor de ringtones RTTTL desde cero para ver si el error era de los ringtones o del programa de Craig. Al final descubriría que había errores de ambos lados. En última instancia cada nota musical del ringtone no es más que un sonido de cierta frecuencia y con una determinada duración. De esto se encargaría la función PlayNote, que emplea el Timer1 para generar ondas PWM con duty cycle de 50% pero de frecuencia variable. Esta frecuencia, como sabemos, viene dada por la fórmula F_PWM = F_CPU / (2 * ICR1). El Timer1 era el único que podía generar fácilmente todas las frecuencias deseadas. Los Timers 0 y 2 también pueden generar ondas PWM pero con un rango de frecuencias muy limitado. Si bien se podía controlar la duración de cada nota musical usando simples delays (como en la siguiente práctica), decidí emplear el Timer0 para que dicha duración fuera inmune a la ejecución del resto del código, especialmente a las rutinas de depuración y logging como la función printf. La función del Timer0 es decrementar mediante interrupciones el contador ticks_ms. /***************************************************************************** * Configuración de los Timers 0 y 1. * El Timer0 trabajará en modo CTC para generar interrupciones cada 1ms. Esto * servirá para medir el tiempo que debe durar la ejecución de cada nota. * El Timer1 funcionará en modo PWM para generar por el pin OC1A/PD5 del * megaAVR la señal que se aplicará al buzzer. ****************************************************************************/ voidSetupTimers(void) { /* Configuración del Timer1 * - Bits WGM: PWM de Fase y Frecuencia Correctas con tope de conteo = ICR1 * - Bits COM: PWM no-invertida en el canal A * - Bits CS: Fuente de reloj = F_CPU (sin prescaler)
  • 688. */ TCCR1A=(1<<COM1A1); TCCR1B=(1<<WGM13)|(1<<CS10); DDRD=(1<<PD5);// Pin OC1A/PD5 salida PWM /* Configuración del Timer0 * - Bits WGM: Modo de operación = CTC * - Bits CS: Fuente de reloj = F_CPU/64 (Factor de prescaler N = 64) * - Periodo de auto-reset = 1 ms */ TCCR0A=(1<<WGM01); TCCR0B=(1<<CS00)|(1<<CS01); OCR0A=124;// Límite de TCNT0 /* Habilitar Interrupción en Coincidencia del Timer0 */ TIMSK0=(1<<OCIE0A); sei(); } Aunque el estándar RTTTL de Nokia esablece que los parámetros de cada nota deben seguir el orden estudiado antes, es decir, <duración><nota><escala><duraciónespecial>, el logging me hizo ver que había ringtones que no respetaban ese orden. Algunas veces ponían la duración-especial al final y otras veces la ponían antes que laescala. Por eso escribí mi función GetLeng para que buscara los parámetros de duración sin tener en cuenta su ubicación. /***************************************************************************** * Devuelve el valor de la Longitud de la nota en ms. * La nota se pasa en n como una cadena terminada en nulo. ****************************************************************************/
  • 689. unsignedintGetLeng(char*n,unsignedintduration,unsignedintbpm) { unsignedintLongitud; /* Procesar la duración de la nota actual, si es que hay. De no haber, luego * se utilizará la duración por defecto recibida como parámetro. */ if(isdigit(*n)) duration=*n++-'0'; if(isdigit(*n)) duration=duration*10+*n++-'0'; /* Calcular la longitud de la nota en milisegundos, en base a su * duración y bpm. */ Longitud=60000/bpm;// Duración de un beat Longitud*=4;// Duración de una nota entera Longitud/=duration;// Duración de la nota actual /* Avanzamos el puntero hasta el final buscando el caracter '.' * Si se le encuentra, la longitud de la nota se debe extender en 50%. */ while(*n!='0'){ if(*n++=='.') Longitud+=Longitud/2;
  • 690. } returnLongitud; } Terminado mi programa vi que Craig se había equivocado al considerar que las frecuencias de las notas empezaban por A y no por C, como se indica enwww.music.sc.edu. Definitivamente mi reproductor de ringtones funcionaba mucho mejor pero aún había algo extraño. Y es que si bien algunos ringtones como Star wars sonaban muy bien, otros como Rudolph sonaban sin inflexiones. De nuevo, no podía creer que el Rudolph que viene en los ejemplos de PICAXE sonara mejor que mi Rudoph. Mi Silent night también se escuchaba pésimo. ¿Cómo era posible que las simples tarjetitas navideñas con su controlador de juguete podían tocar mejor música que un potente microcontrolador de 20 MIPS (millones de instrucciones por segundo)? Yo estaba casi convencido de que el problema estaba otra vez en los ringtones. Hallé varios ringtones de Rudolph en Internet pero ninguno sonaba parecido al Rudoph de PICAXE. Así que decidí ver el manual de PICAXE y descubrí algo que no se especifica en el estándar RTTTL o que por lo menos no es obvio para quienes no conocemos esos detalles de la cultura músical. Descubrí que cada nota debe sonar en realidad solo los 7/8 de su periodo y la fracción restante de 1/8 debe ser de un silencio. Dada la estructura de mi código, fue muy fácil modificarlo para que la nota deje de sonar cuando falte la octava parte de su duración. Este tiempo está dado por la variablesilence_ms. Y para ir un poco más allá decidí que el programa ofreciera la opción de tocar los ringtones con los silencios descritos o sin ellos. Debemos presionar la tecla punto (.) para conmutar entre ambos modos de reproducción. /****************************************************************************** * Gestor de Interrupción en Coincidencia del Timer0. * Esta función se ejecuta cada 1 ms exactamente. *****************************************************************************/ ISR(TIMER0_COMPA_vect) { if(ticks_ms) { ticks_ms--; if((ticks_ms<silence_ms)&&(silence==1))
  • 691. OCR1A=0;// Poner Duty cycle = 0 para apagar el buzzer } } Ahora sí, mi Rudolph me hace evocar mis mejores momentos de navidad y mi Silent nightsuena celestialmente mejor. Dragon ball y Transformers suenan genial, The Godfather yExorcist se oyen casi mejor que en el cine. Definitivamente, quedó bien. Simulación del reproductor de ringtones RTTTL Éste debe uno de los proyectos más grandes en código pero que trabajan en el hardware más sencillo, así que será muy fácil ponerlo en práctica. Pero si deseas escucharlo previamente en una simulación también suena bien en Proteus.
  • 692. Simulación del reproductor de ringtones en formato RTTTL Reproductor de Ringtones en formato PICAXE El compilador Basic de los microcontroladores PICAXE ofrece el comando Tune para tocar ringtones en formato RTTTL. El formato de Tune tiene tres variantes, para ser usados según las limitaciones del modelo de PICAXE. A continuación se muestra el más básico. TUNE pin, speed, (note, note, note...)
  • 693. En pin se indica el pin por donde saldrá la onda cuadrada que exitará el piezo-buzzer, enspeed se especifica el tempo, es decir, losbeats por minuto a que se reproduce el ringtone, y por último, encerrados entre paréntesis, se encuentran todas las notas del ringtone, expresadas todas en bytes hexadecimales. Un ejemplo se su uso sería más o menos así. TUNE 3, 4, ($69,$02,$41,$42,$EB,$2C,$6B,$04,$42,$6B,$E9,$2C,$69,$02,$41,$42,$2B,$67, $2B,$2B,$69,$69,$6B,$69,$27,$67,$A6) Vamos viendo que este formato no es precisamente el que se indica en el estándar RTTTL de Nokia. Lo denominé formato RTTTL de PICAXE por ser una adaptación del RTTTL original que la gente de PICAXE desarrolló para reducir en lo posible el procesamiento y la memoria utilizada por los ringtones originales. Para decirlo de una forma simple, las notas del ringtone ya están preprocesadas. De modo que decodificar la frecuencia y la duración de cada una de ellas será mucho más fácil para el programa. Parece una gran idea de los ingenieros de PICAXE y fue ésa la razón inicial que me motivo a realizar este programa. Sin embargo, creo que la compresión se les fue un poco de la mano, ya que el sonido del ringtone pierde bastante calidad. Eso lo comprobaremos al final. En primer lugar hablemos de los beats por minuto (b o bpm) a que suena el ringtone. El valor de speed en el comando es en realidad un índice a una tabla que contiene los verdaderos valores de bpm. La tabla de abajo nos indica que speed solo puede conducir a 15 valores de bpm, con lo cual se dejan de lado la mitad de los posibles valores que el estándar RTTTL original establece para este parámetro. Tabla Nota speed bpm (beats por minuto) 1 812 2 406 3 270 4 203 5 162 6 135 7 116
  • 694. Tabla Nota speed bpm (beats por minuto) 8 101 9 90 10 81 11 73 12 67 13 62 14 58 15 54 Ahora sigamos con los bytes hexadecimales que representan las notas. Como vemos abajo, cada byte se divide entres campos. Hay dos bits que marcan la duración de la nota actual. Aquí notamos que faltan las fracciones 1/16 y 1/32 que contempla el estándar de Nokia. Eso es todo en cuanto a la duración, es decir, no se considera la duración especial. Las notas sí están completas, al menos en nombres (desde C hasta B). Lo que falta ahora es la escala que corresponde a la octava 7. Los tonos agudos por tanto no se dejarán escuchar en el ringtone. Recordemos que en cada escala la frecuencia de la nota se duplica. Puedes revisar la tabla de frecuencias presentada en la práctica anterior si es necesario.
  • 695. Bueno, creo que eso es todo lo que necesitamos saber para ponernos a escribir el programa. El comando TUNE permite especificar un pin cualquiera del PICAXE al cual se conectará el buzzer. Podemos hacer lo mismo, considerando que la onda cuadrada se puede generar usando delays en vez de algún Timer. De hecho, es así como trabaja el comando TUNE de PICAXE. Eso también le suma las dilataciones de hasta 8/9 (en vez de los 7/8) de periodo que debería sonar cada nota, según advierte su manual. Así que ante tantas limitaciones de este formato RTTTL de PICAXE no vamos a molestarnos en emular la función TUNE «al pie de la letra». Tomaremos nuestro programa anterior y le haremos las modificaciones necesarias. El circuito desde luego será el mismo.
  • 696. Circuito para el reproductor de Ringtones en formato RTTTL /************************************************************************ ****** * FileName: main.c * Purpose: Reproductor de Ringtones en formato RTTTL de PICAXE * Processor: ATmel AVR * Compiler: AVR IAR C y AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. * * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta
  • 697. * licencia y las notas de autor y copyright de arriba. ************************************************************************* ****/ #include "avr_compiler.h" #include "usart.h" void SetupTimers(void); void Tune(PGM_P pRingtone); void delay_ms(unsignedint t); /* Fuente de ringtones * http://www.picaxe.com/downloads/ */ PROGMEMconstchar rt01[]={6,0x27,0x69,0x27,0xE4,0x27,0x69,0x27,0xE4,0xC2,0x02,0xEB,0xC0,0x0 0,0xE7,0xE9,0x29,0x00,0x6B,0x29,0x27,0x69,0x27,0xE4,0xE9,0x29,0x00,0x6B,0 x29,0x27,0x69,0x27,0xE4,0xC2,0x02,0x05,0x42,0x2B,0xC0,0xC4,0x00,0x27,0x24 ,0x27,0x65,0x22,0xE0,0x30}; PROGMEMconstchar rt02[]={6,0x67,0x44,0x42,0x40,0xE7,0x67,0x44,0x42,0x40,0xE9,0x69,0x45,0x4 4,0x42,0x6B,0x67,0x6B,0x42,0x47,0x47,0x45,0x42,0xC4,0x67,0x44,0x42,0x40,0 xE7,0x66,0x67,0x44,0x42,0x40,0xE9,0x69,0x45,0x44,0x42,0x47,0x47,0x46,0x47 ,0x46,0x47,0x48,0x49,0x47,0x44,0x42,0x00,0x07,0x44,0x44,0x44,0x43,0x44,0x 44,0x44,0x43,0x44,0x47,0x40,0x42,0xC4,0x45,0x45,0x45,0x45,0x45,0x44,0x44, 0x44,0x44,0x44,0x42,0x42,0x44,0xC2,0x30}; PROGMEMconstchar rt03[]={5,0x22,0x27,0x67,0x69,0x67,0x66,0x24,0x20,0x24,0x29,0x69,0x6B,0x6 9,0x67,0x26,0x22,0x26,0x2B,0x6B,0x40,0x6B,0x69,0x27,0x24,0x62,0x62,0x24,0 x29,0x26,0xE7,0x30};// we wish you PROGMEMconstchar rt04[]={4,0x22,0x27,0x67,0x69,0x67,0x66,0x24,0x24,0x24,0x29,0x69,0x6B,0x6 9,0x67,0x26,0x22,0x22,0x2B,0x6B,0x40,0x6B,0x69,0x27,0x24,0x22,0x24,0x29,0 x26,0xE7,0x22,0x27,0x67,0x69,0x67,0x66,0x24,0x24,0x24,0x29,0x69,0x6B,0x69 ,0x67,0x26,0x22,0x22,0x2B,0x6B,0x40,0x6B,0x69,0x27,0x24,0x22,0x24,0x29,0x 26,0xA7,0x22,0x27,0x27,0x27,0xE6,0x26,0x27,0x26,0x24,0xE2,0x29,0x2B,0x69, 0x69,0x67,0x67,0x02,0x22,0x22,0x24,0x29,0x26,0xE7,0x30};
  • 698. PROGMEMconstchar rt05[]={5,0x07,0x45,0x04,0x02,0x00,0x02,0x04,0x00,0x42,0x44,0x45,0x42,0x0 4,0x42,0x00,0x2B,0xC0,0x02,0x44,0x05,0x02,0x04,0x45,0x07,0x02,0x44,0x46,0 x07,0x49,0x4B,0x10,0x0B,0x09,0x07,0x30}; PROGMEMconstchar rt06[]={6,0x60,0x60,0x40,0x40,0x2A,0x29,0x27,0x25,0xE0,0x60,0x60,0x27,0x6 5,0x27,0x65,0x24,0xE0,0x22,0x42,0x42,0x00,0x2A,0x29,0xE7,0x44,0x42,0x00,0 x40,0x6A,0x29,0x6A,0x69,0xE5,0x20,0x40,0x40,0x2A,0x29,0x27,0x25,0xE0,0x60 ,0x60,0x27,0x65,0x27,0x65,0x24,0xE0,0x22,0x42,0x42,0x00,0x2A,0x29,0xE7,0x 44,0x42,0x00,0x40,0x6A,0x29,0x69,0x67,0xE5,0x30};// let it snow /************************************************************************ ****** * Main function ************************************************************************* ****/ int main(void) { SetupTimers();// Configurar Timers 1 usart_init();// Inicializar USART a 9600 - 8N1 printf("r Ringtones player "); printf("r ================ r"); while(1) { printf("r Deck the halls"); Tune(rt05); delay_ms(2000); printf("r Jingle bells"); Tune(rt02);
  • 699. delay_ms(2000); printf("r We wish you a merry christmas"); Tune(rt04); delay_ms(2000); printf("r Silent night"); Tune(rt01); delay_ms(2000); } } /************************************************************************ ***** * Toca las notas del ringtone apuntado por pRingtone. ************************************************************************* ***/ void Tune(PGM_P pRingtone) {// C C# D D# E F F# G G# A A# B staticPROGMEMconstunsignedint NoteFreqs[]={262,277,294,311,330,349,370,392,415,440,466,494}; staticPROGMEMconstunsignedchar Octaves[]={6,7,5}; staticPROGMEMconstunsignedint Bpms[]={0,812,406,270,203,162,135,116,101,90,81,73,67,62,58,54}; staticPROGMEMconstunsignedchar Durations[]={4,8,1,2}; unsignedint Frecuencia, Longitud, bpm; unsignedchar speed, note, octave, duration; /* Obtener speed */
  • 700. speed = pgm_read_byte(pRingtone++); /* Bucle para leer y tocar todas las notas del ringtone actual */ while((note = pgm_read_byte(pRingtone++))!=0x30) { /****************************************************** * Obtener la Frecuencia de la nota en Hz *****************************************************/ octave = pgm_read_byte(&Octaves[(note&0x30)>>4]); Frecuencia =0;// Frecuencia por defecto, pausa if((note&0x0F)<12) Frecuencia = pgm_read_word(&NoteFreqs[note&0x0F]); Frecuencia <<=(octave-5);// Aplicar la octava /****************************************************** * Obtener la Longitud de la nota en milisegundos *****************************************************/ duration = pgm_read_byte(&Durations[(note&0xC0)>>6]); bpm = pgm_read_word(&Bpms[speed]); Longitud =60000/bpm;// Duración de un beat Longitud *=4;// Duración de una nota entera Longitud /= duration;// Duración de la nota actual /******************************************************* * La señal de la nota es generada por el Timer1 * trabajando en modo PWM. La frecuencia de la onda PWM * está dada por la fórmula. F_PWM = F_CPU/(2*ICR1). ******************************************************/
  • 701. if( Frecuencia ==0) OCR1A =0;// Poner Duty cycle = 0 para apagar el buzzer else{ ICR1 =(unsignedint)(F_CPU/(2*Frecuencia)); OCR1A = ICR1/2;// Poner Duty cycle = 50% } delay_ms((Longitud*7)/8);// Tiempo que se tocará la nueva nota OCR1A =0;// Poner Duty cycle = 0 para apagar el buzzer delay_ms(Longitud/8);// Tiempo que se tocará el silencio } } /************************************************************************ ***** * Configuración de los Timers * El Timer1 funcionará en modo PWM para generar por el pin OC1A/PD5 del * megaAVR la señal que se aplicará al buzzer. ************************************************************************* ***/ void SetupTimers(void) { /* Configuración del Timer1 * - Bits WGM: PWM de Fase y Frecuencia Correctas con tope de conteo = ICR1 * - Bits COM: PWM no-invertida en el canal A * - Bits CS: Fuente de reloj = F_CPU (sin prescaler) */ TCCR1A =(1<<COM1A1); TCCR1B =(1<<WGM13)|(1<<CS10);
  • 702. DDRD =(1<<PD5);// Pin OC1A/PD5 salida PWM } void delay_ms(unsignedint t) { while(t--) delay_us(1000); } Descripción del programa En este programa también se utiliza el Timer1 para generar la onda PWM con duty cycle de 50% que exitará el buzzer. El tiempo que dura cada nota en cambio se basa en delays y ya no en el Timer0. Comparado con la anterior práctica este programa es muchísimo más sencillo, así que no queda mucho que explicar. Cada cadena de ringtone tuvo que ser modificada para los requerimientos del lenguaje C. Un numero hexadecimal en C empieza por 0x en vez del $ de Basic. Es solo cosa de usar la herramienta reemplazar de cualquier editor de texto. Otro punto que quiero destacar es que estos ringtones de PICAXE no son cadenas terminadas en nulo. El byte 0x00 de hecho representa una nota musical más. Pero sí hay un byte que no se usa como nota y es el 0x30. Recuerda que esos dos bits iguales a 1 son los que no se usan en las octavas. Así que empleé ese caracter como delimitador de la cadena. La función Tune reproduce cada nota del ringtone hasta encontrar el 0x30. Ante todas las limitaciones descritas antes de este simplificado formato RTTTL de PICAXE, no debe extrañarnos que los ringtones suenen muy pobres al lado de los originales ringtones RTTTL. Si al final te llega a interesar este formato RTTTL alternativo, puedes encontrar más ringtones similares en la web de PICAXE. También vas a encontrar varios paquetes de ellos al descargar los archivos de esta práctica. Y por si fuera poco el software de PICAXE también incluye una utilidad para convertir los ringtones originales en ringtones PICAXE: Simulación del reproductor de ringtones de PICAXE Aunque esta práctica es bastante sencilla de implementar, puede que primero desees escuchar cómo suenan los ringtones PICAXE en una simulación en Proteus.
  • 703. Simulación del reproductor de ringtones en formato de PICAXE Protocolo del Bus I2C El I2C (Inter Integrated Circuits) es un bus de comunicaciones serial síncrono de dos líneas que fue originalmente desarrollado porPhilips Semiconductors (ahora nxp semiconductors) desde los inicios de los „80. Hoy es un estándar aceptado y respaldado por los fabricantes de dispositivos semiconductores. El bus I2C permite la comunicación entre múltiples dispositivos (en teoría más de 1000), todos conectados paralelamente a las dos líneas. Las transferencias de datos siempre se realizan entre dos dispositivos a la vez y en una relación maestro – esclavo.
  • 704. Los dispositivos maestros son normalmente los microcontroladores y los dispositivos esclavos pueden ser memorias, conversores DAC y ADC, controladores de LCD, sensores de todos los tipos, etc. Ahora bien, para que todos los dispositivos se puedan comunicar sin entorpecerse unos y otros, sin que haya pérdidas o colisiones en las transferencias de datos, sin que los dispositivos rápidos se desentiendan de los dispositivos lentos, etc., se deben de seguir ciertas reglas estándar, cierto protocolo. Todas las especificaciones software y hardware del protocolo del bus I2C están descritas en el I2C-bus specification and user manual. Es un documento PDF de 50 páginas (la versión que veo en este momento: rev. 03, de 2007). No obstante, si reducimos las funciones del bus I2C a redes donde solo haya un dispositivo maestro y uno o varios (hasta 112) dispositivos esclavos, si limitamos la velocidad de transferencia a un máximo de 1 Mbit/s (que no es poca cosa, ¿verdad?), entonces el estándar I2C se simplifica más o menos a las siguientes cinco páginas. Topología del bus I2C. Consideremos entonces las siguientes características: Las transferencias de datos se llevan a cabo mediante dos líneas: línea serial de datos SDA y línea serial de reloj SCL. Ambas son bidireccionales. SDA se encarga de conducir los datos entre el dispositivo maestro y los esclavos. SCL es la señal de reloj que sincroniza los datos que viajan por la línea SDA. El dispositivo maestro (microcontrolador) es quien siempre tiene la iniciativa de la comunicación: el maestro genera la señal de reloj y controla cuando se transmiten o reciben los datos. Puede haber varios esclavos en la red I2C, pero el maestro solo se comunica con uno a la vez. Por eso cada dispositivo esclavo debe ser identificado por una dirección única.
  • 705. Hay muchos conceptos más en el estándar. Algunos serán descritos en lo sucesivo y otros no nos conciernen directamente. Ante cualquier duda no resuelta aquí, puedes revisar el documento de la especificación citado antes. Transferencias de Datos Los datos que se transfieren por el bus I2C deben viajar ―para decirlo fácil― en forma de paquetes, aquí llamados transferencias. Como se ve en la siguiente figura, unatransferencia empieza con un START y termina con un STOP. Entre estas señales van los datos propiamente dichos. Cada dato debe ser de 8 bits (1 byte) y debe ir seguido de un noveno bit, llamado bit de reconocimiento (ACK o NACK). Transferencias de datos sobre el bus I2C. La transferencia mostrada arriba tiene dos bytes pero puede varios más (sin restricción) o puede haber un solo byte por paquete. Los datos son transferidos por la línea SDA y son acompañados y sincronizados por los pulsos de reloj de la línea SCL. Para transmitir un bit primero hay que poner la línea SDA a 1 ó 0 según sea el caso, y luego colocar un pulso en la línea SCL. Los datos pueden viajar de ida y de vuelta porSDA sin colisionar porque es el maestro quien controla cuándo se transmite o recibe un dato. De ese modo, el control de SDA puede ser asumido tanto por el maestro como por el esclavo y ambos dispositivos podrán intercambiar los roles de transmisor o receptor. Eso sí, en cualquier caso, el control de la línea SCL siempre (excepto en el Clock Stretching) es asumido por el maestro. Condiciones START, STOP y START Repetida Como dijimos, los paquetes de datos transferidos por el bus I2C deben ir enmarcados por un Start y un Stop. Ambas señales son generadas por el maestro.
  • 706. Una condición START es una transición de Alto a Bajo en la línea SDA cuando SCLestá en Alto. Se le representa por la letra S. Después de Start el bus se considera ocupado. Una condición STOP es una transición de Bajo a Alto en la línea SDA mientras SCLestá en Alto. Está simbolizada por la letra P. Después de Stop las dos líneas están en Alto y el bus se considera libre. Se usa Stop para cerrar la transferencia de un paquete de datos o para abortar una transferencia previa que quedó truncada. Condiciones START y STOP. La señal de una condición START repetida es exactamente igual a la de START. La diferencia es de tipo “ocasional”: aunque en principio cada transferencia debe ir enmarcada por un Start y un Stop, el estándar contempla la posibilidad de iniciar una nueva transferencia sobre una anterior que no ha sido cerrada con un Stop. El Startde la nueva transferencia se llama entonces Start Repetida y su símbolo es Rs. Este punto lo entenderemos mejor en las prácticas. El Bit de Reconocimiento (ACK o NACK) Según las figuras mostradas, cada byte transferido debe ir seguido de un noveno bit, llamado Acknowledge bit (bit de reconocimiento, en inglés). Este bit siempre debe ser devuelto por el dispositivo receptor (maestro o esclavo) tras cada byte recibido. Si el bit de reconocimiento es 0 significa que el dato fue reconocido y aceptado. Este bit se denomina ACK (Acknowledge). Si el bit de reconocimiento es 1 significa que el dato recibido aún no es aceptado. Se usa este mecanismo para indicar que el receptor está ocupado realizando alguna tarea interna. Este bit se denomina NACK (Not Acknowledge). El Byte de Control Como se sabe, las comunicaciones por el bus I2C se llevan a cabo siguiendo la relaciónmaestro – esclavo. Eso significa que es el maestro (microcontrolador) quien ordena con cuál esclavo se va a comunicar o si los siguientes datos se van a transmitir o recibir; el esclavo solo obedece. Pues bien, esa orden viaja en el primer byte de cada transferencia y es más conocido como byte de control.
  • 707. Según lo mostrado en la siguiente figura, 7 bits del byte de control contienen la dirección del esclavo con el cual se desea entablar la comunicación y el bit R/W establece si los siguientes bytes serán de lectura o escritura. Como siempre, R/W = 0 indica una escritura yR/W = 1 indica una lectura. Formato del byte de control (primer byte). Todos los esclavos deben recibir el byte de control, pero solo el que halle su dirección en él será el que prosiga la comunicación. Los demás esclavos se deben mantener al margen hasta un nuevo aviso (otra condición de Start). Velocidad de Transferencia de Datos Como cada bit de dato transferido sobre la línea SDA debe ser validado por la señal de reloj SCL, podemos deducir que la velocidad de transferencia está determinada por la frecuencia de la señal de SCL. Por ejemplo, una velocidad de 100 kbits/s implica que cada bit se transmite en 1s/100k = 10µs, lo que nos dice que cada semiperiodo de la señal de reloj vale en promedio 5 µs. Estos datos se detallan en el Estándar I2C y también suelen ir indicados en los datasheets de los dispositivos I2C. El estándar del bus I2C soporta cuatro modos de operación: Standard Mode, con una velocidad de hasta 100 kbit/s. Fast mode, con una velocidad de hasta 400 kbit/s. Fast mode plus, con una velocidad de hasta 1 Mbit/s. High-speed mode, con una velocidad de hasta 3.4 Mbit/s. Los valores límites implican que los dispositivos más rápidos son compatibles con los dispositivos más lentos; lo contrario, por supuesto, no es factible. Así, entenderemos que todos ellos podrían trabajar en una misma red si operan, por ejemplo, a 20 kHz, 50 kHz ó 100 kHz. El Módulo TWI de los AVR TWI es la sigla de Two Wire Interface (Interface serial de dos líneas). Es el periférico de los AVR que realiza las funciones del protocolo I2C a nivel hardware. El
  • 708. módulo TWI soporta los tres siguientes modos de operación, de los cuales ahora estudiaremos el primero: Modo I2C de maestro único. El microcontrolador trabaja como maestro, controlando uno o varios dispositivos esclavos como EEPROMs, termómetros, RTCs, etc. Modo I2C de esclavo. El microcontrolador trabajará como esclavo frente a algún otro microcontrolador que hace de maestro. Modo I2C de maestros múltiples. Es una extensión del primer modo, solo que ahora el microcontrolador compartirá la red I2C con otros microcontroladores maestros. Puede inclusive alternar su rol entre maestro y esclavo. A diferencia de otros microcontroladores, en los AVR no hay que configurar previamente uno de estos modos de operación. En cualquier momento el AVR puede pedir el control de bus enviando una condición START, y si lo obtiene, trabajará como maestro (transmisor o receptor). El módulo TWI alcanza velocidades de hasta 400 k-bits/s (esto es Fast mode) Control del Módulo TWI Estos son los principales Registros de E/S que conducen las operaciones del módulo TWI: TWDR (Registro de Datos del TWI). Es el registro donde se cargan los datos a transmitir y donde se depositan los datos que llegan. Como el bus I2C es half dúplex , los datos pueden viajar en ambas direcciones pero no al mismo tiempo. El maestro debe controlar el tráfico. TWBR. Es el registro que establece la velocidad de transferencia de datos. Actúa con algunos bits del registro TWSR. TWCR (Registro de Control del TWI). Con este registro se puede habilitar el módulo TWI, se pueden generar las condiciones Start y Stop, se puede dar inicio a una transferencia de datos, se pueden configurar las interrupciones del módulo TWI, etc. TWSR(Registro de Estado del TWI). Este registro se actualiza automáticamente para indicar el resultado de la última operación I2C ejecutada. Por ejemplo, leyendo este registro podremos saber si un dato fue enviado satisfactoriamente o no. TWAR (Registro de Dirección del TWI). La dirección a que hace alusión es solo para cuando el microcontrolador trabaja en modo esclavo. TWAMR (Registro de máscara de dirección del TWI). Sirve para enmascarar la dirección contenida en el registro TWAR. En seguida presentamos los mapas de bits de los registros de control y estado a los que nos referiremos en adelante. Sus nombres no reflejan del todo sus funciones.
  • 709. Veremos luego que hay algunos bits de control en el registro de estado y viceversa. Los bits sin nombre no tienen efecto. Registro TWCR TWCR TWINT TWEA TWSTA TWSTO TWWC TWS7 TWS6 TWS5 TWS4 TWS3 TWEN --- TWIE TWPS1 TWPS0 Registro TWSR TWSR --- En lo sucesivo trabajaremos constantemente con el registro de control TWCR. Su uso es un poco especial debido al bit TWINT. El flag TWINT se activa por hardware para indicar la culminación de alguna operación, pero como todo flag del AVR, TWINT se debe limpiar por software escribiendo un 1. Otra consecuencia de la naturaleza del bit TWINT es que no se deberían usar las instrucciones de lectura-modificación-escritura. Por eso, aunque solo tengamos que acceder a un bit de TWCR, debemos escribir el registro completo. Lo malo de esto es que debemos recordar los valores relevantes que los otros bits de TWCR tenían antes para volverlos a escribir. Por ejemplo, para una condición START se debe setear el bit TWSTA y limpiar el bitTWINT. La siguiente sentencia lo hace pero sin olvidar que el bit TWEN valía 1. /* Iniciar Condición START */ TWCR=(1<<TWSTA)|(1<<TWINT)|(1<<TWEN); Velocidad de Transferencias de Datos Recordemos que cada bit de dato se valida con un pulso del reloj. Por tanto la velocidad de transferencia de datos será igual a la frecuencia de la señal de SCL, que es controlada por maestro. Esta frecuencia depende del valor de los registros TWBR, TWSR y por supuesto de la frecuencia del reloj del sistema, que es la misma frecuencia del procesador definida como F_CPU en archivo avr_compiler.h. #ifndef F_CPU /* Define la frecuencia de CPU (en Hertz) por defecto, si aún no ha * sido definida. */ #define F_CPU 8000000UL #endif // XTAL de 8 MHz
  • 710. Se calcula con la siguiente fórmula: Donde: TWBR es el valor del registro TWBR. TWPS es el valor conformado por los bits de prescaler TWPS1:TWPS0, contenidos en el registro TWSR. Por ser dos bits, TWPS puede valer 0, 1, 2 ó 3. Observa que la Frecuencia de SCL es inversamente proporcional a TWBR y TWPS. Para valores “altos” de TWPS la Frecuencia de SCL disminuirá exponencialmente. Si ponemos TWPS a 0, es decir, si no utilizamos el prescaler, la fórmula original se reduce a Sin el prescaler, ahora la Frecuencias de SCL estará “limitada” a valores relativamente altos. Por ejemplo, la Frecuencia más baja que podríamos conseguir con el XTAL más alto (de 20MHz) sería 20MHz/(16+2*255) = 38 kHz. Esta frecuencia no es tan baja considerando que todos los dispositivos I2C debieran operar al menos a 100 kHz. Con la fórmula reducida, podemos despejar TWBR para calcular su valor. El código de inicialización del módulo TWI (I2C) también es simplifica. #define I2C_BAUD 100000UL // 100khz. Valor por defecto //**************************************************************************** // Inicializar el módulo TWI. // Configurar Frecuencia de reloj a I2C_BAUD. //**************************************************************************** voidi2c_init(void) { /* Establecer valor de prescaler de TWI a 0 */
  • 711. TWSR&=~((1<<TWPS1)|(1<<TWPS0)); /* Establecer Frecuencia de SCL */ TWBR=((F_CPU/I2C_BAUD)-16)/2; /* Habilitar la interface TWI */ TWCR=(1<<TWEN); } Se dice que la Interferencia Electromagnética (EMI) solo afecta las transmisiones cuando la velocidad del bus es de alrededor de 400 kbps (Fast mode). Para evitar esto, el módulo TWI tiene incorporado un filtro que adapta ligeramente las señales para que sean inmunes a dicha interferencia. Condición Start y Start Repetida Para enviar una condición START primero se setea el bit TWSTA y luego se limpia el bitTWINT. También se pueden escribir los dos bits al mismo tiempo pero no invertir el orden. Si el bus está libre, el Start iniciará normalmente; de lo contrario, si el bus está acaparado por otro maestro, el módulo TWI esperará a que el bus esté disponible nuevamente para iniciar la condición Start. Durante todo ese lapso el bit TWSTApermanecerá seteado y el bit TWINTpermanecerá en 0. Apenas la condición Start se haya terminado de enviar, el bit TWINT se seteará automáticamente. Así que se puede sondear este bit para saber si la condición Start fue bien enviada. La función alternativa del bitTWINT es que al activarse puede disparar una interrupción si esa interrupción está habilitada. Por otro lado, el bit TWSTA se debe limpiar por software. La procedimiento software para generar una condición START repetida es el mismo. No hay bits de control exclusivos para una START repetida. El hardware del TWI sabrá diferenciar estos dos eventos por el momento en que ocurren y reportará los resultados en el registro TWSR. Tabla Código de estado (TWSR & 0xFC) Código de estado (TWSR & 0xFC) Estado del bus I2C y del módulo TWI 0x08 Se ha transmitido una condición START
  • 712. Tabla Código de estado (TWSR & 0xFC) Código de estado (TWSR & 0xFC) Estado del bus I2C y del módulo TWI 0x10 Se ha transmitido una condición START Repetida Y finalmente el código para la condición Start podría quedar así: //**************************************************************************** // Envía una Condición START o START Repetida. // Retorna 1 si la Condición START se envió satisfactoriamente, 0 si falló. //**************************************************************************** chari2c_start(void) { /* Setear bit TWSTA y limpiar flag TWINT para iniciar Condición START */ TWCR=(1<<TWSTA)|(1<<TWINT)|(1<<TWEN); /* Esperar a que la Condición START se termine de enviar */ while((TWCR&(1<<TWINT))==0); /* Limpiar bit TWSTA */ TWCR=(1<<TWEN); /* Comprobar si la transferencia de START fue satisfactoria */ if(((TWSR&0xFC)==0x08)||((TWSR&0xFC)==0x10)) return1; return0; } Condición Stop
  • 713. Para enviar una condición Stop primero se setea el bit TWSTO y luego se limpia el bitTWINT. También se pueden escribir los dos bits al mismo tiempo pero no vale invertir el orden. La condición Stop se iniciará inmediatamente puesto que se supone que el bus está en posesión del AVR actual. Cuando la condición Stop se termine de enviar el bit TWSTO se limpiará automáticamente y el bit TWINT se seteará al mismo tiempo también automáticamente. Se puede sondear cualquiera de estos bits para saber si la condición Stop se terminó de enviar. Aunque también en este caso se activa el bit TWINT, ello no es condición para poder disparar una interrupción. La activación de TWINT puede generar una interrupción ante una condición Stop pero cuando el AVR está trabajando en modo esclavo //**************************************************************************** // Envía una Condición STOP //**************************************************************************** voidi2c_stop(void) { /* Setear bit TWSTO y limpiar flag TWINT para iniciar Condición STOP */ TWCR=(1<<TWSTO)|(1<<TWINT)|(1<<TWEN); /* Esperar a que la Condición STOP se termine de enviar */ while(TWCR&(1<<TWSTO)); } Transmitir Dato y Recibir Bit ACK/NACK El byte (de dato o de control) a transmitir se debe cargar en el registro TWDR. Se asume que en este punto el bus está en posesión del AVR y que no hay ninguna transferencia previa en marcha, o sea que el bit TWINTdebe estar seteado. A continuación debemos limpiar el bit TWINT para iniciar la transferencia. Del otro lado, si el esclavo recibe el dato correctamente y lo admite, entonces responderá afirmativamente enviando un bit 0 llamado ACK (Acknowledge). De lo contrario, responderá con un bit 1 llamado NACK (Not Acknowledge). Cuando el AVR reciba esta respuesta seteará el bit TWINT, hecho que puede disparar la interrupción de TWI si está habilitada.
  • 714. El resultado del byte enviado y del bit ACK/NACK recibido será registrado en el registro de estado TWSR. Puesto que el módulo TWI puede reconocer si el dato enviado fue unbyte de control para escritura (SLA+W), un byte de control para lectura (SLA+R) o si fue un byte de dato; el registro TWSR podrá contener uno de los siguientes 6 códigos de estado. Tabla Código de estado (TWSR & 0xFC) Código de estado (TWSR & 0xFC) Estado del bus I2C y del módulo TWI 0x18 Se ha transmitido SLA+W; se ha recibido ACK 0x20 Se ha transmitido SLA+W; se ha recibido NACK 0x40 Se ha transmitido SLA+R; se ha recibido ACK 0x48 Se ha transmitido SLA+R; se ha recibido NACK 0x28 Se ha transmitido un byte de dato; se ha recibido ACK 0x30 Se ha transmitido un byte de dato; se ha recibido NACK Finalmente, con toda la información presentada podemos escribir el código para enviar un byte por el bus I2C en modo Maestro. Normalmente se asocia el 0 con una respuesta negativa, pero en mi función el 0 es el valor original del bit ACK asignado por el protocolo I2C. nota que al final solo se evalúa si se recibió un ACK, sin revisar explícitamente si el caso contrario se trata de un NACK. De ese modo quise simplificar el código. Esta función retorna 1 cuando el esclavo respondió con un bit NACK o cuando hubo un error de transferencia. Esto último sería muy raro. Un caso en que se podría presentar es cuando el AVR pierde el control del bus ante otro microcontrolador maestro, pero como estamos trabajando en un sistema de un solo maestro, tampoco sería caso. En conclusión, si la función no retorna un 0 (de ACK) es casi seguro que el esclavo respondió con unNACK. Yo sé que en programación el “casi” nunca se debería pasar por alto, pero en esta situación en particular la respuesta ante un NACK o un error desconocido sería la misma: volver a enviar el dato. //*********************************************************************** ***** // Envía el byte 'data' y devuelve 0 si el esclavo respondió con un bit ACK
  • 715. // En otro caso, devuelve 1. //*********************************************************************** ***** char i2c_write(char data) { /* Cargar dato a transmitir */ TWDR = data; /* Limpiar flag TWINT para iniciar la transferencia */ TWCR =(1<<TWINT)|(1<<TWEN); /* Esperar a que finalice la transferencia */ while((TWCR &(1<<TWINT))==0); /* Leer el resultado de la transferencia y retornar 0 si el esclavo * respondió con un bit ACK. */ if(((TWSR&0xFC)==0x18)||((TWSR&0xFC)==0x40)||((TWSR&0xFC)==0x28)) return0; /* Se llega a este punto si el esclavo respondió con un NACK o si * hubo un error en la transferencia */ return1; } Recibir Dato y Transmitir Bit ACK/NACK Un esclavo no puede enviar un dato cuando quiera. Es el maestro quien le ordena que lo haga. Es decir, para recibir un dato asumimos que ya enviamos al esclavo un byte de control para lectura (por ejemplo, utilizando la funcióni2c_write). Tras recibir esa orden el esclavo inició la transferencia del dato respectivo.
  • 716. El AVR acepta el dato enviado y lo deposita en el registro TWDR. En seguida seteará el ya familiar flag TWINT. También en esta ocasión se puede usar este evento para disparar una interrupción si está habilitada. Con interrupción o sin ella, ya se puede leer el registro TWDR. Puesto que se recibe un dato, ahora es el AVR el que tiene que responder con el bit ACK o NACK. Para ello primero debemos escribir 0 (para NACK) ó 1 (para ACK) en el bit TWEA (TWI Enable Acknowledge). Luego podemos iniciar su envío, como siempre, limpiando el bit TWINT. Por supuesto, aquí también es posible escribir ambos bits al mismo tiempo. El flag TWINT se seteará automáticamente después de que nuestra respuesta se haya enviado. Como de costumbre, al final de todo el registro de estado TWSR reflejará el resultado de la operación. Asumiendo que no hay ningún otro microcontrolador pugnando por la posesión del bus I2C, los códigos del registro TWSR se reducen a dos. Tabla Código de estado (TWSR & 0xFC) Código de estado (TWSR & 0xFC) Estado del bus I2C y del módulo TWI 0x50 Se ha recibido un byte de dato; se ha retornado ACK 0x58 Se ha recibido un byte de dato; se ha retornado NACK Puesto que estos códigos se originan después de haber leído el dato pedido, no tiene mayor relevancia conocerlos, sobre todo si se retorna un NACK, que es como dar por terminada la transferencia. Así que no los he incluido mi función i2c_read. De hecho no los he usado nunca. Pero si tú deseas, puedes adaptar tu propio código. //*********************************************************************** ***** // Lee un byte de dato y envía el bit ACK/NACK. // ack = 0 es ACK y ack = 1 es NACK. //*********************************************************************** ***** char i2c_read(char ack) { /* Esperar a que termine de llegar el dato pedido */ while((TWCR &(1<<TWINT))==0);
  • 717. /* Enviar el bit ACK o NACK al esclavo */ if(ack ==1) TWCR =(1<<TWINT)|(1<<TWEN);// Esto es NACK else TWCR =(1<<TWEA)|(1<<TWINT)|(1<<TWEN);// Esto es ACK /* Esperar a que se termine de enviar el bit ACK/NACK */ while((TWCR &(1<<TWINT))==0); /* Retornar el dato que llego anteriormente */ return TWDR; } Interrupciones del Módulo TWI En las comunicaciones RS-232 los datos suelen viajar tan lento que el microcontrolador puede aprovechar los tiempos que duran las transferencias para ejecutar otras funciones. En las comunicaciones I2C el uso de las interrupciones para detectar el inicio o final de los datos solo es beneficioso para el microcontrolador cuando opera en modo de Esclavo. La interrupción del módulo TWI puede ser disparada por cualquiera de los eventos que activa el flag TWINT, del registro TWCR. El bit TWINT es el único que no se limpia automáticamente al ejecutarse la rutina de interrupción ISR. Pero como los demás flags, al limpiarse por software se debe escribir un 1. La interrupción del módulo TWI se habilita seteando los bits TWIE (TWI Interrupt Enable) de TWCR e I de SREG. Registros del Módulo TWI El Registro TWCR Registro TWCR TWCR TWINT TWEA TWSTA TWSTO TWWC TWEN --- TWIE
  • 718. Registro de Microcontrolador TWINT TWI Interrupt Flag Este bit se setea por hardware cuando el módulo TWI ha terminado una operación actual y espera una respuesta del programa. Si los bits I del registro SREG y TWIE de TWCR están seteados, el programa saltará al Vector de interrupción. Mientras el flag TWINT valga 1 se extenderá el periodo bajo de SCL (clock stretching). TWINT se debe limpiar por software escribiendo un 1. Este flag no se limpia automáticamente por hardware cuando se ejecuta la rutina de interrupción ISR. Al limpiar este flag se iniciarán las operaciones del TWI, así que los accesos a los registros TWAR, TWSR y TWDR deberían estar completos antes de limpiar TWINT. TWEA TWI Enable Acknowledge Bit El bit TWEA controla la generación del pulso de reconocimiento. Si se escribe 1 en el bit TWEA, se generará un pulso ACK en las siguientes condiciones: 1. Se recibió la dirección del microcontrolador en modo esclavo. 2. Se recibió una llamada general cuando el bit TWGCE de TWAR valía 1 3. Se recibió un byte de dato o de control, actuando el AVR como maestro o esclavo. Si se escribe 0 en TWEA, el AVR se desconecta virtualmente del bus I2C, porque si algún otro maestro trata de contactarlo se le responderá con puros NACK. Después se podrá setear TWEA nuevamente para que el AVR pueda volver a reconocer su dirección. TWSTA TWI START Condition Bit Se debe escribir en el bit TWSTA cuando se desea tomar el bus I2C, para actuar como maestro. El hardware del TWI revisa si el bus está disponible y genera una condición START si está libre. Pero si el bus no está libre, el TWI espera hasta que detecte una condición STOP, y luego genera una nueva condición START para tomar el control del bus. El bit TWSTA se debe limpiar por software después de transmitir la condición START. TWSTO TWI STOP Condition Bit Cuando se escribe 1 en el bit TWSTO en modo maestro se generará una condición STOP en el bus I2C. Cuando se ejecute la condición STOP el bit TWSTOP se
  • 719. limpiará automáticamente. En modo esclavo, se puede setear el bit TWSTO para recuperarse de una condición de error. Esto no generará una condición STOP, sino hará que el módulo TWI regrese a su estado de esclavo no direccionado y liberará las líneas SCL y SDA. TWWC TWI Write Collision Flag El bit TWWC se setea cuando se trata de escribir en el registro TWDR cuando TWINT valga 0. Este flag se limpia al escribir un dato en TWDR cuando TWINT valga 1. TWEN TWI Enable Bit El bit TWEN habilita las operaciones de la interface TWI. Cuando se escribe 1 en TWEN, el TWI toma el control de los pines de E/S SCL y SDA, se habilitan los filtros de picos y los limitadores slew-rate. Si se escribe 0 en TWEN, se apaga el TWI y se terminarán todas las transmisiones I2C, sin importar las operaciones en curso. TWIE TWI Interrupt Enable Cuando se escribe 1 en este bit, y el bit I del registro SREG vale1, la interrupción de TWI se reactivará mientras el flag TWINT permanezca activo. El Registro TWSR Registro TWCR TWCR TWINT TWEA TWSTA TWSTO TWWC TWEN --- TWIE Registro de Microcontrolador Bits 7:3 TWI Status Bits 1:0 TWPS: TWI Prescaler Bits Estos 5 bits reflejan el estado del módulo TWI y de la lógica I2C. Al revisar estos bits de estado el programa debería enmascarar con 0 los dos bits de prescaler para que la revisión sea independiente de la configuración del prescaler. Estos bits son de lectura y escritura y controlan el prescaler del generador de baudios del TWI. Tabla TWPS1
  • 720. TWPS1 TWPS0 Valor del prescaler 0 0 1 0 1 4 1 0 16 1 1 64 Librería Para Bus I2C en los AVR Se usará esta librería en todas las prácticas con bus I2C, desde las memorias EEPROM, pasando por los sensores de temperatura, los relojes RTC y los expansores de E/S. La librería consta de los archivos i2c.h e i2c.c. i2c.c recopila las funciones del módulo TWI elaboradas anteriormente. i2c.h contiene los prototipos de las funciones y macros de configuración. En realidad la única macro que se podría editar es la directiva que define la frecuencia del bus I2C. //*********************************************************************** ***** // CONFIGURACIÓN DE LA FRECUENCIA DEL BUS I2C //*********************************************************************** ***** #ifndef I2C_BAUD /* Define la velocidad de transmisión del módulo TWI (en bits/segundo), si * aún no ha sido definida. */ #define I2C_BAUD #endif 100000UL // 100khz. Valor por defecto
  • 721. Por defecto está establecido a 100000UL = 100kHz, pero se puede cambiar por otro valor ya sea aquí mismo o poniendo el define en el archivo principal del proyecto. El valor que se indique no necesariamente será la frecuencia real. La función i2c_init del archivo i2c.c utiliza la fórmula estudiada en la sección Velocidad de Transferencias de Datos y establecerá la frecuencia que resulte más cercana al valor indicado, con redondeo hacia abajo. Por supuesto, esta frecuencia también dependerá del reloj del sistema. Por ejemplo, para un XTAL de 8MHz la frecuencia I2C resultante sí será exactamente de 100kHz, bueno, al menos en teoría, porque en la práctica los dispositivos tienen la facultad de frenar la velocidad del bus. /************************************************************************ ****** * FileName: * Purpose: Master i2c.h Librería de funciones para el módulo TWI en modo I2C * Processor: ATmel AVR con módulo TWI * Compiler: AVR IAR C y AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. * * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta * licencia y las notas de autor y copyright de arriba. ************************************************************************* ****/ #include "avr_compiler.h" //*********************************************************************** *****
  • 722. // CONFIGURACIÓN DE LA FRECUENCIA DEL BUS I2C //*********************************************************************** ***** #ifndef I2C_BAUD /* Define la velocidad de transmisión del módulo TWI (en bits/segundo), si * aún no ha sido definida. */ #define I2C_BAUD 100000UL // 100khz. Valor por defecto #endif //*********************************************************************** ***** // PROTOTIPOS DE FUNCIONES //*********************************************************************** ***** void i2c_init(void);// Inicializa el bus I2C char i2c_start(void);// Envía un START void i2c_stop(void);// Envía un STOP char i2c_write(char data);// Envía un byte y recibe el bit ACK/NACK char i2c_read(char ack);// Recibe un byte y envía el bit ACK/NACK #define i2c_restart i2c_start /************************************************************************ ****** * FileName: i2c.c
  • 723. * Purpose: Master Librería de funciones para el módulo TWI en modo I2C * Processor: ATmel AVR con módulo TWI * Compiler: AVR IAR C y AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. * * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta * licencia y las notas de autor y copyright de arriba. ************************************************************************* ****/ #include "i2c.h" //*********************************************************************** ***** // Inicializar el módulo TWI. // Configurar Frecuencia de reloj a I2C_BAUD. //*********************************************************************** ***** void i2c_init(void) { /* Establecer valor de prescaler de TWI a 0 TWSR &=~((1<<TWPS1)|(1<<TWPS0)); /* Establecer Frecuencia de SCL */ TWBR =((F_CPU/I2C_BAUD)-16)/2; */
  • 724. /* Habilitar la interface TWI */ TWCR =(1<<TWEN); } //*********************************************************************** ***** // Envía una Condición START o START Repetida. // Retorna 1 si la Condición START se envió satisfactoriamente, 0 si falló. //*********************************************************************** ***** char i2c_start(void) { /* Setear bit TWSTA y limpiar flag TWINT para iniciar Condición START */ TWCR =(1<<TWSTA)|(1<<TWINT)|(1<<TWEN); /* Esperar a que la Condición START se termine de enviar */ while((TWCR &(1<<TWINT))==0); /* Limpiar bit TWSTA */ TWCR =(1<<TWEN); /* Comprobar si la transferencia de START fue satisfactoria */ if(((TWSR&0xFC)==0x08)||((TWSR&0xFC)==0x10)) return1; return0; }
  • 725. //*********************************************************************** ***** // Envía una Condición STOP //*********************************************************************** ***** void i2c_stop(void) { /* Setear bit TWSTO y limpiar flag TWINT para iniciar Condición STOP */ TWCR =(1<<TWSTO)|(1<<TWINT)|(1<<TWEN); /* Esperar a que la Condición STOP se termine de enviar */ while( TWCR &(1<<TWSTO)); } //*********************************************************************** ***** // Envía el byte 'data' y devuelve 0 si el esclavo respondió con un bit ACK // En otro caso, devuelve 1. //*********************************************************************** ***** char i2c_write(char data) { /* Cargar dato a transmitir */ TWDR = data; /* Limpiar flag TWINT para iniciar la transferencia */ TWCR =(1<<TWINT)|(1<<TWEN); /* Esperar a que finalice la transferencia */
  • 726. while((TWCR &(1<<TWINT))==0); /* Leer el resultado de la transferencia y retornar 0 si el esclavo * respondió con un bit ACK. */ if(((TWSR&0xFC)==0x18)||((TWSR&0xFC)==0x40)||((TWSR&0xFC)==0x28)) return0; /* Se llega a este punto si el esclavo respondió con un NACK o si * hubo un error en la transferencia */ return1; } //*********************************************************************** ***** // Lee un byte de dato y envía el bit ACK/NACK. // ack = 0 es ACK y ack = 1 es NACK. //*********************************************************************** ***** char i2c_read(char ack) { /* Esperar a que termine de llegar el dato pedido */ while((TWCR &(1<<TWINT))==0); /* Enviar el bit ACK o NACK al esclavo */ if(ack ==1) TWCR =(1<<TWINT)|(1<<TWEN);// Esto es NACK else TWCR =(1<<TWEA)|(1<<TWINT)|(1<<TWEN);// Esto es ACK
  • 727. /* Esperar a que se termine de enviar el bit ACK/NACK */ while((TWCR &(1<<TWINT))==0); /* Retornar el dato que llego anteriormente */ return TWDR; } Memorias EEPROM Seriales 24xxx Las EEPROM seriales que estudiaremos en esta ocasión pertenecen a la familia 24xxxx. Hay varias compañías que fabrican muchos de estos modelos. De hecho, la misma Microchip también provee los suyos. Tabla de memorias seriales eeprom i2c Capacidad Dispositivo Conexión en cascada Bits Bytes 24xx01 1K 128 No 24xx02 2K 256 No 24xx04 4K 512 No 24xx08 8K 1 K No 24xx16 16 K 2 K No 24xx32 32 K 4 K Sí 24xx64 64 K 8 K Sí 24xx128 128 K 16 K Sí 24xx256 256 K 32 K Sí
  • 728. Tabla de memorias seriales eeprom i2c Capacidad Dispositivo Conexión en cascada Bits Bytes 24xx512 512 K 64 K Sí En la tabla mostrada arriba se aprecia la diversidad de memorias disponibles (y todavía es posible hallar unas cuantas más, aunque menos usuales). Lo que se deduce de inmediato es que el número final de la identificación de cada dispositivo es la capacidad de la EEPROM en bits. Por ejemplo, la EEPROM 24xx64 tiene 64 Kbits = 8 Kbytes de memoria. También se puede notar que la capacidad deconexión en cascada divide a las EEPROMs en dos grupos: las que la tienen y las que no. Las que la tienen pueden reconfigurar su dirección de esclavo de modo que se puedan colgar varias EEPROMs de ese tipo en la misma red I2C. Por ejemplo, es posible tener hasta 8 EEPROMs 24xx256 para juntar un total de 128 KBytes. La división indicada coincide con el modo de acceso al contenido de las EEPROMs. Por ejemplo, la forma de leer o escribir un byte en una 24xx16 difiere ligeramente de la forma de hacerlo en una 24xx32. A mí me parece que es más fácil empezar con las EEPROMs del segundo grupo (de 32 Kbits para arriba). Así que para nuestras prácticas vamos a tomar una de éstas, y para que no sea ni mucho ni poco elegiremos el modelo 24xx128. Lo olvidaba: el significado de las xx suele variar según el fabricante. En los modelos de Microchip estas xx pueden ser AA, LC o FC y distinguen básicamente la velocidad de reloj y tensión de operación del dispositivo. La EEPROM Serial 24xx128 Esto es parte del datasheet. Sus principales características son: Los tres modelos 24AA128, 24LC128 y 24FC128 se diferencian únicamente por operar a diferente velocidad. Memoria de 128 Kbits = 16 Kbytes. Frecuencia máxima de reloj de 400 KHz (Full Speed mode). Tiempo de escritura máximo de 5 ms. Retención de datos > 200 años. 1 000 000 de ciclos de lectura escritura. Modo de escritura por página de 64 bytes.
  • 729. Conexión en cascada hasta de 8 dispositivos. Tensión de operación entre 2.5 V y 5.5 V. Descripción de Pines Diagrama de pines de la EEPROM 24xx128. A0, A1 y A2. Pines que establecen parte de la dirección de esclavo de este dispositivo. Leer siguiente sección. Vss y Vcc. Tierra y alimentación del dispositivo. SDA y SCL. Línea serial de datos y línea serial de reloj. El pin WP (Write Protection) o protección de escritura. Conectado a tierra desactiva la protección de escritura, es decir, el contenido de la memoria podrá ser leído y escrito. Si WP se conecta a VCC, la memoria se podrá leer pero no escribir. Dirección del Dispositivo Recordemos que cada dispositivo esclavo conectado al bus I2C debe estar identificado por una dirección de 7 bits. Pues bien, en la gran mayoría de los esclavos parte de esa dirección suele ser fija y la otra parte, reconfigurable vía hardware. Por ejemplo, en las EEPROMs seriales de la familia 24xxx, la dirección de esclavo, en binario, es 1010xxx, siendo xxx la parte reconfigurable por los pines A2, A1 y A0 del dispositivo. Así se podrán formar 8 direcciones diferentes para conectar hasta 8 EEPROMs de este tipo. Como sabemos, la dirección de esclavo debe estar contenida en el byte de control de cada paquete de datos transmitido. El bit restante de dicho byte (R/W) indica si los siguientes bytes serán de lectura (R/W = 1) o de escritura (R/W = 0).
  • 730. El byte de control (Dirección de esclavo + bit R/W). Lectura y Escritura Aleatorias de Bytes Hay dos formas de realizar operaciones de lectura y escritura de datos en las EEPROMs 24xxx: una es byte por byte (acceso a un byte por cada paquete transmitido) y la otra es en bloques (varios bytes por cada paquete transmitido). En este apartado nos enfocamos a la primera forma, conocida como acceso aleatorio, porque se debe especificar la dirección de cada byte de dato accedido. La locación de memoria a acceder depende de un registro interno llamado Puntero de memoria, el cual puede llegar a ser de 16 bits (2 bytes). Tras cada lectura el Puntero de memoria se incrementa en 1. Para las escrituras funciona ligeramente diferente. Secuencia de escritura de un byte en la EEPROM 24xx128. La figura de arriba indica que para escribir un Data Byte en la dirección Address de la memoria se debe seguir la siguiente secuencia: Enviar una Condición START (iniciar transferencia). Enviar el byte de control para escritura (Slave address + Write). Enviar el byte alto de Address. Enviar el byte bajo de Address. Enviar el Data Byte. Enviar una Condición STOP (cerrar transferencia).
  • 731. Tras la Condición STOP empieza el ciclo de escritura interno del dato enviado. Este ciclo dura a lo mucho 5 ms. Hay que poner un delay. Ahora pasemos al proceso de lectura de una posición aleatoria de la EEPROM 24128. De nuevo, solo seguimos los pasos que nos indica el datasheet, graficados en el siguiente esquema: Secuencia de lectura de un byte de la EEPROM 24xx128. El esquema indica que para leer un Data Byte de la dirección Address de la memoria se deben seguir los siguientes pasos: Enviar una condición START. Enviar el byte de control para escritura (Slave address + Write). Enviar el byte bajo de Address. Enviar el byte alto de Address. Enviar una condición START (llamada START repetida aunque sea lo mismo). Enviar el byte de control para lectura (Slave address + Read). Leer el byte de dato y devolver un NACK. Enviar una condición STOP. Nota que, aunque vayamos a leer un dato de la EEPROM, primero debemos especificar de qué dirección, es decir, debemos escribir su dirección (en el Puntero de memoria). Por eso el primer byte de control es para escritura. Luego se vuelve a enviar el Byte de Control, esta vez para la lectura del dato en sí. Práctica: Acceso Aleatorio a la EEPROM 24xxx
  • 732. Lo que hace el programa es grabar cada uno de los caracteres de una cadena de texto en las primeras posiciones de la EEPROM serial y a continuación los lee todos de allí y los visualiza en el programa terminal. La EEPROM puede ser una de las que Microchip denomina Smart o de alta densidad. Es decir, vale desde una 24xx32 hasta una 24xx512. El pin WP (protección contra escritura) está conectado a GND ya que la memoria será accedida para lectura y escritura. Según el datasheet, el valor recomendable de las resistencias de pull-up para velocidades de bus menores o iguales a 100KHz es de 10K más o menos. No es fácil dar un valor exacto porque dependerá tanto de la velocidad del bus como de su capacitancia. Este último parámetro a su vez depende en gran medida del circuito (ni siquiera es lo mismo un circuito de placa impresa que uno montado en un breadboard). Circuito para la EEPROM I2C y el microcontrolador AVR. Nota: las versiones anteriores de Proteus VSM requerirán que las resistencias sean digitales. Se pueden escoger las partes llamadas PULLUP o cambiar una resistencia analógica a digital editando su ventana de propiedades, tal como se ve abajo. De lo contrario, las simulaciones producirán errores o consumirán más ciclos de CPU de lo necesario. Algunas resistencias tienen una ventana de propiedades diferente y para cambiar su naturaleza analógica será necesario sobrescribirla en el cuadro de texto que aparece al hacer clic en Edit all properties as text.
  • 733. Sobre el programa, la documentación en línea de la función main no deja nada más que decir. Y sobre las funciones write_24xxx y read_24xxx tampoco hay mayor misterio: son traducciones casi literales de los procedimientos descritos antes. Tras cada byte enviado con write_24xxx se recibe el correspondiente bit de Acknowledge, aunque no se tomen en cuenta porque se da por hecho que se tratan de bits ACK (que el esclavo reconoce todos los bytes). No debemos confundir las direcciones de los datos dentro de la memoria con la dirección del dispositivo. Esta última está contenida en la constante 0xA0 = 10100000. Los tres bits 3, 2 y 1 valen 0 porque en el circuito los pines A2, A1 y A0 están conectados a GND. El bit 0 vale inicialmente 0 para indicar una escritura y se pone a 1 con 0xA0 |0x01 para cuando se desee una lectura. /****************************************************************************** * FileName: main.c * Purpose: Acceso aleatorio a la EEPROM serial 24xxx (>=32 kBits) * Processor: ATmel AVR con módulo TWI * Compiler: AVR IAR C y AVR GCC (WinAVR) * Author: * Shawn Johnson. http://www.cursomicros.com.
  • 734. * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. * * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta * licencia y las notas de autor y copyright de arriba. *****************************************************************************/ #include "avr_compiler.h" #include "usart.h" #include "i2c.h" voidwrite_24xxx(unsignedintaddress,chardata); charread_24xxx(unsignedintaddress); intmain(void) { charc;unsignedinta; charcad[]=" Este texto fue escrito en la EEPROM y leído de ella "; unsignedintlon=strlen(cad);// Obtener longitud de cad i2c_init();// 100 kHz usart_init();// 9600 - 8N1 puts("nr Test de la EEPROM serial 24xxx "); puts("nr ============================== nrnr");
  • 735. // Escribir todos los caracteres de 'cad' en las primeras // posiciones de la EEPROM serial for(a=0;a<lon;a++) { c=cad[a];// Obtener elemento a de cad write_24xxx(a,c);// Escribir c en dirección a } // Leer las 'lon' primeras posiciones de la EEPROM y mostrarlas // en el terminal serial RS232 for(a=0;a<lon;a++) { c=read_24xxx(a);// Leer dirección a putchar(c);// Enviar a terminal } sleep_enter();// Entrar en modo Standby } //**************************************************************************** // Escribe el dato 'data' en la dirección 'address' de la EEPROM serial. //**************************************************************************** voidwrite_24xxx(unsignedintaddress,chardata) { i2c_start();// START
  • 736. i2c_write(0xA0);// Slave address + Write i2c_write(address>>8);// Address high byte i2c_write(address);// Address low byte i2c_write(data);// Data to EEPROM i2c_stop();// STOP delay_us(5000);// Tiempo de ciclo de escritura interno } //**************************************************************************** // Lee un byte de dato de la dirección 'address' de la EEPROM serial. //**************************************************************************** charread_24xxx(unsignedintaddress) { chardata; i2c_start();// START i2c_write(0xA0);// Slave address + Write i2c_write(address>>8);// Address high byte i2c_write(address);// Address low byte i2c_restart();// Repeated START i2c_write(0xA0|0x01);// Slave address + Read data=i2c_read(1);// Read data & send NACK i2c_stop();// STOP returndata; }
  • 737. Acknowledge Polling Con lo visto hasta ahora puede ser más que suficiente para usar una EEPROM serial en cualquier aplicación. Sin embargo, quienes tenemos esta manía por la microelectrónica siempre queremos saber algo más. Así que, tras hurgar en el datasheet, encontré la sección Acknowledge Polling o sondeo del bit de reconocimiento. Allí dice que mientras la EEPROM esté ocupada responderá con un NACK a los bytes que se le envíen. Cuando responda con unACK significa que la EEPROM está lista de nuevo. Podemos usar esa característica para optimizar los accesos a la memoria. Por ejemplo, en vez de esperar 5 ms para que la EEPROM termine de grabar un dato, se podría poner el siguiente procedimiento. Diagrama de flujo del bucle Acknowledge Polling. El código quedaría así: // ... i2c_stop();// Cierre de algún paquete previo // El siguiente bucle envía un START y el Byte de Control (con RW = 0) // hasta que se obtenga un ACK (0) como respuesta. Entonces la EEPROM // estará lista para la siguiente operación. do{
  • 738. i2c_restart();// START condition ack=i2c_write(0xA0);// Control Byte }while(ack!=0); // Siguiente operación // ... En este código también se toma la dirección de esclavo 0xA0, asumiendo que los pines A2, A1 y A0 de la EEPROM I2C están conectados a GND. Se pone la función i2c_restarten vez del i2c_start presupuesto para asegurar que se inicia otra transferencia. Práctica: Uso de Bucle Acknowledge Polling El programa recibe los datos llegados del terminal RS232 y actúa según lo siguiente: Si es un carácter imprimible (letra, número, espacio, etc.) o la tecla Intro, se guarda su valor en la EEPROM en posiciones consecutivas a partir de la dirección 0x0000. Si es la tecla de Retroceso, se “borra” el carácter anterior. Si es la tecla Escape, se escribe un 0x00 (como fin de cadena) y se procede a leer todo lo grabado. Es el mismo circuito de la práctica previa.
  • 739. Circuito para la EEPROM I2C y el microcontrolador AVR. Lo que hace la función principal main es lo de menos. De nuevo vamos a concentrarnos enwrite_24xxx y read_24xxx. Estas funciones son similares a las de la práctica anterior. De hecho, si observas bien, sus accesos a la EEPROM son completamente equivalentes cuando ella está libre; es decir, el código del bucle Acknowledge Polling: do{// Bucle Acknowledge Polling i2c_restart();// ack = i2c_write(0xA0);// Enviar START y Byte de control (para escritura) }while(ack!=0);// Hasta que sea reconocido se reduce a la siguiente rutina: i2c_start();// Enviar START y ack = i2c_write(0xA0);// Byte de control (para escritura) y el resto se equipara por sí solo. Muchos suelen usar el bucle Acknowledge Polling al final de la función de escritura, en sustitución del acostumbrado delay de 5 ms (de hecho, así lo sugiere el datasheet). Sin embargo, en tal caso el programa se quedará en el bucle casi el mismo tiempo esperando la escritura completada.
  • 740. En ciertas aplicaciones sería mejor que todo o parte de dicho tiempo pasara mientras el procesador ejecuta otras tareas. Eso es lo que se consigue poniendo el bucle al inicio y testeando la disponibilidad de la EEPROM justo antes de una nueva operación de lectura o escritura. /************************************************************************ ****** * FileName: * Purpose: Polling * Processor: * Compiler: * Author: main.c Acceso aleatorio a la EEPROM 24xxx + bucle Acknowledge ATmel AVR con módulo TWI AVR IAR C y AVR GCC (WinAVR) Shawn Johnson. http://www.cursomicros.com. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. * * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta * licencia y las notas de autor y copyright de arriba. ************************************************************************* ****/ #include "avr_compiler.h" #include "usart.h" #include "i2c.h" #include <ctype.h> void write_24xxx(unsignedint address,char data); char read_24xxx(unsignedint address); int main(void) {
  • 741. char c;unsignedint a=0; i2c_init();// 100 kHz usart_init();// 9600 - 8N1 puts("nr www.cursomicros.com nr"); puts("nr Lo que escriba se irá grabando en la EEPROM nr"); puts("nr Pulse Escape para leer en contenido grabado... nrr"); while(1) { if(kbhit())// Si llegó algún dato { c = getchar();// Leer dato if(isprint(c)||(c=='r'))// Si c es imprimible o si es Enter { write_24xxx(a++, c); putchar(c); } elseif((c=='b')&&(a))// Si c es Retroceso y si a es > 0 { putchar(c); a--; } elseif(c==27)// Si c es tecla Escape,... {
  • 742. write_24xxx(a++, '0');// Almacenar un 0x00 (fin de cadena) a =0;// Resetear puntero de memoria while( c=read_24xxx(a++))// Volcar los datos de la EEPROM putchar(c);// hasta encontrar un 0x00 a =0; } } } } //*********************************************************************** ***** // Escribe el dato 'data' en la dirección 'address' de la EEPROM serial. //*********************************************************************** ***** void write_24xxx(unsignedint address,char data) { char ack; do{// Bucle Acknowledge Polling i2c_restart();// Enviar START y ack = i2c_write(0xA0);// Byte de control (para escritura) }while(ack!=0);// Hasta que sea reconocido i2c_write(address >>8);// Byte alto de address i2c_write(address);// Byte bajo de address i2c_write(data);// Escribir dato i2c_stop();// }
  • 743. //*********************************************************************** ***** // Lee un byte de dato de la dirección 'address' de la EEPROM serial. //*********************************************************************** ***** char read_24xxx(unsignedint address) { char data, ack; do{// Bucle Acknowledge Polling i2c_restart();// Enviar START y ack = i2c_write(0xA0);// Byte de control (para escritura) }while(ack!=0);// Hasta que sea reconocido i2c_write(address >>8);// Byte alto de address i2c_write(address);// Byte bajo de address i2c_restart();// i2c_write(0xA0|0x01);// Byte de control (para lectura) data = i2c_read(1);// Leer y enviar un NACK i2c_stop();// return data; } Lectura Secuencial y Escritura por Páginas Por lo visto previamente, para mover un byte de dato a/desde la 24xxx el paquete transferido debía incluir algunos bytes extras, como el byte de control (dos veces en las lecturas) y de dirección de memoria (hasta dos bytes). Este proceso puede ser pesado para algunas aplicaciones, donde se transfieran grandes cantidades de datos.
  • 744. Afortunadamente, también es posible mover varios datos por paquete. A eso se le llamalectura secuencial y escritura por páginas. En general, bastará con especificar la dirección de la primera locación a acceder. Después de cada lectura o escritura, el puntero de memoria se incrementará automáticamente para acceder a la siguiente locación. Como lo evidencian los diagramas de tiempos, los procedimientos software seguidos en ambos casos es muy similar a como se hacía con un único byte: empieza igual y termina igual, solo varía la cantidad de data bytes transferidos y un “detallito” que a continuación se describe. Por ejemplo, en la lectura la diferencia es que cada byte leído debe ser respondido con unACK, salvo el último, el cual debe ser respondido con un NACK. Esto es perfectamente compatible con la lectura de un solo byte ya que ahí el único byte es a la vez el último. Esquema de una lectura secuencial de N+1 datos. Por otro lado, para escribir un bloque de bytes se sigue el mismo procedimiento que para escribir un solo byte. El obstáculo ahora radica en que el número de bytes enviados por paquete es limitado y en rangos restringidos, según el espacio de las páginas. Por eso se llama escritura por páginas. Ahora, ¿qué son las páginas?
  • 745. Esquema de una escritura por páginas. Antes, las EEPROMs I2C poseen buffers internos donde reciben temporalmente los datos. En la 24xx128 este buffer es de 64 bytes, lo que le permite recibir hasta 64 bytes seguidos. Al cerrar el paquete (con el STOP) el buffer entero será volcado a las celdas de la memoria y empezará el ciclo de escritura interno. Lo bueno es que este ciclo durará lo mismo que cuando se escribe un solo byte, o sea, 5 ms a lo sumo. Ahora bien, según el tamaño del buffer interno, el cuerpo de la 24xxxx se puede dividir en bloques o páginas. Para la 24xx128 estamos hablando de 16KB/64 = 512 páginas; cadapágina empieza en una dirección múltiplo de 64 y termina en una múltiplo de 64 menos 1. El punto es que una vez establecido el puntero de memoria, su valor se incrementará tras cada byte enviado, hasta llegar al límite de la página actual, luego de lo cual volverá a apuntar al inicio de la página. Como consecuencia, los siguientes bytes enviados sobrescribirían los datos allí presentes. No olvides esa precaución. Práctica: Acceso Secuencial a la 24xxx Supongamos que queremos copiar todo el contenido de una 24xx128 a otra. Si utilizáramos el direccionamiento aleatorio, por cada byte de dato copiado habría que transferir otros 3 ó 4 (de control y dirección), con el consiguiente desperdicio de ciclos de CPU. Además, si cada byte de dato se graba en cerca de 5 ms, los 16384 bytes de la 24xx128 tardarían más de 80 segundos en grabarse. En esta práctica veremos cómo optimizar ese trabajo.
  • 746. En una EEPROM se ha conectado el pin WP a GND para habilitar la escritura en ella y en la otra dicho pin está sujetada a VCC porque será usada como de solo lectura. Son detalles. En cambio, sí es crucial la diferencia en las conexiones de los pines A2, A1 y A0 entre las dos EEPROMs. Habrás notado que el valor de las resistencias de pull-up del bus I2C ha disminuido. Esto revela, aunque grosso modo, su comportamiento ante el incremento tanto de la velocidad del bus como de su capacitancia debido al aumento de dispositivos I2C colgados. Circuito para la EEPROM I2C y el microcontrolador AVR. En vez de mover los bytes de datos uno por uno de una EEPROM a la otra, el programa mueve 512 bloques de 32 bytes cada uno, aunque hubiera sido mejor que cada bloque fuera de 64 bytes para aprovechar todo el tamaño del buffer de página de la 24xx128. Creo que eso lo podrías hacer tú. Los pasos de las funciones EEPROM1_read y EEPROM2_write son muy similares a los deread_24xxx y write_24xxx estudiadas en la práctica anterior. Los pequeños cambios ya fueron descritos en la teoría y también se repiten en los comentarios del código. /****************************************************************************** * FileName: main.c * Purpose: EEPROMs 24xxx: Escritura por Páginas & Lectura Secuencial.
  • 747. * Processor: ATmel AVR con módulo TWI * Compiler: AVR IAR C y AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. * * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta * licencia y las notas de autor y copyright de arriba. *****************************************************************************/ #include "avr_compiler.h" #include "usart.h" #include "i2c.h" voidEEPROM2_write(unsignedintaddress,char*p,unsignedcharsize); voidEEPROM1_read(unsignedintaddress,char*p,unsignedcharsize); intmain(void) { unsignedinti,Address; unsignedchark; charbuf[32];// Buffer de 32 bytes i2c_init();// 100 kHz usart_init();// 9600 - 8N1
  • 748. printf("nr 24XX128 cloner"); while(1) { printf("nrr Press ENTER to start "); while(getchar()!='r')// Esperar mientras carácter leído continue;// no sea 'r' = 0x0D = ENTER Address=0x0000; k=0; for(i=0;i<512;i++)// 512 bloques de 32 bytes { EEPROM1_read(Address,buf,32);// Leer bloque de 32 bytes EEPROM2_write(Address,buf,32);// Grabar bloque de 32 bytes Address+=32; if(++k>=32){// Dar mensaje cada 1 KB copiado printf("rn%5u bytes copiados",Address+1); k=0; } } printf("rnTerminado "); } }
  • 749. //**************************************************************************** // Escribe los 'size' primeros bytes del array 'p' en la EEPROM serial 2 // a partir de la direccion 'address'. // Nota: los 'size' bytes no deben rebasar la página actual. //**************************************************************************** voidEEPROM2_write(unsignedintaddress,char*p,unsignedcharsize) { unsignedcharack,i; do{// Bucle Acknowledge Polling i2c_restart();// Enviar START y ack=i2c_write(0xA1);// Slave address + Write }while(ack!=0);// Hasta recibir ACK i2c_write(address>>8);// Address high byte i2c_write(address);// Address low byte for(i=0;i<size;i++)// Bucle para enviar size bytes de datos i2c_write(*p++);// Escribir dato apuntado por p i2c_stop();// Iniciar escritura de bloque } //**************************************************************************** // Lee del array 'p' los 'size' bytes consecutivos de la EEPROM serial 1 // a partir de la dirección 'address'.
  • 750. // Tras cada byte leído se envía un ACK, salvo en el último, donde se envía // un NACK. //**************************************************************************** voidEEPROM1_read(unsignedintaddress,char*p,unsignedcharsize) { unsignedcharack,i; do{// Bucle Acknowledge Polling i2c_restart();// Enviar START y ack=i2c_write(0xA0);// Slave address + Write }while(ack!=0);// Hasta recibir ACK i2c_write(address>>8);// Address high byte i2c_write(address);// Address low byte i2c_restart();// i2c_write(0xA0|0x01);// Slave address + Read for(i=0;i<size-1;i++)// Bucle para leer size-1 datos *p++=i2c_read(0);// Leer y enviar un ACK *p=i2c_read(1);// Leer y enviar un NACK i2c_stop();// } Memorias 24xxx de Baja Densidad
  • 751. Cuando empezamos con las EEPROM seriales I2C vimos que había diversos modelos. No solo se diferencian por su capacidad de almacenamiento, sino también por el tamaño de sus páginas internas y soporte de conexión en cascada. Los dos últimos aspectos ya los vimos de cerca y los pusimos en práctica. Recordemos la siguiente tabla. Tabla de memorias seriales eeprom i2c Capacidad Dispositivo Conexión en cascada Bits Bytes 24xx01 1K 128 No 24xx02 2K 256 No 24xx04 4K 512 No 24xx08 8K 1 K No 24xx16 16 K 2 K No 24xx32 32 K 4 K Sí 24xx64 64 K 8 K Sí 24xx128 128 K 16 K Sí 24xx256 256 K 32 K Sí 24xx512 512 K 64 K Sí Las EEPROMs que pueden conectarse en cascada corresponden al grupo de las llamadasSmart o de alta densidad y las que no, pertenecen al grupo Standard o memorias de baja densidad. Parece solo una cuestión de denominación, pero no. La principal diferencia es el modo de direccionamiento. Como sabemos, la dirección de esclavo de todos estos dispositivos es 1010xxx. En el caso de las EEPROMs de alta densidadlas xxx se configuran por los pines A2, A1 y A0 para así permitir la conexión en cascada de varios dispositivos iguales.
  • 752. Por otro lado, al no soportar conexión en cascada, en las EEPROMs de baja densidadesos pines no significan nada. ¿Entonces qué pasa con las xxx? Forman parte de la dirección de los datos. O sea, mientras que la dirección de un dato en una EEPROM de alta densidad se especifica en dos bytes separados, en las EEPROMs de baja densidad dicha dirección va en un byte y se complementa con los tres bits de esas xxx, esto es, los tres bits altos de la dirección se hallan incrustados en el byte de control, tal como se ven en las siguientes figuras. Con los 11 bits en total se logra abarcar un rango de hasta 2048 bytes de datos (los de una 24xx16). Obviamente, el esquema de direccionamiento descrito también se debe cumplir en los accesos secuenciales de las EEPROM de baja densidad. Escritura de 1 byte de dato en una EEPROM de baja densidad. Lectura de 1 byte de dato de una EEPROM de baja densidad. Práctica: Detección Automática de Memoria Aparte de sus memorias de programa, de datos EEPROM, etc., sabemos que los AVR guardan en su chip códigos de ID(Identificación de Dispositivo) que nos permiten reconocerlos, por ejemplo, desde un programador.
  • 753. Si hubiera algo parecido en las EEPROM I2C, se podrían hacer programas con accesos de escritura optimizados aprovechando el tamaño completo de los buffers de página. Desafortunadamente, no hay. La presente práctica es una versión en lenguaje C del algoritmo propuesto y desarrollado en ensamblador por Lucio Di Jasio en la nota de aplicación AN690 deMicrochip Technology Inc. (Lucio Di Jasio también es autor de algunos muy buenos libros de microcontroladores.) El lugar de la EEPROM corresponde más bien a un zócalo de 8 pines. Se supone que el programa debería reconocer a cualquier dispositivo que allí pusiéramos. Circuito para la EEPROM I2C y el microcontrolador AVR. La estructura de las funciones de escritura y lectura de datos ha variado respecto de nuestros códigos anteriores, aunque aún se puede notar una clara equivalencia. Los cambios más notorios son que el bucle Acknowledge Polling se coloca después de escrito el dato en WriteByte y que la rutina de direccionamiento se empaqueta aparte en la funciónSetAddress. Como sea, son rutinas accesorias. La función clave del programa es MemDetect. Lo primero que hace es detectar si la 24xxx es de tipo Smart o Standard. El mecanismo de Lucio Di Jasio no está estrictamente sustentado pero funciona. La idea es ésta: La escritura de un 1 en la dirección 0x000 de la EEPROM asumiendo que se trata de unSmart produce dos posibles resultados. WriteByte(1,0x000,1); // Escribir 1 en dirección 0x000 (como Smart)
  • 754. Si la EEPROM es una Smart, se escribe un 1 en 0x000. Eso es obvio, ¿verdad? Si la EEPROM es una Standard, se escribe un 0 en 0x000 y un 1 en 0x001. ¿Por qué? Porque la operación es interpretada como una escritura por páginas de dos bytes. A continuación se ejecuta una lectura de la dirección 0x000, pero esta vez asumiendo que la memoria es del tipo Standard. dato = ReadByte(0,0x000); // Leer dirección 0x000 (como Standard) De nuevo hay dos posibles resultados: Si la EEPROM es una Standard, la lectura será correcta y se obtendrá el 0. Si la EEPROM es una Smart, se consigue leer el 1 de 0x000, pese a ser una operación con direccionamiento incompleto. Éste era el punto ambiguo. Así que en este punto ya sabemos de qué tipo de memoria se trata, si es de las grandes o de las pequeñas. Lo que falta ahora es encontrar su tamaño exacto. El truco para hallarla es saber que en una EEPROM de N bytes (con direcciones desde 0 hasta N-1) la dirección N equivale otra vez a la dirección 0; la dirección N+1, a la 1, y así. Es como si se desbordara el puntero de memoria. Entonces lo que sigue en el código de MemDetect es buscar que se cumpla esa condición. Se comparan los datos de las direcciones 0x00 y size. Si son iguales, se escribe un dato diferente (temp+1) en 0x00 y se vuelve a comparar con el dato de size. Si son iguales otra vez, es que en efecto la dirección 0x00 corresponde a la dirección size. El programa trabaja asumiendo que el Watchdog está habilitado. No está habilitado por software, así que se debería habilitar por hardware mediante su fuse correspondiente. /************************************************************************ ****** * FileName: main.c * Overview: Programa para detectar automáticamente la capacidad de una * EEPROM serial 24xxxx conectada al bus I2C. * Processor: ATmel AVR con módulo TWI * Compiler: AVR IAR C y AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com. * Jasio. Basado en el AN690 de Microchip Technology, por Lucio Di *
  • 755. * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. * * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta * licencia y las notas de autor y copyright de arriba. ************************************************************************* ****/ #include "avr_compiler.h" #include "usart.h" #include "i2c.h" char ReadByte(char smart,int address); void WriteByte(char smart,int address,char dato); char SetAddress(char smart,int address); unsignedint MemDetect(void); int main(void) { unsignedint size; i2c_init();// 100 kHz usart_init();// 9600 - 8N1 wdt_reset(); printf("nr Automatic detection of memory size"); printf("nr =================================="); wdt_reset();
  • 756. size = MemDetect(); printf("nrr Esta EEPROM es de %u bytes", size); while(1) { wdt_reset();// Pxq el WDT está habilitado } } /************************************************************************ ***** * Detección automática del tamaño de la memoria 24XXX. * Los tamaños de las memorias corresponden a los siguientes modelos. * * SIZE * MODEL | 128 24xx01/21/41 | 256 24xx02/62 | 512 24xx04 * | 1024 24xx08 * | 2048 24xx16/164 | 4096 24xx32 | 8192 24xx65/64 * * Standard I2C * * * Smart * Serial * | 16384 24xx128 | 32768 24xx256 ************************************************************************* ***/ unsignedint MemDetect(void) { char dato, temp, smart, type;
  • 757. unsignedint size; WriteByte(1,0x000,1);// Escribir 1 en dirección 0x000 (como Smart) dato = ReadByte(0,0x000);// Leer dirección 0x000 (como Standard) if(dato==0)// Si dato leído es 0 { smart =0;// Es una EEPROM Standard size =128;// size = 128 byte type =01;// Empezar con type = 24xx01 } else// De otro modo { smart =1;// Sí es una EEPROM Smart size =4096;// size = 4096 bytes type =32;// Empezar con type = 24xx32 } /*** El siguiente código busca el tamaño de la EEPROM ***/ temp = ReadByte(smart,0x00);// Leer dirección 0x000 do{ dato = ReadByte(smart, size);// Leer dirección size if(dato==temp)// Si los datos leídos son iguales { WriteByte(smart,0x00, temp+1);// Escribir temp+1 en dirección 0x00 dato = ReadByte(smart, size);// Leer dirección size
  • 758. if(dato==(temp+1))// Si aún son iguales, eureka! break;// Salir del bucle do-while } size *=2;// Duplicar size type *=2;// Duplicar type }while((type&0x10)==0); return size; } /************************************************************************ ***** * Lee un byte de la dirección 'address' de la EEPROM serial. * El tipo de direccionamiento se pasa en la variable 'smart'. ************************************************************************* ***/ char ReadByte(char smart,int address) { char ctrbyte, data; ctrbyte = SetAddress(smart, address);// set address pointer /* enter here for sequential reading */ i2c_restart(); i2c_write(ctrbyte|0x01);// Es un comando de lectura data = i2c_read(1);// Leer y enviar NACK i2c_stop(); return data; } /************************************************************************ *****
  • 759. * Escribe el byte de 'dato' en la dirección 'address' de la EEPROM serial. * El tipo de direccionamiento se pasa en la variable 'smart'. ************************************************************************* ***/ void WriteByte(char smart,int address,char dato) { unsignedchar ack, i=128; SetAddress(smart, address);// set address pointer i2c_write(dato);// output DATO i2c_stop(); do{// Bucle Acknowledge Polling wdt_reset(); i2c_restart();// i2c_start ??? ack = i2c_write(0xA0);// Un comando de escritura }while((ack!=0)&&(--i)); i2c_stop(); } /************************************************************************ ***** * Establece el Puntero de Direcciones de la EEPROM a 'address'. * El parámetro 'smart' determina lo siguiente: * - Si smart = 1 -> la EEPROM es es direccionada como Smart. * - Si smart = 0 -> la EEPROM es es direccionada como Standard. * Retorna el Byte de Control usado. Servirá en la función 'ReadByte'. ************************************************************************* ***/ char SetAddress(char smart,int address) {
  • 760. char ctrbyte; i2c_start(); if(smart) { ctrbyte =0xA0;// Guardar Byte de control i2c_write(ctrbyte);// Enviar byte de control i2c_write(address>>8);// Enviar MSByte de address } else { ctrbyte =((unsignedchar)(address>>8))<<1; ctrbyte |=0xA0;// Sumar MSByte de address con 0xA0 i2c_write(ctrbyte);// Enviar byte de control } i2c_write(address);// Enviar LSByte de address return ctrbyte; } El Reloj de Tiempo Real DS1307 ¿Para qué queremos un reloj-calendario en chip separado? ¿Acaso no es a fin de cuentas un simple contador de segundos que se puede implementar cómodamente con un Timer cualquiera de un microcontrolador cualquiera? No es tan difícil construir un completo RTC con un microcontrolador. La parte del reloj es la más simple; solo ajustamos bien la temporización y listo. Luego implementamos los algoritmos del calendario para reconocer los meses que tienen 28, 29, 30 ó 31 días, los algoritmos para realizar las compensaciones de los años bisiestos,... Los relojes-calendario no suelen ser el elemento principal en los dispositivos electrónicos, sino un aditamento que es preferible que opere independientemente, incluso con su propia fuente de alimentación. Por ejemplo, el procesador de tu computadora podrá ser muy potente pero aun así el reloj del sistema va en otro chip y usa su propia batería.
  • 761. Justificada la necesidad de los RTCs, veremos que existe una casi incalculable cantidad de RTCs (de diversas marcas, de diversas características). De los relojes de Dallas semiconductors con interface I2C tomaremos en primer lugar el DS1307. Con decirte que es como empezar con el PIC16F84A en el mundo de los microcontroladores... Características del DS1307 El DS1307 es uno de los RTCs I2C más fáciles de usar debido en parte a sus limitaciones. Aun así, sus características son muy provechosas y son principalmente las que, con pocas variaciones, se repiten en los modelos de la serie DS1337, DS1338,DS1339, DS1340, DS1375. Veamos: Computa los segundos, minutos, horas, días de la semana, días del mes, meses y años (de 2000 hasta 2099). Aparte de los registros de hora y fecha del RTC, ofrece una SRAM de 56 bytes que se podrían usar como RAM extendida del microcontrolador. Provee por el pin SQW/OUT una señal cuadrada de frecuencia programable. Alimentación alterna usando una batería. En ausencia o deficiencia de la alimentación principal de Vcc, el DS1307 pasa automáticamente a alimentarse de la batería. Soporta el protocolo I2C en Standard Mode (máxima frecuencia de reloj de 100 kHz) Descripción Funcional de Pines del DS1307 Tabla Pines del DS1307 Diagrama de pines del RTC DS1307 en PDIP SDA y SCL. Pines de interface I2C. Vcc y GND. Pines de alimentación. Vcc es típicamente de 5 V. X1 y X2. Pines para conectar un XTAL de cuarzo estándar externo de 32.768 KHz.Los capacitores para estabilizar el circuito oscilador se incluyen internamente. Vbat. Pin para conectar opcionalmente una batería de 2.0 a 3.5 V. Normalmente el DS1307 operará con su fuente del pin Vcc. En ausencia de dicha tensión o cuando su nivel caiga por debajo de Vbat, el DS1307 empezará a trabajar con la batería.
  • 762. SQW/OUT. Por aquí el DS1307 puede sacar una onda cuadrada de cuatro frecuencias: 1 Hz, 4.096 kHz, 8.192 kHz ó 32.768 kHz. Se configura con el registro de control. Es un pin de drenador abierto y por tanto necesitará de una pull-up si se usa. Diagrama de Bloques del DS1307 Diagrama de bloques simplificado del DS1307 Algunas de las partes que se aprecian en el esquema de arriba ya fueron aludidas antes. Ahora citaremos los elementos más relevantes que en adelante nos servirán para entender mejor el funcionamiento del DS1307: Los registros de fecha y hora del RTC. El registro de control. Configura la señal del pin SQW/OUT. El puntero de registros. Contiene la dirección del registro a acceder. Los 56 registros de propósito general que se pueden usar como RAM libre de usuario. Los Registros de Hora y Fecha del DS1307 El primer segmento de la SRAM corresponde a los registros de función del RTC y son conocidos como Timekeeping registers o Timekeeper registers. Allí se almacenan los datos de hora y fecha en formato BCD, como en la mayoría de los RTCs. Tabla de Registros de Hora y Fecha del DS1307 Dirección Registro Rango
  • 763. Nibble alto Nibble bajo 0x00 CH 10 segundos segundos 00 – 59 0x01 10 minutos minutos 00 – 59 0x02 0 12/24 0x03 0000 día (de semana) 1 – 7 0x04 10 día día (de mes) 1 – 31 0x05 10 mes mes 1 – 12 0x06 10 año año 00 – 99 10HR 10HR horas AM/PM 1-12 / 0-23 Como se ve, en general, el nibble bajo de cada registro contiene el dígito de las unidades y el nibble alto contiene el dígito de las decenas de cada dato BCD. Las visibles excepciones son los nibbles altos de los segundos y de las horas. En el registro de segundos el bit 7 es CH(Clock Halt o reloj detenido). No sé qué hace allí habiendo espacio en el registro de control. Pero bueno, yo no diseñé este dispositivo. El bit CH indica si el oscilador del DS1307 está en marcha (0) o está detenido (1). Siendo de lectura o escritura, significa que: Si escribimos un 0 en CH, arrancamos el DS1307. Si escribimos un 1 en CH, detenemos el oscilador del DS1307 y su operación. Sirve para ahorrar energía cuando no se le necesite. Es como “enviarlo a un modoSleep”. El valor inicial de CH tras conectar la alimentación es 1. Es decir, si vemos un 1 en CHcuando no debería, tal vez sea porque falló la alimentación y el dispositivo se “reinició”. La validez de los datos de fecha y hora, por tanto, no estará garantizada. No olvides eso. El registro de las horas también es algo tedioso. Su bit 6 (12/24) establece si el DS1307 operará en modo de 12 horas o de 24 horas si vale 1 ó 0, respectivamente. En modo 24 horas el bit 5 (10HR / AMPM) complementa el dígito de decenas de horas. En modo 12 horas el bit 5 señala AM (0) o PM (1). El Registro de Control del DS1307
  • 764. Además de los registros de fecha y hora, los RTCs cuentan con registros de controlstatus para las funcionalidades extra que ofrecen: registros para programar alarmas (si las hubiera), registros para configurar algunas interrupciones, registros para configurar otros timers (si los hubiera), etc. Solemos empezar a trabajar con el DS1307 diciendo que es de los más fáciles porque solo tiene un registro de control, y de pocos bits efectivos :) Su función básica es configurar la onda cuadrada que saldrá por el pin SQW/OUT del DS1307. Registro Registro de Control del DS1307 OUT --- --- SQWE --- --- RS1 RS0 Registro de Microcontrolador SQWE Square Wave Enable 1 = Por el pin SQW/OUT sale una onda cuadrada cuya frecuencia se determina por los bits RS1 y RS0 0 = El estado del pin SQW/OUT depende del bit OUT OUT Output Control Siendo el bit SQWE = 0: 1 = El pin SQW/OUT se queda en 1 lógico constante 0 = El pin SQW/OUT se queda en 0 lógico constante BORF Brown-out Reset Flag Este bit se pone a uno cuando se produce un Reset Brown-out. Este bit se pone a cero por un reset Power-on o escribiendo un cero lógico en el flag. Tabla RS1 RS0 RS1 RS0 Frecuencia de onda del pin SQW/INT 00 1 Hz 01 4.096 kHz 10 8.192 kHz 11 32.768 kHz
  • 765. Acceso a los Registros del DS1307 Todos los registros del DS1307, ya sean de fecha y hora, el de control o los registros de propósito general, se acceden igual. Primero escribimos en el puntero de registros la dirección del registro a acceder y luego efectuamos la lectura o escritura del registro. Cada transferencia debe seguir las normas del protocolo I2C, empezando con un STARTy terminando con un STOP. Tras cada lectura o escritura el puntero de registros se incrementa automáticamente en 1 para apuntar al siguiente registro, lo cual nos permite acceder a varios registros secuencialmente por cada transferencia. En los siguientes diagramas verás que leer y escribir los registros de un RTC es más simple que leer y escribir datos en una EEPROM I2C, tanto en modo individual como en bloques. Después de todo, el protocolo I2C es único. Además, ahora las direcciones de registros son de un byte, no hacen falta rutinas para comprobar la disponibilidad del dispositivo, etc. Secuencia de escritura de uno o varios registros en el DS1307. El procedimiento descrito paso a paso es: Enviar una Condición START. Enviar el byte de control (dirección de esclavo con R/W = 0, para escritura). Enviar la dirección del primer (o único) registro a escribir, Register Address en la imagen. Escribir uno o tantos registros como se desee, Register Data 0, 1, x en la imagen. Enviar una Condición STOP. Ahora revisemos el procedimiento para leer uno varios registros del DS1307.
  • 766. Secuencia de lectura de uno o varios registros del RTC DS1307. Ésta es la secuencia disgregada: Enviar una condición START. Enviar el byte de control (dirección de esclavo con R/W = 0, para escritura). Enviar la dirección del primer (o único) registro a leer, Register Address en la imagen. Enviar una condición START (llamada START repetida aunque sea lo mismo). Enviar el byte de control (dirección de esclavo con R/W = 1, para lectura). Leer uno o tantos registros como se desee. A cada registro leído se responde con unACK, excepto al último, al cual se le devuelve un NACK. Un único dato es el último. Enviar una condición STOP. Dirección de Esclavo del DS1307 Todos los RTC I2C de Dallas Semiconductor tienen la misma dirección de esclavo, que es fija e igual a 1101000. Sin opción a reconfiguración, implica que solo se puede tener una de estas partes en un bus I2C. ¡Para qué más! Recordemos que la dirección de esclavo viaja en el primer byte o byte de control y va acompañado por el bit R/W para indicar si los siguientes bytes serán de lectura (R/W = 1) o de escritura (R/W = 0). El byte de control (dirección de esclavo + bit R/W) para el DS1307.
  • 767. Práctica: Uso del RTC DS1307 Lo que hace el programa es mostrar la fecha y hora en el terminal serial cada segundo. En el arranque se pide la puesta de fecha y hora iniciales. Como hacer parpadear un LED, ¿verdad? ¡Qué más se puede hacer con un RTC! Nota que la resistencia de pull-up del pin SQW/OUT del DS0307 no tiene restricciones y en su lugar bien se podría activar la pull-up interna del AVR. Circuito para el RTC DS1307 y el microcontrolador AVR. Las funciones ds1307_SetTimeDate y ds1307_GetTimeDate acceden a todos los registros de fecha y hora del DS1307 de manera secuencial para acelerar la transferencia. Como es obvio, también se les puede acceder individualmente como al Registro de Controlen /*** Configurar el Registro de Control para generar una onda cuadrada de 1 Hz por el pin SQW/OUT ***/ i2c_start();// START i2c_write(0xD0);// Slave address + Write i2c_write(0x07);// Dirección de Registro de Control
  • 768. i2c_write(0b00010000);// Escribir en Registro de Control i2c_stop();// STOP Todos los RTCs suelen tener un pin por el que sacan una onda cuadrada o una señal de interrupción que les permite tener una mejor comunicación con el microcontrolador. De ese modo el microcontrolador ya no tendrá que sondear el RTC para ver si los datos se actualizaron. En el caso del DS1307, este pin es SQW/OUT. En el programa se le ha configurado para que saque onda cuadrada de 1 Hz, la cual por supuesto está sincronizada con el progreso del reloj. Creo que no lo dije antes, pero los registros de fecha y hora del DS1307 se actualizan en el flanco de bajada de dicha onda. Con ese propósito se ha conectado el pin SQW/OUT al pin INT2 del AVR y se ha habilitado la interrupción INT2 con el flanco de bajada. No hay ningún inconveniente si se prefiere usar las interrupciones INT0 o INT1. /* Configurar y habilitar la Interrupción Externa INT2 * para que se dispare en cada flanco de bajada del pin INT2 */ EICRA=0x20; EIMSK=0x04; sei(); Pasando a otro tema, recordemos que el registro de horas tiene dos posibles formatos: el de 12 horas (con el bit6 = 1) y el de 24 horas (con el bit6 = 0). Como en el programa no se activa dicho bit, queda claro que se trabaja en modo de 24 horas. Y para terminar, habrás notado que mi función getbcd no restringe el ingreso de datos en ningún caso. Por ejemplo, podrías poner un 80 en los segundos. En tal caso el RTC funcionará “graciosamente” hasta que los desbordamientos de los registros los lleven a estados válidos. No quise escribir más código para no agrandarlo demasiado. /****************************************************************************** * FileName: main.c * Purpose: Uso del reloj/calendario I2C DS1307 * Processor: ATmel AVR con módulo TWI * Compiler: AVR IAR C y AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com.
  • 769. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. * * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta * licencia y las notas de autor y copyright de arriba. * * Disclaimer: El uso de este software queda bajo su completa responsabilidad. *****************************************************************************/ #include "avr_compiler.h" #include "usart.h" #include "i2c.h" /*** Prototipos de funciones ***/ voidds1307_setup(void); voidds1307_SetTimeDate(char,char,char,char,char,char,char); voidds1307_GetTimeDate(char*,char*,char*,char*,char*,char*,char*); voidDisplayTimeDate(void); chargetbcd(void); //**************************************************************************** // ISR o manejador de interrupción INT2. // Esta interrupción INT2 se dispara con el flanco de bajada. //**************************************************************************** ISR(INT2_vect)
  • 770. { DisplayTimeDate(); } /*** Función principal ***/ intmain(void) { i2c_init();// 100 kHz usart_init();// 9600 - 8N1 printf("nr DS1307: 64 x 8, Serial, I2C Real-Time Clock"); printf("nr ===========================================nr"); ds1307_setup(); /* Configurar y habilitar la Interrupción Externa INT2 * para que se dispare en cada flanco de bajada del pin INT2 */ EICRA=0x20; EIMSK=0x04; sei(); while(1) { /* Entrar en modo sleep (Idle Mode). * Power-down takes the USART to crashed, why?
  • 771. */ SMCR=0x01; sleep_enter(); } } //**************************************************************************** // Inicializa la fecha y hora del DS1307 y lo configura para generar una // onda cuadrada de 1 Hz por el pin SQW/OUT. //**************************************************************************** voidds1307_setup(void) { charsegs,mins,hors,diasem,dia,mes,anio; // Escribir valores iniciales de fecha y hora en el DS1307 ds1307_SetTimeDate(segs,mins,hors,diasem,dia,mes,anio); /*** Configurar el Registro de Control para generar una onda cuadrada de 1 Hz por el pin SQW/OUT ***/ i2c_start();// START i2c_write(0xD0);// Slave address + Write i2c_write(0x07);// Dirección de Registro de Control i2c_write(0x10);// Escribir en Registro de Control 00010000 i2c_stop();// STOP /*** Poner valores iniciales de fecha y hora del DS1307 ***/
  • 772. printf("nr Pon año [0..99]: ");anio=getbcd(); printf("nr Pon mes [1..12]: ");mes=getbcd(); printf("nr Pon día [1..31]: ");dia=getbcd(); printf("nr Pon día [1.. 7]: ");diasem=getbcd(); printf("nr Pon hora [0..23]: ");hors=getbcd(); printf("nr Pon minutos [0..59]: ");mins=getbcd(); printf("nr Pon segundos [0..59]: ");segs=getbcd(); printf("nr"); } //**************************************************************************** // Escribe los registros de Hora y Fecha del DS1307. // Los parámetros deben estar en formato BCD. //**************************************************************************** voidds1307_SetTimeDate(charseg,charmin,charhor,chardia,chardds,charmes,charanio) { i2c_start();// START i2c_write(0xD0);// Slave address + Write i2c_write(0x00);// Dirección de registro Seconds i2c_write(seg);// Escribir en registro i2c_write(min);// " " " i2c_write(hor);// " " " i2c_write(dia);// " " " i2c_write(dds);// " " " i2c_write(mes);// " " " i2c_write(anio);// " " "
  • 773. i2c_stop();// STOP } //**************************************************************************** // Lee los registros de Hora y Fecha del DS1307. // Los parámetros salen en formato BCD. //**************************************************************************** voidds1307_GetTimeDate(char*seg,char*min,char*hor,char*dds,char*dia,char*mes,char*ani) { i2c_start();// START i2c_write(0xD0);// Slave address + Write i2c_write(0x00);// Dirección de registro Seconds i2c_restart();// Repeated START i2c_write(0xD0|0x01);// Slave address + Read *seg=i2c_read(0);// Leer registro y enviar ACK *min=i2c_read(0);// " " " *hor=i2c_read(0);// " " " *dds=i2c_read(0);// " " " *dia=i2c_read(0);// " " " *mes=i2c_read(0);// " " " *ani=i2c_read(1);// Leer último registro y enviar NACK i2c_stop();// STOP } //**************************************************************************** // Lee los registros de fecha y hora del DS1307 y los visualiza en el
  • 774. // terminal serial. // Formato de salida de visualización: 14/03/2009 05:32:45 //**************************************************************************** voidDisplayTimeDate(void) { charseg,min,hor,diasem,dia,mes,ani; /* Leer registros del DS1307 */ ds1307_GetTimeDate(&seg,&min,&hor,&diasem,&dia,&mes,&ani); if(seg&0x80)// Si el reloj se detuvo (CH = 1?) { printf("nr El RTC se detuvo"); } else { /* Mostrar los datos leídos */ printf("rn %2x/%02x/20%02x %02x:%02x:%02x",dia,mes,ani,hor,min,seg); } } //**************************************************************************** // Lee un número BCD de dos dígitos del terminal serial. //**************************************************************************** chargetbcd(void) {
  • 775. unsignedcharc,buff[3],i=0; while(1){ c=getchar(); if(('0'<=c)&&(c<='9')&&(i<2)){// Si c es dígito 0..9 y si i>0 buff[i++]=c;// Guardar en buffer putchar(c);// Eco } elseif((c=='b')&&(i)){// Si c es BACKSPACE y si i>0 i--;// putchar(c);// Eco } elseif((c=='r')&&(i))// Si c es ENTER y si i>0 break;// Salir del bucle } c=buff[0]-'0'; if(i>1){// (i==2) c<<=4; c|=(buff[1]-'0'); } returnc; } El RTC con Trickle Charger DS1340 Como dije antes, en los RTCs I2C de Dallas el DS1307 es como el modelo base. Muchas de sus capacidades, como el uso de una batería de respaldo por si falla la alimentación principal, se repiten en otras partes.
  • 776. Así que, asumiendo que al menos conoces elDS1307 y sabes cómo controlarlo, esta presentación del DS1340 será lo más somera posible, enfocando sobre todo sus características más distintivas. Algunas de ellas son: Tiene un Cargador Trickle, que permite al DS1340 ir recargando la batería cuando no esté operando con la alimentación principal de Vcc. Tiene un calibrador del oscilador, con el que se le puede dar una mayor precisión al RTC. Soporte del protocolo I2C a velocidad Fast Mode (hasta 400 kHz). No tiene memoria SRAM genérica. Diagrama de pines del DS1340 (Varía en el DS1340C). Al ver el dibujo del DS1340 puedes notar su gran similitud con el DS1307. El pin de batería alterna ahora se llama VBACKUP. Aquí la mayor diferencia sería el pin FT/OUT. Con ayuda de una pull-up externa, este pin puede quedarse en un estado fijo o sacar una onda cuadrada de 512 Hz. La frecuencia de esta onda podía ser de 4 frecuencias en elDS1307. Los RTCs de la serie DS1340C vienen en encapsulados de 16 pines, aunque la gran mayoría es del tipo NC (sin conexión). Sucede que este modelo incluye el XTAL internamente. Los Registros del DS1340 La siguiente tabla muestra todos los registros del DS1340. Puedes ver que hay dos grupos: los registros de fecha y hora, y los registros de control-estado (Control, Trickle charger y Flag). Este modelo no tiene registros SRAM de propósito general. Tabla Registros del DS1340 Contenido Dirección Nombre Rango Nibble alto 0x00 Seconds Nibble bajo EOSC 10 segundos segundos 00 –59
  • 777. Tabla Registros del DS1340 Contenido Dirección Nombre Rango Nibble alto Nibble bajo 10 minutos minutos 0x01 Minutes 0x02 Century/hours CEB CB 10 horas horas 0-1/0-23 0x03 Day 0000 día (de semana) 1–7 0x04 Date 10 día día (de mes) 1 – 31 0x05 Month 10 mes mes 1 – 12 0x06 Year 10 año año 00 – 99 0x07 Control OUT FT 0x08 Trickle charger TCS3 TCS2 TCS1 TCS0 DS1 DS0 ROUT1 ROUT0 --- 0x09 Flag OSF 0 S 0 00–59 CAL4 CAL3 CAL2 CAL1 CAL0 --- 0 0 0 0 0 --- Los bits EOSC (del registro seconds) y OSF (del registro Flags) son parientes. EOSC (Enable Oscillator) arranca el oscilador (cuando se le escribe un 0) o lo detiene (cuando se le escribe un 1). OSF (Oscillator Stop Flag) es un flag que se activa a 1 cuando el oscilador se detiene por algún motivo, como una falla de la alimentación. Se debe limpiar por software. Nota también que el registro de horas solo acepta el formato de 24 horas, aunque ahora se le han sumado los bits CEB y CB. CB bascula cuando se pasa del año 2099 al año 2000, siempre que en CEB se haya escrito 1. Para nuestros tiempos podemos dejar CEB en 0 y simplemente nos olvidamos del resto. Bien, ahora solo nos queda hablar de los registros Control y Trickle charger. El Registro CONTROL Control register tiene dos funciones: configurar la señal del pin FT/OUT y calibrar el oscilador del DS1340.
  • 778. Solemos empezar a trabajar con el DS1307 diciendo que es de los más fáciles porque solo tiene un registro de control, y de pocos bits efectivos :) Su función básica es configurar la onda cuadrada que saldrá por el pin SQW/OUT del DS1307. Registro CONTROL del DS1307 OUT FT S CAL4 CAL3 CAL2 CAL1 CAL0 Registro de Microcontrolador bits 5-0 S – CAL4 – CAL3 – CAL2 – CAL1 – CAL0 Estos bits representan un número relacionado con la cantidad de ciclos de reloj que se añadirán o restarán en el circuito del oscilador. Es una calibración manual que puede ignorarse dejando todos estos bits a 0 (su valor por defecto). FT Frecuency Test 1 = Por el pin FT/OUT sale una señal que bascula a 512 Hz. 0 = El estado del pin FT/OUT depende del bit OUT OUT Output Control Siendo el bit FT = 0: 1 = El pin FT/OUT se queda en 1 lógico constante 0 = El pin FT/OUT se queda en 0 lógico constante Se sabe que el RTC basa su operación en la señal de 1 Hz generada a partir del circuito del XTAL de 32 kHz. Es decir, hay todo un circuito divisor que convierte esos 32 kHz en una señal de 1 Hz, la cual debiera ser lo más perfecta posible; cosa que en la práctica nunca se dará. Por más pequeño que sea el error, producirá una desviación de varios segundos por año. Pues bien, el DS1340 provee un mecanismo para paliar el error del oscilador restando o sumando algunos pulsos de reloj en el circuito divisor. Esta calibración manual es una labor muy engorrosa por depender de varios factores y situaciones; de modo que no la describiremos aquí. Posteriormente se verá el DS3232, un RTC de precisión que tiene la capacidad de auto-calibrarse. El Cargador Trickle y el Registro TRICKLE El registro Trickle Charger tiene la exclusiva función de configurar el Cargador Trickle. El cargador Trickle es un circuito que puede recargar la batería conectada al pinVBACKUP. Tiene un diodo y tres resistencias como sus elementos representativos.
  • 779. La corriente puede fluir por ellos dado que el nivel de Vcc debe ser mayor que el deVBACKUP (que no debe pasar de 3.7V). Esquema básico del Trickle Charger del DS1340. Al inicio el cargador Trickle está desactivado, o sea no hay conexión entre los terminalesVcc y VBACKUP , como se muestra arriba. Cerrando los switches adecuadamente podemos obtener las distintas configuraciones que se muestran en la siguiente tabla. Es más fácil ver primero el circuito resultante y fijarse luego en el código que le da lugar. Por ejemplo, si quieres activar el Cargador Trickle y que esté formado por el diodo y la resistencia de 4 K, el valor que debes cargar en el registro Trickle es 10101011. Si no deseas activar el cargador Trickle, puedes tomar el código 00000000 (así inicia el DS1340). Tabla Registro Trickle del DS1340 Registro Trickle FUNCIÓN TCS3 TCS2 TCS1 TCS0 DS1 DS0 ROUT1 ROUT0 1 0 1 0 0 1 0 1 Sin diodo, resistor 250 1 0 1 0 1 0 0 1 Un diodo, resistor 250 1 0 1 0 0 1 1 0 Sin diodo, resistor 2k 1 0 1 0 1 0 1 0 Un diodo, resistor 2k 1 0 1 0 0 1 1 1 Sin diodo, resistor 4k 1 0 1 0 1 0 1 1 Un diodo, resistor 4k x x x x 0 0 x x Sin Trickle charger
  • 780. Tabla Registro Trickle del DS1340 Registro Trickle FUNCIÓN TCS3 TCS2 TCS1 TCS0 DS1 DS0 ROUT1 ROUT0 x x x x 1 1 x x Sin Trickle charger x x x x x x 0 0 Sin Trickle charger La lógica nos recomienda que, si habilitamos el cargador, deberíamos usar el diodo (no vaya a ser que la batería se descargue en la fuente de Vcc si ésta baja su nivel :). Acceso a los Registros del DS1340 A estas alturas ya debes haber captado bien los procedimientos seguidos para acceder a los registros internos de cualquier dispositivo I2C, que no son establecidos por cada uno de ellos, sino que son fijados por el protocolo I2C. Así que no se presentan los diagramas de acceso para no redundar. Si deseas, puedes revisar Acceso a los Registros del DS1307, que es igual, excepto porque: En el DS1340 el puntero de registros tiene alcance limitado: solo permite realizar accesos secuenciales hasta el registro de dirección 0x07. Los registros subsiguientes (de direcciones 0x08 y 0x09), por tanto, deberían ser accedidos individualmente. También habíamos dicho que todos los RTC de esta familia tienen la misma dirección de esclavo 1101000. Práctica: Uso del RTC DS1340 En el programa se aprecia el uso de los recursos del DS1340, excepto la calibración del oscilador. Hasta donde yo sé, ningún DS1340 viene en empaque PDIP, así que dudo que lo puedas armar en un breadboard :( Además el modelo DS1340C tiene un empaque de 16 pines. Si tienes uno de esos, será mejor que revises la disposición de pines en el datasheet. Verás que la mayoría de los pines son NC (No connection), aunque en el circuito todos ellos se deben conectar a GND. Fuera de eso, la práctica debería funcionar igual.
  • 781. Circuito para el RTC DS1340 y el microcontrolador AVR. Gran parte de este programa es muy parecido al de la anterior práctica. Veamos algunas novedades. La frecuencia de onda del pin FT/OUT no podía ser otra que 512 Hz. Se pudo conectar el pin FT/OUT a cualquiera de los tres pines INT del AVR y programar su interrupción, como en la práctica anterior. Y la ISR habría cambiado para visualizar los datos luego de contar 256 interrupciones. Pero, ¿alguien dijo contar? Si de contar pulsos externos se trata, quién mejor para hacerlo que un Timer trabajando en modo contador. /* Configurar el Timer0 para operar en modo contador (sin prescaler). * El Timer0 incrementará con cada flanco de bajada del pin T0. */ TCCR0A=0X00; TCCR0B=0X06; TCNT0=0x00;// Iniciar la cuenta en 0 De ese modo el Timer0 se desbordará con cada 256 pulsos, desbordamiento que disparará su interrupción porque así se programado y en la ISR habrá que contar dos de estas interrupciones para recién mostrar la hora.
  • 782. /* Habilitar la interrupción de desbordamiento del Timer0 */ TIMSK0|=(1<<TOIE0); sei(); /****************************************************************************** * FileName: main.c * Purpose: Uso del RTC I2C con Trickle Charger DS1340 * Processor: ATmel AVR con módulo TWI * Compiler: AVR IAR C y AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. * * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta * licencia y las notas de autor y copyright de arriba. * * Disclaimer: El uso de este software queda bajo su completa responsabilidad. *****************************************************************************/ #include "avr_compiler.h" #include "usart.h" #include "i2c.h" /*** Prototipos de funciones ***/ voidds1340_setup(void);
  • 783. voidds1340_SetTimeDate(char,char,char,char,char,char,char); voidds1340_GetTimeDate(char*,char*,char*,char*,char*,char*,char*); voidds1340_write(charaddress,chardata); chards1340_read(charaddress); voidDisplayTimeDate(void); chargetbcd(void); //**************************************************************************** // ISR o manejador de interrupción (del Timer0). // La interrupción del Timer0 debería dispararse cada 500 ms. //**************************************************************************** ISR(TIMER0_OVF_vect) { staticunsignedcharints=0; /* Este bloque se debe ejecutar cada dos interrupciones, cada 1 segundo */ if(++ints>=2) { DisplayTimeDate(); ints=0; } } /*** Función principal ***/ intmain(void) {
  • 784. i2c_init();// 100 kHz usart_init();// 9600 - 8N1 printf("nr DS1340: I2C RTC with Trickle Charger"); printf("nr ====================================nr"); ds1340_setup(); /* Configurar el Timer0 para operar en modo contador (sin prescaler). * El Timer0 incrementará con cada flanco de bajada del pin T0. */ TCCR0A=0X00; TCCR0B=0X06; TCNT0=0x00;// Iniciar la cuenta en 0 /* Habilitar la interrupción de desbordamiento del Timer0 */ TIMSK0|=(1<<TOIE0); sei(); while(1) { /* Entrar en modo sleep (Idle mode). */ SMCR=0x01; sleep_enter(); } }
  • 785. //**************************************************************************** // Pone valores iniciales de fecha y hora en el DS1340. // Se configura el DS1340 para sacar por el pin FT/OUT una señal de 512 Hz // El oscilador corre sin alteración de sus ciclos (sin calibración). // Se habilita el Trickle Charger con un diodo y una resistencia de 2 K. // Se limpia el flag OSF, que de seguro se activó. //**************************************************************************** voidds1340_setup(void) { charsegs,mins,hors,diasem,dia,mes,anio; // Configurar el pin FT/OUT para generar una señal que bascula a 512 Hz // y dejar sin modificación los ciclos del oscilador (sin calibración). ds1340_write(0x07,0x40);// Escribir en Control Register 01000000 // Activar el Trickle Charger con un diodo y una resistencia de 2 Kohm. ds1340_write(0x08,0xaa);// Escribir en Trickle-Charger Register 10101010 // Limpiar el bit OSF (Oscilator Stop Flag). Se activa en el encendido ds1340_write(0x09,0x00);// Escribir en Flag Register 00000000
  • 786. /*** Poner valores iniciales de fecha y hora del DS1340 ***/ printf("nr Pon año [0..99]: ");anio=getbcd(); printf("nr Pon mes [1..12]: ");mes=getbcd(); printf("nr Pon día [1..31]: ");dia=getbcd(); printf("nr Pon día [1.. 7]: ");diasem=getbcd(); printf("nr Pon hora [0..23]: ");hors=getbcd(); printf("nr Pon minutos [0..59]: ");mins=getbcd(); printf("nr Pon segundos [0..59]: ");segs=getbcd(); printf("nr"); // Escribir valores iniciales de fecha y hora en el DS1340 ds1340_SetTimeDate(segs,mins,hors,diasem,dia,mes,anio); } //**************************************************************************** // Escribe los registros de Hora y Fecha del DS1340. // Los parámetros deben estar en formato BCD. //**************************************************************************** voidds1340_SetTimeDate(charseg,charmin,charhor,chardia,chardds,charmes,charanio) { i2c_start(); i2c_write(0xD0);// Slave address + Write i2c_write(0x00);// Seconds register address i2c_write(seg);// Data to register i2c_write(min);// " " " i2c_write(hor);// " " "
  • 787. i2c_write(dds);// " " " i2c_write(dia);// " " " i2c_write(mes);// " " " i2c_write(anio);// " " " i2c_stop(); } //**************************************************************************** // Lee los registros de Hora y Fecha del DS1340. // Los parámetros salen en formato BCD. //**************************************************************************** voidds1340_GetTimeDate(char*seg,char*min,char*hor,char*dds,char*dia,char*mes,char*ani) { i2c_start(); i2c_write(0xD0);// Slave address + Write i2c_write(0x00);// Dirección de primer registro i2c_restart(); i2c_write(0xD0|0x01);// Slave address + Read *seg=i2c_read(0);// Leer registro y enviar ACK *min=i2c_read(0);// " " " *hor=i2c_read(0);// " " " *dds=i2c_read(0);// " " " *dia=i2c_read(0);// " " " *mes=i2c_read(0);// " " " *ani=i2c_read(1);// Leer último registro y enviar NACK i2c_stop();
  • 788. } //**************************************************************************** // Escribe 'data' en el registro de dirección 'address' del DS1340. //**************************************************************************** voidds1340_write(charaddress,chardata) { i2c_start(); i2c_write(0xD0);// Slave address + Write i2c_write(address);// Register address i2c_write(data);// Data to register i2c_stop(); } //**************************************************************************** // Lee el registro de dirección 'address' del DS1340. //**************************************************************************** chards1340_read(charaddress) { charreg; i2c_start(); i2c_write(0xD0);// Slave address + Write i2c_write(address);// Register address i2c_restart(); i2c_write(0xD0|0x01);// Slave address + Read
  • 789. reg=i2c_read(1);// Leer registro y enviar NACK i2c_stop(); returnreg; } //**************************************************************************** // Lee los registros de fecha y hora del DS1340 y los visualiza en el // terminal serial. // Formato de salida de visualización: 14/03/2009 05:32:45 //**************************************************************************** voidDisplayTimeDate(void) { charsegs,mins,hors,diasem,dia,mes,anio; charflags; flags=ds1340_read(0x09);// Leer registro 'Flag Register' if(flags&0x80)// Si el RTC se detuvo (OSF = 1?) { printf("nr El RTC se detuvo"); } else { /* Leer registros de fecha y hora del DS1340 */ ds1340_GetTimeDate(&segs,&mins,&hors,&diasem,&dia,&mes,&anio);
  • 790. /* Mostrar los datos leídos */ printf("rn %2x/%02x/20%02x %02x:%02x:%02x",dia,mes,anio, (hors&0x3F),// Ignorar bits CEB y CB (por si acaso :) mins, (segs&0x7F));// Ignorar bit EOSC (por si acaso :) } } //**************************************************************************** // Lee un número BCD de dos dígitos del terminal serial. //**************************************************************************** chargetbcd(void) { unsignedcharc,buff[3],i=0; while(1){ c=getchar(); if(('0'<=c)&&(c<='9')&&(i<2)){// Si c es dígito 0..9 y si i>0 buff[i++]=c;// Guardar en buffer putchar(c);// Eco } elseif((c=='b')&&(i)){// Si c es BACKSPACE y si i>0 i--;// putchar(c);// Eco } elseif((c=='r')&&(i))// Si c es ENTER y si i>0 break;// Salir del bucle
  • 791. } c=buff[0]-'0'; if(i>1){// (i==2) c<<=4; c|=(buff[1]-'0'); } returnc; } El RTC de Gran Precisión DS3232 Cuando buscamos información sobre RTCs un tema que se nos presenta con recurrencia cuasi obsesiva es el de la precisión. Al inicio solo buscamos información práctica que nos sirva para el momento. Y la verdad es que en ese momento el hecho de que un RTC pueda tener una imprecisión de 0.03 segundos por hora honestamente nos interesa un bledo. Pero cuando vamos a emprender algún proyecto duradero nos empieza a preocupar que un error casi imperceptible como el citado pueda provocar una desviación de varios minutos por año, comúnmente cuatro o cinco en un RTC estándar operando a temperatura ambiente. Como sabemos, los osciladores usados por los dispositivos digitales están basados en un cristal de cuarzo. El cristal funciona como un circuito RLC-C, muy conocido con el nombre de oscilador Pearson (busca en Google si quieres saber más). Lo puedes ver en la siguiente figura. Circuito del oscilador del DS3232. Los capacitores de los costados ayudan a estabilizar el oscilador para conseguir un mejor factor de calidad Q (mejor precisión, para no hablar raro).
  • 792. Por más que los RTCs incluyan los capacitores más adecuados internamente y en algunos casos también un cristal de muy buen corte, el oscilador sufrirá ligeras desviaciones debidas a factores externos como el ruido o la humedad ambientales pero principalmente la temperatura. El punto es que el DS3232 está diseñado para poder regular el valor de sus capacitores, ya que son ellos los mayores afectados por la temperatura. Los capacitores serán auto-calibrados de acuerdo con la temperatura que toma gracias al sensor que lleva incorporado. Este sistema es muy sofisticado en los RTCs y se conoce como TCXO(Temperature-Compensated Crystal Oscillator). Otras características del DS3232 adicionales a las ya esperadas son: Precisión de hasta ±2ppm. Esto da una desviación menor de 2 minutos por año. 236 registros SRAM de propósito general. Dos alarmas programables. Sensor de temperatura de ±3°C de precisión. La idea no es brindar una oferta 2 por 1, “combo RTC + sensor”, no. Si bien puede usarse este sensor para medir temperaturas, debe evitarse someter el dispositivo a condiciones extremas a propósito. Salida de señal de interrupción para las alarmas. Operación en Fast mode (velocidad de transferencia de datos de hasta 400 kbits/s). Uso de batería de respaldo. Descripción Funcional de Pines del DS3232 Diagrama de pines del RTC DS3232. Los pines novedosos que merecen explicación son:
  • 793. RST. Es un pin de E/S donde se puede conectar un pulsador para resetear el RTC. A la vez, si el DS3232 detecta que el nivel de Vcc cae por debajo de 2.5V (valor típico), pondrá RST en 0. Es de drenador abierto pero ya incorpora una pull-up de 50 k. 32kHz. Por aquí se puede sacar una onda cuadrada de 32 kHz (difícil de olvidar). INT/SQW. Es parecido al pin SQW/OUT del DS1307. Es de drenador abierto sin pull-up interna y puede quedarse en un estado fijo o sacar una onda de cuatro frecuencias (1 Hz, 1.024 kHz, 4.096 kHz u 8.192 kHz). Adicionalmente, puede activarse por acción de las alarmas. Los Registros del DS3232 Impresionante la cantidad de registros que tiene este RTC. Pero enseguida te los presento para que te sientas en confianza. Tabla de Registros del DS3232 Contenido Dirección Nombre Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0 0x00 10 segundos segundos Seconds 0x01 10 minutos minutos Minutes 0x02 12/24 horas Hours 0x03 0000 día (de semana) Day 0x04 10 día día (de mes) Date 0x05 Century 10 mes mes / siglo Month/Century 0x06 10 año año Year 0x07 A1M1 10 segundos segundos Alarm 1 seconds 0x08 A1M2 10 minutos minutos Alarm 1 minutes 0x09 A1M3 12/24 AMPM 10 horas 10HR AMPM 10 horas horas Alarm 1 hours
  • 794. Tabla de Registros del DS3232 Contenido Dirección Nombre Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0 10HR día (de semana) día (de mes) 0x0A A1M4 DY/DT Alarm 1 day Alarm 1 date 10 minutos Alarm 2 minutes 10 día 0x0B A1M2 10 minutos 0x0C A1M3 12/24 AMPM 10 horas horas 10HR Alarm 2 hours día (de semana) día (de mes) 0x0D A1M4 DY/DT Alarm 2 day Alarm 2 date 10 día 0x0E EOSC BBSQW CONV RS2 RS1 INTCN A2IE A1IE Control 0x0F OSF BB32kHz CRATE1 CRATE0 EN32kHz BSY 0x10 SIGN DATA DATA DATA DATA DATA DATA DATA Aging Offset 0x11 SIGN DATA DATA DATA DATA DATA DATA DATA MSB of Temp 0x12 DATA DATA 0 0 0 0 0 0 LSB of Temp 0x13 0 0 0 0 0 0 0 0 No usado 0x14 x X x x x x x x SRAM ... ... ... ... ... ... ... ... ... SRAM 0xFF x X x x x x x x SRAM A2F A1F Control/Status En el primer grupo, correspondiente a los registros de fecha y hora. Destaca el bit Centurydel registro Month/Century. Él se activa a 1 al pasar del año 99 al año 00.
  • 795. La Alarma y los Registros de Alarma El segundo grupo lo forman los registros de las alarmas. Cuando las alarmas están activas, el DS3232 comparará periódicamente sus registros con los registros de fecha y hora. Cuando encuentre coincidencias activará la alarma. Tan simple como eso. Se deduce por tanto que ambos tipos de registros deberían tener el mismo formato, sin considerar los bits AxMy ni los bits DY/DT. Los bits AxMy y DY/DT sirven para programar la frecuencia de activación de las alarmas. Todas las combinaciones válidas se muestran en las siguientes tablas. Los valores no presentes no deberían ser considerados. Registros de Alarma del DS3232 Máscaras en registros de alarma 1 DY/DT Razón de alarma A1M4 A1M3 A1M2 A1M1 × 1 1 1 1 Una vez por segundo × 1 1 1 0 Cuando coincidan segundos × 1 1 0 0 Cuando coincidan segundos y minutos × 1 0 0 0 Cuando coincidan segundos, minutos y hora 0 0 0 0 0 Cuando coincidan segundos, minutos, hora y date 1 0 0 0 0 Cuando coincidan segundos, minutos, hora y día Tabla Registros de Alarma del DS3232 Máscaras en registros de alarma 2 DY/DT Razón de alarma A2M4 × A2M3 A2M2 1 1 1 Una vez por minuto (00 segundos de
  • 796. Registros de Alarma del DS3232 Máscaras en registros de alarma 1 DY/DT Razón de alarma A1M4 A1M3 A1M2 A1M1 cada minuto) × 1 1 0 Cuando coincidan minutos × 1 0 0 Cuando coincidan minutos y hora 0 0 0 0 Cuando coincidan minutos, hora y date 1 0 0 0 Cuando coincidan minutos, hora y día Hasta ahora solo hemos descrito parte de la programación de las alarmas. Aún queda por establecer si sus coincidencias activarán (a 0) o no el pin INT/SQW para comunicarle el evento al microcontrolador. En los registros Control y Control/Status, están los bits de habilitación y los bits de flag de cada alarma. Si una alarma está habilitada, la coincidencia activará el flag respectivo y luego el pin INT/SQW se pondrá a 0, siempre que el bit INTCN sea 1. Los Registros CONTROL y CONTROL/STATUS El registro Control programa la señal del pin INT/SQW, las interrupciones de las alarmas, la frecuencia de medida de la temperatura del chip, y el estado del oscilador. Registro Registros de control del DS3232 EOSC BBSQW CONV RS2 RS1 Registro de Microcontrolador EOSC Enable Oscillator 1 = Detiene el oscilador del DS3232 0 = Arranca el oscilador del DS3232 BBSQW Battery-Backed Square-Wave Enable INTCN A2IE A1IE
  • 797. 1 = La onda cuadrada del pin INT/SQW sigue activa si el DS3232 se alimenta de la batería 0 = Aunque se haya programado la onda cuadrada del pin INT/SQW, ésta se apaga cuando el DS3232 pase alimentarse de la batería CONV Convert Temperature Normalmente el DS3232 utiliza su sensor para tomar la temperatura del chip automática y periódicamente (según la frecuencia programada). Pero si se quiere forzar una conversión, se debe usar este bit. 1 = Inicia una nueva conversión de temperatura 0 = No inicia una conversión INTCN Interrupt Control 1 = El pin INT/SQW se usa como señal de interrupción de las alarmas, siempre que hayan sido habilitadas (leer más abajo). La coincidencia de cualquier alarma activa este pin a 0. 0 = Por el pin INT/SQW sale un onda cuadrada cuya frecuencia depende de los bits RS2 y RS1. RS2:RS1: Rate Select Siendo el bit INTCN = 0: Tabla RS1 RS0 RS1 RS0 Frecuencia de onda del pin INT/SQW 00 01 1.024 kHz 10 4.096 kHz 11 A1IE 1 Hz 8.193 kHz Alarm1 Interrupt Enable 1 = Habilita interrupción de alarma 1
  • 798. 0 = Inhabilita interrupción de alarma 1 A2IE Alarm2 Interrupt Enable 1 = Habilita interrupción de alarma 2 0 = Inhabilita interrupción de alarma 2 El registro Control/Status tiene bits para establecer la frecuencia de conversión delsensor de temperatura, bits para configurar la señal del pin 32kHz y cuatro bits de flag. Registro de control y estado del DS3232 OSF BB32kHz CRATE1 CRATE0 EN32kHz BSY A2F A1F Registro de Microcontrolador OSF Oscillator Stop Flag 1 = Oscilador detenido (por deficiencias en Vcc y Vbat, primera alimentación,...) 0 = El oscilador sigue en marcha BB32kHz Battery-Backed 32kHz Output 1 = La onda cuadrada del pin 32kHz sigue activa si el DS3232 se alimenta de la batería 0 = Aunque se haya programado la onda cuadrada del pin 32kHz, ésta se apagará cuando el DS3232 pase alimentarse de la batería CRATE1: Conversion Rate CRATE0 El DS3232 toma y convierte la temperatura del chip periódicamente. Según el valor obtenido calibrará sus capacitores variables. Tabla CRATE1 : CRATE0 CRATE1 : CRATE0 Frecuencia de conversión de temperatura 00 Cada 64 segundos 01 Cada 128 segundos
  • 799. 10 11 BSY Cada 256 segundos Cada 512 segundos Enable Oscillator 1 = El sensor de temperatura está realizando un conversión. Debe evitarse forzar una nueva conversión con el bit CONV del registroControl. 0 = No hay una conversión en curso EN32kHz Enable 32kHz Output 1 = Habilita la onda cuadrada del pin 32kHz 0 = Inhabilita la onda cuadrada del pin 32kHz A1F Alarm1 Flag 1 = Se cumplió la coincidencia de la alarma 1. Se debe limpiar por software 0 = No se cumplió la coincidencia A2F Alarm2 Flag 1 = Se cumplió la coincidencia de la alarma 2. Se debe limpiar por software 0 = No se cumplió la coincidencia El Registro AGING OFFSET Como tantas veces se dijo, el DS3232 usa su sensor para tomar la temperatura del chip y con el valor obtenido calibra sus capacitores variables automáticamente. Pues bien, si tú eres todo un experto y crees que puedes ayudarle con la calibración, puedes usar este registro. El DS3232 hace su trabajo basándose solo en la temperatura y tú sabes que eso no siempre basta, que hay otros factores que desvían el oscilador. Dejando todo el registro a 0 te ahorras la “molestia”. Los Registros de Temperatura El DS3232 guarda los valores de la temperatura que toma en estos dos registros. Juntos forman un dato de 10 bits con signo (SIGN) y de complemento a 2. El primer registro contiene la parte entera y el segundo registro contiene los dos bits de la parte fraccionaria, lo cual le da una resolución de 0.25°C. Primer Registro de temperatura del DS3232
  • 800. SIGN DATA DATA DATA DATA DATA DATA DATA 0 0 0 0 0 Segundo Registro de temperatura del DS3232 SIGN SIGN 0 Ejemplos: Tabla Dato Dato Valor de la temperatura 00000000 00 0.0°C 00000101 01 5.25°C 11110110 00 -10.0°C 11110101 01 -10.75°C Si eres novicio con estos códigos, puedes echarle un vistazo al apartado dedicado al primer sensor de temperatura. Acceso a los Registros del DS3232 La dirección de esclavo sigue siendo 0xD0 (incluyendo bit R/W = 0), el puntero de registros puede barrer toda la SRAM para accesos secuenciales totales. Qué más puedo decir... Si tienes dudas, puedes releer Acceso a los Registros del DS1307, que es igual. Práctica: Uso del RTC de Precisión DS3232 En esta práctica con el DS3232 solo se verá el uso de los registros de fecha y hora, y algunas configuraciones de los registros Control, Control/Status y Aging Offset. Tal vez ponga ejemplos más completos en adelante. El Circuito Hasta donde yo vi, el DS3232 tampoco viene en empaque PDIP, así que dudo que lo puedas armar en un breadboard :( Fuera de eso, la práctica debería funcionar igual.
  • 801. Circuito para el RTC DS3232 y el microcontrolador AVR. El Código Fuente /****************************************************************************** * FileName: main.c * Purpose: Uso del RTC de gran precisión y con XTAL incorporado DS3232 * Processor: ATmel AVR con módulo TWI * Compiler: AVR IAR C y AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. * * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta
  • 802. * licencia y las notas de autor y copyright de arriba. * * Disclaimer: El uso de este software queda bajo su completa responsabilidad. *****************************************************************************/ #include "avr_compiler.h" #include "usart.h" #include "i2c.h" /*** Prototipos de funciones ***/ voidds3232_setup(void); voidds3232_SetTimeDate(char,char,char,char,char,char,char); voidds3232_GetTimeDate(char*,char*,char*,char*,char*,char*,char*); voidds3232_write(charaddress,chardata); chards3232_read(charaddress); voidDisplayTimeDate(void); chargetbcd(void); //**************************************************************************** // ISR o manejador de interrupción INT2. // Esta interrupción INT2 se dispara con el flanco de bajada. // Posible bug. Al inicio se dispara doble interrupción? //**************************************************************************** ISR(INT2_vect) { DisplayTimeDate();
  • 803. } /*** Función principal ***/ intmain(void) { i2c_init();// 100 kHz usart_init();// 9600 - 8N1 printf("nr DS3232: RTC I2C de gran precisión con cristal integrado y SRAM"); printf("nr ==============================================================nr"); ds3232_setup(); /* Configurar y habilitar la Interrupción Externa INT2 * para que se dispare en cada flanco de bajada del pin INT2 */ EICRA=0x20; EIMSK=0x04; sei(); while(1) { /* Entrar en modo sleep (Idle Mode). */ SMCR=0x01; sleep_enter(); }
  • 804. } //**************************************************************************** // Pone valores iniciales de fecha y hora en el DS3232. // Se configura la generación por el pin INT/SQW de una onda cuadrada de 1 Hz. // Se desactiva la señal de 32 kHz del pin 32kHz. // No se añade calibración extra a los capacitores del oscilador. // Se deja la frecuencia de muestreo de temperatura a 64/segundo. // No se habilitan las interrupciones de las alarmas. //**************************************************************************** voidds3232_setup(void) { charsegs,mins,hors,diasem,dia,mes,anio; // Configurar el pin INT/SQW para que saque una onda cuadrada de 1 Hz, // incluso si el DS3232 se alimenta de la batería. Dejar sin habilitar // las interrupciones de las alarmas. ds3232_write(0x0E,0x40);// Escribir en registro 'Control' 01000000 // Desactivar la onda cuadrada de 32 kHz del pin 32kHz, mantener la // razón de 64 muestros de temperatura por segundo y limpiar los flags // de las alarmas y del oscilador. ds3232_write(0x0F,0x00);// Escribir en registro 'Control/Status' 00000000
  • 805. // No añadir ni sustraer nada a los valores que el DS3232 autocalcula // para calibrar los capacitores de su oscilador. ds3232_write(0x10,0x00);// Escribir en registro 'Aging Offset' 00000000 /*** Poner valores iniciales de fecha y hora del DS3232 ***/ printf("nr Pon año [0..99]: ");anio=getbcd(); printf("nr Pon mes [1..12]: ");mes=getbcd(); printf("nr Pon día [1..31]: ");dia=getbcd(); printf("nr Pon día [1.. 7]: ");diasem=getbcd(); printf("nr Pon hora [0..23]: ");hors=getbcd(); printf("nr Pon minutos [0..59]: ");mins=getbcd(); printf("nr Pon segundos [0..59]: ");segs=getbcd(); printf("nr"); // Escribir valores iniciales de fecha y hora en el DS3232 ds3232_SetTimeDate(segs,mins,hors,diasem,dia,mes,anio); } //**************************************************************************** // Escribe los registros de Hora y Fecha del DS3232. // Los parámetros deben estar en formato BCD. //**************************************************************************** voidds3232_SetTimeDate(charseg,charmin,charhor,chardia,chardds,charmes,charanio)
  • 806. { i2c_start(); i2c_write(0xD0);// Slave address + Write i2c_write(0x00);// Register address i2c_write(seg);// Data to register i2c_write(min);// " " i2c_write(hor);// " " i2c_write(dds);// " " i2c_write(dia);// " " i2c_write(mes);// " " i2c_write(anio);// " " i2c_stop(); } //**************************************************************************** // Lee los registros de Hora y Fecha del DS3232. // Los parámetros salen en formato BCD. //**************************************************************************** voidds3232_GetTimeDate(char*seg,char*min,char*hor,char*dds,char*dia,char*mes,char*ani) { i2c_start(); i2c_write(0xD0);// Slave address + Write i2c_write(0x00);// Register address i2c_restart(); i2c_write(0xD0|0x01);// Slave address + Read *seg=i2c_read(0);// Read register and send ACK
  • 807. *min=i2c_read(0);// " " " *hor=i2c_read(0);// " " " *dds=i2c_read(0);// " " " *dia=i2c_read(0);// " " " *mes=i2c_read(0);// " " " *ani=i2c_read(1);// Read last register and send NACK i2c_stop(); } //**************************************************************************** // Escribe 'data' en el registro de dirección 'address' del DS3232. //**************************************************************************** voidds3232_write(charaddress,chardata) { i2c_start(); i2c_write(0xD0);// Slave address + Write i2c_write(address);// Register address i2c_write(data);// Data to register i2c_stop(); } //**************************************************************************** // Lee el registro de dirección 'address' del DS3232. //**************************************************************************** chards3232_read(charaddress) {
  • 808. charreg; i2c_start(); i2c_write(0xD0);// Slave address + Write i2c_write(address);// Register address i2c_restart(); i2c_write(0xD0|0x01);// Slave address + read reg=i2c_read(1);// Read register and send NACK i2c_stop(); returnreg; } //**************************************************************************** // Lee los registros de fecha y hora del DS3232 y los visualiza en el // terminal serial. // Formato de salida de visualización: 14/03/2009 05:32:45 //**************************************************************************** voidDisplayTimeDate(void) { charsegs,mins,hors,diasem,dia,mes,anio; charstat; stat=ds3232_read(0x0F);// Leer registro 'Control/Status' if(stat&0x80)// Si el RTC se detuvo (OSF = 1?) { printf("nr El RTC se detuvo");
  • 809. } else { /* Leer registros de fecha y hora del DS3232 */ ds3232_GetTimeDate(&segs,&mins,&hors,&diasem,&dia,&mes,&anio); /* Mostrar los datos leídos */ printf("rn %2x/%02x/20%02x %02x:%02x:%02x",dia, (mes&0x7F),// Ignorar bit Century (por si acaso :) anio,hors,mins,segs); } } //**************************************************************************** // Lee un número BCD de dos dígitos del terminal serial. //**************************************************************************** chargetbcd(void) { unsignedcharc,buff[3],i=0; while(1){ c=getchar(); if(('0'<=c)&&(c<='9')&&(i<2)){// Si c es dígito 0..9 y si i>0 buff[i++]=c;// Guardar en buffer putchar(c);// Eco } elseif((c=='b')&&(i)){// Si c es BACKSPACE y si i>0
  • 810. i--;// putchar(c);// Eco } elseif((c=='r')&&(i))// Si c es ENTER y si i>0 break;// Salir del bucle } c=buff[0]-'0'; if(i>1){// (i==2) c<<=4; c|=(buff[1]-'0'); } returnc; } El Reloj de Tiempo Real PCF8563 Philips, con su subsidiara NXP Semiconductors, fabrica otra de las familias de RTCs muy populares. En esta ocasión estudiaremos el Reloj de Tiempo Real /Calendario PCF8563, parte de la subfamilia conformada por los dispositivos PCF8563, PCF8573, PCF8583,PCF8593. Características del PCF8563 Computa segundos, minutos, horas, días de semana, días de mes, meses y años (hasta el 2099). Tiene una alarma programable. Tiene un Timer para programar temporizaciones. Tiene detección de baja tensión pero no ofrece alimentación alterna de batería. Provee por el pin CLKOUT una señal de hasta 4 frecuencias configurables. Tiene salida de interrupciones, pin INT, tanto de la alarma como del Timer.
  • 811. Los pormenores del dispositivo, como límites de tensión de alimentación, consumo de energía, etc., los puedes ver en el datasheet. Descripción Funcional de Pines del PCF8563 Tabla Pines del PCF8563 Diagrama de pines del RTC PCF8563 en PDIP. OSCI y OSCO. Son los pines para conectar un XTAL externo de 32.768 kHz. Los capacitores de estabilización, como siempre, los lleva dentro. Vdd y Vss. Pines de alimentación. SDA y SCL. Pines de interface I2C. CLKOUT. Puede sacar una onda cuadrada de 4 frecuencias (1 Hz, 32 Hz, 1024 Hz o 32768 Hz). Es un pin de drenador abierto que necesitará de una pull up externa. INT. Saca la señal de activación de interrupciones de la Alarma o del Timer. Los Registros del PCF8563 Tabla de Registros del PCF8563 Contenido Dirección Nombre Rango Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 0x00 control_status_1 TEST1 0 STOP 0 0x01 control_status_2 0 0 0 0x02 VL_seconds 10 segundos VL Bit 2 Bit 1 Bit 0 TESTC 0 TI_TP AF TF segundos 0 0 AIE TIE ----00 – 59
  • 812. Tabla de Registros del PCF8563 Contenido Dirección Nombre Rango Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0 0x03 Minutes 10 minutos minutos 00 – 59 0x04 Hours 10 hora horas 00 – 23 0x05 Days 10 día día (de mes) 0 – 31 0x06 Weekdays 0000 día (de semana) 0–6 0x07 century_months C mes 1 – 12 0x08 Years 10 año año 00 – 99 0x09 minute_alarm AE 10 minutos alarma minutos alarma --- 0x0A hour_alarm AE 10 horas alarma horas alarma --- 0x0B day_alarm AE 10 días alarma días alarma (de mes) --- 0x0C weekday_alarm AE 000 días alarma (de semana) --- 0x0D CLKOUT_control FE 0 0 0 0 0 FD1 FD0 --- 0x0E timer_control TE 0 0 0 0 0 TD1 TD0 --- 0x0F Timer × × × × × × × 10 mes × --- En esta sección explicamos los registros de fecha y hora y el primer registro de control. Los demás serán descritos junto con el módulo al que corresponden. Sobre los registros de fecha y hora no hay mucho que aclarar. Los días de semana van de 0 a 6 (no de 1 a 7), solo hay formato de 24 horas, y los bits VL y C son de funciones muy conocidas por nosotros: El bit VL (Voltage Low) se activa a 1 cuando la tensión de alimentación falla y cae (por debajo de 1V). En esta situación la información de los registros no es confiable. Se limpia por software. El bit C (Century) se activa al pasar del año 99 al año 00. En nuestro siglo estará en 0.
  • 813. Sobre el registro control_status_1, el bitSTOP es el más relevante. Poniéndolo a 1 se detiene el reloj del RTC, aunque la señal del pin CLKOUT todavía puede estar disponible. Poniéndolo a 0 el RTC opera normalmente. Si el bit TEST1 está en 0 el RTC funciona convencionalmente. Si lo seteamos, el RTC opera en “modo de prueba”, esto es, no en tiempo real con su oscilador de XTAL, sino en cámara lenta y guiado por la señal de reloj externa aplicada al pin CLKOUT, el cual se adapta para tal función automáticamente. Es algo que nos hace recordar al modo OCD de los microcontroladores. La activación del bit TESTC es condición necesaria pero no suficiente para suprimir el delay del reset POR del dispositivo. Salvo que realmente te interese eso, no debería preocuparte si lo dejas en 0 ó 1. A mí me da igual. El registro control_status_2 tiene dos propósitos: uno, controlar las interrupciones delTimer y de la alarma y dos, configurar la acción del pin INT. Control del Pin CLKOUT Todo queda a cargo del registro CLKOUT control, mostrado a continuación. Registro de control del rtc PCF8563 FE 0 0 0 0 0 FD1 FD0 Si el bit FE vale 0, el pin CLKOUT queda en alta impedancia, sin señal. Si FE vale 1,CLKOUT saca una onda cuadrada con frecuencia determinada por los bits FD1 y FD0. Tabla FD1 FD0 FD1 FD0 Frecuencia de onda del pin CLKOUT 00 32 768 Hz 01 1024 Hz 10 32 Hz 11 1 Hz La Alarma del PCF8563 La alarma se activa cuando se produce una coincidencia entre sus registros que tengan a 0 el bit AE (Alarm Enable) y sus correspondientes registros de tiempo. Así, podríamos programar la alarma para una coincidencia solo entre los registros de hora, por ejemplo.
  • 814. La coincidencia setea el bit AF (Alarm Flag) del registro control_status_2 y generará una interrupción activando el pin INT a 0, si está habilitada. Para esta habilitación se setea el bit AIE (Alarm Interrupt Enable) del mismo registro control_status_2. El Timer y el Registro TIMER El Timer es un registro de 8 bits que cuenta en modo descendente desde el valor que se le cargue hasta 0. Cuando llegue al final del conteo se activa el bit TF del registrocontrol_status_2. Esta condición puede generar una interrupción activando el pin INT a 0 si se setea el bit TIE (Timer Interrupt Enable), también de control_status_2. La activación y frecuencia de conteo del Timer queda a cargo del registro timer_control. Registro Registro TIMER del PCF8563 TE 0 0 0 0 0 TD1 TD0 Si TE (Timer Enable) vale 0, el Timer se detiene. El Timer corre con el bit TE puesto a 1. Los bits TD1 y TD0 establecen la frecuencia de su reloj. Tabla de frecuencias del PCF8563 TD1 TD0 Frecuencia de reloj del Timer 00 4 096 Hz 01 64 Hz 10 1 Hz 11 1/60 Hz Programar este Timer para generar temporizaciones es tan fácil como hacerlo con algún Timer de un microcontrolador. Sin embargo, las temporizaciones así obtenidas serán difícilmente aceptables, no solo por las pequeñas frecuencias de reloj disponibles, sino por la casi irremediable imprecisión acarreada por los tiempos de acceso al dispositivo. Me refiero al tiempo que demoran la condición START, etc. Acceso a los Registros del PCF8563 El puntero de registros tiene autoincremento y puede abarcar a todos los registros del RTC. La dirección de esclavo es 0xA2 (incluyendo bit RW=0). ¿Qué más hace falta?
  • 815. Práctica: Uso del PCF8563 En esta práctica se aprecia el uso básico del PCF8563. No se programan las alarmas ni el Timer. El circuito Circuito para el RTC PCF8563 y el microcontrolador AVR. El Código Fuente Si encuentras cosas que crees que merecen aclaración, puedes revisar las explicaciones de las prácticas con los RTCs DSxxx. Son todos los programas muy parecidos. De hecho, los creé empezando con el “Ctrl + C / Ctrl + V”, y luego hice las adaptaciones. ;) /****************************************************************************** * FileName: main.c * Purpose: Uso del Reloj de tiempo real / Calendario PCF8563 * Processor: ATmel AVR con módulo TWI * Compiler: AVR IAR C y AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com.
  • 816. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. * * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta * licencia y las notas de autor y copyright de arriba. * * Disclaimer: El uso de este software queda bajo su completa responsabilidad. *****************************************************************************/ #include "avr_compiler.h" #include "usart.h" #include "i2c.h" /*** Prototipos de funciones ***/ voidpcf8563_setup(void); voidpcf8563_SetTimeDate(char,char,char,char,char,char,char); voidpcf8563_GetTimeDate(char*,char*,char*,char*,char*,char*,char*); voidpcf8563_write(charaddress,chardata); charpcf8563_read(charaddress); voidDisplayTimeDate(void); chargetbcd(void); //**************************************************************************** // ISR o manejador de interrupción INT2. // La interrupción INT2 se dispara con el flanco de bajada.
  • 817. // Posible bug. Al inicio se dispara doble interrupción? //**************************************************************************** ISR(INT2_vect) { DisplayTimeDate(); } /*** Función principal ***/ intmain(void) { i2c_init();// 100 kHz usart_init();// 9600 - 8N1 printf("nr PCF8563: Reloj de tiempo real / Calendario"); printf("nr ==========================================nr"); delay_us(60000);// pcf8563 Start up delay delay_us(60000);// pcf8563_setup(); /* Configurar y habilitar la Interrupción Externa INT2 * para que se dispare en cada flanco de bajada del pin INT2 */ EICRA=0x20; EIMSK=0x04;
  • 818. sei(); while(1) { /* Entrar en modo sleep (Idle Mode). */ SMCR=0x01; sleep_enter(); } } //**************************************************************************** // Pone valores iniciales de fecha y hora en el PCF8563. // Se configura la generación por el pin CLKOUT de una onda cuadrada de 1 Hz. // No se configura la funcionalidad del pin INT porque no se usa la alarma ni // el timer, no por ahora. //**************************************************************************** voidpcf8563_setup(void) { charsegs,mins,hors,diasem,dia,mes,anio; // Configurar el registro 'CLKOUT_control' para sacar por el pin CLKOUT // una onda cuadrada de 1 Hz. pcf8563_write(0x0D,0x83);// 10000011 // Configurar el registro 'Control_status_1' para operación normal del
  • 819. // RTC(no EXT_CLK test mode, reloj del RTC en marcha, soporte 'Power-on // reset override'). No es necesario porque es su estado por defecto. pcf8563_write(0x00,0x08);// 00001000 /*** Poner valores iniciales de fecha y hora del PCF8563 ***/ printf("nr Pon año [0..99]: ");anio=getbcd(); printf("nr Pon mes [1..12]: ");mes=getbcd(); printf("nr Pon día [1..31]: ");dia=getbcd(); printf("nr Pon día [1.. 7]: ");diasem=getbcd(); printf("nr Pon hora [0..23]: ");hors=getbcd(); printf("nr Pon minutos [0..59]: ");mins=getbcd(); printf("nr Pon segundos [0..59]: ");segs=getbcd(); printf("nr"); // Escribir valores iniciales de fecha y hora en el PCF8563 pcf8563_SetTimeDate(segs,mins,hors,diasem,dia,mes,anio); } //**************************************************************************** // Escribe los registros de Hora y Fecha del PCF8563. // Los parámetros deben estar en formato BCD. //**************************************************************************** voidpcf8563_SetTimeDate(charseg,charmin,charhor,chardia,chardds,charmes,charanio) {
  • 820. i2c_start();// START i2c_write(0xA2);// Slave address + Write i2c_write(0x02);// Register address i2c_write(seg);// Data to register i2c_write(min);// " " i2c_write(hor);// " " i2c_write(dia);// " " i2c_write(dds);// " " i2c_write(mes);// " " i2c_write(anio);// " " i2c_stop();// STOP } //**************************************************************************** // Lee los registros de Hora y Fecha del PCF8563. // Los parámetros salen en formato BCD. //**************************************************************************** voidpcf8563_GetTimeDate(char*seg,char*min,char*hor,char*dds,char*dia,char*mes,char*ani) { i2c_start();// START i2c_write(0xA2);// Slave address + Write i2c_write(0x02);// Register address i2c_restart();// Repeated START i2c_write(0xA2|0x01);// Slave address + Read *seg=i2c_read(0);// Data from register *min=i2c_read(0);// " " "
  • 821. *hor=i2c_read(0);// " " " *dia=i2c_read(0);// " " " *dds=i2c_read(0);// " " " *mes=i2c_read(0);// " " " *ani=i2c_read(1);// " " " i2c_stop();// STOP } //**************************************************************************** // Escribe 'data' en el registro de dirección 'address' del PCF8563. //**************************************************************************** voidpcf8563_write(charaddress,chardata) { i2c_start();// START i2c_write(0xA2);// Slave address + Write i2c_write(address);// Register address i2c_write(data);// Data to register i2c_stop();// STOP } //**************************************************************************** // Lee el registro de dirección 'address' del PCF8563. //**************************************************************************** charpcf8563_read(charaddress) { charreg;
  • 822. i2c_start();// START i2c_write(0xA2);// Slave address + Write i2c_write(address);// Register address i2c_restart();// Repeated START i2c_write(0xA2|0x01);// Slave address + Read reg=i2c_read(1);// Data from register i2c_stop();// STOP returnreg; } //**************************************************************************** // Lee los registros de fecha y hora del PCF8563 y los visualiza en el // terminal serial. // Formato de salida de visualización: 14/03/2009 05:32:45 //**************************************************************************** voidDisplayTimeDate(void) { charsegs,mins,hors,diasem,dia,mes,anio; /* Leer registros de fecha y hora del PCF8563 */ pcf8563_GetTimeDate(&segs,&mins,&hors,&diasem,&dia,&mes,&anio); if(segs&0x80)// Si el voltaje cayó (VL = 1?) { printf("nr Bajo voltaje detectado."); printf("nr Los datos no son fiables.");
  • 823. } /* Mostrar los datos leídos */ printf("rn %2x/%02x/20%02x %02x:%02x:%02x",dia, (mes&0x7F),// Ignorar bit Century (por si acaso :) anio,hors,mins, (segs&0x7F));// Filtrar bit VL } //**************************************************************************** // Lee un número BCD de dos dígitos del terminal serial. //**************************************************************************** chargetbcd(void) { unsignedcharc,buff[3],i=0; while(1){ c=getchar(); if(('0'<=c)&&(c<='9')&&(i<2)){// Si c es dígito 0..9 y si i>0 buff[i++]=c;// Guardar en buffer putchar(c);// Eco } elseif((c=='b')&&(i)){// Si c es BACKSPACE y si i>0 i--;// putchar(c);// Eco }
  • 824. elseif((c=='r')&&(i))// Si c es ENTER y si i>0 break;// Salir del bucle } c=buff[0]-'0'; if(i>1){// (i==2) c<<=4; c|=(buff[1]-'0'); } returnc; } El sensor de temperatura DS1621 Con este dispositivo empezamos otra larga travesía, esta vez por los territorios de los sensores de temperatura I2C. Yo prefiero decir sensor porque la palabra termómetro me suena a eso que el médico te pone en la boca cuando te enfermas ;) Por ser el primer modelo tratado, esta exposición será más extensa. Luego comprobarás que, salvo raras excepciones, manejar los demás sensores de temperatura I2C es más que similar sin importar el modelo y fabricante. En la práctica verás que adaptar el programa de un termómetro a otro requiere conocer al menos ciertas características fundamentales. Para el DS1621 éstas son: Tiene una resolución de 9 bits. Permite medir temperaturas desde –55°C hasta +125°C con incrementos de 0.5°C. Tiene un tiempo de conversión máximo de 750 ms. Puede operar en modo continuo, donde realiza conversiones sin cesar, y en modo One shot, donde realiza una conversión cada vez que se le pida. Puede trabajar como termostato con histéresis programable. Soporta el protocolo I2C en Fast Mode (máxima frecuencia de reloj de 400 kHz). Descripción de Pines del DS1621 Tabla Pines del DS1621
  • 825. Diagrama de pines del termómetro DS1621 en PDIP. SDA y SCL. Pines de interface I2C. Vcc y GND. Pines de alimentación. Vcc es típicamente de 5 V. A0, A1 y A2. Pines para configurar parte de la dirección de esclavo. TOUT. Pin de salida del termostato. Puede indicar si la temperatura ha superado ciertos niveles, los cuales son programables por el usuario. No se usa en aplicaciones simples ni complejas. Si se usa, debe llevar una pull up externa por ser de drenador abierto. Set de Instrucciones del DS1621 La mayoría de las instrucciones permiten acceder a los registros internos del DS1621. Sus funciones serán ampliadas en adelante. Tabla Códigos de instruccion del DS1621 Código Instrucción Función 0xAA Read Temperature Lee la última temperatura convertida 0xEE Start Convert T Inicia la conversión de temperatura 0x22 Stop Convert T Detiene la conversión de temperatura 0xAC Access Config Lectura o escritura en el registro de Configuración 0xA1 Access TH Lectura o escritura en el registro TH 0xA2 Access TL Lectura o escritura en el registro TL 0xA8 Read Counter Lee el valor de Count_Remain
  • 826. Tabla Códigos de instruccion del DS1621 Código Instrucción Función 0xA9 Lee el valor de Count_Per_C Read Slope Los Registros del DS1621 En los modelos de Dallas lo distintivo es que el acceso a los registros se realiza mediante las instrucciones o comandos, en vez de por sus direcciones, aunque desde el punto de vista del software se pueden ver e interpretar igual. Como si se trataran de sus direcciones, los códigos de instrucciones se envían después del byte de control (Slave address + Write). Si la instrucción implica una escritura de datos, estos se envían después. Si la instrucción indica una lectura de datos, se envía otro byte de control (Slave address + Read) y se procede a la lectura, todo según elprotocolo I2C. En general, los registros de los sensores de temperatura se dividen en tres categorías: El registro de temperatura. Allí se guarda la temperatura convertida. Es un registro de solo lectura de 2 bytes, de los cuales solo 9 bits son significativos. Se le accede con la instrucción Read Temperature. La lectura está disponible en todo momento. El registro de configuración. Controla y configura la operación del dispositivo. Suele ser de 1 byte. Con eso basta y sobra. Se le accede con la instrucción Access Config. Algunos de sus bits son EEPROM por lo que su escritura requerirá a lo sumo 10 ms. Los registros del termostato. Son dos: uno para establecer el límite superior TH y otro para establecer el límite inferior TL. Cada uno tiene el mismo tamaño y formato que el registro de temperatura. Se les accede con las instrucciones Access TH yAccess TL. La función del termostato se explica después. El Registro de Configuración del DS1621 De todos solo el bit 1SHOT tiene uso imprescindible; los bits DONE y NVB tienen uso alternativo; y los bits del termostato (THF, TLF y POL)... bueno, el termostato es otro tema. Registro Registro de Configuración del DS1621 DONE THF TLF Registro de Microcontrolador NVB --- --- POL 1SHOT
  • 827. DONE Conversion Done bit 1 = Conversión completada 0 = Conversión en progreso En el DS1621 una conversión de temperatura dura como mucho 750 ms. En la práctica no se suele tener tanta prisa por leer la temperatura, por lo que es más frecuente hacer una espera de más de 1 segundo que sondear este bit. THF Temperature High Flag (Termostato) 1 = La temperatura a igualado o superado el valor de TH. Se limpia por software 0 = La temperatura aún no llega a TH. TLF Temperature Low Flag (Termostato) 1 = La temperatura a igualado o ni bajado del valor de TL. Se limpia por software 0 = La temperatura aún no llega a TL. NVB Non Volatile Memory Busy Flag Los registros del termostato TH y TL, y algunos bits del registro de Configuración son EEPROM. La escritura en dichas locaciones tiene un tiempo típico de 4 ms y máximo de 10 ms. En ese lapso no se deben escribir más datos. En las prácticas resulta más cómodo poner un delay que sondear este bit. 1 = Hay una escritura de EEPROM en progreso 0 = No hay escrituras de EEPROM en progreso POL Output Polarity Bit (Termostato) 1 = Activo en alto 0 = Activo en bajo 1SHOT One Shot Mode 1 = Establece operación del DS1621 en modo One Shot 0 = Establece operación del DS1621 en modo de Conversiones Continuas Modo One shot y de Conversiones Continuas
  • 828. Hay dos formas conocidas en las que los sensores de temperatura pueden trabajar. En los DS162x se llaman modo One Shot y modo Continuo. En el modo One Shot el sensor toma la temperatura solo cuando se le ordena. El resto del tiempo el dispositivo permanece en modo Standby para ahorrar energía. Una vez configurado este modo, se debe iniciar una conversión con la instrucción Start Convert. Cuando el DS1621 acabe la conversión (luego de 750 ms a lo sumo) se actualiza el registro de temperatura, se activa el flagDONE (del registro de Configuración) y luego el dispositivo regresa a su estado Standby. En el modo de Conversiones Continuas el sensor toma y convierte la temperatura sin cesar. Una vez establecido este modo, el DS1621 queda en Standby y se debe usar la instrucción Start Convert para arrancar las conversiones continuas. Con eso las conversiones se realizan una a continuación de otra. El proceso se puede detener con la instrucción Stop Convert, con lo cual el DS1621 terminará la conversión actual para luego regresar a su Standby. No es tan diferente del modo One shot después de todo. Formato del Dato de Temperatura El valor de la temperatura convertida se almacena en un registro de uno o dos bytes, según el dispositivo. La siguiente exposición de su formato es aplicable a todos los termómetros I2C, o por lo menos a los considerados en este sitio. Es una interpretación parcialmente alternativa que yo suelo usar y es lo que verás en los mis programas. Disposición del registro de temperatura. El primer byte contiene la parte entera del dato. Se trata de un número con signo de complemento a dos. Asumo que entiendes lo que significa eso. Es un concepto básico en sistemas digitales. Los termómetros simples como el TC74 (visto más adelante) solo tienen este byte. El segundo byte (si lo hubiera) lleva la parte fraccionaria, justificada a la izquierda. Sus nbits significativos representan un múltiplo de . Para efectos de cálculo n es la cantidad de bits que hay desde el primer 1 que aparece empezando por la derecha. Los 0‟s a la derecha no valen nada, ¿recuerdas? Finalmente el valor de temperatura se halla sumando la parte entera (positiva o negativa) y la parte fraccionaria (siempre positiva). Por ejemplo.
  • 829. Los sensores como el DS1621 o LM75 solo tienen un bit fraccional significativo. Por eso es más fácil decir que cuando dicho bit es 1 vale 1×(1/21) = 0.5 °C. Función de Termostato Un termostato es un dispositivo capaz de mantener constante la temperatura de un medio. La forma en que el DS1621 pretende cumplir esta función es mediante la acción del pinTOUT. Este pin se activa cuando la temperatura llega o supera el valor del registro TH y se desactiva cuando la temperatura iguala o cae por debajo del valor del registro TL. A eso se resume el tan mentado termostato. Es el diseñador quien debe establecer los límites de TH y TL y aprovechar la señal deTOUT para lo que considere apropiado; por ejemplo, para encender un ventilador o un calefactor cuando la temperatura alcance los límites programados. Esa aplicación puede parecer interesante, pero como proyecto de escuela de un estudiante de 10 años. Nosotros sabemos que el mundo no es en blanco y negro, que no basta con encender o apagar unos artefactos, sino que es necesario regular su funcionamiento. Pero ése es un tema que cae mejor en el “control de procesos”, una materia que desarrolla potentes herramientas para implementar verdaderos sistemas controlados y donde -creo- los pobres termostatos hardware de estos termómetros tienen muy poco que hacer. Así es como trato de justificar el hecho de ignorar todo lo relacionado a los termostatos, pese a ser una funcionalidad muy presente de estos dispositivos ;) Dirección de Esclavo del DS1621 Los valores de los bits A0, A1 y A2 corresponden a los pines homólogos del dispositivo. En adelante veremos que esta misma dirección también es usada por otros termómetros I2C, incluso de otros fabricantes.
  • 830. El byte de control (dirección de esclavo + bit R/W) para el DS1621. Práctica 1: Uso del DS1621: Modo One Shot En esta práctica el DS1621 operará en modo One Shot. La temperatura se lee y convierte cuando se pulsa la tecla ENTER. Se leen los dos bytes del registro de temperatura pero solo se considera la parte entera. En las siguientes prácticas se verán las demás funciones de este sensor. Circuito para el sensor de temperatura DS1621 y el microcontrolador AVR. El código fuente En la función DisplayTemp después de iniciar la conversión se espera a que ella termine comprobando que el bit DONE se active a 1. Como será una espera larga
  • 831. (hasta de 750 ms), lo combiné con un delay de 10 ms. A veces será más cómodo quitar toda esta rutina y cambiarla por un delay de 750 ms o más. voidDisplayTemp(void) { //... ds1621_StartConvert();// Iniciar conversión do{ delay_ms(10); done=ds1621_ReadConfig()&0x80; }while(done==0);// Esperar conversión completada temp=ds1621_ReadTemperature();// Leer registro de Temperatura //... } /****************************************************************************** * FileName: main.c * Purpose: Uso del termómetro digital DS1621: One Shot mode * Processor: ATmel AVR con módulo TWI * Compiler: AVR IAR C y AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. * * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta
  • 832. * licencia y las notas de autor y copyright de arriba. *****************************************************************************/ #include "avr_compiler.h" #include "usart.h" #include "i2c.h" /*** Prototipos de funciones ***/ signedchards1621_ReadTemperature(void); voidDisplayTemp(void); voidds1621_WriteConfig(char); chards1621_ReadConfig(void); voidds1621_StartConvert(void); /*** Función principal ***/ intmain(void) { i2c_init();// 100 kHz usart_init();// 9600 - 8N1 printf("nr DS1621: Sensor de temperatura"); printf("nr =============================nr"); ds1621_WriteConfig(0x01);// Establecer modo One Shot printf("nr Pulse ENTER para leer temperatura");
  • 833. while(1) { if(kbhit())// Si llegó algún dato { if(getchar()=='r')// Y si es ENTER DisplayTemp(); } } } //**************************************************************************** // Lee la temperatura del DS1621 y la muestra en el terminal serial. //**************************************************************************** voidDisplayTemp(void) { inttemp; chardone; ds1621_StartConvert();// Iniciar conversión do{ delay_us(10000); done=ds1621_ReadConfig()&0x80; }while(done==0);// Esperar conversión completada temp=ds1621_ReadTemperature();// Leer registro de Temperatura
  • 834. printf("rn Temp = %d °C",temp);// Mostrar temperatura leída } //**************************************************************************** // Escribe 'data' en el registro de Configuración del DS1621 //**************************************************************************** voidds1621_WriteConfig(chardata) { i2c_start(); i2c_write(0x90);// Slave address + Write i2c_write(0xAC);// Access Config instruction i2c_write(data);// Data to Config register i2c_stop(); delay_us(12000);// EEPROM write cycle period > 10ms } //**************************************************************************** // Escribe el registro de Configuración del DS1621 //**************************************************************************** chards1621_ReadConfig(void) { chardata; i2c_start(); i2c_write(0x90);// Slave address + Write i2c_write(0xAC);// Access Config instruction
  • 835. i2c_restart(); i2c_write(0x90|0x01);// Slave address + Read data=i2c_read(1);// Data from register i2c_stop(); returndata; } //**************************************************************************** // Inicia una/la conversión de temperatura. //**************************************************************************** voidds1621_StartConvert(void) { i2c_start(); i2c_write(0x90);// Slave address + Write i2c_write(0xEE);// Start Convert T instruction i2c_stop(); } //**************************************************************************** // Lee la temperatura del DS1621. // Solo se toma la parte entera con signo, no la parte fraccionaria. //**************************************************************************** signedchards1621_ReadTemperature(void) { signedchardata; i2c_start();
  • 836. i2c_write(0x90);// Slave address + Write i2c_write(0xAA);// Read Temperature instruction i2c_restart(); i2c_write(0x90|0x01);// Slave address + Read data=i2c_read(0);// Read integer term i2c_read(1);// Read fractional term (dummy read) i2c_stop(); returndata; } Práctica 2: Uso del DS1621: Modo Continuo Ahora el DS1621 operará en modo de Conversiones Continuas. El sufijo full en el nombre del programa significa que esta vez trabajaremos con todo el registro de temperatura, tanto con la parte entera como la fraccionaria. Utilizaremos el mismo circuito de la práctica previa.
  • 837. El código fuente Cuando el programa va a trabajar con la parte entera y fraccionaria de la temperatura se prefiere multiplicar el dato por 10 (u otro valor según convenga) para convertirlo en un número entero. El dato es procesado así y vuelto a separar en sus partes entera y fraccionaria solo cuando sea necesario; por ejemplo para visualizar su valor. Esa operación era conveniente porque los datos que entregan los sensores no son números de punto flotante que los compiladores puedan entender. Es decir, no podríamos meter esos datos directamente en variables de tipo float del lenguaje C/C++. Los verdaderos números de punto flotante son de 32 bits (simple precisión) o de 64 bits (doble precisión) y deben cumplir el formato establecido en el estándar IEEE 754. /****************************************************************************** * FileName: main.c * Purpose: Uso del termómetro digital DS1621: Continuous conversion mode * Processor: ATmel AVR con módulo TWI * Compiler: AVR IAR C y AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. * * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta * licencia y las notas de autor y copyright de arriba. *****************************************************************************/ #include "avr_compiler.h" #include "usart.h" #include "i2c.h"
  • 838. /*** Prototipos de funciones ***/ intds1621_ReadTemperature(void); voidDisplayTemp(void); voidds1621_WriteConfig(char); chards1621_ReadConfig(void); voidds1621_StartConvert(void); voidds1621_StopConvert(void); voiddelay_ms(unsignedintt); /*** Función principal ***/ intmain(void) { unsignedchari; i2c_init();// 100 kHz usart_init();// 9600 - 8N1 printf("nr DS1621: Sensor de temperatura"); printf("nr ============================="); ds1621_WriteConfig(0x00);// Establecer modo Continuo while(1) { ds1621_StartConvert();// Iniciar conversiones continuas
  • 839. printf("nrr DS1621 Convirtiendo..."); for(i=0;i<10;i++) { delay_ms(800);// > 750 ms DisplayTemp();// Visualizar temperatura } ds1621_StopConvert();// Detener conversiones continuas printf("nrr DS1621 en Standby por 10s"); delay_ms(10000); } } //**************************************************************************** // Lee la temperatura del DS1621 y la muestra en el terminal serial. //**************************************************************************** voidDisplayTemp(void) { inttemp; temp=ds1621_ReadTemperature();// Obtener temperatura printf("rn Temp = %d.%d °C",temp/10,abs(temp%10)); }
  • 840. //**************************************************************************** // Escribe 'data' en el registro de Configuración del DS1621 //**************************************************************************** voidds1621_WriteConfig(chardata) { i2c_start(); i2c_write(0x90);// Slave address + Write i2c_write(0xAC);// Access Config instruction i2c_write(data);// Data to Config register i2c_stop(); delay_ms(12);// EEPROM write cycle period > 10ms } //**************************************************************************** // Inicia una/la conversión de temperatura. //**************************************************************************** voidds1621_StartConvert(void) { i2c_start(); i2c_write(0x90);// Slave address + Write i2c_write(0xEE);// Start Convert T instruction i2c_stop(); } //**************************************************************************** // Detiene una/la conversión de temperatura. //****************************************************************************
  • 841. voidds1621_StopConvert(void) { i2c_start(); i2c_write(0x90);// Slave address + Write i2c_write(0x22);// Stop Convert T instruction i2c_stop(); } //**************************************************************************** // Lee la temperatura del DS1621. // Devuelve el valor de la temperatura multiplicado por 10. //**************************************************************************** intds1621_ReadTemperature(void) { intdata; signedcharhigh,low; i2c_start(); i2c_write(0x90);// Slave address + Write i2c_write(0xAA);// Read Temperature instruction i2c_restart(); i2c_write(0x90|0x01);// Slave address + Read high=i2c_read(0);// Read integer term low=i2c_read(1);// Read fractional term i2c_stop(); data=high;
  • 842. data*=10;// Multiplicar por 10 if(low&0x80)// ¿ Sumar 5°C ? data+=5; returndata; } //**************************************************************************** // delay_ms() //**************************************************************************** voiddelay_ms(unsignedintt){ while(t--) delay_us(1000); } Práctica 3 Uso del DS1621: Alta Resolución El DS1621 realiza la conversión de temperatura “jugando” con algunos contadores, dos de los cuales generarán en última instancia el resultado final. Ellos se llaman Counter y Slope Accumulator. A grandes rasgos, digamos que el primero produce el grueso de la temperatura y el segundo ayuda a afinar la conversión. El hardware del DS1621 utiliza un circuito digital algo pobre para calcular la conversión (basado en contadores y comparadores binarios básicamente). Pero nos ofrece la posibilidad de acceder a sus contadores principales para calibrar la conversión “a mano” y así poder conseguir una mejor resolución. Debemos utilizar la siguiente fórmula: Donde: Temp_read es la parte entera del registro de temperatura. La parte fraccionaria será ignorada en pos de la calibración manual. Count_remain es el valor restante del Counter. Se le accede enviando la instrucciónRead Counter.
  • 843. Count_per_c es valor que quedó en el Slope Accumulator. Se le accede mediante la instrucción Read Slope. Debo hacer la salvedad de que dado el margen de error intrínseco del dispositivo de hasta ±1/2 °C en el rango 0–70 °C (aumenta en otros rangos), creo que no tiene mucho sentido obsesionarse con el hecho de lograr una alta precisión. Pasando a la práctica, superficialmente el programa funciona como la primera práctica: el DS1621 opera en modo One Shot y realiza una conversión cada vez que se presione la tecla ENTER. Se aplicará la fórmula presentada para obtener una mayor resolución de conversión y se mostrará la temperatura con dos dígitos decimales. Utilizaremos el mismo de la práctica anterior. El código fuente /****************************************************************************** * FileName: main.c * Purpose: Uso del termómetro digital DS1621: high resolution * Processor: ATmel AVR con módulo TWI * Compiler: AVR IAR C y AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com.
  • 844. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. * * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta * licencia y las notas de autor y copyright de arriba. *****************************************************************************/ #include "avr_compiler.h" #include "usart.h" #include "i2c.h" /*** Prototipos de funciones ***/ intds1621_ReadTemperature(void); voidDisplayTemp(void); voidds1621_WriteConfig(char); unsignedchards1621_ReadByte(char); voidds1621_StartConvert(void); voiddelay_ms(unsignedintt); /*** Función principal ***/ intmain(void) { i2c_init();// 100 kHz usart_init();// 9600 - 8N1
  • 845. printf("nr DS1621: Sensor de temperatura"); printf("nr =============================nr"); ds1621_WriteConfig(0x01);// Establecer modo One Shot printf("nr Pulse ENTER para leer temperatura"); while(1) { if(kbhit())// Si llegó algún dato { if(getchar()=='r')// Y si es ENTER DisplayTemp(); } } } //**************************************************************************** // Lee la temperatura del DS1621 y la muestra en el terminal serial. //**************************************************************************** voidDisplayTemp(void) { inttemp; chardone; ds1621_StartConvert();// Iniciar conversión
  • 846. do{ delay_ms(10); done=ds1621_ReadByte(0xAC)&0x80;// Leer Registro de Configuración }while(done==0);// Esperar conversión completada temp=ds1621_ReadTemperature();// Leer temperatura printf("rn Temp = %d.%d °C",temp/100,abs(temp%100)); } //**************************************************************************** // Escribe 'data' en el registro de Configuración del DS1621 //**************************************************************************** voidds1621_WriteConfig(chardata) { i2c_start(); i2c_write(0x90);// Slave address + Write i2c_write(0xAC);// Access Config instruction i2c_write(data);// Data to Config register i2c_stop(); delay_ms(12);// EEPROM write cycle period > 10ms } //**************************************************************************** // Lee el registro de Configuración del DS1621 //**************************************************************************** unsignedchards1621_ReadByte(charcmd)
  • 847. { chardata; i2c_start(); i2c_write(0x90);// Slave address + Write i2c_write(cmd);// Instruction code i2c_restart(); i2c_write(0x90|0x01);// Slave address + Read data=i2c_read(1);// Read register and send NACK i2c_stop(); returndata; } //**************************************************************************** // Inicia una/la conversión de temperatura. //**************************************************************************** voidds1621_StartConvert(void) { i2c_start(); i2c_write(0x90);// Slave address + Write i2c_write(0xEE);// Start Convert T instruction i2c_stop(); } //**************************************************************************** // Lee la temperatura del DS1621. // Devuelve el valor de la temperatura multiplicado por 100.
  • 848. //**************************************************************************** intds1621_ReadTemperature(void) { inttemp_read,count_per_c,count_remain; i2c_start(); i2c_write(0x90);// Slave address + Write i2c_write(0xAA);// Read Temperature instruction i2c_restart(); i2c_write(0x90|0x01);// Slave address + Read temp_read=i2c_read(0);// Read integer term i2c_read(1);// Read fractional term (dummy read) i2c_stop(); count_remain=ds1621_ReadByte(0xA8);// Leer valor restante de Counter count_per_c=ds1621_ReadByte(0xA9);// Leer valor de Slope Accumulator return(100*temp_read-25+(100*(count_per_c-count_remain))/count_per_c); } //**************************************************************************** // delay_ms() //**************************************************************************** voiddelay_ms(unsignedintt){ while(t--) delay_us(1000); }
  • 849. El sensor de temperatura DS1624 Como hermano mayor del DS1621, podríamos esperar que el DS1624 será más difícil de manejar. Por fortuna, las pocas diferencias existentes vendrán a nuestro favor. En primer lugar, el DS1624 no tiene función de termostato. Así que te puedes olvidar de todo lo referente a él (los registros de histéresis y los bits del registro de configuración relacionados). ¡Qué alivio! Había explicado antes mis razones para ignorar este termostato. Tabla Pines del DS1624 Diagrama de pines del termómetro DS1624 en PDIP. En segundo lugar, el DS1624 no ofrece acceso a sus registros internos con los que realiza la conversión de temperatura. Significa que no podremos ajustar la precisión como en la tercera práctica del DS1621. Yo sí los extraño. En tercer lugar diré que el DS1624 cuenta con una EEPROM de 256 bytes de propósito general. El acceso a esta memoria es muy similar a hacerlo con una EEPROM 24xxx, desde los accesos individuales o escrituras por páginas (de 8 bytes en el DS1624) hasta la forma de usar el bucle Acknowledge Polling. Personalmente considero estos aditamentos como prescindibles. Si quiero más EEPROM, pues busco un microcontrolador que la tenga en mayor cantidad. De todos modos, pondré algunas funciones de ejemplo al final. En cuarto lugar tenemos la diferencia más sobresaliente por la que muchos pueden elegir este termómetro. El DS1624 tiene una resolución de 13 bits. Como la mayoría, sigue midiendo temperaturas en el rango de –55°C a +125°C, pero ahora lo hace con incrementos de 0.03125 °C. Este incremento se debe a que la parte fraccionaria es de 5 bits y ya sabemos que eso da 1/32 = 0.03125. Abajo se muestra la disposición del registro de temperatura.
  • 850. Disposición del registro de temperatura del DS1624. Por lo demás, diré que el DS1624 funciona igual que el DS1621. Tiene dos modos de operación: de Conversiones Continuas y One Shot. En cualquier modo, permanecerá en Standby mientras no se esté convirtiendo una temperatura, esto es, luego de terminada una conversión en One Shot o después de detener las conversiones (con Stop Convert T) en modo de Conversiones Continuas. Salvando las diferencias citadas, el software debería ser compatible, ya que el DS1624 también usa las mismas instrucciones (ver siguiente sección), la misma dirección de esclavo, etc. El Registro de Configuración del DS1624 Esto es lo que queda al quitar los bits relacionados al termostato del DS1621. Solo el bit1SHOT tiene uso imprescindible. El registro de Configuración está implementado en EEPROM. Una escritura en él tomará un tiempo típico de 10 ms y máximo de 50 ms. En ese lapso el DS1624 responderá con un NACK a los siguientes datos que reciba. Se puede usar eso como flag, o poner un delay apropiado antes de realizar más lecturas o escrituras. Registro de Configuración del DS1624 DONE 1 0 0 1 0 1 1SHOT Registro de Microcontrolador DONE Conversion Done bit 1 = Conversión completada 0 = Conversión en progreso En el DS1624 una conversión de temperatura suele durar 400 ms y 1000 ms como mucho. Muchas veces será preferible esperar más de 1 segundo antes que sondear este bit. 1SHOT One Shot Mode 1 = Establece operación del DS1624 en modo One Shot
  • 851. 0 = Establece operación del DS1624 en modo de Conversiones Continuas Instrucciones del DS1624 La única novedad es la presencia de Access Memory. Tabla Código Código Instrucción Función 0xAA Read Temperature Lee la última temperatura convertida 0xEE Start Convert T Inicia la conversión de temperatura 0x22 Stop Convert T Detiene la conversión de temperatura 0xAC Access Config Lectura o escritura en el registro de Configuración 0x17 Access Memory Lee o escribe en la EEPROM Práctica: Uso del DS1624: Modo One shot El DS1624 es controlado en modo One Shot. Si se desea ver una operación en modo de conversiones continuas, se puede revisar la segunda práctica del DS1621. Los cambios en el código serían mínimos, en las funciones ds1621_ReadTemperature y DisplayTempbásicamente.
  • 852. Circuito para el sensor de temperatura DS1624 y el microcontrolador AVR. El código fuente /****************************************************************************** * FileName: main.c * Purpose: Uso del termómetro digital y memoria DS1624: One Shot mode * Processor: ATmel AVR con módulo TWI * Compiler: AVR IAR C y AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. * * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta * licencia y las notas de autor y copyright de arriba.
  • 853. *****************************************************************************/ #include "avr_compiler.h" #include "usart.h" #include "i2c.h" /*** Prototipos de funciones ***/ intds1624_ReadTemperature(void); voidDisplayTemp(void); voidds1624_WriteConfig(char); chards1624_ReadConfig(void); voidds1624_StartConvert(void); voidds1624_WriteEEPROM(charaddress,chardata); chards1624_ReadEEPROM(charaddress); voidds1624_ready(void); voiddelay_ms(unsignedintt); /*** Función principal ***/ intmain(void) { i2c_init();// 100 kHz usart_init();// 9600 - 8N1 printf("nr DS1624: Digital Thermometer and Memory"); ds1624_WriteConfig(0x01);// Establecer modo One Shot
  • 854. printf("nrr Pulse ENTER para leer temperatura"); while(1) { if(kbhit())// Si llegó algún dato { if(getchar()=='r')// Y si es ENTER DisplayTemp(); } } } //**************************************************************************** // Lee la temperatura del DS1624 y la muestra en el terminal serial. //**************************************************************************** voidDisplayTemp(void) { inttemp; chardone; ds1624_StartConvert();// Iniciar conversión /* El siguiente bucle de espera puede sustituirse por un delay > 1000ms */ do{ delay_ms(10);
  • 855. done=ds1624_ReadConfig()&0x80; }while(done==0);// Esperar conversión completada temp=ds1624_ReadTemperature();// Leer temperatura printf("rn Temp = %d.%d °C",temp/100,abs(temp%100)); } //**************************************************************************** // Escribe 'data' en el registro de Configuración del DS1624 //**************************************************************************** voidds1624_WriteConfig(chardata) { i2c_start(); i2c_write(0x90);// Slave address + Write i2c_write(0xAC);// Access Config instruction i2c_write(data);// Data to Config register i2c_stop(); delay_ms(15);// EEPROM write cycle period > 10ms } //**************************************************************************** // Lee el registro de Configuración del DS1624 //**************************************************************************** chards1624_ReadConfig(void) {
  • 856. chardata; i2c_start(); i2c_write(0x90);// Slave address + Write i2c_write(0xAC);// Access Config instruction i2c_restart(); i2c_write(0x90|0x01);// Slave address + Read data=i2c_read(1);// Get register data i2c_stop(); returndata; } //**************************************************************************** // Inicia una/la conversión de temperatura. //**************************************************************************** voidds1624_StartConvert(void) { i2c_start(); i2c_write(0x90);// Slave address + Write i2c_write(0xEE);// Start Convert T instruction i2c_stop(); } //**************************************************************************** // Lee la temperatura del DS1624. // Devuelve el valor de la temperatura multiplicado por 100. //****************************************************************************
  • 857. intds1624_ReadTemperature(void) { constunsignedcharfrac[]={0,3,6,9,13,16,19,22,25,28,31,34,38, 41,44,47,50,53,56,59,63,66,69,72,75,78,81,84,88,91,94,97}; intdata; signedcharhigh; unsignedcharlow; i2c_start(); i2c_write(0x90);// Slave address + Write i2c_write(0xAA);// Read Temperature instruction i2c_restart(); i2c_write(0x90|0x01);// Slave address + Read high=i2c_read(0);// Read integer term low=i2c_read(1);// Read fractional term i2c_stop(); data=high; data*=100;// Multiplicar por 100 low>>=3;// Justificar a la derecha data+=frac[low];// Sumar parte fraccionaria redondeada returndata; } //**************************************************************************** // FUNCIONES PARA ACCEDER A LA EEPROM DEL DS1624 //****************************************************************************
  • 858. //**************************************************************************** // Escribe 'data' en la dirección 'address' de la EEPROM del DS1624 //**************************************************************************** voidds1624_WriteEEPROM(charaddress,chardata) { i2c_start(); i2c_write(0x90);// Slave address + Write i2c_write(0x17);// Access Memory instruction i2c_write(address);// EEPROM address i2c_write(data);// Data to EEPROM i2c_stop(); delay_ms(15);// EEPROM write cycle period > 10ms } //**************************************************************************** // Lee de la dirección 'address' de la EEPROM del DS1624 //**************************************************************************** chards1624_ReadEEPROM(charaddress) { chardata; i2c_start(); i2c_write(0x90);// Slave address + Write i2c_write(0x17);// Access Memory instruction i2c_write(address);// EEPROM address i2c_restart();
  • 859. i2c_write(0x90|0x01);// Slave address + Read data=i2c_read(1);// Data from EEPROM i2c_stop(); returndata; } //**************************************************************************** // Espera hasta que el DS1624 esté listo para recibir nuevas lecturas o // escrituras de datos. //**************************************************************************** voidds1624_ready(void) { charack; do{ i2c_start();// Enviar START + ack=i2c_write(0x09);// Slave address + Write i2c_stop();// }while(ack!=0);// Hasta que se reciba un ACK (0) } //**************************************************************************** // delay_ms() //**************************************************************************** voiddelay_ms(unsignedintt){ while(t--) delay_us(1000); }
  • 860. Descripción del programa Empiezo por comentar la función ds1624_ReadTemperature. Sabemos que de los 13 bits de resolución de este sensor 5 bits le corresponden a la parte fraccionaria. Eso significa que la parte fraccionaria puede tener uno de los siguientes 32 posibles valores (columna central). Nota que 5 dígitos decimales dan la falsa impresión de una precisión extrema, cuando en realidad solo se pueden tener 32 valores discretos. Por eso pienso que no tendría sentido trabajar con todos ellos. Lo que hice en el programa fue redondear estos valores a dos dígitos decimales, multiplicarlos por 100 y colocarlos en la matriz constante frac. El título del código dice termómetro digital y memoria, aunque no se vea ninguna rutina con dicha EEPROM. En caso de ser necesario, se pueden usar los siguientes procedimientos. de acceso. La escritura en la EEPROM dura entre 10 ms y 50 ms. En lugar del delay puesto también se podría usar la funciónds1624_ready. Lo mismo es válido en la escritura del registro de configuración, dado que también es EEPROM.
  • 861. La EEPROM del DS1624 también soporta acceso secuencial, como las 24xxx. En la lectura se pueden obtener los 256 bytes si se quisiera, pero en las escrituras se graban un máximo de 8 bytes por transferencia. Solo los tres primeros bits del puntero de memoria se incrementan automáticamente. No pongo más códigos porque creo que ya estoy redundando demasiado. //**************************************************************************** // FUNCIONES PARA ACCEDER A LA EEPROM DEL DS1624 //**************************************************************************** //**************************************************************************** // Escribe 'data' en la dirección 'address' de la EEPROM del DS1624 //**************************************************************************** voidds1624_WriteEEPROM(charaddress,chardata) { i2c_start(); i2c_write(0x90);// Slave address + Write i2c_write(0x17);// Access Memory instruction i2c_write(address);// EEPROM address i2c_write(data);// Data to EEPROM i2c_stop(); delay_ms(15);// EEPROM write cycle period > 10ms } //**************************************************************************** // Lee de la dirección 'address' de la EEPROM del DS1624 //**************************************************************************** chards1624_ReadEEPROM(charaddress)
  • 862. { chardata; i2c_start(); i2c_write(0x90);// Slave address + Write i2c_write(0x17);// Access Memory instruction i2c_write(address);// EEPROM address i2c_restart(); i2c_write(0x90|0x01);// Slave address + Read data=i2c_read(1);// Data from EEPROM i2c_stop(); returndata; } //**************************************************************************** // Espera hasta que el DS1624 esté listo para recibir nuevas lecturas o // escrituras de datos. //**************************************************************************** voidds1624_ready(void) { charack; do{ i2c_start();// Enviar START + ack=i2c_write(0x09);// Slave address + Write i2c_stop();// }while(ack!=0);// Hasta que se reciba un ACK (0) }
  • 863. //**************************************************************************** // delay_ms() //**************************************************************************** voiddelay_ms(unsignedintt){ while(t--) delay_us(1000); } El Sensor de Temperatura LM75 No encuentro grandes razones para elegir entre un termómetro y otro. Puedo ver en cada uno ciertas ventajas o limitaciones pero son pequeñas. Aquí tenemos algunas características que nos pintan las potencialidades del LM75. Tiene una resolución de 9 bits, 8 de parte entera y 1 de parte fraccionaria, pudiendo medir temperaturas desde –55°C hasta +125°C, con incrementos de 0.5°C. Tiempo de conversión típico de 100 ms y máximo de 300 ms. Tiene una imprecisión de ±2°C entre –25°C y +100°C. Velocidad de transferencia máxima 400 kbits/s (Fast mode). Una vez puesto en marcha, el LM75 convierte la temperatura constante y sucesivamente. Los registros de temperatura se actualizan cuando termina una conversión y están disponibles para lectura en todo momento. Sin embargo se deberá tener en cuenta que cada acceso al LM75 reiniciará la conversión actual. Como no hay forma de conocer el momento exacto en que termina una conversión, será necesario poner pausas lo suficientemente grandes para permitir que los registros de temperatura se actualicen normalmente. El LM75 no tiene un modo de conversión tipo One Shot pero sí se puede poner en modoStandby o Shut down para ahorrar energía, de modo que sin mucha imaginación podremos armar rutinas que lo emulen.
  • 864. Diagrama de pines del termómetro LM75 en SOP-8. El pin O.S. (Overtemperature Shutdown) es la salida del termostato. Sobra decir que con los pines A0, A1 y A2 permiten conectar hasta 8 de estos dispositivos en una misma red I2C. El Termostato del LM75 Si hay alguna parte del LM75 que destaca con más claridad respecto de otros termómetros es que tiene un termostato ligeramente más sofisticado. El mecanismo del termostato puede cambiar el nivel del pin O.S. cuando la temperatura llegue o pase los valores de los registros THYST Set Point y TOS Set Point. La forma en que conmuta dicho pin dependerá del modo en que opere el termostato, el cual puede sermodo de Interrupciones o modo Comparador. Este último sería el equivalente a la función única del termostato del DS1621 (o similar). También tiene la capacidad de reconfirmar las temperaturas extremas para asegurarse de que se han alcanzado los límites de histéresis programados. A pesar de todo, yo sigo pensando que no es tan atractivo a menos que opere en modoStand alone, o sea, sin conexión a un microcontrolador. Así que no profundizaré en este tema. Los Registros del LM75 Aquí tenemos nuevamente los tres tipos de registros de un termómetro típico. El registro de temperatura, el registro de configuración y los registros del termostato. Tabla de Registro del LM75 Registro Dirección Tamaño Función Temperature 0x00 2 bytes Contienen la última temperatura convertida Configuration 0x01 1 bytes Configura y controla la operación del LM75 T HYST Set Point 0x10 2 bytes Establece el límite superior del termostato T OS Set Point 0x11 2 bytes Establece el límite inferior del termostato El registro de temperatura tiene el siguiente formato, ya familiar. Obviamente los registros del termostato también tienen la misma estructura. Para ver ejemplos sobre la interpretación de este formato ir a la sección correspondiente del DS1621.
  • 865. Disposición del registro de temperatura del LM75. En el registro de configuración los 3 bits superiores no están implementados, los 4 bits siguientes pertenecen al termostato y no serán detallados aquí. Al final nos quedamos solos con el bit Shut Down. Registro de configuración del LM75 0 0 0 Fault Queue 1 Fault Queue 0 OS Polarity Com/Int ShutDown Registro de Microcontrolador Fault Queue 1: Fault Queue bits Fault Queue 0: Establecen la cantidad de veces que el dispositivo tendrá que detectar los valores de umbral para la función del termostato. Así funciona como filtro. OS Polarity Overtemperature Shutdown Polarity bit Establece si el pin de salida del termostato O.S. se activará en 1 ó en 0 Comp/Int Comparator Interrupt mode select bit Establece si el termostato operará en modo Comparador o deInterrupción ShutDown Shut Down bit 1 = Pone al LM75 en modo Shut Down o Standby 0 = El LM75 opera normalmente convirtiendo temperaturas sin cesar Acceso a los Registros del LM75 Todo lo que necesitamos saber es que el LM75 tiene la misma dirección de esclavo 0x90(incluyendo bit R/W = 0) y que los procedimientos de acceso... Bueno, mejor velos en el código de la siguiente práctica. Práctica: Uso del LM75 El hecho de que el LM75 sea similar al DS1621 se reflejará en que este programa es una adaptación del segundo programa del DS1621. Siempre se puede ignorar la parte fraccionaria pero nosotros seguiremos considerándola. Sería un desperdicio no hacerlo.
  • 866. Para tal caso mejor sería trabajar con un dispositivo ad hoc como el TC74, estudiado después de terminar esta práctica. El LM75 es quizá el sensor bandera deNational Semiconductors. Pero hay otros mejores de la misma familia LM7X. En adelante se pueden encontrar ejemplos con el LM76 o LM73. Circuito para el sensor de temperatura LM75 y el microcontrolador AVR. El código fuente /****************************************************************************** * FileName: main.c * Purpose: Uso del sensor de temperatura digital LM75 * Processor: ATmel AVR con módulo TWI * Compiler: AVR IAR C y AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. *
  • 867. * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta * licencia y las notas de autor y copyright de arriba. *****************************************************************************/ #include "avr_compiler.h" #include "usart.h" #include "i2c.h" /*** Prototipos de funciones ***/ voidlm75_config(char); intlm75_GetTemperature(void); voidDisplayTemp(void); voiddelay_ms(unsignedintt); /*** Función principal ***/ intmain(void) { unsignedchari; i2c_init();// 100 kHz usart_init();// 9600 - 8N1 printf("nr LM75: Sensor de temperatura"); while(1)
  • 868. { lm75_config(0x00);// Arrancar las conversiones continuas printf("nrr LM75 Convirtiendo..."); for(i=0;i<10;i++) { delay_ms(800);// Debe ser mayor que 300 ms DisplayTemp(); } lm75_config(0x01);// Entrar en modo Shutdown o Standby printf("nrr LM75 en Standby por 10s"); delay_ms(10000); } } //**************************************************************************** // Lee la temperatura del LM75 y la muestra en el terminal serial. //**************************************************************************** voidDisplayTemp(void) { inttemp; temp=lm75_GetTemperature();// Leer temperatura
  • 869. printf("rn Temp = %d.%d °C",temp/10,abs(temp%10)); } //**************************************************************************** // Escribe 'data' en el registro de Configuración del LM75 //**************************************************************************** voidlm75_config(chardata) { i2c_start(); i2c_write(0x90);// Slave address + Write i2c_write(0x01);// Configuration register address i2c_write(data);// Data to register i2c_stop(); } //**************************************************************************** // Lee la temperatura del LM75. // El valor devuelto está multiplicado por 10. //**************************************************************************** intlm75_GetTemperature(void) { intdata; signedcharhigh,low; i2c_start(); i2c_write(0x90);// Slave address + Write i2c_write(0x00);// Temperature register address
  • 870. i2c_restart(); i2c_write(0x90|0x01);// Slave address + Read high=i2c_read(0);// Read integer term low=i2c_read(1);// Read fractional term i2c_stop(); data=high; data*=10; if(low&0x80)// ¿ Sumar 5°C ? data+=5; returndata; } //**************************************************************************** // delay_ms() //**************************************************************************** voiddelay_ms(unsignedintt){ while(t--) delay_us(1000); } El Sensor de Temperatura LM76 Para ser honesto, ya me cansé de repetir casi las mismas cosas, así que dejaremos la teoría pertinente de lado aunque sea por esta ocasión. La única gran diferencia con que nos toparemos en estas prácticas con el LM76 y LM73 estará en el formato de los registros de temperatura. Práctica: Uso del LM76
  • 871. Circuito para el sensor de temperatura LM76 y el microcontrolador AVR. El código fuente /****************************************************************************** * FileName: main.c * Purpose: Uso del termómetro digital LM76 * Processor: ATmel AVR con módulo TWI * Compiler: AVR IAR C y AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. * * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta * licencia y las notas de autor y copyright de arriba.
  • 872. *****************************************************************************/ #include "avr_compiler.h" #include "usart.h" #include "i2c.h" /*** Prototipos de funciones ***/ intlm76_ReadTemperature(void); voidDisplayTemp(void); voidlm76_config(char); voiddelay_ms(unsignedintt); /*** Función principal ***/ intmain(void) { i2c_init();// 100 kHz usart_init();// 9600 - 8N1 printf("nr LM76: Digital Temperature Sensor"); while(1) { lm76_config(0x00);// Arrancar las conversiones continuas printf("nrr LM76 Convirtiendo..."); for(inti=0;i<10;i++)
  • 873. { delay_ms(1100);// Debe ser mayor que 1000 ms DisplayTemp(); } lm76_config(0x01);// Entrar en modo Shutdown o Standby printf("nrr LM76 en Standby por 10s"); delay_ms(10000); } } //**************************************************************************** // Lee la temperatura del LM76 y la muestra en el terminal serial. //**************************************************************************** voidDisplayTemp(void) { inttemp; temp=lm76_ReadTemperature();// Leer temperatura printf("rn Temp = %d.%d °C",temp/10,abs(temp%10)); } //**************************************************************************** // Escribe 'data' en el registro de Configuración del LM76
  • 874. //**************************************************************************** voidlm76_config(chardata) { i2c_start(); i2c_write(0x90);// Slave address + Write i2c_write(0x01);// Configuration register address i2c_write(data);// Data to Configuration register i2c_stop(); } //**************************************************************************** // Lee la temperatura del LM76. // Devuelve el valor de la temperatura multiplicado por 10. //**************************************************************************** intlm76_ReadTemperature(void) { constunsignedcharfrac[]={0,1,1,2,3,3,4,4,5,6,6,7,8,8,9,9}; signedintdata; signedcharhigh; unsignedcharlow; i2c_start(); i2c_write(0x90);// Slave address + Write i2c_write(0x00);// Temperature register address i2c_restart(); i2c_write(0x90|0x01);// Slave address + Read high=i2c_read(0);// Read first byte
  • 875. low=i2c_read(1);// Read second byte i2c_stop(); data=high; data*=20;// Multiplicar por 20 if(low&0x80) data+=10;// Sumar LSbit low&=0x7F;// Limpiar MSbit low>>=3;// Justificar a la derecha data+=frac[low];// Sumar parte fraccionaria redondeada returndata; } //**************************************************************************** // delay_ms() //**************************************************************************** voiddelay_ms(unsignedintt){ while(t--) delay_us(1000); } Práctica: Uso del LM73
  • 876. Circuito para el sensor de temperatura LM73 y el microcontrolador AVR. El código fuente /****************************************************************************** * FileName: main.c * Purpose: Uso del termómetro digital LM73: modo One shot y de 14 bits * Processor: ATmel AVR con módulo TWI * Compiler: AVR IAR C y AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. * * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta * licencia y las notas de autor y copyright de arriba.
  • 877. *****************************************************************************/ #include "avr_compiler.h" #include "usart.h" #include "i2c.h" /*** Prototipos de funciones ***/ voidDisplayTemp(void); voidlm73_write(charaddress,chardata); charlm73_ReadCtrlStat(void); intlm73_ReadTemperature(void); unsignedintlm73_ReadID(void); voiddelay_ms(unsignedintt); /* Posibles direcciones de esclavo (con bitRW = 0) del LM73 según su versión (LM73-0/1) y la conexión del pin ADDR */ //#define SLAVE 0x90 // LM73-0, ADDR pin -> float #define SLAVE 0x92 // LM73-0, ADDR pin -> GND //#define SLAVE 0x94 // LM73-0, ADDR pin -> VDD //#define SLAVE 0x98 // LM73-1, ADDR pin -> float //#define SLAVE 0x9A // LM73-1, ADDR pin -> GND //#define SLAVE 0x9C // LM73-1, ADDR pin -> VDD #define CONFIG 0X00 // CONFIGURATION register address #define CTRL_STAT 0X04 // CONTROL/STATUS register address
  • 878. /*** Función principal ***/ intmain(void) { i2c_init();// 100 kHz usart_init();// 9600 - 8N1 printf("nr LM73: Digital Temperature Sensor"); if((lm73_ReadID()&0xFFF0)==0x0190) printf("nrr A National LM73 Thermometer has been detected."); // Establecer resolución de 14 bits e inhabilitar los temporizadores de // bus-idle de los pines SCL y SDA para ahorrar más enegía. lm73_write(CTRL_STAT,0xE0);// Write to CONTROL/STATUS register // El LM73 inicia en modo de Conversiones Continuas. En seguida lo // pondremos en modo Standby para establecer una operación One-Shot. // Va seguido de un delay > 112 ms (máximo tiempo de conversión para // una resolución de 14 bits) para que termine la conversion actual // y asegurar un buen ingreso en modo Standby :) lm73_write(CONFIG,0x80);// Write to CONFIGURATION register delay_ms(120);
  • 879. printf("nrr Press ENTER to get temperature"); while(1) { if(kbhit())// Si llegó algún dato { if(getchar()=='r')// Y si es ENTER DisplayTemp(); } } } //**************************************************************************** // Lee la temperatura del LM73 y la muestra en el terminal serial. //**************************************************************************** voidDisplayTemp(void) { inttemp; chardav; // Inicia una conversión de temperatura seteando el bit ONE SHOT y // manteniendo a 1 el bit PD (Power Down) del registro CONFIGURATION lm73_write(CONFIG,0x84);// Write to CONFIGURATION register // El siguiente bucle espera a que termine la conversión. Como el LM73
  • 880. // opera con resolución de 14 bits, puede sustituirse por un delay > 112ms do{ delay_ms(5); dav=lm73_ReadCtrlStat();// Read CONTROL/STATUS register }while((dav&0x01)==0);// If DAV bit = 1 => Conversion is finished temp=lm73_ReadTemperature();// Leer temperatura printf("rn Temp = %d.%d °C",temp/100,abs(temp%100)); } //**************************************************************************** // Escribe el byte 'data' en el registro de dirección 'address' del LM73 //**************************************************************************** voidlm73_write(charaddress,chardata) { i2c_start(); i2c_write(SLAVE);// Slave address + Write i2c_write(address);// Register address i2c_write(data);// Data to Config register i2c_stop(); } //**************************************************************************** // Lee el registro de CONTROL/STATUS del LM73 //****************************************************************************
  • 881. charlm73_ReadCtrlStat(void) { chardata; i2c_start(); i2c_write(SLAVE);// Slave address + Write i2c_write(CTRL_STAT);// CONTROL/STATUS register address i2c_restart(); i2c_write(SLAVE|0x01);// Slave address + Read data=i2c_read(1);// Get register data i2c_stop(); returndata; } //**************************************************************************** // Lee la temperatura del LM73. // Devuelve el valor de la temperatura multiplicado por 100. //**************************************************************************** intlm73_ReadTemperature(void) { constunsignedcharfrac[]={0,3,6,9,13,16,19,22,25,28,31,34,38,41,44, 47,50,53,56,59,63,66,69,72,75,78,81,84,88,91,94,97}; intdata; signedcharhigh; unsignedcharlow; i2c_start(); i2c_write(SLAVE);// Slave address + Write
  • 882. i2c_write(0x00);// TEMPERATURE register address i2c_restart(); i2c_write(SLAVE|0x01);// Slave address + Read high=i2c_read(0);// Read MSbyte low=i2c_read(1);// Read LSbyte i2c_stop(); data=high; data*=200;// Multiplicar por 200 if(low&0x80) data+=100;// Sumar LSbit low&=0x7F;// Limpiar MSbit low>>=2;// Justificar a la derecha data+=frac[low];// Sumar parte fraccionaria redondeada returndata; } //**************************************************************************** // Lee el registro IDENTIFICATION del LM73 //**************************************************************************** unsignedintlm73_ReadID(void) { unsignedintid; i2c_start(); i2c_write(SLAVE);// Slave address + Write i2c_write(0x07);// IDENTIFICATION register address
  • 883. i2c_restart(); i2c_write(SLAVE|0x01);// Slave address + Read id=i2c_read(0);// MSbyte id<<=8; id|=i2c_read(1);// LSbyte i2c_stop(); returnid; } //**************************************************************************** // delay_ms() //**************************************************************************** voiddelay_ms(unsignedintt){ while(t--) delay_us(1000); } El Mini Sensor de Temperatura TC74 Éste es un pequeño termómetro fabricado por Microchip. Es ideal para proyectos sencillos por su bajo precio y porque cuenta con los recursos mínimamente necesarios. No tiene función de termostato ni memorias RAM o EEPROM adicionales. Viene en empaques pequeños y con una forma (T0-220) que permite captar rápidamente la temperatura del medio o material al que se le aplica.
  • 884. El mini sensor de temperatura TC74 en empaque TO-220. Las características generales del sensorTC74 para comparar su performance con la de otros termómetros son: Tiene una resolución de 8 bits y permite medir temperaturas desde –40°C hasta +125°C con incrementos de 1°C. Su tiempo de conversión típico es de 125 ms, salvo la primera conversión (después del POR), la cual puede durar hasta 250 ms. Máxima velocidad de transferencia de datos de 100 kbit/s (Standard Mode). Se pueden encontrar con 8 direcciones de esclavo diferentes para conectar hasta 8 termómetros TC74 en un mismo bus I2C. Operación del TC74 En cuanto a la conversión de temperatura el TC74 solo tiene un modo de operación, el cual sería comparable con el modo de conversiones continuas del DS1621 (o similar). Mientras esté activado, el TC74 convertirá la temperatura constantemente y depositará el resultado en el registro de temperatura, el cual se puede leer en todo momento. El TC74 también puede ser puesto en modo Standby para reducir el consumo de energía. Al salir del Standby el TC74 reanuda la conversión de inmediato. Sabemos que en la práctica podemos combinar las características descritas para manejar el termómetro de forma que parezca una operación tipo One Shot. Los Registros del TC74 La sencillez de este dispositivo se refleja en el hecho de que solo tiene dos registros y de 8 bits cada uno. Como siempre TEMP es de solo lectura y CONFIG es de lectura/escritura. Ambos se inician con 0x00 tras el reset POR. Tabla Registro
  • 885. Registro Dirección Función TEMP 0x00 CONFIG 0x01 Contiene la última temperatura convertida Registro de configuración A continuación, el mapa de bits del registro CONFIG. Los 6 primeros bits no están implementados y siempre se leerán como 0s. Registro CONFIG del TC74 SHDN DATA_RDY 0 0 0 0 0 0 Registro de Microcontrolador SHDN Shut Down bit (read-write bit) 0 = El TC74 opera normalmente convirtiendo las temperaturas sucesivamente 1 = El TC74 permanece en estado Standby DATA_RD Y Data Ready bit (read only bit) 0 = La conversión está en progreso 1 = La conversión de temperatura ha terminado En el TC74 una conversión de temperatura dura como mucho 150 ms, excepto la primera, la cual puede llegar a 250 ms. Se puede evitar el sondeo de este bit poniendo un delay apropiado. Este bit es de solo lectura. Se limpia automáticamente por hardware al entrar en modo Standby o luego de un reset. El registro de temperatura TEMP almacena la temperatura convertida más recientemente. Su valor no cambiará si el dispositivo entra en modo Standby. El formato del dato sigue siendo de complemento a 2 y con el primer bit como signo. Si tuvieras dudas sobre esto, podrías revisar la teoría relacionada del DS1621. El acceso a ambos registros es de modo individual y siguiendo el procedimiento esperado, los cuales se exponen en el código de la siguiente práctica. Dirección de Esclavo del TC74
  • 886. Es posible poner en paralelo hasta 8 sensores TC74 en un bus I2C. La pregunta es ¿cómo se hace esto si este sensor no tiene pines de dirección como otros dispositivos I2C? Sucede que en realidad el TC74 puede venir con diferentes direcciones de esclavo. Cada uno se identifica con un código diferente inscrito en su empaque. De este código lo que ahora nos interesa es el AX que sigue al TC74. Allí la X vale por los 3 bits inferiores de la dirección de esclavo. Los 4 bits superiores son siempre iguales a 1001. Por ejemplo, el termómetro que Microchip distribuye como dispositivo genérico tiene el código TC74A5-xxx (el valor de las xxx no viene al caso), es decir, su dirección de esclavo es 1001 101. Un TC74 estándar en empaque TO-220 con código completo. Los otros códigos pertenecen a los TC74 con las siguientes direcciones (de todos modos, estos modelos no son fáciles de hallar): El TC74A0-XXX tiene dirección de esclavo 1001 000 El TC74A1-XXX tiene dirección de esclavo 1001 001 El TC74A2-XXX tiene dirección de esclavo 1001 010 El TC74A3-XXX tiene dirección de esclavo 1001 011 El TC74A4-XXX tiene dirección de esclavo 1001 100 El TC74A5-XXX tiene dirección de esclavo 1001 101 El TC74A6-XXX tiene dirección de esclavo 1001 110 El TC74A7-XXX tiene dirección de esclavo 1001 111 Práctica: Uso del TC74 El programa es una adaptación más de los tantos programas vistos anteriormente. El TC74 permanece en modo Standby y se reactiva cuando el microcontrolador recibe un
  • 887. ENTER por el puerto serie. Después de leer la temperatura se vuelve a enviar el termómetro a modo Standby. Circuito para el sensor de temperatura TC74 y el microcontrolador AVR. El código fuente /****************************************************************************** * FileName: main.c * Purpose: Uso del mini sensor digital de temperatura TC74 * Processor: ATmel AVR con módulo TWI * Compiler: AVR IAR C y AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. * * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta
  • 888. * licencia y las notas de autor y copyright de arriba. *****************************************************************************/ #include "avr_compiler.h" #include "usart.h" #include "i2c.h" /*** Prototipos de funciones ***/ voidDisplayTemp(void); voidtc74_config(chardata); chartc74_read(charaddress); /*** Función principal ***/ intmain(void) { i2c_init();// 91 kHz usart_init();// 9600 - 8N1 printf("nr TC74: Tiny Serial Digital Thermal Sensor"); tc74_config(0x80);// Poner TC74 en Standby printf("nrr Pulse ENTER para obtener la temperatura "); while(1) {
  • 889. if(kbhit())// Si llegó algún dato { if(getchar()=='r')// Y si es ENTER DisplayTemp(); } } } //**************************************************************************** // Obtiene la temperatura del TC74 y la muestra en el terminal serial. //**************************************************************************** voidDisplayTemp(void) { signedchartemp; charconf; tc74_config(0x00);// Arrancar el conversor /* El siguiente bucle espera a que termine la conversión. Eso pasará * cuando el bit DATA_RDY (bit 6) del registro CONFIG se active a 1. * Esta rutina se puede sustituir por un delay > 125ms, ó > 250ms si es * la primera conversión. */ do{// delay_us(10000);// Para no sondear demasiado seguido conf=tc74_read(0x01);// Leer registro CONFIG
  • 890. }while((conf&0x40)==0);// Hasta que conf.6 = 1 temp=tc74_read(0x00);// Leer temperatura tc74_config(0x80);// Poner TC74 en Standby printf("rn Temp = %d °C",temp); } //**************************************************************************** // Escribe 'data' en el registro de configuración (CONFIG) del TC74. //**************************************************************************** voidtc74_config(chardata) { i2c_start(); i2c_write(0x9A);// Slave address + Write i2c_write(0x01);// CONFIG register address i2c_write(data);// Data to register i2c_stop(); } //**************************************************************************** // Lee el registro de dirección 'address' del TC74. // - address = 0x00 => Registro de temperatura TEMP // - address = 0x01 => Registro de configuración CONFIG //****************************************************************************
  • 891. chartc74_read(charaddress) { signedchardata; i2c_start(); i2c_write(0x9A);// Slave address + Write i2c_write(address);// Register address i2c_restart(); i2c_write(0x9A|0x01);// Slave address + Read data=i2c_read(1);// Read register and send NACK i2c_stop(); returndata; } Práctica: Uso del TMP100 TMP101
  • 892. Circuito para el sensor de temperatura TMP100 y el microcontrolador AVR. El código fuente /****************************************************************************** * FileName: main.c * Purpose: Uso del termómetro digital TMP100/TMP101 * Processor: ATmel AVR con módulo TWI * Compiler: AVR IAR C y AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. * * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta * licencia y las notas de autor y copyright de arriba. *****************************************************************************/ #include "avr_compiler.h" #include "usart.h" #include "i2c.h" /* Posibles direcciones de esclavo (con bitRW = 0) del TMP100/101 según su versión (TMP100 - TMP101) y la conexión de los pines ADD0 - ADD1 */ // #define SLAVE 0x90 // TMP101, ADD0 -> GND // #define SLAVE 0x92 // TMP101, ADD0 -> Float // #define SLAVE 0x94 // TMP101, ADD0 -> VDD
  • 893. #define SLAVE 0x90 // TMP100, ADD0 -> GND, ADD1 -> GND // #define SLAVE 0x92 // TMP100, ADD0 -> Float, ADD1 -> GND // #define SLAVE 0x94 // TMP100, ADD0 -> VDD, ADD1 -> GND // #define SLAVE 0x98 // TMP100, ADD0 -> GND, ADD1 -> VDD // #define SLAVE 0x9A // TMP100, ADD0 -> Float, ADD1 -> VDD // #define SLAVE 0x9C // TMP100, ADD0 -> VDD, ADD1 -> VDD // #define SLAVE 0x96 // TMP100, ADD0 -> GND, ADD1 -> Float // #define SLAVE 0x9E // TMP100, ADD0 -> VDD, ADD1 -> Float /*** Prototipos de funciones ***/ inttmp100_ReadTemperature(void); voidDisplayTemp(void); voidtmp100_config(char); voiddelay_ms(unsignedintt); /*** Función principal ***/ intmain(void) { i2c_init();// 100 kHz usart_init();// 9600 - 8N1 printf("nr TMP100: Digital Temperature Sensor"); // Configurar el TMP100/101 con una resolución de 12 bits y ponerlo // en modo Standby para realizar conversiones One shot. // Se entrará realmente en Standby cuando se TMP100/101 termine la
  • 894. // conversión actual. tmp100_config(0x61); printf("nrr Press ENTER to get Temperature"); while(1) { if(kbhit())// Si llegó algún dato { if(getchar()=='r')// Y si es ENTER DisplayTemp(); } } } //**************************************************************************** // Lee la temperatura del TMP100/101 y la muestra en el terminal serial. //**************************************************************************** voidDisplayTemp(void) { inttemp; tmp100_config(0xE1);// Iniciar conversión // No hay forma de saber el momento preciso en que termina la conversión,
  • 895. // así que habrá que esperar el tiempo adecuado de acuerdo con la // resolución establecida para el TMP100/101, más de 320 ms para 12 bits delay_ms(350);// > 320 ms for 12 bits resolution temp=tmp100_ReadTemperature();// Leer temperatura tmp100_config(0x61);// Clear OS/ALERT bit ??? printf("rn Temp = %d.%d °C",temp/10,abs(temp%10)); } //**************************************************************************** // Escribe 'data' en el registro de Configuración del TMP100/101 //**************************************************************************** voidtmp100_config(chardata) { i2c_start(); i2c_write(SLAVE);// Slave address + Write i2c_write(0x01);// Configuration register address i2c_write(data);// Data to Configuration register i2c_stop(); } //**************************************************************************** // Lee la temperatura del TMP100/101.
  • 896. // Devuelve el valor de la temperatura multiplicado por 10. //**************************************************************************** inttmp100_ReadTemperature(void) { constunsignedcharfrac[]={0,1,1,2,3,3,4,4,5,6,6,7,8,8,9,9}; signedintdata; signedcharhigh; unsignedcharlow; i2c_start(); i2c_write(SLAVE);// Slave address + Write i2c_write(0x00);// Temperature register address i2c_restart(); i2c_write(SLAVE|0x01);// Slave address + Read high=i2c_read(0);// Read first byte low=i2c_read(1);// Read second byte i2c_stop(); data=high; data*=10;// Multiplicar por 10 low>>=4;// Justificar a la derecha data+=frac[low];// Sumar parte fraccionaria redondeada returndata; } //**************************************************************************** // delay_ms()
  • 897. //**************************************************************************** voiddelay_ms(unsignedintt){ while(t--) delay_us(1000); } El Expansor de E/S PCA9554/A Cuando yo veo que me faltan pines en un microcontrolador para alguna aplicación, pues busco otro microcontrolador. Además también está la opción de usar registros de desplazamiento (como los 74HC597 y74HC595.) para ampliar las señales de entrada o salida del sistema si fuera necesario. Pero supongo que si estos expansores existen, es porque deben servir para algo, o para alguien. En fin... Al ser de poco uso, no hay muchos dispositivos expansores. Quizá el PCF8574 sea el más conocido. El PCF8574 tiene un diseño pedestre y poco robusto a mi criterio. Por otro lado están los expansores como elMCP23008 de Microchip, que, aunque de gran velocidad, son muy pesados de manejar. El PCA9554 es de NXP Semiconductors y es la versión mejorada del PCF8574. Aun así, te puedo garantizar que es más fácil de usar: es casi tan simple como manejar un puerto cualquiera de un AVR. Tan solo reconoce las siguientes características del PCA9554. Provee 8 pines de E/S bidireccionales configurables pin por pin (¿familiar?). Todos los pines configurados como entradas tienen resistencias de pull-up. Puede provocar una interrupción si alguno de los pines de entrada cambia su nivel. Velocidad de operación en modo Fast Mode (hasta de 400 kbits/s). El PCA9554A es el hermano del PCA9554. Solo los diferencia la dirección de dispositivo. Descripción de Pines del PCA9554 / A El PCA9554 viene en diversos empaques. El siguiente diagrama corresponde al PDIP.
  • 898. Diagrama de pines del PCA9554 / PCA9554A. IO0...IO7. Pines de entrada/salida. Los pines configurados como entradas se ponen en alta impedancia gracias a sus resistencias internas de pull-up de 100 k. Como fuente pueden entregar hasta 85 mA en total y como sumidero pueden recibir hasta 100 mA en total. INT. Este pin se activa a 0 cuando se detecta un cambio de tensión en cualquier pin de E/S configurado como entrada. Esa condición permanecerá hasta que se lea el puerto o hasta que el pin de E/S retorne a su estado previo. Es de drenador abierto y necesita de una pull-up externa. VDD y VSS. Pines de alimentación A2, A1 y A0. Pines para configurar parte de la dirección de dispositivo. SDA y SCL. Pines de interface I2C. Registros del PCA9554 / A Tabla Dirección Dirección Registro Función 0x00 Input Port Leer pines del puerto 0x01 Output Port Escribir en pines del puerto 0x10 Polarity Inversion Invertir lectura de pines del puerto 0x11 Configuration Configurar dirección de pines del puerto
  • 899. Input Port. Leyendo este registro se obtienen los valores de los pines del puerto, tanto si están configurados como entradas o como salidas. Es de solo lectura y las escrituras no tienen sentido. Output Port. Escribiendo en este registro se establecen los niveles de los pines configurados como salidas. No surte efecto en los pines de entrada. La lectura de este registro no es de utilidad. Configuration. Escribiendo en este registro se configura la dirección de los pines del puerto. Un 1 es para entrada y un 0 es para salida. Funciona parecido a los registros DDR de los AVR, pero al revés. Tras encender la alimentación todos los pines son entradas. Polarity Inversion. Si está todo a 0, no pasa nada. Si escribimos 1s en los bits de este registro, haremos que la lectura de los pines correspondientes se invierta. Por ejemplo, si en el puerto hay 0xF0 y Polarity Inversion vale 0xFF, el valor leído deInput Port será 0x0F. ¡Tanto para eso! Si quisiéramos invertir algún dato, sabemos que bien lo podemos hacer vía software. Francamente, no le encuentro sentido a este registro. Dirección de Esclavo del PCA9554 / A He aquí la única diferencia entre el PCA9554 y el PCA9554A: la dirección de esclavo. Verás a su vez que son las mismas direcciones que las del PCF8574 y del PCF8574A. Al igual que en las EEPROM 24xxxx, los bits A2, A1 y A0 corresponden a la conexión de los pines del mismo nombre. Tabla Dirección de Esclavo del PCA9554 Byte de control (Slave address + bit R/W) del PCA9554 y PCA9554A, respectivamente. Acceso a los Registros del PCA9554 / A Conociendo la dirección de esclavo, los registros, sus direcciones y para qué sirven, y las transferencias de datos en el protocolo I2C, el resto es como armar un rompecabezas de piezas conocidas, salvo una: En el PCA9554 el puntero de registros no se incrementa automáticamente luego de cada acceso. Eso significa que si se ejecutará por ejemplo una lectura secuencial (procedimiento válido), se leería el mismo registro una y otra vez.
  • 900. Acceso a los registros del PCA9554 para escritura. Acceso a los registros del PCA9554 para lectura. Práctica 1 Uso del PCA9554 / A Se controla un keypad o teclado de 4×4 con el PCA9554.
  • 901. Circuito para el expansor PCA9554A y el microcontrolador AVR. El código fuente /****************************************************************************** * FileName: main.c * Purpose: Expansor de E/S para bus I2C PCA9554. Control de teclado. * Processor: ATmel AVR con módulo TWI * Compiler: AVR IAR C y AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. *
  • 902. * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta * licencia y las notas de autor y copyright de arriba. *****************************************************************************/ #include "avr_compiler.h" #include "usart.h" #include "i2c.h" // Direcciones de los registros del PCA9554/PCA9554A #define PortIn 0x00 // Input Port register address #define PortOut 0x01 // Output Port register address #define PolarInv 0x02 // Polarity Inversion register address #define Config 0x03 // Configuration register address /*** Prototipos de funciones ***/ voidpca9554_write(charaddress,chardata); charpca9554_read(charaddress); charkeypad_scan(void); /*** Función principal ***/ intmain(void) { charkey; i2c_init();// 100 kHz
  • 903. usart_init();// 9600 - 8N1 puts("nr PCA9554: Expansor de E/S para bus I2C"); puts("nr =====================================nr"); // Establecer polaridad normal del puerto, no invertida pca9554_write(PolarInv,0x00); // Configurar dirección de puerto: nibble alto entrada y nibble bajo salida pca9554_write(Config,0xF0); // Escribir 0xF0 en puerto del PCA9554 pca9554_write(PortOut,0xF0); while(1) { if((PINC&0x08)==0)// ¿PC3 = 0? ¿INT = 0? { if(key=keypad_scan()) { putchar(key); delay_us(30000);// Debounce while(keypad_scan());// Esperar teclado libre } } }
  • 904. } //**************************************************************************** // Escribe 'data' en el registro de dirección 'address' del PCA9554. // Se asume que los pines A2, A1 y A0 están conectados a GND. //**************************************************************************** voidpca9554_write(charaddress,chardata) { i2c_start(); i2c_write(0x40);// Slave address + Write i2c_write(address);// Register address i2c_write(data);// Data to register i2c_stop(); } //**************************************************************************** // Lee el registro de dirección 'address' del PCA9554. // Se asume que los pines A2, A1 y A0 están conectados a GND. //**************************************************************************** charpca9554_read(charaddress) { charreg; i2c_start(); i2c_write(0x40);// Slave address + Write i2c_write(address);// Register address i2c_start();// CCS C forces a Restart here
  • 905. i2c_write(0x40|0x01);// Slave address + Read reg=i2c_read(1);// Data from register and send NACK i2c_stop(); returnreg; } //**************************************************************************** // Escanea el teclado y retorna el valor ASCII de la primera tecla que // encuentre pulsada. De otro modo retorna 0x00. //**************************************************************************** charkeypad_scan(void) { unsignedcharCol,Row; charRowMask,ColMask,val,key=0x00; // Col0 Col1 Col2 Col3 constcharkeys[4][4]={{'7','8','9','A'},// Row 0 {'4','5','6','B'},// Row 1 {'1','2','3','C'},// Row 2 {'.','0','#','D'}};// Row 3 RowMask=0xFE;// Inicializar RowMask 11111110; for(Row=0;Row<4;Row++) { pca9554_write(PortOut,RowMask);
  • 906. ColMask=0x10;// Inicializar ColMask 00010000 for(Col=0;Col<4;Col++) { val=pca9554_read(PortIn); if((val&ColMask)==0)// ¿Tecla pulsada ? { key=keys[Row][Col];// Guardar código de tecla pulsada goto_exit; } ColMask<<=1;// Desplazar ColMask para escanear }// siguiente columna RowMask<<=1;// Desplazar RowMask para escanear RowMask|=0x01;// siguiente fila } _exit: // Escribir 0xF0 en puerto del PCA9554 pca9554_write(PortOut,0xF0); returnkey;// Retornar código hallado }
  • 907. El Expansor de E/S PCF8574/A El PCF8574 es un expansor con puerto de 8 pines paralelos, parecido al PCA9554, pero más rústico. El PCF8574 se diferencia del PCF8574A por la dirección de esclavo. Sus principales características son: Provee 8 pines cuasi-bidireccionales. Siempre puede actuar como entrada pero como salida tiene limitaciones. Como entrada cada pin activa su resistencia de pull-up. El acceso al puerto es directo. No tiene registros de lectura o escritura ni registro de configuración de dirección de los pines. Puede soportar grandes corrientes en su puerto de I/O como sumidero (hasta 100 mA sumando todos los pines), pero no como fuente. En un bus se pueden colgar hasta 8 PCF8574 y 8 PCF8574A más configurando parte de sus direcciones de esclavo con los pines A2, A1 y A0. Velocidad de bus I2C de hasta 100 kbits/s (Standard Mode). Diagrama de pines del PCF8574 / PCF8574A en empaque PDIP. El pin INT es de drenador abierto y necesitará de una resistencia de pull-up externa para ponerse en alto. Estará en ese estado hasta que cualquiera de los pines de I/O que actúan como entradas detecte un cambio de nivel, momento en que INT se activa a 0. INTvolverá a alto cuando el pin de I/O que cambió retorne a su estado o cuando sea leído o cuando sea escrito. Operación del PCF8574 / A ¿Qué significa que el PCF8574/ PCF8574A tenga un puerto cuasi-bidireccional y que no tenga registros para configurar la dirección de sus pines? Lo explico rápido. Al escribir 0 en un pin, éste se convierte automáticamente en salida y su estado será, por supuesto, bajo.
  • 908. Al escribir 1 en un pin, éste se convierte automáticamente en entrada. Pero incluso así, a su salida se verá un nivel alto gracias a su resistencia de pull-up. Este estado alto puede ser suficiente para excitar las entradas de otros dispositivos como ICs o transistores, aunque será incapaz de sostenerse como para encender un LED por la poquísima corriente que deja pasar la pull-up. La lectura de los pines del puerto devuelve sus valores actuales, ya sea que se estén comportando como entradas o como salidas. Tras encender la alimentación todos los pines se comportan como entradas. Acceso al Puerto del PCF8574 / A Como el PCF8574 no tiene registros de puerto ni de configuración, el acceso a su puerto es directo: se envía el byte de control (dirección de esclavo + orden de lectura/escritura) y se efectúa la operación respectiva. Como tampoco tiene un puntero de registros, las operaciones secuenciales acceden siempre al mismo puerto. Por lo menos evita enviar START y STOP a cada rato. Las direcciones de esclavo del PCF8574 y PCF8574A son las mismas que para el PCA9554 y PCA9554A, respectivamente. Puedes verlas en Dirección de Esclavo del PCA9554 / PCF8574A. Acceso al puerto del PCF8574 para escritura.
  • 909. Acceso al puerto del PCF8574 para lectura. Práctica 2 Uso del PCF8574 / A Esta vez usamos el expansor para controlar un display LCD. El modo de acceso es mediante un bus de 4 bits y sin usar el bit busy flag. Circuito para el expansor PCF8574 y el microcontrolador AVR. El código fuente /****************************************************************************** * FileName: main.c * Purpose: Expansor de E/S para bus I2C PCF8574. Control de LCD. * Processor: ATmel AVR con módulo TWI * Compiler: AVR IAR C y AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com.
  • 910. * * Copyright (C) 2008 - 2013 Shawn Johnson. All rights reserved. * * License: Se permiten el uso y la redistribución de este código con * modificaciones o sin ellas, siempre que se mantengan esta * licencia y las notas de autor y copyright de arriba. *****************************************************************************/ #include "avr_compiler.h" #include "i2c.h" voidlcd_init(void); voidlcd_write(chardat); voidlcd_nibble(charnibble); voidlcd_puts(char*); voidlcd_gotorc(charr,charc); voidlcd_clear(void); voidlcd_clear(void); voiddelay_ms(unsignedintt); staticcharlcd_DBUS; #define lcd_E 1 // Pin 1 de PCF8574 -> Enable #define lcd_RS 0 // Pin 0 de PCF8574 -> Resietr Select intmain(void) {
  • 911. i2c_init();// 100 kHz lcd_init(); while(1){ lcd_puts(" CURSOMICROS "); lcd_gotorc(2,1); lcd_puts(" LCD on PCF8574 "); delay_ms(700); lcd_clear(); delay_ms(700); } } //**************************************************************************** // Ejecuta la inicialización software completa del LCD. La configuración es: // Interface de 4 bits, despliegue de 2 líneas y caracteres de 5x7 puntos. //**************************************************************************** voidlcd_init(void) { lcd_DBUS=0x00; i2c_start(); i2c_write(0x40);// PCF8574 Slave address + Write i2c_write(lcd_DBUS); i2c_stop(); delay_ms(45);// > 40 ms lcd_nibble(0x30);// Function Set: 8-bit
  • 912. delay_ms(5);// > 4.1 ms lcd_nibble(0x30);// Function Set: 8-bit delay_ms(1);// > 100 µs lcd_nibble(0x30);// Function Set: 8-bit delay_ms(1);// > 40 µs lcd_nibble(0x20);// Function Set: 4-bit delay_ms(1);// > 40 µs lcd_nibble(0x20);// Function Set: 4-bit, 2lines, 4×7font lcd_nibble(0x80);// lcd_write(0x0C);// Display ON/OFF Control lcd_write(0x01);// Clear Display delay_ms(2); lcd_write(0x06);// Entry Mode Set } //**************************************************************************** // Envía una instrucción al LCD. // Pre-condición: Estado de línea lcd_RS // - Si lcd_RS = 0, 'dat' va al Registro de Comandos // - Si lcd_RS = 1, 'dat' va a la DDRAM o CGRAM //**************************************************************************** voidlcd_write(chardat) { lcd_nibble(dat);// Nibble alto lcd_nibble(dat<<4);// Nibble bajo }
  • 913. //**************************************************************************** // Envía el nibble alto de 'nibble' al LCD. //**************************************************************************** voidlcd_nibble(charnibble) { i2c_start(); i2c_write(0x40);// PCF8574 Slave address + Write lcd_DBUS=(nibble&0xF0)|(lcd_DBUS&0x0F); i2c_write(lcd_DBUS); lcd_DBUS|=(1<<lcd_E);// E high i2c_write(lcd_DBUS); lcd_DBUS&=~(1<<lcd_E);// E low i2c_write(lcd_DBUS); i2c_stop(); } //**************************************************************************** // Envía una instrucción de dato al LCD. //**************************************************************************** voidlcd_puts(char*s) { unsignedcharc,i=0; lcd_DBUS|=(1<<lcd_RS); while(c=s[i++]) lcd_write(c);
  • 914. } //**************************************************************************** // Ubica el cursor del LCD en la columna c de la línea r. //**************************************************************************** voidlcd_gotorc(charr,charc) { if(r==1)r=0x80;// Línea 1 elser=0xC0;// Línea 2 lcd_DBUS&=~(1<<lcd_RS); lcd_write(r+c-1); } //**************************************************************************** // Limpia la pantalla del LCD y regresa el cursor al inicio. //**************************************************************************** voidlcd_clear(void) { lcd_DBUS&=~(1<<lcd_RS); lcd_write(0x01); delay_ms(2);// > 1.5 ms } //**************************************************************************** // delay_ms() //****************************************************************************
  • 915. voiddelay_ms(unsignedintt){ while(t--) delay_us(1000); } Un curso de microcontroladores como éste implica abarcar tres áreas: Conocer el microcontrolador. Un microcontrolador es un circuito integrado genérico cuyas partes debemos adaptar para que funcionen según los requerimientos de nuestro diseño. Obviamente no podríamos programar lo que no conocemos. Conocer los periféricos externos. Un microCONTROLADOR no sería muy útil si no tiene qué controlar. Muchos dispositivos a controlar o mediante los cuales se va a controlar son comunes de la electrónica analógica, como transistores, relés, diodos LED, registros de desplazamiento e incluso los motores, y se da por hecho que el lector ya conoce lo suficiente de ellos. También están los periféricos que difícilmente pudo el alumno haber operado antes sin ayuda de un microcontrolador o una computadora, como por ejemplo, LCDs, los motores de pasos, los sensores de temperatura digitales, etc. Es todo este segundo grupo de periféricos externos el que se cubre en un curso de microcontrolador como éste. Conocer un lenguaje de programación. Conocer un lenguaje de programación es un mundo aparte y es raro que una persona trate de conocer un microcontrolador al mismo tiempo que va aprendiendo el lenguaje. El lenguaje C en particular es un tema que normalmente se aprende por separado. Los lenguajes de alto nivel son mucho más potentes que el ensamblador aunque su aprendizaje demanda un mayor esfuerzo. Para empezar a programar en ensamblador nos puede bastar con aprender unas 50 palabras (las instrucciones básicas). En cambio dominar un lenguaje de alto nivel como el C es como aprender a hablar en un nuevo idioma. No basta con memorizar palabras nuevas, sino que debemos aprender a manejar una nueva estructura gramatical. Además, los procesadores no son como las personas: si en un código de 100 líneas te olvidaste de una sola coma, los compiladores no te lo pasarán por alto. Por qué C y no Basic Ciertamente, el Basic es el lenguaje más fácil de aprender (no es exactamente la razón de su nombre). Y aunque los programadores en C de computadoras miren con desdén a los que usan el Basic, en el mundo de los microcontroladores los compiladores Basic no tienen motivo para sentirse menos. De hecho, algunos pueden ser casi tan eficientes como los mejores compiladores C.
  • 916. Las características (muchas veces complejas) del C fueron ideadas para el trabajo con sofisticados proyectos, propios de las computadoras. Muchas de esas características ya no resultan tan ventajosas en el limitado hardware de los microcontroladores y se convierten en prescindibles. La simplicidad de los compiladores Basic para microcontroladores también permite que varios de ellos mantengan una compatibilidad entre sus códigos que no se encuentra entre los compiladores C. Ésas podrían ser razones más que convincentes para empezar por el Basic y, de hecho, es la opción que muchos han elegido en especial con los microcontroladores PIC. ¿Por qué nosotros no? Porque es verdad comprobable que los mejores programadores trabajan en C (no siempre exclusivamente, pero lo manejan). Por consiguiente, los proyectos más fantásticos que se pueden encontrar están en C. Es más, la mayoría de, por no decir todos, los programadores de Basic tarde o temprano se ven obligados a aprender el C. No sé tú, pero yo opino que esa razón pesa más. Además, dada la robustez y la aceptación del lenguaje C, se lo ha tomado como referencia para lenguajes de otros propósitos como Java, JavaScript, php o de Matlab, entre otros. Así que, el C podrá servirte para trabajar en otros campos. El programador de C podría, inclusive, aprender luego el Basic sin el menor esfuerzo; lo contrario no es cierto. Pienso que, comparado con el Basic para microcontroladores, el C es infinitamente más difícil de aprender. Quienes lo usan, en gran parte, son personas que han tenido experiencia programando computadoras, personas que han estudiado más de un libro para dominarlo. Es literalmente como aprender un nuevo idioma, y eso no es algo que se hace de la noche a la mañana. ¿Eso no suena muy alentador? Para simplificar las cosas, en este capítulo no voy a exponer todas las reglas del lenguaje C, aunque sí la mayoría; digamos el 95 % de lo necesario. El resto: o es solo aplicable a las computadoras, o son temas raros o que difieren demasiado entre de compilador a otro y conviene más revisarlos en sus respectivos manuales. También, y para ahorrar los ejemplos prácticos, asumo que no eres un novato cualquiera, asumo que conoces algo de programación (aunque sea en ensamblador), que sabes cómo usar las subrutinas, que sabes cómo emplear los bucles, que sabes lo que significa redirigir el flujo de un programa, que sabes para qué sirven las variables, etc. Estructura de un programa en C Tomaremos en cuenta este sencillísimo ejemplo, escrito para los compiladores AVR IAR C y AVR GCC. /******************************************************************************
  • 917. * FileName: main.c * Purpose: LED parpadeantwe * Processor: ATmel AVR * Compiler: AVR IAR C & AVR GCC (WinAVR) * Author: Shawn Johnson. http://www.cursomicros.com. *****************************************************************************/ #include "avr_compiler.h" //**************************************************************************** // delay_ms //**************************************************************************** voiddelay_ms(unsignedintt) { while(t--) delay_us(1000); } //**************************************************************************** // Función principal //**************************************************************************** intmain(void) { DDRB=0x01;// Configurar pin PB0 como salida for(;;)
  • 918. { PORTB|=0x01;// Poner 1 en pin PB0 delay_ms(400);// PORTB&=0xFE;// Poner 0 en pin PB0 delay_ms(300); } } No hay que ser muy perspicaz para descubrir lo que hace este programa: configura el pin PB0 como salida y luego lo setea y lo limpia tras pausas. Es como hacer parpadear un LED conectado al pin PB0. Parpadea porque el bloque de while se ejecuta cíclicamente. Los elementos más notables de un programa en C son las sentencias, las funciones, las directivas, los comentarios y los bloques. A continuación, una breve descripción de ellos. Los comentarios Los comentarios tienen el mismo propósito que en ensamblador: documentar y “adornar” el código. Es todo es texto que sigue a las barritas // y todo lo que está entre los signos /* y */. Se identifican fácilmente porque suelen aparecer en color verde. Ejemplos. // Éste es un comentario simple /* Ésta es una forma de comentar varias líneas a la vez. Sirve mucho para enmascarar bloques de código. */
  • 919. Las sentencias Un programa en C, en lugar de instrucciones, se ejecuta por sentencias. Una sentencia es algo así como una mega instrucción, que hace lo que varias instrucciones del ensamblador. Salvo casos particulares, donde su uso es opcional, una sentencia debe finalizar con un punto y coma (;). Así que también podemos entender que los ; sirven para separar las sentencias. Alguna vez leí que el compilador C lee el código como si lo absorbiera con una cañita, línea por línea, una a continuación de otra (evadiendo los comentarios por supuesto). Por ejemplo, la función main del programa de arriba bien puede escribirse del siguiente modo. //**************************************************************************** // Función principal //**************************************************************************** intmain(void){DDRB=0x01;for(;;){PORTB|=0x01;delay_ms(400); PORTB&=0xFE;delay_ms(300);}} ¿Sorprendido? Podrás deducir que los espacios y las tabulaciones solo sirven para darle un aspecto ordenado al código. Es una buena práctica de programación aprender a acomodarlas. Las sentencias se pueden clasificar en sentencias de asignación, sentencias selectivas, sentencias iterativas, de llamadas de función, etc. Las describiremos más adelante. Los bloques Un bloque establece y delimita el cuerpo de las funciones y algunas sentencias mediante llaves ({}). Como ves en el ejemplo de arriba, las funciones main y pausa tienen sus bloques, así como los bucles while y for. Creo que exageré con los comentarios, pero sirven para mostrarnos dónde empieza y termina cada bloque. Podrás ver cómo las tabulaciones ayudan a distinguir unos bloques de otros. Afortunadamente, los editores de los buenos compiladores C pueden resaltar cuáles son las llaves de inicio y de cierre de cada bloque. Te será fácil acostumbrarte a usarlas. Las directivas Son conocidas en el lenguaje C como directivas de preprocesador, de preprocesador porque son evaluadas antes de compilar el programa. Como pasaba en el ensamblador, las directivas por sí mismas no son código ejecutable. Suelen ser indicaciones sobre cómo se compilará el código.
  • 920. Entre las pocas directivas del C estándar que también son soportadas por los compiladores C para AVR están #include (para incluir archivos, parecido al Assembler), #define (mejor que el #define del ensamblador) y las #if, #elif, #endif y similares. Fuera de ellas, cada compilador maneja sus propias directivas y serán tratadas por separado. Las funciones Si un programa en ensamblador se puede dividir en varias subrutinas para su mejor estructuración, un programa en C se puede componer de funciones. Por supuesto que las funciones son muchísimo más potentes y, por cierto, algo más complejas de aprender. Por eso ni siquiera el gran espacio que se les dedica más adelante puede ser suficiente para entenderlas plenamente. Pero, no te preocupes, aprenderemos de a poco. En un programa en C puede haber las funciones que sean posibles, pero la nunca debe faltar la función principal, llamada main. Donde quiera que se encuentre, la función main siempre será la primera en ser ejecutada. De hecho, allí empieza y no debería salir de ella. Variables y Tipos de Datos En ensamblador todas las variables de programa suelen ser registros de la RAM crudos, es decir, datos de 8 bits sin formato. En los lenguajes de alto nivel estos registros son tratados de acuerdo con formatos que les permiten representar números de 8, 16 ó 32 bits (a veces más grandes), con signo o sin él, números enteros o decimales. Esos son los tipos de datos básicos. Las variables de los compiladores pueden incluso almacenar matrices de datos del mismo tipo (llamadas arrays) o de tipos diferentes (llamadasestructuras). Estos son los tipos de datos complejos. Los siguientes son los principales tipos de datos básicos del lenguaje C. Observa que la tabla los separa en dos grupos, los tipos enteros y los tipos de punto flotante. Tabla de variables y tipos de datos del lenguaje C Tipo de dato Tamaño en bits Rango de valores que puede adoptar char 8 0 a 255 ó -128 a 127 signed char 8 -128 a 127 unsigned char 8 0 a 255
  • 921. Tabla de variables y tipos de datos del lenguaje C Tipo de dato Tamaño en bits Rango de valores que puede adoptar (signed) int 16 -32,768 a 32,767 unsigned int 16 0 a 65,536 (signed) short 16 -32,768 a 32,767 unsigned short 16 0 a 65,536 (signed) long 32 -2,147,483,648 a 2,147,483,647 unsigned long 32 0 a 4,294,967,295 (signed) long long (int) 64 -263 a 263 - 1 unsigned long long(int) 64 0 a 264 - 1 float 32 ±1.18E-38 a ±3.39E+38 double 32 ±1.18E-38 a ±3.39E+38 double 64 ±2.23E-308 a ±1.79E+308 Afortunadamente, a diferencia de los compiladores para PIC, los compiladores para AVR suelen respetar bastante los tipos establecidos por el ANSI C. Algunos compiladores también manejan tipos de un bit como bool (o boolean) o bit, pero con pequeñas divergencias que pueden afectar la portabilidad de los códigos además de confundir a los programadores. Esos tipos son raramente usados. Por defecto el tipo double es de 32 bits en los microcontroladores. En ese caso es equivalente al tipo float. Los compiladores más potentes como AVR IAR C y AVR GCC sin embargo ofrecen la posibilidad de configurarlo para que sea de 64 bits y poder trabajar con datos más grandes y de mayor precisión. Los especificadores signed (con signo) mostrados entre paréntesis son opcionales. Es decir, da lo mismo poner int que signed int, por ejemplo. Es una redundancia que se suele usar para “reforzar” su condición o para que se vea más ilustrativo. El tipo char está pensado para almacenar caracteres ASCII como las letras. Puesto que estos datos son a fin de cuentas números también, es común usar este tipo para almacenar números de 8 bits. Es decir es equivalente a signed char o unsigned char,
  • 922. dependiendo de la configuración establecida por el entorno compilador. Y como es preferible dejar de lado estas cuestiones, si vamos a trabajar con números lo mejor es poner el especificador signed ounsigned en el código. Quizá te preguntes cuál es la diferencia entre los tipos de datos int y short si aparentemente tienen el mismo tamaño y aceptan el mismo rango de valores. Esa apariencia es real en el entorno de los microcontroladores AVR. Es decir, al compilador le da lo mismo si ponemos int oshort. Sucede que el tipo short fue y siempre debería ser de 16 bits, en tanto que int fue concebido para adaptarse al bus de datos del procesador. Esto todavía se cumple en la programación de las computadoras, por ejemplo, un dato int es de 32 bits en un Pentium IV y es de 64 bits en un procesador Core i7. De acuerdo con este diseño un tipo int debería ser de 8 bits en un megaAVR y de 32 bits en un AVR32. Sin embargo, la costumbre de relacionar el tipo intcon los 16 bits de las primeras computadoras como las legendarias 286 se ha convertido en tradición y en regla de facto para los microcontroladores. Actualmente solo en CCS C el tipo intes de 8 bits. Es irónico para ser el compilador que menos respeta los tipos de datos del ANSI C. A pesar de todo, se nota que todavía pueden aparecer ciertas imprecisiones en los tipos de datos que pueden perturbar la portabilidad de los programas entre los diferentes compiladores. Es por esto que el lenguaje C/C++ provee la librería stdint.h para definir tipos enteros que serán de un tamaño específico independientemente de los procesadores y de la plataforma software en que se trabaje. Tabla de variables y tipos de datos del lenguaje C Tipo de dato Tamaño en bits Rango de valores que puede adoptar int8_t 8 -128 a 127 uint8_t 8 0 a 255 int16_t 16 -32,768 a 32,767 uint16_t 16 0 a 65,536 int32_t 32 -2,147,483,648 a 2,147,483,647 uint32_t 32 0 a 4,294,967,295 int64_t 64 -263 a 263 - 1 uint64_t 64 0 a 264 - 1
  • 923. Es fácil descubrir la estructura de estos tipos para familiarizarse con su uso. Para ello debemos en primer lugar incluir en nuestro programa el archivo stdint.h con la siguiente directiva. #include <stdint.h> Esta inclusión ya está hecha en el archivo avr_compiler.h que se usa en todos los programas de cursomicros, así que no es necesario volverlo a hacer. Aunque el objetivo de este archivo es permitir la compatibilidad de códigos entre los compiladores AVR IAR C y AVR GCC, debemos saber que en AVR IAR C el archivo avr_compiler.h solo está disponible al usar la librería DLIB. Como las prácticas de cursomicros trabajan sobre la librería CLIB, he evitado recurrir a los tipos extendidos de stdint.h. Finalmente, existen además de los vistos arriba otros tipos y especificadores de datos que no son parte del lenguaje C pero que fueron introducidos por los compiladores pensando en las características especiales de los microcontroladores. Muchos de ellos son redundantes o simples alias y algunos que sí son de utilidad como el tipo PGM_P los veremos en su momento. Declaración de variables Esta parte es comparable, aunque lejanamente a cuando se identifican las variables del ensamblador con la directiva .def. No se puede usar una variable si antes no se ha declarado. La forma general más simple de hacerlo es la siguiente: data_typemyvar; donde data_type es un tipo de dato básico o complejo, del compilador o definido por el usuario ymyvar es un identificador cualquiera, siempre que no sea palabra reservada. Ejemplos. unsignedchard;// Variable para enteros de 8 bits sin signo charb;// Variable de 8 bits (para almacenar // caracteres ascii) signedcharc;// Variable para enteros de 8 bits con signo inti;// i es una variable int, con signo signedintj;// j también es una variable int con signo unsignedintk;// k es una variable int sin signo También es posible declarar varias variables del mismo tipo, separándolas con comas. Así nos ahorramos algo de tipeo. Por ejemplo:
  • 924. floatarea,side;// Declarar variables area y side de tipo float unsignedchara,b,c;// Declarar variables a, b y c como unsigned char Especificadores de tipo de datos A la declaración de una variable se le puede añadir un especificador de tipo como const, static,volatile, extern, register, etc. Dichos especificadores tienen diversas funciones y, salvo const, se suelen usar en programas más elaborados. Como no queremos enredarnos tan pronto, lo dejaremos para otro momento. Una variable const debe ser inicializada en su declaración. Después de eso el compilador solo permitirá su lectura mas no su escritura. Ejemplos: constinta=100;// Declarar constante a intb;// Declarar variable b //... b=a;// Válido b=150;// Válido a=60;// Error! a es constante a=b;// Error! a es constante Por más que las variables constantes sean de solo lectura, ocuparán posiciones en la RAM del microcontrolador. En CodeVisionAVR es posible configurar para que sí residan en FLASH pero por compatibilidad se usa muy poco. Por eso muchas veces es preferible definir las constantes del programa con las clásicas directivas #define (como se hace en el ensamblador). #define a 100 // Definir constante a Sentencias selectivas Llamadas también sentencias de bifurcación, sirven para redirigir el flujo de un programa según la evaluación de alguna condición lógica. Las sentencias if e if–else son casi estándar en todos los lenguajes de programación. Además de ellas están las sentencias if–else escalonadas y switch–case.
  • 925. La sentencia if La sentencia if (si condicional, en inglés) hace que un programa ejecute una sentencia o un grupo de ellas si una expresión es cierta. Esta lógica se describe en el siguiente esquema. Diagrama de flujo de la sentencia if. La forma codificada sería así: sentenciaA; if(expression)// Si expression es verdadera, // ejecutar el siguiente bloque {// apertura de bloque sentenciaB; sentenciaC; // algunas otras sentencias }// cierre de bloque sentenciaX; Después de ejecutar sentenciaA el programa evalúa expression. Si resulta ser verdadera, se ejecutan todas las sentencias de su bloque y luego se ejecutará la sentenciaX. En cambio, si expression es falsa, el programa se salteará el bloque de if y ejecutará sentenciaX. La sentencia if – else
  • 926. La sentencia if brinda una rama que se ejecuta cuando una condición lógica es verdadera. Cuando el programa requiera dos ramas, una que se ejecute si cierta expression es cierta y otra si es falsa, entonces se debe utilizar la sentecia if – else. Tiene el siguiente esquema. Diagrama de flujo de la sentencia if – else. Expresando lo descrito en código C, tenemos: (Se lee como indican los comentarios.) SentenciaA; if(expression)// Si expression es verdadera, ejecutar {// este bloque sentenciaB; sentenciaC; // ... } else// En caso contrario, ejecutar este bloque { sentenciaM; sentenciaN; // ... } sentenciaX;
  • 927. // ... Como ves, es bastante fácil, dependiendo del resultado se ejecutará uno de los dos bloques de la sentencia if – else, pero nunca los dos a la vez. La sentencia if – else – if escalonada Es la versión ampliada de la sentencia if – else. En el siguiente boceto se comprueban tres condiciones lógicas, aunque podría haber más. Del mismo modo, se han puesto dos sentencias por bloque solo para simplificar el esquema. if(expression_1)// Si expression_1 es verdadera ejecutar {// este bloque sentencia1; sentencia2; } elseif(expression_2)// En caso contrario y si expression_2 es {// verdadera, ejecutar este bloque sentencia3; sentencia4; } elseif(expression_3)// En caso contrario y si expression_3 es {// verdadera, ejecutar este bloque sentencia5; sentencia6; } else// En caso contrario, ejecutar este bloque { sentencia7; sentencia8;
  • 928. };// ; opcional // todo... Las “expresiones” se evalúan de arriba abajo. Cuando alguna de ellas sea verdadera, se ejecutará su bloque correspondiente y los demás bloques serán salteados. El bloque final (deelse) se ejecuta si ninguna de las expresiones es verdadera. Además, si dicho bloque está vacío, puede ser omitido junto con su else. La sentencia switch La sentencia switch brinda una forma más elegante de bifurcación múltiple. Podemos considerarla como una forma más estructurada de la sentencia if – else – if escalonada, aunque tiene algunas restricciones en las condiciones lógicas a evaluar, las cuales son comparaciones de valores enteros. Para elaborar el código en C se usan las palabras reservadas switch, case, break y default. El siguiente esquema presenta tres case‟s pero podría haber más, así como cada bloque también podría tener más sentencias. switch(expression) { caseconstante1:// Si expression = constante1, ejecutar este bloque sentencia1; sentencia2; break; caseconstante2:// Si expression = constante2, ejecutar este bloque sentencia3; sentencia4; break; caseconstante3:// Si expression = constante3, ejecutar este bloque sentencia5; sentencia6; break;
  • 929. default:// Si expression no fue igual a ninguna de las // constantes anteriores, ejecutar este bloque sentencia7; sentencia8; break; } sentenciaX; // todo... donde constante1, constante2 y constante3 deben ser constantes enteras, por ejemplo, 2, 0x45, „a‟, etc. („a‟ tiene código ASCII 165, que es, a fin de cuentas, un entero.) expresion puede ser una variable compatible con entero. No es una expresión que conduce a una condición lógica como en los casos anteriores. El programa solo ejecutará uno de los bloques dependiendo de qué constante coincida conexpression. Usualmente los bloques van limitados por llaves, pero en este caso son opcionales, dado que se pueden distinguir fácilmente. Los bloques incluyen la sentencia break. ¿Qué es eso? La sentencia break hace que el programa salga del bloque de switch y ejecute la sentencia que sigue (en el boceto, sentenciaX). ¡Atento!: de no poner break, también se ejecutará el bloque del siguiente case, sin importar si su constante coincida con expression o no. No sería necesario poner el default si su bloque estuviera vacío. Sentencias iterativas Las sentencias de control iterativas sirven para que el programa ejecute una sentencia o un grupo de ellas un número determinado o indeterminado de veces. Así es, esta sección no habla de otra cosa que de los bucles en C. El lenguaje C soporta tres tipos de bucles, las cuales se construyen con las sentencias while, do– while y for. El segundo es una variante del primero y el tercero es una versión más compacta e intuitiva del bucle while. La sentencia while
  • 930. El cuerpo o bloque de este bucle se ejecutará una y otra vez mientras (while, en inglés) una expresión sea verdadera. Diagrama de flujo de las sentencia while. El bucle while en C tiene la siguiente sintaxis y se lee así: mientras (while) expression sea verdadera, ejecutar el siguiente bloque. sentenciaA; while(expression)// Mientras expression sea verdadera, ejecutar el // siguiente bloque { sentenciaB; sentenciaC; // ... };// Este ; es opcional sentenciaX; // ... Nota que en este caso primero se evalúa expression. Por lo tanto, si desde el principioexpression es falsa, el bloque de while no se ejecutará nunca. Por otro lado, si expression no deja de ser verdadera, el programa se quedará dando vueltas “para siempre”. La sentencia do - while
  • 931. Como dije antes, es una variación de la sentencia while simple. La principal diferencia es que la condición lógica (expression) de este bucle se presenta al final. Como se ve en la siguiente figura, esto implica que el cuerpo o bloque de este bucle se ejecutará al menos una vez. Diagrama de flujo de las sentencia do – while. La sintaxis para la sentencia do – while es la siguiente y se lee: Ejecutar (do) el siguiente bloque, mientras (while) expression sea verdadera. sentenciaA; do { sentenciaB; sentenciaC; // ... }while(expression);// Este ; es mandatorio sentenciaX; // ... La sentencia for Las dos sentencias anteriores, while y do – while, se suelen emplear cuando no se sabe de antemano la cantidad de veces que se va a ejecutar el bucle. En los casos donde el bucle involucra alguna forma de conteo finito es preferible emplear la sentencia for. (Inversamente, al ver un for en un programa, debemos suponer que estamos frente a algún bucle de ese tipo.)
  • 932. Ésta es la sintaxis general de la sentencia for en C: for(expression_1;expression_2;expression_3) { sentencia1; sentencia2; // ... };// Este ; es opcional Ahora veamos por partes cómo funciona: expression_1 suele ser una sentencia de inicialización. expression_2 se evalúa como condición lógica para que se ejecute el bloque. expression_3 es una sentencia que debería poner coto a expression_2. Por la forma y orden en que se ejecutan estas expresiones, el bucle for es equivalente a la siguiente construcción, utilizando la sentencia while. Primero se ejecuta expression_1 y luego se ejecuta el bloque indicado tantas veces mientras expression_2 sea verdadera. expression_1; while(expression_2) { sentencia1; sentencia2; // ... expression_3; } No obstante, de esa forma se ve más rara aún; así que, mejor, veamos estos ejemplos, que son sus presentaciones más clásicas. (i es una variable y a y b son constantes o variables): for(i=0;i<10;i++)
  • 933. { sentencias; } Se lee: para (for) i igual a 0 hasta que sea menor que 10 ejecutar sentencias. La sentencia i++indica que i se incrementa tras cada ciclo. Así, el bloque de for se ejecutará 10 veces, desde quei valga 0 hasta que valga 9. En este otro ejemplo las sentencias se ejecutan desde que i valga 10 hasta que valga 20. Es decir, el bucle dará 11 vueltas en total. for(i=10;i<=20;i++) { sentencias; } El siguiente bucle for empieza con i inicializado a 100 y su bloque se ejecutará mientras i sea mayor o igual a 0. Por supuesto, en este caso i se decrementa tras cada ciclo. for(i=100;i>=0;i--) { sentencias; } Se pueden hacer muchas más construcciones, todas coincidentes con la primera plantilla, pero también son menos frecuentes. Sentencias con bloques simples Cuando las sentencias selectivas (como if) o de bucles (como while o for) tienen cuerpos o bloques que constan de solo una sentencia, se pueden omitir las llaves. Aun así, es aconsejable seguir manteniendo las tabulaciones para evitarnos confusiones. Por ejemplo, las siguientes sentencias: if(a>b) { a=0;
  • 935. else b--; while(a>=b) a=a+b; for(i=0;i<=10;i++) a=a*2; Los operadores Sirven para realizar operaciones aritméticas, lógicas, comparativas, etc. Según esa función se clasifican en los siguientes grupos. Operadores aritméticos Además de los típicos operadores de suma, resta, multiplicación y división, están los operadores de módulo, incremento y decremento. Tabla de Operadores aritméticos Operador Acción + Suma - Resta * Multiplicación / División % Módulo. Retorna el residuo de una división entera. Solo se debe usar con números enteros. ++ Incrementar en uno -- Decrementar en uno
  • 936. Ejemplos: inta,b,c;// Declarar variables a, b y c a=b+c;// Sumar a y b. Almacenar resultado en c b=b*c;// Multiplicar b por c. Resultado en b b=a/c;// Dividir a entre c. Colocar resultado en b a=a+c–b;// Sumar a y c y restarle b. Resultado en a c=(a+b)/c;// Dividir a+b entre c. Resultado en c b=a+b/c+b*b;// Sumar a más b/c más b×b. Resultado en b c=a%b;// Residuo de dividir a÷b a c a++;// Incrementar a en 1 b--;// Decrementar b en 1 ++c;// Incrementar c en 1 --b;// Decrementar b en 1 ¿Te recordaron a tus clases de álgebra del colegio? A diferencia de esas matemáticas, estas expresiones no son ecuaciones; significan las operaciones que indican sus comentarios. Por lo visto, los operadores ++ y -- funcionan igual si están antes o después de una variable en una expresión simple. Sin embargo, hay una forma (tal vez innecesaria y confusa para un novato, pero muy atractiva para los que ya estamos acostumbrados a su uso) que permite escribir código más compacto, es decir, escribir dos sentencias en una. Si ++ o -- están antes del operando, primero se suma o resta 1 al operando y luego se evalúa la expresión. Si ++ o -- están después del operando, primero se evalúa la expresión y luego se suma o resta 1 al operando. inta,b;// Declarar variables enteras a y b a=b++;// Lo mismo que a = b; y luego b = b + 1;
  • 937. a=++b;// Lo mismo que b = b + 1; y luego a = b; if(a++<10)// Primero comprueba si a < 10 y luego {// incrementa a en 1 // algún código } if(++a<10)// Primero incrementa a en 1 y luego {// comprueba si a < 10 // algún código } Operadores de bits Se aplican a operaciones lógicas con variables a nivel binario. Aquí tenemos las clásicas operaciones AND, OR inclusiva, OR exclusiva y la NEGACIÓN. Adicionalmente, he incluido en esta categoría los operaciones de desplazamiento a la derecha y la izquierda. Si bien son operaciones que producen resultados análogos a los de las instrucciones de ensamblador los operadores lógicos del C pueden operar sobre variables de distintos tamaños, ya sean de 1, 8, 16 ó 32 bits. Tabla de operadores de bits Operador Acción & AND a nivel de bits | OR inclusiva a nivel de bits ^ OR exclusiva a nivel de bits ~ Complemento a uno a nivel de bits << Desplazamiento a la izquierda
  • 938. Tabla de operadores de bits Operador Acción >> Desplazamiento a la derecha Ejemplos: charm;// variable de 8 bits intn;// variable de 16 bits m=0x48;// m será 0x48 m=m&0x0F;// Después de esto m será 0x08 m=m|0x24;// Después de esto m será 0x2F m=m&0b11110000;// Después de esto m será 0x20 n=0xFF00;// n será 0xFF00 n=~n;// n será 0x00FF m=m|0b10000001;// Setear bits 0 y 7 de variable m m=m&0xF0;// Limpiar nibble bajo de variable m m=m^0b00110000;// Invertir bits 4 y 5 de variable m m=0b00011000;// Cargar m con 0b00011000 m=m>>2;// Desplazar m 2 posiciones a la derecha // Ahora m será 0b00000110 n=0xFF1F; n=n<<12;// Desplazar n 12 posiciones a la izquierda // Ahora n será 0xF000; m=m<<8;// Después de esto m será 0x00 Fíjate en la semejanza entre las operaciones de desplazamiento con >> y << y las operaciones del rotación del ensamblador. Cuando una variable se desplaza hacia un
  • 939. lado, los bits que salen por allí se pierden y los bits que entran por el otro lado son siempre ceros. Es por esto que en la última sentencia, m = m << 8, el resultado es 0x00. Por cierto, en el lenguaje C no existen operadores de rotación. Hay formas alternativas de realizarlas. Desplazamientos producidos por los operadores << y >>. Operadores relacionales Se emplean para construir las condiciones lógicas de las sentencias de control selectivas e iterativas, como ya hemos podido apreciar en las secciones anteriores. La siguiente tabla muestra los operadores relacionales disponibles. Tabla de Operadores relacionales Operador Acción == Igual != No igual > Mayor que < Menor que >= Mayor o igual que <= Menor o igual que Operadores lógicos Generalmente se utilizan para enlazar dos o más condiciones lógicas simples. Por suerte, estos operadores solo son tres y serán explicados en las prácticas del curso. Tabla de Operadores lógicos Operador Acción && AND lógica
  • 940. Tabla de Operadores lógicos Operador Acción || OR lógica ! Negación lógica Ejemplos: if(!(a==0))// Si a igual 0 sea falso { // sentencias } if((a<b)&&(a>c))// Si a<b y a>c son verdaderas { // sentencias } while((a==0)||(b==0))// Mientras a sea 0 ó b sea 0 { // sentencias } Composición de operadores Se utiliza en las operaciones de asignación y nos permite escribir código más abreviado. La forma general de escribir una sentencia de asignación mediante los operadores compuestos es: obtectop=expression; que es equivalente a la sentencia object=objectopexpression;
  • 941. op puede ser cualquiera de los operadores aritméticos o de bit estudiados arriba. O sea, oppuede ser +, - , *, /, %, &, | , ^, ~, << ó >>. Nota: no debe haber ningún espacio entre el operador y el signo igual. Ejemplos: inta;// Declarar a a+=50;// Es lo mismo que a = a + 50; a+=20;// También significa sumarle 20 a a a*=2;// Es lo mismo que a = a * 2; a&=0xF0;// Es lo mismo que a = a & 0xF0; a<<=1;// Es lo mismo que a = a << 1; Precedencia de operadores Una expresión puede contener varios operadores, de esta forma: b=a*b+c/b;// a, b y c son variables A diferencia del lenguaje Basic, donde la expresión se evalúa de izquierda a derecha, en esta sentencia no queda claro en qué orden se ejecutarán las operaciones indicadas. Hay ciertas reglas que establecen dichas prioridades; por ejemplo, las multiplicaciones y divisiones siempre se ejecutan antes que las sumas y restas. Pero es más práctico emplear los paréntesis, los cuales ordenan que primero se ejecuten las operaciones de los paréntesis más internos. Eso es como en el álgebra elemental de la escuela, así que no profundizaré. Por ejemplo, las tres siguientes sentencias son diferentes. b=(a*b)+(c/b); b=a*(b+(c/b)); b=((a*b)+c)/b); También se pueden construir expresiones condicionales, así: if((a>b)&&(b<c))// Si a>b y b<c, ... { // ... }
  • 942. Las funciones Una función es un bloque de sentencias identificado por un nombre y puede recibir y devolver datos. En bajo nivel, en general, las funciones operan como las subrutinas de Assembler, es decir, al ser llamadas, se guarda en la Pila el valor actual del PC (Program Counter), después se ejecuta todo el código de la función y finalmente se recobra el PC para regresar de la función. Dada su relativa complejidad, no es tan simple armar una plantilla general que represente a todas las funciones. El siguiente esquema es una buena aproximación. data_type1function_name(data_type2arg1,data_type3arg2,...) { // Cuerpo de la función // ... returnSomeData;// Necesario solo si la función retorna algún valor } Donde: function_name es el nombre de la función. Puede ser un identificador cualquiera. data_type1 es un tipo de dato que identifica el parámetro de salida. Si no lo hubiera, se debe poner la palabra reservada void (vacío, en inglés). arg1 y arg2 (y puede haber más) son las variables de tipos data_type1, data_type2..., respectivamente, que recibirán los datos que se le pasen a la función. Si no hay ningún parámetro de entrada, se pueden dejar los paréntesis vacíos o escribir un void entre ellos. Funciones sin parámetros Para una función que no recibe ni devuelve ningún valor, la plantilla de arriba se reduce al siguiente esquema: voidfunction_name(void) { // Cuerpo de la función
  • 943. } Y se llama escribiendo su nombre seguido de paréntesis vacíos, así: function_name(); La función principal main es otro ejemplo de función sin parámetros. Dondequiera que se ubique, siempre debería ser la primera en ejecutarse; de hecho, no debería terminar. voidmain(void) { // Cuerpo de la función } Funciones con parámetros (por valor) Por el momento, solo estudiaremos las funciones que pueden tener varios parámetros de entrada pero solo uno de salida. Si la función no tiene parámetros de entrada o de salida, debe escribirse un void en su lugar. El valor devuelto por una función se indica con la palabra reservada return. Según el comportamiento de los parámetros de entrada de la función, estos se dividen enparámetros por valor y parámetros por referencia. Lo expuesto en este apartado corresponde al primer grupo porque es el caso más ampliamente usado. Con esto en mente podemos seguir. Para llamar a una función con parámetros es importante respetar el orden y el tipo de los parámetros que ella recibe. El primer valor pasado corresponde al primer parámetro de entrada; el segundo valor, al segundo parámetro; y así sucesivamente si hubiera más. Cuando una variable es entregada a una función, en realidad se le entrega una copia suya. De este modo, el valor de la variable original no será alterado. Mejor, plasmemos todo esto en el siguiente ejemplo. intminor(intarg1,intarg2,intarg3) { intmin;// Declarar variable min min=arg1;// Asumir que el menor es arg1
  • 944. if(arg2<min)// Si arg2 es menor que min min=arg2;// Cambiar a arg2 if(arg3<min)// Si arg3 es menor que min min=arg3;// Cambiar a arg3 returnmin;// Retornar valor de min } voidmain(void) { inta,b,c,d;// Declarar variables a, b, c y d /* Aquí asignamos algunos valores iniciales a 'a', 'b' y 'c' */ /* ... */ d=minor(a,b,c);// Llamar a minor // En este punto 'd' debería ser el menor entre 'a', 'b' y 'c' while(1);// Bucle infinito } En el programa mostrado la función minor recibe tres parámetros de tipo int y devuelve uno, también de tipo int, que será el menor de los números recibidos. El mecanismo funciona así: siempre respetando el orden, al llamar a minor el valor de a se copiará a la variable arg1; el valor de b, a arg2 y el valor de c, a arg3. Después de ejecutarse el código de la función el valor de retorno (min en este caso) será copiado a una variable temporal y de allí pasará a d. Aunque el C no es tan implacable con la comprobación de tipos de datos como Pascal, siempre deberíamos revisar que los datos pasados sean compatibles con los que la función espera, así como los datos recibidos, con los que la función devuelve. Por ejemplo, estaría mal llamar a la función minor del siguiente modo: d=minor(-15,100,5.124);// Llamar a minor
  • 945. Aquí los dos primeros parámetros están bien, pero el tercero es un número decimal (de 32 bits), no compatible con el tercer parámetro que la función espera (entero de 16 bits). En estos casos el compilador nos mostrará mensajes de error, o cuando menos de advertencia. Parámetros por referencia La función que recibe un parámetro por referencia puede cambiar el valor de la variable pasada. La forma clásica de estos parámetros se puede identificar por el uso del símbolo &, tal como se ve en el siguiente boceto de función. intminor(int&arg1,int&arg2,int&arg3) { // Cuerpo de la función. // arg1, arg2 y arg3 son parámetros por referencia. // Cualquier cambio hecho a ellos desde aquí afectará a las variables // que fueron entregadas a esta función al ser llamada. } No voy profundizar al respecto porque he visto que muchos compiladores C no soportan esta forma. Otra forma de pasar un parámetro por referencia es mediante los punteros, pero eso lo dejamos para el final porque no es nada nada fácil para un novato. Prototipos de funciones El prototipo de una función le informa al compilador las características que tiene, como su tipo de retorno, el número de parámetros que espera recibir, el tipo y orden de dichos parámetros. Por eso se deben declarar al inicio del programa. El prototipo de una función es muy parecido a su encabezado, se pueden diferenciar tan solo por terminar en un punto y coma (;). Los nombres de las variables de entrada son opcionales. Por ejemplo, en el siguiente boceto de programa los prototipos de las funciones main, func1 yfunc2 declaradas al inicio del archivo permitirán que dichas funciones sean accedidas desde cualquier parte del programa. Además, sin importar dónde se ubique la función main, ella siempre será la primera en ejecutarse. Por eso su prototipo de función es opcional. #include <avr.h>
  • 946. voidfunc1(charm,longp);// Prototipo de función "func1" charfunc2(inta);// Prototipo de función "func2" voidmain(void);// Prototipo de función "main". Es opcional voidmain(void) { // Cuerpo de la función // Desde aquí se puede acceder a func1 y func2 } voidfunc1(charm,longp) { // Cuerpo de la función // Desde aquí se puede acceder a func2 y main } charfunc2(inta) { // Cuerpo de la función // Desde aquí se puede acceder a func1 y main } La llamada a main, por supuesto, no tiene sentido; solo lo pongo para ilustrar. Si las funciones no tienen prototipos, el acceso a ellas será restringido. El compilador solo verá las funciones que están implementadas encima de la función llamadora o, de lo contrario, mostrará errores de “función no definida”. El siguiente boceto ilustra este hecho. (Atiende a los comentarios.) #include <avr.h>
  • 947. voidmain(void) { // Cuerpo de la función // Desde aquí no se puede acceder a func1 ni func2 porque están abajo } voidfunc1(charm,longp) { // Cuerpo de la función // Desde aquí se puede acceder a main pero no a func2 } charfunc2(inta) { // Cuerpo de la función // Desde aquí se puede acceder a func1 y main } Para terminar, dado que los nombres de las variables en los parámetros de entrada son opcionales, los prototipos de func1 y func2 también se pueden escribir asi voidfunc1(char,long); charfunc2(int); Variables locales y variables globales Los lenguajes de alto nivel como el C fueron diseñados para desarrollar los programas más grandes y complejos que se puedan imaginar, programas donde puede haber cientos de variables, entre otras cosas. ¿Imaginas lo que significaría buscar nombres para cada variable si todos tuvieran que ser diferentes? Pues bien, para simplificar las cosas, el C permite tener varias variables con el mismo nombre. Así es. Esto es posible gracias a que cada variable tiene un ámbito, un área desde donde será accesible. Hay diversos tipos de ámbito, pero empezaremos por
  • 948. familiarizarnos con los dos más usados, que corresponden a lasvariables globales y variables locales. Las variables declaradas fuera de todas las funciones y antes de sus implementaciones tienen carácter global y podrán ser accedidas desde todas las funciones. Las variables declaradas dentro de una función, incluyendo las variables del encabezado, tienen ámbito local. Ellas solo podrán ser accedidas desde el cuerpo de dicha función. De este modo, puede haber dos o más variables con el mismo nombre, siempre y cuando estén en diferentes funciones. Cada variable pertenece a su función y no tiene nada que ver con las variables de otra función, por más que tengan el mismo nombre. En la mayoría de los compiladores C para microcontroladores las variables locales deben declararse al principio de la función. Por ejemplo, en el siguiente boceto de programa hay dos variables globales (speed y limit) y cuatro variables locales, tres de las cuales se llaman count. Atiende a los comentarios. charfoo(long);// Prototipo de función intspeed;// Variable global constlonglimit=100;// Variable global constante voidinter(void) { intcount;// Variable local /* Este count no tiene nada que ver con el count de las funciones main o foo */ speed++;// Acceso a variable global speed vari=0;// Esto dará ERROR porque vari solo pertenece // a la función foo. No compilará.
  • 949. } voidmain(void) { intcount;// Variable local count /* Este count no tiene nada que ver con el count de las funciones inter o foo */ count=0;// Acceso a count local speed=0;// Acceso a variable global speed } charfoo(longcount)// Variable local count { intvari;// Variable local vari } Algo muy importante: a diferencia de las variables globales, las variables locales tienen almacenamiento temporal, es decir, se crean al ejecutarse la función y se destruyen al salir de ella. ¿Qué significa eso? Lo explico en el siguiente apartado. Si dentro de una función hay una variable local con el mismo nombre que una variable global, la precedencia en dicha función la tiene la variable local. Si te confunde, no uses variables globales y locales con el mismo nombre. Variables static Antes de nada debemos aclarar que una variable static local tiene diferente significado que unavariable static global. Ahora vamos a enfocarnos al primer caso por ser el más común. Cuando se llama a una función sus variables locales se crearán en ese momento y cuando se salga de la función se destruirán. Se entiende por destruir al hecho de que la locación de memoria que tenía una variable será luego utilizada por el compilador para otra variable local (así se economiza la memoria). Como consecuencia, el valor de las variables locales no será el mismo entre llamadas de función. Por ejemplo, revisa la siguiente función, donde a es una variable local ordinaria. voidincrem()
  • 950. { inta;// Declarar variable a a++;// Incrementar a } Cualquiera que haya sido su valor inicial, ¿crees que después de llamar a esta función 10 veces, el valor de a se habrá incrementado en 10?... Pues, no necesariamente. Cada vez que se llame a increm se crea a, luego se incrementa y, al terminar de ejecutarse la función, se destruye. Para que una variable tenga una locación de memoria independiente y su valor no cambie entre llamadas de función tenemos dos caminos: o la declaramos como global, o la declaramos comolocal estática. Los buenos programadores siempre eligen el segundo. Una variable se hace estática anteponiendo a su declaración el especificador static. Por defecto las variables estáticas se auto inicializan a 0, pero se le puede dar otro valor en la misma declaración (dicha inicialización solo se ejecuta la primera vez que se llama a la función), así: staticintvar1;// Variable static (inicializada a 0 por defecto) staticintvar2=50;// Variable static inicializada a 50 Ejemplos. voidincrem() { staticinta=5;// Variable local estática inicializada a 5 a++;// Incrementar a } voidmain() { inti;// Declarar variable i // El siguiente código llama 10 veces a increm for(i=0;i<10;i++)
  • 951. increm(); // Ahora la variable a sí debería valer 15 while(1);// Bucle infinito } Variables volatile A diferencia de los ensambladores, los compiladores tienen cierta “inteligencia”. Es decir, piensan un poco antes de traducir el código fuente en código ejecutable. Por ejemplo, veamos el siguiente pedazo de código para saber lo que suele pasar con una variable ordinaria: intvar;// Declarar variable var //... var=var;// Asignar var a var El compilador creerá (probablemente como nosotros) que la sentencia var = var no tiene sentido (y quizá tenga razón) y no la tendrá en cuenta, la ignorará. Ésta es solo una muestra de lo que significa optimización del código. Luego descubrirás más formas de ese trabajo. El ejemplo anterior fue algo burdo, pero habrá códigos con redundancias aparentes y más difíciles de localizar, cuya optimización puede ser contraproducente. El caso más notable que destacan los manuales de los compiladores C para microcontroladores es el de las variables globales que son accedidas por la función de interrupción y por cualquier otra función. Para que un compilador no intente “pasarse de listo” con una variable debemos declararla comovolatile, anteponiéndole dicho calificador a su declaración habitual. Por ejemplo, en el siguiente boceto de programa la variable count debe ser accedida desde la función interrupt como desde la función main; por eso se le declara como volatile. Nota: el esquema de las funciones de interrupción suele variar de un compilador a otro. Éste es solo un ejemplo. volatileintcount;// count es variable global volátil voidinterrupt(void)// Función de interrupción {
  • 952. // Código que accede a count } voidmain(void)// Función principal { // Código que accede a count } Arrays y Punteros Probablemente éste sea el tema que a todos nos ha dado más de un dolor de cabeza y que más hemos releído para captarlo a cabalidad. Hablo más bien de los punteros. Si ellos el C no sería nada, perdería la potencia por la que las mejores empresas lo eligen para crear sus softwares de computadoras. Pero bueno, regresando a lo nuestro, estos temas se pueden complicar muchísimo más de lo que veremos aquí. Solo veremos los arrays unidimensionales y los punteros (que en principio pueden apuntar a todo tipo de cosas) los abocaremos a los datos básicos, incluyendo los mismos arrays. Aun así, te sugiero que tengas un par de aspirinas al lado. Los arrays o matrices Un array es una mega variable compuesto de un conjunto de variables simples del mismo tipo y ubicadas en posiciones contiguas de la memoria. Con los arrays podemos hacer todos lo que hacíamos con las tablas (de búsqueda) del ensamblador y muchísimo más. Un array completo tiene un nombre y para acceder a cada uno de sus elementos se utilizan índices entre corchetes ([ ]). Los índices pueden estar indicados por variables o constantes. En el siguiente esquema se ve que el primer elemento de un array tiene índice 0 y el último, N-1, siendo N la cantidad de elementos del array. Estructura de un array unidimensional de N elementos. Declaración de arrays
  • 953. Para declarar un array unidimensional se utiliza la siguiente sintaxis: data_typeidentifier[NumElementos]; Donde data_type es un tipo de dato cualquiera, identifier es el nombre del array yNumElementos es la cantidad de elementos que tendrá (debe ser un valor constante). De este modo, el índice del primer elemento es 0 y el del último es NumElements - 1. Por ejemplo, las siguientes líneas declaran tres arrays. charletters10];// letters es un array de 10 elementos de tipo char longHexTable[16];// HexTable es un array de 16 elementos de tipo long intaddress[100];// address es un array de 100 elementos de tipo int Para el array letters el primer elemento es letters[0] y el último, letters[9]. Así, tenemos 10 elementos en total. Si quisiéramos asignar a cada uno de los elementos de letters los caracteres desde la „a‟ hasta la „j‟, lo podríamos hacer individualmente así: letters[0]='a';// Aquí el índice es 0 letters[1]='b';// Aquí el índice es 1 letters[2]='c';// ... letters[3]='d';// letters[4]='e'; letters[5]='f'; letters[6]='g'; letters[7]='h'; letters[8]='i'; letters[9]='j';// Aquí el índice es 9 Pero así no tiene gracia utilizar arrays. En este caso lo mejor es utilizar un bucle, así: (Nota: los caracteres son, al fin y al cabo, números en códigos ASCII y se les puede comparar.) charc; for(c='a';c<='j';c++) letters[i]=c;
  • 954. Inicialización de arrays Los elementos de un array se pueden inicializar junto con su declaración. Para ello se le asigna una lista ordenada de valores encerrados por llaves y separados por comas. Por supuesto, los valores deben ser compatibles con el tipo de dato del array. Este tipo de inicialización solo está permitido en la declaración del array. Ejemplos: unsignedcharmask[3]={0xF0,0x0F,0x3C};// Ok inta[5]={20,56,87,-58,5000};// Ok charvocals[5]={'a','e','i','o','u'};// Ok intc[4]={5,6,0,-5,0,4};// Error, demasiados inicializadores También es posible inicializar un array sin especificar en su declaración el tamaño que tendrá, dejando los corchetes vacíos. El tamaño será pre calculado y puesto por el compilador. Ésta es una forma bastante usada en los arrays de texto, donde puede resultar muy incómodo estar contando las letras de una cadena. Por ejemplo: inta[]={70,1,51};// Un array de 3 elementos charvocals[]={'a','e','i','o','u'};// Un array de 5 elementos charmsg[]="Este es un array de caracteres";// Un array of 31 elementos ¿Por qué el último array tiene 31 elementos si solo se ven 30 letras? Lo sabremos luego. Cadenas de texto terminadas en nulo Son arrays de tipo de dato char. Hay dos características que distinguen a estas cadenas de los demás arrays. Primero: su inicialización se hace empleando comillas dobles y segundo, el último término del array es un carácter NULL (simplemente un 0x00). De ahí su nombre. Ejemplos: charGreet[10]="Hello";// Un array de 10 elementos charmsg[]="Hello";// Un array de 6 elementos El array Greet tiene espacio para 10 elementos, de los cuales solo los 5 primeros han sido llenados con las letras de Hello, el resto se rellena con ceros. El array msg tiene 6 elementos porque además de las 5 letras de “Hello” se le ha añadido unNull (0x00) al final (claro que no se nota). Es decir, la inicialización de msg es equivalente a:
  • 955. charmsg[]={'H','e','l','l','o',0x00};// Un array de 6 elementos Visto gráficamente, msg tendría la siguiente representación: Estructura de una cadena de texto. Los punteros Los punteros suelen ser el tema que más cuesta entender en programación. Pero si ya llegaste aquí, es el momento menos indicado para detenerte. Los punteros son un tipo de variables muy especial. Son variables que almacenan las direcciones físicas de otras variables. Si tenemos la dirección de una variable, tenemos acceso a esa variable de manera indirecta y podemos hacer con ellas todo lo que queramos. Declaración de punteros Los punteros pueden apuntar a todo tipo de variables, pero no a todas al mismo tiempo. La declaración de un puntero es un tanto peculiar. En realidad, se parece a la declaración de una variable ordinaria solo que se pone un asterisco de por medio. En este punto debes recordar las declaraciones de todo tipo de variables que hemos visto, incluyendo las influenciadas por los calificadores const, static, etc. Todas excepto los arrays; ¿por qué? La forma general de declarar un puntero es la siguiente: data_type*PointerName; Los siguientes ejemplos muestran lo fácil que es familiarizarse con la declaración de los punteros: int*ip;// ip es un puntero a variable de tipo int char*ucp;// cp es un puntero a variable de tipo char unsignedchar*ucp;// Puntero a variable de tipo unsigned char constlong*clp;// Puntero a constante de tipo long float*p1,*p2;// Declara dos punteros a variable de tipo float
  • 956. Apuntando a variables Decimos que una variable puntero “apunta” a una variable x si contiene la dirección de dicha variable. Para ello se utiliza el operador &, el cual extrae la dirección de la variable a la que acompaña. Un puntero siempre debería apuntar a una variable cuyo tipo coincida con el tipo del puntero. En los siguientes ejemplos vemos cómo apuntar a variables de tipo básico, como int, char o float. Más adelant