Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
Introduccin
Los compiladores de alto nivel
Los compiladores de alto nivel son las herramientas de programacin mas potentes que existen. En resumidas
cuentas, el compilador traducir el programa escrito en su lenguaje (C, Basic, Pascal u otro) en cdigo
ensamblador y luego generar los archivos de depuracin (*.dbg, *.cof, *.d31, etc.) necesarios y de ejecucin
(*.hex).
Al trabajar con un lenguaje de alto nivel el programador ya no tiene que preocuparse (o lo hace muy poco) por
las caractersticas hardware ni por el ensamblador nativo de cada microcontrolador. Esto simplifica de manera
asombrosa el desarrrollo de proyectos.
Los compiladores se encargan de traducir el cdigo fuente al cdigo objeto de cada microcontrolador sin
inportar mucho cul sea. Por ejemplo, un cdigo escrito para un PIC16F84 podra ser facilmente compilado
para un PIC16F877A u otro, y viceversa. Inclusive es posible adaptar un cdigo para un microcontrolador de
otra marca, por ejemplo, de Freescale o Atmel. Eso se llama portabilidad.
Por qu C y no Basic?
Ciertamente, el Basic es el lenguaje ms fcil de aprender (no es exactamente la razn de su nombre). Y
aunque los programadores en C de ordenadores miren con desdn 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 caractersticas (muchas veces complejas) del C fueron ideadas para el trabajo con sofisticados proyectos,
propios de los ordenadores. Muchas de esas caractersticas ya no resultan tan ventajosas en el limitado
hardware de los microcontroladors y se convierten en prescindibles.
Adems, la simplicidad de los compiladores Basic para microcontroladores tambin permite que varios de
ellos, como MBasic o PIC Basic Pro (por citar algunos) mantengan una compatibilidad entre sus cdigos que
no se encuentra entre los compiladores C.
sas podran ser razones ms que convincentes para empezar por el Basic y, de hecho, es la opcin que
muchos han elegido. 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 ms fantsticos y alucinantes que se pueden encontrar estn
en C. Es ms, la mayora 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 razn pesa ms.
Adems, dada la robustez y la aceptacin del lenguaje C, se lo ha tomado como referencia para lenguajes de
otros propsitos como Java, JavaScript, php o de Matlab, entre otros. As que, el C podr servirte para trabajar
en otros campos. El programador de C podra, inclusive, aprender luego el Basic sin el menor esfuerzo; lo
contrario no es cierto.
Qu compilador C utilizar?
No quiero burlarme de nadie, pero una vez le en Internet el comentario de un novato: Quiero programar
microcontroladores en C. Ya descargu el Visual C++. Qu ms necesito? :). Aparte del lenguaje, nada
tiene que ver un compilador para ordenadores con los compiladores para Cs. Poco tiene que ver un
compilador para PICs que otro para otros Cs. Inclusive, poco tiene que ver un compilador de PICs de una
compaa con otro de otra compaa.
Veamos grosso modo algunos aspectos de los compiladores de PICs ms conocidos.
Hi-tech C. Es uno de los compiladores producidos por la empresa htsoft. Es quiz el ms eficiente y el que
mejor soporta el lenguaje C estndar. Su entorno IDE tambin incluye el mejor depurador ICD. Como
contraparte, su apego al hardware del C le resta algo de portabilidad. Tampoco luce libreras incorporadas
como otros productos. Pero su principal desventaja es su elevado precio. Y, por si fuera poco, el compilador
para la familia de partes PIC18 se vende por separado.
IAR C. Los compiladores C de la compaa iar systems tienen bsicamente las mismas caractersticas
mencionadas de los compiladores de htsoft, incluyendo sus propios depuradores. As mismo, las versiones
para los PIC16 y PIC18 se distribuyen por separado. Actualmente, no s por qu, ya no est disponible la
primera versin.
CCS C. La empresa ccsinfo decidi dotar a sus compiladores C una capa extra que asla al programador de los
recursos intrnsecos del C. Esto puede afectar la portabilidad de sus cdigos a otros compiladores, pero
resulta inmejorable, si solo se trabaja en el lenguaje de CCS C, para transportar los cdigos de un PIC a otro
(de cualquier familia) con un esfuerzo sin comparacin. Adems, incluye en un solo paquete los compiladores
para los PICs de las familias Baseline, Midrange (PIC16 bsicamente) y High performance (PIC18).
Al igual que los softwares anteriores, sus libreras estndar, como stdlib.h, stdio.h, string.h y math.h, son
muy completas y potentes; pero CCS C supera a sus rivales al incorporar libreras para controlar todos los
mdulos internos del PIC y tambin muchsimos dispositivos externos.
Mikro C. La compaa Mikroelektronika vende compiladores para PICs en los lenguajes C (MikroC), Basic
(MikroBasic) y Pascal (MikroPascal).
Yo dira que el estilo de Mikro C se parece al de Hi-tech C y sus facilidades tratan de acercarse a las de CCS
C: aunque en muchos casos an es necesario acceder a los registros internos del PIC, cuenta con libreras para
controlar sus mdulos internos. Tambin tiene una apreciable cantidad de libreras para interfacear
dispositivos externos. Lo malo es que todas ellas estn precompiladas y no se podran modificar, en caso de
ser necesario.
Mikroelektronika y CCS tambin comercializan sus propias tarjetas de entrenamiento para el aprendizaje de
sus productos. Para ms informacin puedes visitar sus sitios web.
MPLAB C18. Excelente compilador desarrollado por los ingenieros de Microchip. No es gratuito como el
MPLAB, pero creo que es el que ofrece la versin demo ms generosa: es 100 % funcional por 60 das.
Lamentablemente, como sugiere su nombre, solo trabaja con las partes PIC18. Quiz lo probemos en otro
momento.
Otros. An hay otros compiladores C (como Bytecraft, BoostC y FedC) que algo menos reconocidos como los
anteriores, lo que no significa que sean malos.
Tambin he visto algunos de cdigo abierto, pero no son buenos: la gente del GNU trabaja ms con el AVR
GCC, un Seor Compilador. Es uno de los pocos casos donde el software libre supera a los comerciales.
Como se puede entrever, est orientado a los microcontroladores AVR, de Atmel. Es, adems, el compilador
ms difcil de todos; por eso lo estudiaremos en el Mdulo 4.
En cuanto a cul compilador usar: la idea de este curso no es aprender a programar con un compilador en
particular, y tampoco pretendo promocionar alguno. Despus de todo, una victoria depende ms de la
habilidad guerrero que de su espada. He visto super programas hechos con el compilador ms modesto.
En este Mdulo 2 uso BoostC porque es muy fcil, porque nos permitir ver ms de cerca cmo funcionan las
cosas dentro del PIC y, sobre todo, porque el salto de l a otros compiladores ser mucho ms fcil que
hacerlo al revs. En el Mdulo 3 migraremos al CCS C (que adems del lenguaje C usa su propio argot) y
en el Mdulo 4 trabajaremos especialmente con AVR GCC.
Estructura de un programa en C
Tomaremos en cuenta este sencillsimo ejemplo, escrito para el compilador Hitech PICC.
#include <pic.h>
void main(void)
{
TRISB0 = 0;
while(1)
{
RB0 = 1;
pausa();
RB0 = 0;
pausa();
}
}
//
//
//
//
//
//
//
//
//
//
No hay que ser muy perspicaz para descubrir lo que hace este programa: configura el pin RB0 como salida y
luego lo setea y lo limpia tras pausas. Es como hacer parpadear un LED conectado al pin RB0. Parpadea
porque el bloque de while se ejecuta cclicamente.
Los elementos ms notables de un programa en C son las sentencias, las funciones, las directivas, los
comentarios y los bloques. A continuacin, una breve descripcin de ellos.
Los comentarios
Los comentarios tienen el mismo propsito que en ensamblador: documentar y adornar el cdigo. Es todo es
texto que sigue a las barritas // y todo lo que est entre los signos /* y */. Se identifican fcilmente porque
suelen aparecer en color verde.
Ejemplos.
// ste es un comentario simple
/*
sta es una forma de comentar varias lneas a la vez.
Sirve mucho para enmascarar bloques de cdigo.
*/
Las sentencias
Un programa en C, en lugar de instrucciones, se ejecuta por sentencias. Una sentencia es algo as como una
mega instruccin, 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 tambin podemos entender que los ; sirven para separar las sentencias. Alguna vez le que el compilador
C lee el cdigo como si lo absorbiera con una caita, lnea por lnea, una a continuacin de otra (evadiendo los
comentarios por supuesto). Por ejemplo, la funcin main del programa de arriba bien pudo escribirse del
siguiente modo.
void main(void) { TRISB0=0; while(1) { RB0=1; pausa(); RB0=0; pausa(); } }
Sorprendido? Podrs deducir que los espacios y las tabulaciones solo sirven para darle un aspecto ordenado
al cdigo. Es una buena prctica de programacin aprender a acomodarlas.
Las sentencias se pueden clasificar en sentencias de asignacin, sentencias selectivas, sentencias iterativas, de
llamadas de funcin, etc. Las describiremos ms 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 dnde empieza y termina cada bloque.
Podrs ver cmo las tabulaciones ayudan a distinguir unos bloques de otros. Afortunadamente, los editores de
los buenos compiladores C pueden resaltar cules son las llaves de inicio y de cierre de cada bloque. Te ser
fcil 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 cdigo
ejecutable. Suelen ser indicaciones sobre cmo se compilar el cdigo.
Entre las pocas directivas del C estndar que tambin son soportadas por los compiladores C para PICs estn
#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 sern tratadas
por separado.
Las funciones
Si un programa en ensamblador se puede dividir en varias subrutinas para su mejor estructuracion, un
programa en C se puede componer de funciones. Por supuesto que las fuciones son muchsimo ms potentes
y, por cierto, algo ms complejas de aprender. Por eso ni siquiera el gran espacio que se les dedica ms
adelante es suficiente para abarcarlas. 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 funcin
principal, llamada main. Donde quiera que se encuentre, la funcin main siempre ser la primera en ser
ejecutada. De hecho, all empieza y no debera salir de ella.
Rango
0 a 255 -128 a 127
Declaracin de variables
Esta parte es comparable, aunque lejanamente a cuando identificbamos nuestras variables del ensamblador
con las directivas equ o cblock endc. No se puede usar una variable si antes no se ha declarado. La forma
general ms simple de hacerlo es la siguiente:
data_type myvar;
donde data_type es un tipo de dato bsico o complejo, del compilador o definido por el usuario y myvar es un
identificador cualquiera, siempre que no sea palabra reservada.
Ejemplos.
unsigned char d;
char b;
signed char c;
int i;
signed int j;
unsigned int k;
//
//
//
//
//
//
//
Tambin es posible declarar varias variables del mismo tipo, separndolas con comas. As nos ahorramos algo
de tipeo. Por ejemplo:
float area, side;
unsigned char a, b, c;
Una variable const debe ser inicializada en su declaracin. Despus de eso el compilador solo permitir su
lectura mas no su escritura. Ejemplos:
const int a = 100;
int b;
// Declarar constante a
// Declarar variable b
//...
b = a;
b = 150;
// Vlido
// Vlido
a = 60;
a = b;
// Error! a es constante
// Error! a es constante
Por ms que las variables constantes sean de solo lectura, ocuparn posiciones en la RAM del C. Por eso
muchas veces es preferible definir las constantes del programa con las clsicas directivas #define (como lo
hacamos en el ensamblador).
#define a 100
// Definir constante a
Sentencias selectivas
Llamadas tambin sentencias de bifurcacin, sirven para redirigir el flujo de un programa segn la evaluacin
de alguna condicin lgica.
Las sentencias if e ifelse son casi estndar en todos los lenguajes de programacin. Adems de ellas estn las
sentencias ifelse escalonadas y switchcase.
La sentencia if
La sentencia if (si condicional, en ingls) hace que un programa ejecute una sentencia o un grupo de ellas si
una expresin es cierta. Esta lgica se describe en el siguiente esquema.
sentenciaA;
if ( expression )
{
// Si expression es verdadera,
//
ejecutar el siguiente bloque
// apertura de bloque
sentenciaB;
sentenciaC;
// algunas otras sentencias
}
// cierre de bloque
sentenciaX;
Despus de ejecutar sentenciaA el programa evala 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 condicin lgica 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.
Como ves, es bastante fcil, dependiendo del resultado se ejecutar uno de los dos bloques de la sentencia if
else, pero nunca los dos a la vez.
Las expresiones se evualan de arriba abajo. Cuando alguna de ellas sea verdadera, se ejecutar su bloque
correspondiente y los dems bloques sern salteados. El bloque final (de else) se ejecuta si ninguna de las
expresiones es verdadera. Adems, si dicho bloque est vaco, puede ser omitido junto con su else.
La sentencia switch
La sentencia switch brinda una forma ms elegante de bifurcacin mltiple. Podemos considerarla como una
forma ms estructurada de la sentencia if else if escalonada, aunque tiene algunas restricciones en las
condiciones lgicas a evaluar, las cuales son comparaciones de valores enteros.
Para elaborar el codigo en C se usan las palabras reservadas switch, case, break y default.
El siguiente esquema presenta tres cases pero podra haber ms, as como cada bloque tambin podra tener
ms sentencias.
switch ( expression )
{
case constante1: // Si expression = constante1, ejecutar este bloque
sentencia1;
sentencia2;
break;
case constante2: // Si expression = constante2, ejecutar este bloque
sentencia3;
sentencia4;
break;
case constante3: // 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 cdigo ascii 165, que es, a fin de cuentas, un entero.)
expresion puede ser una variable compatible con entero. No es una expresin que conduce a una condicin
lgica como en los casos anteriores.
El programa solo ejecutar uno de los bloques dependiendo de qu constante coincida con expression.
Usualmente los bloques van limitados por llaves, pero en este caso son opcionales, dado que se pueden
distinguir fcilmente. 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, tambin se ejecutar el bloque del siguiente case, sin
importar si su constante coincida con expression o no.
No sera necesario poner el default si su bloque estuviera vaco.
Sentencias iterativas
Las sentencias de control iterativas sirven para que el programa ejecute una sentencia o un grupo de ellas un
nmero determinado o indeterminado de veces. As es, esta seccin 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 versin mas 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 ingls) una expresin sea
verdadera.
{
sentenciaB;
sentenciaC;
// ...
};
sentenciaX;
// ...
// Este ; es opcional
Nota que en este caso primero se evala expression. Por lo tanto, si desde el principio expression 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 variacin de la sentencia while simple. La principal diferencia es que la condicin
lgica (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.
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 algn 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
Por la forma y orden en que se ejecutan estas expresiones, el bucle for es equivalente a la siguiente
construccin, 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 ms rara an; as que, mejor, veamos estos ejemplos, que son sus
presentaciones ms clsicas. (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 que i 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 ms construcciones, todas coincindentes con la primera plantilla, pero tambin son
menos frecuentes.
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 aritmticas, lgicas, comparativas, etc. Segn esa funcin se clasifican en los
siguientes grupos.
Operadores aritmticos
Adems de los tpicos operadores de suma, resta, multiplicacion y divisin, estn los operadores de mdulo,
incremento y decremento.
Operador
Accin
+
Suma
Resta
*
Multiplicacin
/
Divisin
% Mdulo. Retorna el residuo de una divisin entera. Solo se debe usar con nmeros enteros.
Operador
++ Incrementar en uno
-Decrementar en uno
Accin
Ejemplos:
int a, 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 ms b/c ms bb. Resultado en b
c = a % b;
// Residuo de dividir ab 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 matemticas, estas expresiones no son
ecuaciones; significan las operaciones que indican sus comentarios.
Por lo visto, los operadores ++ y -- funcionan igual si estn antes o despus de una variable en una expresin
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 cdigo ms compacto, es decir, escribir dos
sentencias en una.
Si ++ o -- estn antes del operando, primero se suma o resta 1 al operando y luego se evala la
expresin.
Si ++ o -- estn despus del operando, primero se evala la expresin y luego se suma o resta 1 al
operando.
int a, b;
a = b++;
a = ++b;
Operadores de bits
Se aplican a operaciones lgicas con variables a nivel binario. Aqu tenemos las clsicas operaciones AND,
OR inclusiva, OR exclusiva y la NEGACIN. Adicionalmente, he incluido en esta categora los operaciones
de desplazamiento a la derecha y la izquierda.
Si bien son operaciones que producen resultados anlogos a los de las instrucciones de ensamblador iorlw y
iorwf para la OR inclusiva, xorlw y xorwf para la OR exclusiva, andlw y andwf para la AND y comf para la
negacin; los operadores lgicos del C pueden operar sobre variables de distintos tamaos, ya sean de 1, 8, 16
32 bits.
Operador
Accin
&
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
>>
Desplazamiento a la derecha
Ejemplos:
char m;
int n;
m
m
m
m
n
n
m
m
m
=
=
=
=
=
=
=
=
=
0x48;
m & 0x0F;
m | 0x24;
m & 0b11110000;
0xFF00;
~n;
m | 0b10000001;
m & 0xF0;
m ^ 0b00110000;
m = 0b00011000;
m = m >> 2;
n = 0xFF1F;
n = n << 12;
m = m << 8;
// variable de 8 bits
// variable de 16 bits
//
//
//
//
//
//
//
//
//
m ser 0x48
Despus de esto m ser 0x08
Despus de esto m ser 0x2F
Despus de esto m ser 0x20
n ser 0xFF00
n ser 0x00FF
Setear bits 0 y 7 de variable m
Limpiar nibble bajo de variable m
Invertir bits 4 y 5 de variable m
Fjate en la semejanza entre las operaciones de desplazamiento con >> y << y las operaciones del rotacin del
ensamblador. La diferencia es que 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 rotacin. Hay formas
alternativas de realizarlas.
Operadores relacionales
Se emplean para construir las condiciones lgicas 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.
Operador
==
!=
>
<
>=
<=
Accin
Igual
No igual
Mayor que
Menor que
Mayor o igual que
Menor o igual que
Operadores lgicos
Generalmente se utilizan para enlazar dos o ms condiciones lgicas simples. Por suerte, estos operadores solo
son tres y sern explicados en las prcticas del curso.
OperadorAccin
&&
||
!
AND lgica
OR lgica
Negacin lgica
Ejemplos:
if( !(a==0) )
{
// sentencias
}
Composicin de operadores
Se utiliza en las operaciones de asignacin y nos permite escribir cdigo ms abreviado. La forma general de
escribir una sentencia de asignacin mediante los operadores compuestos es:
obtect op= expression;
op puede ser cualquiera de los operadores aritmticos o de bit estudiados arriba. O sea, op puede ser +, - , *, /,
%, &, | , ^, ~,<< >>. Nota: no debe haber ningn espacio entre el operador y el signo igual.
Ejemplos:
int a;
a += 50;
a += 20;
a *= 2;
a &= 0xF0;
a <<= 1;
//
//
//
//
//
//
Declarar a
Es lo mismo que a
Tambin significa
Es lo mismo que a
Es lo mismo que a
Es lo mismo que a
= a + 50;
sumarle 20 a a
= a * 2;
= a & 0xF0;
= a << 1;
Precedencia de operadores
Una expresin 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 expresin se evala de izquierda a derecha, en esta sentencia no
queda claro en qu orden se ejecutarn 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 ms prctico emplear los parntesis, los cuales ordenan que primero se ejecuten las operaciones de los
parntesis ms 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);
// Si a>b
b<c, ...
Las funciones
Una funcin 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), despus se ejecuta todo el cdigo de la funcin y
finalmente se recobra el PC para regresar de la funcin.
Dada su relativa complejidad, no es tan simple armar una plantilla general que represente a todas las
funciones. El siguiente esquema es una buena aproximacin.
data_type1 function_name (data_type2 arg1, data_type3 arg2, ... )
{
// Cuerpo de la funcin
// ...
return SomeData;
Donde:
La funcin principal main es otro ejemplo de funcin sin parmetros. Dondequiera que se ubique, siempre
debera ser la primera en ejecutarse; de hecho, no debera terminar.
void main (void)
{
// Cuerpo de la funcin
}
return min;
}
void main (void)
{
int a, b, c, d;
// Declarar variables a, b, c y d
En el programa mostrado la funcin minor recibe tres parmetros de tipo int y devuelve uno, tambin de tipo
int, que ser el menor de los nmeros 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. Despus de ejecutarse el cdigo de la funcin, 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 comprobacin de tipos de datos como Pascal, siempre deberamos
revisar que los datos pasados sean compatibles con los que la funcin espera, as como los datos recibidos,
con los que la funcin devuelve. Por ejemplo, estara mal llamar a la funcin minor del siguiente modo:
d = minor(-15, 100, 5.124); // Llamar a minor
Aqu los dos primeros parmetros estn bien, pero el tercero es un nmero decimal (de 24 32 bits), no
compatible con el tercer parmetro que la funcin espera (entero de 8 16 bits). En estos casos el compilador
nos mostrar mensajes de error, o cuando menos de advertencia.
No voy profundizar al respecto porque he visto que muchos compiladores C no soportan esta forma. Otra
forma de pasar un parmetro por referencia es mediante los punteros, pero eso lo dejamos para el final porque
no es nada nada fcil para un novato.
Prototipos de funciones
El prototipo de una funcin le informa al compilador las caractersticas que tiene, como su tipo de retorno, el
nmero de parmetros que espera recibir, el tipo y orden de dichos parmetros. Por eso se deben declarar al
inicio del programa.
El prototipo de una funcin 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 y func2
declaradas al inicio del archivo permitirn que dichas funciones sean accedidas desde cualquier parte del
programa. Adems, sin importar dnde se ubique la funcin main, ella siempre ser la primera en ejecutarse.
Por eso su prototipo de funcin es opcional.
#include <pic.h>
void func1(char m, long p); // Prototipo de funcin "func1"
char func2(int a);
// Prototipo de funcin "func2"
void main(void);
// Prototipo de funcin "main". Es opcional
void main(void)
{
// Cuerpo de la funcin
// Desde aqu se puede acceder a func1 y func2
}
void func1(char m, long p)
{
// Cuerpo de la funcin
// Desde aqu se puede acceder a func2 y main
}
char func2(int a)
{
// Cuerpo de la funcin
// 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 estn implementadas encima de la funcin llamadora o, de lo contrario, mostrar errores de funcin no
definida. El siguiente boceto ilustra este hecho. (Atiende a los comentarios.)
#include <pic.h>
void main(void)
{
// Cuerpo de la funcin
// Desde aqu no se puede acceder a func1 ni func2 porque estn abajo
}
void func1(char m, long p)
{
// Cuerpo de la funcin
Para terminar, dado que los nombres de las variables en los parmetros de entrada son opcionales, los
prototipos de func1 y func2 tambin se pueden escribir asi
void func1(char, long);
char func2(int );
// Prototipo de funcin
int speed;
// Variable global
const long limit = 100; // Variable global constante
void inter(void)
{
int count;
// Variable local
/* Este count no tiene nada que ver con el count
de las funciones main o foo */
speed++;
vari = 0;
}
void main(void)
{
int count;
// Variable
/* Este count no tiene nada que
de las funciones inter o foo
count = 0;
// Acceso a
speed = 0;
// Acceso a
}
char foo(long count)
// Variable
{
int vari;
// Variable
}
local count
ver con el count
*/
count local
variable global speed
local count
local vari
Algo muy importante: a diferencia de las variables globales, las variables locales tienen almacenamiento
temporal, es decir, se crean al ejecutarse la funcin y se destruyen al salir de ella. Qu significa eso? Lo
explico en el siguiente apartado.
Si dentro de una funcin hay una variable local con el mismo nombre que una variable global, la precedencia
en dicha funcin 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 una variable static
global. Ahora vamos a enfocarnos al primer caso por ser el ms comn.
Cuando se llama a una funcin sus variables locales se crearn en ese momento y cuando se salga de la
funcin se destruirn. Se entiende por destruir al hecho de que la locacin de memoria que tena 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 funcin.
Por ejemplo, revisa la siguiente funcin, donde a es una variable local ordinaria.
void increm()
{
int a;
a++;
}
// Declarar variable a
// Incrementar a
Cualquiera que haya sido su valor inicial, crees que despus de llamar a esta funcin 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 funcin, se destruye.
Para que una variable tenga una locacin de memoria independiente y su valor no cambie entre llamadas de
funcin tenemos dos caminos: o la declaramos como global, o la declaramos como local esttica. Los buenos
programadores siempre eligen el segundo.
Una variable se hace esttica anteponiendo a su declaracin el especificador static. Por defecto las variables
estticas se autoinicializan a 0, pero se le puede dar otro valor en la misma declaracin (dicha inicializacin
solo se ejecuta la primera vez que se llama a la funcin), as:
static int var1;
static int var2 = 50;
Ejemplos.
void increm()
{
static int a = 5;
// Variable local esttica inicializada a 5
a++;
// Incrementar a
}
void main()
{
int i;
// Declarar variable i
// El siguiente cdigo llama 10 veces a increm
for(i=0; i<10; i++)
increm();
// Ahora la variable a s debera 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 cdigo fuente en cdigo ejecutable. Por ejemplo, veamos el siguiente pedazo de cdigo
para saber lo que suele pasar con una variable ordinaria:
int var;
//...
var = var;
El compilador creer (probablemente como nosotros) que la sentencia var = var no tiene sentido (y quiz
tenga razn) y no la tendr en cuenta, la ignorar. sta es solo una muestra de lo que significa optimizacin
del cdigo. Luego descubrirs ms formas de ese trabajo.
El ejemplo anterior fue algo burdo, pero habr cdigos con redundancias aparentes y ms difciles de
localizar, cuya optimizacin puede ser contraproducente. El caso ms notable que destacan los manuales de
los compiladores C para microcontroladores es el de las variables globales que son accedidas por la funcin de
interrupcin y por cualquier otra funcin.
Para que un compilador no intente pasarse de listo con una variable debemos declararla como volatile,
anteponindole dicho calificador a su declaracin habitual.
Por ejemplo, en el siguiente boceto de programa la variable count debe ser accedida desde la funcin interrupt
como desde la funcin main; por eso se le declara como volatile. Nota: el esquema de las funciones de
interrupcin suele variar de un compilador a otro. ste es solo un ejemplo.
volatile int count;
void interrupt(void)
// Funcin de interrupcin
{
// Cdigo que accede a count
}
void main(void)
// Funcin principal
{
// Cdigo que accede a count
}
Arrays y Punteros
Probablemente ste sea el tema que a todos nos ha dado ms de un dolor de cabeza y que ms hemos reledo
para captarlo a cabalidad. Hablo ms bien de los punteros. Si ellos el C no sera nada, perdera la potencia por
la que las mejores empresas lo eligen para crear sus softwares de ordenadores.
Pero bueno, regresando a lo nuestro, estos temas se pueden complicar muchsimo ms 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 bsicos, incluyendo los mismos arrays. Aun as, te sugiero que tengas un par
de aspirinas al lado.
Declaracin de arrays
Para declarar un array unidimensional se utiliza la siguiente sintaxis:
data_type identifier[ NumElementos ];
Donde data_type es un tipo de dato cualquiera, identifier es el nombre del array y NumElementos 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.
Para el array letters el primer elemento es letters[0] y el ltimo, letters[9]. As, tenemos 10 elementos en total.
Si quisiramos asignar a cada uno de los elementos de letters los caracteres desde la a hasta la j, lo
podramos hacer individualmente as:
letters[0]
letters[1]
letters[2]
letters[3]
letters[4]
letters[5]
letters[6]
letters[7]
letters[8]
letters[9]
=
=
=
=
=
=
=
=
=
=
'a';
'b';
'c';
'd';
'e';
'f';
'g';
'h';
'i';
'j';
// Aqu el ndice es 0
// Aqu el ndice es 1
// ...
//
// Aqu el ndice es 9
Pero as no tiene gracia utilizar arrays, verdad? En este caso lo mejor es utilizar un bucle, as: (Nota: los
caracteres son, al fin y al cabo, nmeros en cdigos ascii y se les puede comparar.)
char c;
for ( c = 'a'; c <= 'j'; c++ )
letters[i] = c;
Inicializacin de arrays
Los elementos de un array se pueden inicializar junto con su declaracin. 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 inicializacin solo est permitido en la declaracin del
array.
Ejemplos:
unsigned char mask[3] = { 0xF0, 0x0F, 0x3C };
// Ok
int a[5] = { 20, 56, 87, -58, 5000 };
// Ok
char vocals[5] = { 'a', 'e', 'i', 'o', 'u' };
// Ok
int c[4] = { 5, 6, 0, -5, 0, 4 };
// Error, demasiados inicializadores
Tambin es posible inicializar un array sin especificar en su declaracin el tamao que tendr, dejando los
corchetes vacos. El tamao ser precalculado y puesto por el compilador. sta es una forma bastante usada en
los arrays de texto, donde puede resultar muy incmodo estar contando las letras de una cadena. Por ejemplo:
int a[] = { 70, 1, 51 };
// Un array de 3 elementos
char vocals[] = { 'a', 'e', 'i', 'o', 'u' };
// Un array de 5 elementos
char msg[] = "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.
Son arrays de tipo de dato char. Hay dos caractersticas que distinguen a estas cadenas de los dems arrays.
Primero: su inicializacin se hace empleando comillas dobles y segundo, el ltimo trmino del array es un
carcter NULL (simplemente un 0x00). De ah su nombre.
Ejemplos:
char Greet[10] = "Hello";
char msg[] = "Hello";
// Un array de 10 elementos
// 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 adems de las 5 letras de Hello se le ha aadido un Null (0x00) al
final (claro que no se nota). Es decir, la inicializacin de msg es equivalente a:
char msg[] = { 'H', 'e', 'l', 'l', 'o', 0x00};
// Un array de 6 elementos
Los punteros
Los punteros suelen ser el tema que ms cuesta entender en programacin. 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 fsicas de
otras variables. Si tenemos la direccin de una variable, tenemos acceso a esa variable de manera indirecta y
podemos hacer con ellas todo lo que queramos ;).
Declaracin de punteros
Los punteros pueden apuntar a todo tipo de variables, pero no a todas al mismo tiempo. La declaracin de un
puntero es un tanto peculiar. En realidad, se parece a la declaracin 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 fcil que es familiarizarse con la declaracin de los punteros:
int * ip;
char * ucp;
unsigned char * ucp;
const long * clp;
float * p1, *p2;
//
//
//
//
Apuntando a variables
Decimos que una variable puntero apunta a una variable x si contiene la direccin de dicha variable. Para
ello se utiliza el operador &, el cual extrae la direccin de la variable a la que acompaa. Un puntero siempre
debera apuntar a una variable cuyo tipo coincida con el tipo del puntero.
En los siguientes ejemplos vemos cmo apuntar a variables de tipo bsico, como int, char o float. Ms
adelante veremos cmo apuntar a arrays.
void main (void)
{
int height, width;
char a, b, c;
float max;
int * ip;
char * cp;
float * fp;
ip = &height;
ip = &width;
cp = &a;
cp = &c;
cp = &a;
// cp apunta a a
// Ahora cp apunta a c
// Ahora cp apunta a a otra vez
fp = &max;
fp = &height;
// fp apunta a max
// Error! height no es una variable float
//...
}
// Variables ordinarias
// p y q son punteros a variables de tipo int
p = &height;
*p = 10;
// p apunta a height
// Esto es como height = 10
p = &width;
*p = 50;
// p apunta a width
// Esto es como width = 50
height = *p;
q = &height;
n = (*p + *q)/2;
// q apunta a height
// Esto es como n = (height + width)/2
//...
}
La expresin *p se debera leer: la variable apuntada por p. Eso tambin ayuda mucho a comprender a los
punteros.
Y para esto se inventaron los punteros? Yo me preguntaba lo mismo en mis inicios. El tema de los punteros
se puede complicar casi hasta el infinito, por eso quiero ir con cuidado y poco a poco para que nadie se
pierda.
Punteros y arrays
Cmo se declara un puntero a un array? Un puntero a un array es simplemente un puntero al tipo de dato del
array. Cuando se asigna un puntero a un array, en realidad el puntero toma la direccin de su primer elemento,
a menos que se especifique otro elemento.
Luego, bastara con modificar el valor del puntero para que apunte a los otros elementos del array. Todo lo
indicado se refleja en el siguiente cdigo:
void main (void)
{
int * p;
// Declara p como puntero a int
int n;
// Alguna variable
int mat[3] = { 78, 98, 26 };
// Array de variables int
p = &mat;
n = *p;
p++;
n = *p;
p++;
n = *p;
//
//
//
//
//
*p = 10;
p--;
*p = 100;
p = mat;
p = NULL;
// ...
Esto da n =
Incrementar
Esto da n =
Incrementar
Esto da n =
78
p para apuntar a siguiente elemento
98
p para apuntar a siguiente elemento
26
En el fondo los arrays y los punteros trabajan de la misma forma, por lo menos cuando referencian a variables
almacenadas en la RAM del microcontrolador. La nica diferencia es que los arrays no pueden direccionar a
datos diferentes de su contenido; por eso tambin se les llama punteros estticos. En la prctica esto significa
que un array es siempre compatible con un puntero, pero un puntero no siempre es compatible con un array.
Por ejemplo, a un array no se le puede asignar otro array ni se le pueden sumar o restar valores para que
apunten a otros elementos. Por lo dems, las operaciones de asignacin son similares para punteros y arrays,
tal como se puede apreciar en el siguiente cdigo. (Por si las moscas, str1 es el array y str2, el puntero.)
void main(void)
{
char str1[] = { 'A', 'r', 'r', 'a', 'y' };
// Esto da a = 'A'
// Esto da a = 'a'
a = str2[0];
a = str2[3];
// Esto da a = 'P'
// Esto da a = 'n'
str1 += 2;
str2 += 2;
str1++;
str2++;
a = *str2;
// Esto da a = 'n'
//
...
Al llamar a interchange le entregamos &i y &j, es decir, las direcciones de i y j. Por otro lado, la funcin
interchange recibir dichos valores en p1 y p2, respectivamente. De ese modo, p1 y p2 estararn apuntando a i
y j, y podremos modificar sus valores.
Ten presente que se mantiene la forma de asignacin puntero = &variable (puntero igual a direccin de
variable).
Ahora veamos ejemplos donde la forma de asignacin cambia a puntero = puntero. Esto incluye a los arrays
porque, recordemos, un puntero siempre puede ser tratado como un array, aunque lo contrario no siempre es
posible.
En el siguiente programa array1 y array2 se pasan a la funcin prom, la cual devuelve el valor promedio de
los elementos del array recibido. Como para ese clculo se necesita conocer la cantidad de elementos que
tiene el array, prom recibe dicho valor en el parmetro size.
float prom ( int * p, int size )
{
int i; float tmp = 0;
for ( i=0; i<size; i++ ) // Bucle para contar i desde 0 hasta size-1.
tmp += p[i];
// Sumar elemento p[i] a tmp.
return ( tmp/size );
// Retornar valor promediado.
}
void main (void)
{
int array1[4] = { 51, 14, 36, 78 };
// Un array de 4 elementos
int array2[] = { -85, 4, 66, 47, -7, 85 }; // Un array de 6 elementos
float avrg;
// Una variable tipo float, para decimales
avrg = prom (array1, 8);
// Ahora avrg debera valer (51 + 14 + 36 + 78 )/8 = 44.75
avrg = prom (array2, 6);
// Ahora avrg debera valer (-85 + 4 + 66 + 47 - 7 + 85 )/6 = 18.3333
while( 1 );
// Bucle infinito
Finalmente, veamos un programa donde se utilizan las Cadenas de texto terminadas en nulo.
Este programa tiene dos funciones auxiliares: mayus convierte la cadena recibida en maysculas, y lon calcula
la longitud del texto almacenado en el array recibido. Ambas funciones reciben el array pasado en un puntero
p dado que son compatibles.
void mayus( char * p )
{
while( *p )
// Mientras carcter apuntado sea diferente de 0x00
{
if( ( *p >= 'a' ) && ( *p <= 'z' ) ) // Si carcter apuntado es
*p = *p - 32;
p++;
// minscula
// Hacerlo mayscula
// Incrementar p para apuntar sig. carcter
}
}
int lon( char * p)
{
int i = 0;
// Declarar variable i e iniciarla a 0.
while( *p )
// Mientras carcter apuntado sea diferente de 0x00
{
i++;
// Incrementar contador.
p++;
// Incrementar p para apuntar sig. carcter
}
return i;
// Retornar i
}
void main (void)
{
int L;
char song1[20] = "Dark Blue";
char song2[20] = "Staring Problem";
char song3[20] = "Ex-Girlfriend";
/* Obtener longitudes de los arrays de
L = lon(song1);
// Debera dar L =
L = lon(song2);
// Debera dar L =
L = lon(song3);
// Debera dar L =
texto */
9
15
13
// Bucle infinito
En el programa se crean tres arrays de texto de 20 elementos (song1, song2 y song3), pero el texto
almacenado en ellos termina en un carcter 0x00.
Segn la tabla de caracteres ascii, las letras maysculas estn ubicadas 32 posiciones por debajo de las
minsculas. Por eso basta con sumarle o restarle ese valor a un carcter ascci para pasarlo a mayscula o
minscula.
En ambas funciones el puntero p navega por los elementos del array apuntado hasta que encuentra el final,
indicado por un carcter nulo (0x00).
Arrays constantes
No es que me haya atrazado con el tema, es solo que los arrays constantes son uno de los temas cuyo
tratamiento vara mucho entre los distintos compiladores. Veamos en qu.
Un array constante es uno cuyos elementos solo podrn ser ledos pero no escritos; tan simple como eso.
En principio, para que un array sea constante a su clsica declaracin con inicializacin de un array se le debe
anteponer el calificador const. Por ejemplo:
const int a[5] = { 20, 56, 87, -58, 5000 };
// Array constante
const char vocals[5] = { 'a', 'e', 'i', 'o', 'u' }; // Array constante
const char text[] = "Este es un array constante de caracteres";
De este modo, los arrays a, vocals y text sern de solo lectura, y sus elementos podrn ser ledos, mas no
escritos. Es como si estuviramos frente a una tabla hecha en ensamblador (de PICs) a base de instrucciones
retlw. De hecho, los compiladores Mikro C, CCS C, Hi-tech C e IAR C, construirn internamente tablas
semejantes para representar estos arrays.
Si estos arrays constantes van a ser ledos directamente y mientras se utilice la notacin de los corchetes para
especificar a cada elemento, todo estar ok.
Por otro lado, cada compilador trabaja diferente el paso de arrays constantes a funciones y su compatibilidad
con los punteros. Por ejemplo, Hi-tech C soporta magistralmente estos temas. Por otro lado, CCS C no acepta
nada, pero ofrece excelentes mtodos alternativos para realizar esas tareas. De modo que ser necesario
revisar el manual de cada compilador en particular.
En otros compiladores, como MPLAB C18 o BoostC, debe adems aadirse el calificador rom (no
perteneciente al C estndar). BoostC solo soporta arrays constantes con datos de 8 bits (de tipo char y
compatibles) y su peculiar forma de declarlas es la siguiente.
rom char * a = "cadena de texto constante en BoostC";
rom char * mat = { 20, 56, 87, -58, 50 };
// Array constante
rom unsigned char * dec = { 10, 20, 30, 40, 50 };
// Array constante
Por lo dems, el acceso a los elementos de estos arrays tiene que seguir siendo mediante ndices y corchetes.
En el compilador MPLAB C18, la palabra rom va junto al const. Por supuesto que en los PIC18 los arrays en
ROM se implementan con ms eficiencia sin recurrir a los limitados retlw.
rom
rom
rom
rom
const
const
const
const
Proteus VSM
Qu es Proteus VSM
A lo largo de este curso no muestro inters personal para promocionar algn producto en particular. Pero al
Csar lo que es del Csar y no tengo mayor reparo en asegurar que Proteus VSM es el mejor software de su
clase que existe para aficionados y profesionales dedicados al desarrollo de proyectos con microcontroladores.
Proteus ha revolucionado el concepto de simulacin de circuitos electrnicos al pasar de las tpicas
presentaciones grficas y textuales de informacin pre-procesada (al estilo PSPICE) a la simulacin
interactiva muchas veces en tiempo real.
Podra seguir listando las potentes caractersticas de Proteus, pero como tampoco pretendo desafiar sus
archivos de ayuda y manuales oficiales, los cuales son muy buenos y de lectura recomendada (para quien
tenga tiempo:), prefiero aconsejarte que le eches un vistazo a los excelentes ejemplos que trae incluido.
Quedars ms que asombrado.
Instalacin de Proteus.
Terminada la instalacin podemos ir al men iniciar de Windows y ver todos los componentes disponibles
incluyendo sus respectivos archivos de ayuda. La aplicacin principal de Proteus se llama ISIS, tanto que el
asistente de instalacin coloc su acceso directo en el escritorio sin ni siquiera preguntrnoslo.
Hablar de USB y Proteus es todo un tema aparte. As que lo dejaremos para ms adelante. All veremos cmo
programar microcontroladores con USB y cmo simularlos en Proteus, incluso en Windows 7 de 64 bits.
Para los PIC: CCS C (no incluye dsPIC), MPLAB C18, MPLAB C30, MPASM, Hi-Tech C para los
PIC10/12/16, Hi-Tech C para los PIC18, Hi-Tech C para los dsPIC.
Para los AVR: solo WinAVR y el ensamblador AVRASM.
Para los 8051: IAR para 8051, Keil uVision4 para 8051 y el ensamblador ASEM-51.
Para los ARM: IAR para ARM y GCC para ARM.
Se ve interesante, sobre todo si consideramos que VSM Studio se distribuye gratuitamente. No obstante VSM
Studio est lejos de igualar al MPLAB para los PIC y ms lejos del Studio 5 para los AVR. Ellos tambin son
IDEs flexibles y se conectan con muchos ms compiladores y con mejor soporte, adems del hecho de que
tambin pueden interactuar con Proteus gracias a los Pluggins respectivos.
Quiz muchas veces nos pueda atraer la extrema ligereza de VSM Studio, cuando nos cansemos de lo que
demoran en cargarse MPLAB o Studio 5. VSM Studio solo pesa cerca de 15 Mb, as que nada perdemos con
descargarlo, instalarlo y probarlo. Su instalacin no tiene nada de extraordinario. Conviene siempre visitar la
web Labcenter Electronics para obtener las versiones ms recientes de sta y otras extensiones de Proteus.
Open GL habilitado
Open GL sin habilitar
Un diseo est compuesto por diversos objetos, desde los cables de interconexin hasta los instrumentos
virtuales como el osciloscopio o el terminal serial. Los objetos estn agrupados segn su categora con sus
respectivos iconos en la barra de herramientas izquierda de ISIS.
A continuacin citamos algunas categoras, llamadas modos:
Components. Representan todos los componentes electrnicos presentes en un
circuito, como los LEDs, condensadores, microcontroladores, etc.
Virtual Instruments. Ofrece los instrumentos de medicin como el Osciloscopio,
voltmetro, frecuencmetro, etc.
Generator, para producir ondas cuadradas, sinusoidales, de reloj, etc.
Simulation Graphs, para visualizar los resultados de la simulacin en grficos, al
estilo de los programas como Electronics Workbench o Cadence Orcad.
Terminals. Nos permitir colocar los terminales de entrada, salida, as como tierra
GND y las fuentes de alimentacin VCC o VDD. Los terminales de entrada INPUT y
salida OUTPUT son muy tiles para conectar elementos sin necesidad de unirlos
directamente con cables.
Etc. Etc.
de
Una vez localizado el componente buscado, le damos doble clic para enviarlo al Object Selector. As podemos
ir sacando los dispositivos restantes. Cuando hayamos terminado con esta parte cerramos esta ventana dndole
al botn Ok, Cancel o, mejor an, presionando Escape en el teclado.
Ahora el panel Object Selector contiene los diversos dispositivos que formarn nuestro circuito. Seleccinalos
uno a uno y luego colcalos en el esquemtico con un clic izquierdo.
ATmegaNN4P o ATmegaNN8P. Puede que en algunos casos el AVR que veas en mis esquemas no se
parece al que tienes en tu Proteus. Lo que importa es que sea o tenga el mismo modelo, el resto es solo
cuestin de apariencia
LED-BLUE, LED-GREEN,... Hay varios tipos de diodos LED disponibles. Para las simulaciones se
suelen usar los LED animados (los que prenden). Puesto que son solo simulaciones, los colores quedan
en segundo plano.
RESISTOR. Al escribir resistor en la casilla Keywords se listarn muchsimas partes, desde los
modelos genricos hasta los resistores comerciales. En principio, podramos elegir cualquiera de ellas;
de todos modos, la impedancia puede reajustarse fcilmente. Tambin, se podra afinar la bsqueda
aadiendo el valor de la resistencia.
BUTTON. As se llaman los infaltables pulsadores.
Para mover un objeto, seleccinalo y luego arrstralo con el botn izquierdo hasta el lugar deseado.
Para rotar un objeto, seleccinalo y luego presiona la tecla + o - del teclado numrico tantas veces
hasta conseguir la orientacin deseada. Otra forma de hacerlo es aplicando los botones de rotacin al
objeto seleccionado. La rotacin solo es posible en ngulos rectos.
Tambin se pueden usar los botones de rotacin y reflexin mostrados abajo, aunque ellos tendrn efecto solo
en los elementos del Object Selector, o sea, antes de colocarlos en la hoja de diseo.
Para eliminar un objeto, dale doble clic derecho, o seleccinalo y usa la tecla supr.
La seleccin de un grupo de objetos se logra encerrndolos en un rectngulo dibujado con el botn izquierdo.
Una vez hecho esto el men contextual del botn derecho desplegar las opciones de edicin de grupo como
copiar, mover o rotar/reflejar. Estas opciones tambin estn presentes en la barra de herramientas.
Entre todo, la forma ms fcil de mover un grupo de objetos seleccionado es arrastrarlos con el botn
izquierdo.
Interconexin de Componentes
Los cables para la interconexin de los elementos del circuito no estn en una categora en especial. Se
colocan directamente, aprovechando la forma de lpiz del cursor y las marcas de snap (esos pequeos
cuadraditos ) que aparecen al acercar el lpiz a algn terminal conectable.
Componentes Innecesarios
Para simplificar la tarea de simulacin Proteus permite obviar algunos elementos en el diseo.
Pines GND y VDD. Tal vez te inquiete la ausencia de los pines GND y VDD de los dispositivos digitales
como los microcontroladores, pero eso no cuenta para la simulacin porque Proteus asume que trabajarn con
alimentacin de 0 y 5 V.
El circuito oscilador del microcontrolador. Los microcontroladores pueden trabajar con varios tipos de
osciladores siendo el ms comn el conformado por un cristal de cuarzo y dos capacitores de estabilizacin.
Sin embargo, debes saber que para los microcontroladores este circuito no tiene directa intervencin en la
simulacin.
La frecuencia de operacin del microcontrolador se establece en su ventana de propiedades, con lo cual el
circuito del XTAL quedara de adorno. Muchas veces se lo suele ignorar para aligerar la carga del diseo y
mejorar en algo la velocidad de simulacin.
Por lo general y como se puede comprobar, muchos de los componentes de Proteus no requieren nada de
edicin y, de hecho, se pueden encontrar partes con modelos que ya tienen todos los parmetros correctamente
calibrados. No obstante, muchas veces ser ms cmodo cambiar alguna propiedad de un dispositivo.
Para editar las propiedades de un objeto hay que darle doble clic izquierdo. As se accede a su ventana de
propiedades, la cual, por supuesto, variar de acuerdo con el objeto tratado.
Por ejemplo, si quisiramos cambiar el valor de una resistencia, solo habra que modificar el parmetro
Resistance en su ventana Edit Component.
El Archivo HEX
Es el mismo que se utiliza para grabar el microcontrolador. Todos los compiladores o ensambladores generan
un archivo HEX al final, aunque su formato puede variar entre Intel hex o Motorola hex, generalmente. Con
este archivo la simulacin en Proteus solo ser fluida (como si fuera poco) y servir de nada si hacemos
pausas para ver por dnde anda la ejecucin del programa del microcontrolador.
El Archivo COFF
COFF (Code Object File Format) es el archivo de depuracin ms extendido y lo utilizan casi todos los
compiladores para PICs y algunos para AVR. Si en siguiente lista encuentras tu compilador, entonces sabrs
que debes buscar el archivo con extensin .COF para cargarlo en tu microcontrolador PIC o AVR.
Los compiladores CCS C para PICs y dsPICs.
Los compiladores de Hitech C para PICs y dsPICs.
El Archivo ELF
Tcnicamente la estructura de los archivos ELF provee mayor informacin de depuracin que sus similares y
es el ms recomendado por Proteus para sus simulaciones. No obstante ello, son pocos los compiladores que
lo promueven.
Los compiladores que sobresalen trabajando con los cargadores ELF son GCC AVR para todos los AVR
incluyendo los ATxMega, su compilador hermano GCC AVR32 para los AVR32 y su compilador primo
hermano GCC ARM para los microcontroladores ARM.
Puesto que hay otras herramientas de simulacin aparte de Proteus que trabajan preferentemente con los
archivos COF, estaba en desarrollo una utilidad que convierte los archivos ELF en archivos COFF o archivos
COFF Extendido de Atmel. Pero la migracin de Atmel al formato ELF desincentiv el proyecto, que
actualmente an est disponible, aunque con algunos bugs.
Vale la pena saber que tambin con el compilador Hitech C se pueden obtener archivos de depuracin ELF
entre algunos otros formatos.
Debemos hacer una observacin especial en los compiladores Basic. O mejor, simplificar las cosas diciendo
que si trabajas con el compilador para PICs Proton Plus, de Crownhill Associates, es el mismo archivo BAS
del cdigo fuente el que debes cargar para tu simulacin. Esta tarea es ms sencilla porque se puede realizar
directamente desde el mismo entorno de Proton Plus.
Pero atencin, si trabajas con el compilador Basic Bascom AVR, el archivo que debes cargar es el que tiene
extensin OBJ. Esta carga es manual.
El archivo OBJ no es exclusivo para simular un programa compilado con Bascom AVR. Tambin los
proyectos en ensamblador con Studio 5 dan como resultado ese archivo y se cargan del mismo modo.
Bueno, para ir directo al grano en la siguiente figura se muestra la configuracin de los fuses relacionados con
el oscilador del sistema.
Para quienes no quieran entrar en rollos o para quienes deseen simulaciones a frecuencias diferentes de 8MHz
pueden escoger cualquiera de las opciones de los fuses CKSEL que indica XTAL externo y escribir la
frecuencia del XTAL usado en la categora Clock Frecuency.
En los PICmicro la frecuencia de operacin del procesador no es la que marca su fuente de reloj, sino que
internamente se divide por 4. Si, por ejemplo, un PIC16F trabaja con un XTAL de 8MHz, entonces su
procesador operar a 2MHz.
En este caso para la simulacin en Proteus lo que cuenta es la frecuencia del XTAL, aunque la ventana de
propiedades indique Processor Clock Frequency.
Adicionalmente los PIC18F (y superiores) pueden multiplicar la frecuencia de su fuente de reloj para
conseguir una velocidad de operacin mayor.
este temporizador no depende de nosotros ya que siempre estar activado. Es solo un tema de Proteus y la
verdad es que, como 1024 ciclos son poco apreciables, se suele ignorar para las simulaciones.
Generate Q Clocks on CLKOUT Pin: Cuando el PIC opera con un oscilador RC externo (de frecuencia Fosc)
en vez de un XTAL, el pin OSC2/CLKOUT sacar una onda cuadrada de frecuencia Fosc/4. Y nosotros
sabemos que a Proteus le interesa un bledo si se usa XTAL, circuito RC u otro oscilador. Por tanto, poniendo
YES en este campo el pin OSC2/CLKOUT mostrar la onda de Fosc/4 (siendo este Fosc la frecuencia
configurada en la ventana de propiedades del PIC), independientemente del oscilador usado. Como sea, una
seal de este orden en Proteus sera demasiado pesada de simular, por lo que es preferible dejarla tal como
est, inhabilitada.
Watchdog Timer Period: El Watchdog es un temporizador que tiene un tiempo base de 18ms, el cual vara
ligeramente con la temperatura del chip. Al igual que todas las partes digitales de Proteus, el PIC no tiene un
parmetro que interacte directamente con la temperatura establecida para el diseo. Si se desea una
simulacin con un valor de Watchdog Timer Period un poquito diferente del tpico, ste es el campo donde se
cambia.
Port Pin Low-High Delay y Port Pin High-Low Delay: En el mundo real los niveles de tensin en los pines
del PIC no permutan instantneamente. Estos tiempos de propagacin son del orden de los nanosegundos y
Proteus suele despreciarlos por defecto para la simulacin. En caso de tener algn diseo donde ese parmetro
realmente te importe debes indicar en este campo los tiempos pertinentes. (Los puedes hallar en el datasheet.)
Data EEPROM Write Delay. Segn su datasheet, los PIC16F tienen una EEPROM cuyos datos se graban en
un tiempo tpico de 4 ms, llegando a 10 ms como mucho. En este campo se puede establecer algn valor en
particular.
Initial contents of EEPROM. Es la misma EEPROM del punto anterior. Se supone que el contenido inicial de
esta memoria tambin puede formar del archivo HEX; as que este campo ser raramente cargado.
Ventanas de Depuracin
Cuando se arranca la simulacin, al final del men Debug aparecen los tems de varias ventanas de
depuracin (Simulation Log, Watch, etc.) que nos recuerdan al men Debug Windows del Studio 5 cuando
se inicia el modo Debugging. Obviamente Proteus soporta mucho ms que un microcontrolador y este men
podr crecer segn el diseo.
Excepto Source Code y Variables, las dems ventanas siempre deberan estar disponibles, incluso para una
depuracin que solo use el archivo HEX.
Simulation Log. Enva un reporte de los detalles de la simulacin. En principio muestra las libreras de
simulacin cargadas, la versin del kernel de SPICE utilizada y el tamao del archivo (HEX, COF u
otro) cargado. Pero, ms importante, tambin desplegar los errores, mensajes y advertencias de los
eventos ocurridos durante la simulacin, normalmente referidos al microcontrolador; por ejemplo,
cortocircuitos (contention on net), acceso a un registro no implementado del microcontrolador, etc. As
que cuando sientas que algo anda mal con la simulacin no dudes en revisar en primer lugar esta
ventana.
AVR Source Code. La estuvimos viendo desde el principio. Si hay varios microcontroladores en el
diseo, se presentar una ventana de cdigo para cada uno. En realidad, eso pasar con todas las
ventanas AVR.... Como se ve en la siguiente figura su men de botn derecho ofrece varias opciones
como configurar los colores, el tipo de fuente, etc. Si haces clic en Dissassembly, veras el cdigo
fuente con su equivalente en ensamblador. Es una vista muy prctica.
AVR Variables. Es para m una de las ms indispensables. Aqu se visualizan las variables que procesa
el programa actualmente. Por ejemplo, en la siguiente figura est mostrando la variable n de la funcin
delay_ms y el color rojo dice que su valor acaba de cambiar a 300. Yo no s qu hara sin esta ventana
;). (Esta ventana corresponde al debugging del programa del LED parpadeante flashled.)
AVR CPU Registers. Muestra el registro de estado SREG, todos los Registros de Trabajo (R0 a R31)
del AVR, incluyendo los punteros X, Y y Z. Tambin muestra la siguiente instruccin a ejecutarse
junto con el valor del Contador de Programa PC. La S que se ve al fondo representa el Puntero de Pila
o Stack.
AVR Program Memory. Visualiza el cdigo del programa en hexadecimal, que poco o nada se
entiende, por eso raramente se le muestra.
AVR Data Memory. Aqu apreciaremos los valores de todos los datos de la RAM del AVR. Lo malo
es que no es nada fcil distinguirlos. Si de examinar las variables del programa se trata, es preferible
ver la ventana AVR Variables, cierto? Bueno, tal vez sirva para descubrir con cierta facilidad cmo
se van colocando los datos en la Pila, que tambin forma parte de la RAM. Una mejor alternativa
puede ser la ventana Watch Window.
Watch Window. Es como la ventanita homnima del Studio 5. All podemos poner solo los datos que
nos interesen, entre variables de la RAM, Registros de Trabajo, Registros de E/S, datos de la
EEPROM interna e incluso de la misma memoria FLASH del AVR. Solo a modo de ejemplo en la
siguiente figura he puesto en mi Watch Window los registros PORTC, PORTB y PIND. Fjate en que
el pin 0 de PORTB vale 1; debe ser que en ese momento el LED parpadeante est prendido.
AVR I/O Registers. Esta ventana muestra todos los registros de E/S del AVR, es decir, los registros
que controlan los puertos, el USART, el conversor ADC, etc. Como ves, tampoco se puede distinguir
mucho porque est en hexadecimal. As que tambin en este caso es preferible optar por la ventana
Watch Window, si es que el Registro de inters no es visualizado en la ventana AVR Variables.
AVR EEPROM Memory. Muestra el contenido de la memoria EEPROM interna del AVR.
MODE establece la operacin del Counter Timer como cronmetro (Time), frecuencmetro o
contador de pulsos (Count).
MANUAL RESET. Es ms cmodo hacer el RESET aqu que desde el pin RST.
RESET POLARITY indica si el RESET ser en el flanco de subida o de bajada del botn anterior.
GATE POLARITY establece si en modo contador el incremento ser con los pulsos positivos o
negativos.
Como prctica, veamos el tiempo exacto que toma una funcin delay_ms(), por ejemplo de nuestro programa
del LED parpadeante flashled.
Ejecutamos toda la sentencia delay_ms(500) con Step Over. Como 500ms es un poco grande veremos
desaparecer la ventana de cdigo por ese lapso, pero al final veremos que el cronmetro midi 500377
us, o sea, 500.377 ms.
Ahora mediremos el tiempo que transcurre entre las sentencias de las lneas 32 y 35 de este mismo cdigo,
solo que esta vez lo haremos empleando la opcin Run To Source Line (ejecutar hasta la lnea de cdigo
indicada).
Sea continuando con la simulacin o reinicindola, debemos seguir los tres pasos indicados en la
siguiente figura.
En este punto el triangulito rojo debe sealar la lnea 32 y el cronmetro debe indicar 00.000000, como
en la siguiente figura. Ahora sigue los dos pasos que all se indican.
Y sorpresa! (La sorpresa es para m porque en verdad no esperaba dar este resultado como ejemplo. ;)
Obtuvimos 500.377 ms otra vez. Si ahora se ha ejecutado ms cdigo, cmo es que el cronmetro calcul el
mismo tiempo?
Sucede que entre las lneas 32 y 35, adems del delay que toma 500377us, estn las sentencias PORTB |=
0X01 y PORTB &= ~0X01, las cuales se ejecutan en 0.5us, y el cronmetro no puede visualizar fracciones de
microsegundos. O sea que la diferencia est solo que no se aprecia por ser muy pequea. Lo que yo hice para
medirla fue bajar la frecuencia del AVR a 1MHz.
Otra forma de medir tiempos de ejecucin entre dos puntos es mediante los breakpoints. Es muy sencillo. Solo
hay que seguir los siguientes pasos. No pongo las capturas de pantalla porque creo que ya estoy abusando. ;)
Los diales Position desplazan las grficas de cada canal hacia arriba o hacia abajo, en el eje Y.
Los diales de tensin fijan la amplitud de voltaje por divisin. Va desde 2mV/div hasta 20V/div.
Con la barra deslizadora de cada canal se marca para que las seales sean tratadas como continuas
(DC), solo sus componentes alternas (AC), el nivel de tierra (GND) o para que no aparezca (OFF).
Las opciones del recuadro Trigger se pueden emplear para muestrear las seales de manera que se tengan
grficas ms fluidas sin desfases. En realidad, se usa raramente porque este osciloscopio es ms bien ideal y
poca falta ese tipo de calibraciones.
En el recuadro Horizontal el dial inferior establece el tiempo base para los canales, va desde 0.5s/div hasta
200ms/div. Mientras ms pequeo sea ms anchas se vern las ondas.
Creo que un buen diseo para probar las funciones de este osciloscopio ser un sencillo circuito amplificador
conformado por un op-amp. Tomaremos las seales de tensin en la entrada y en la salida.
El circuito a implementar es el mostrado abajo. Al menos al inicio no te relajes y rmalo tal como est. Al opamp no se le cambia nada, el alternador tiene 1khz de frecuencia y 100mV de amplitud. Donde veo que
muchos meten la pata es en las fuentes de alimentacin. Si tienes dudas puedes regresar a la seccin Edicin
de las Propiedades de los Objetos.
Circuito de prueba.
Al correr la simulacin notaremos solo una onda.
Pero luego de una adecuada calibracin de los diales tendremos una imagen as: (Fjate en la posicin de los
diales.)
Grficos de Simulacin
Dada la potencia del osciloscopio de Proteus, el uso de grficos para visualizar datos de la simulacin podra
parecer redundante. Bastar con revisar los ejemplos incluidos en Proteus para darse cuenta de que eso no es
del todo cierto.
Los grficos de Proteus son muy parecidos a los obtenidos por programas como PSPICE o Electronics
Workbench. De hecho, utilizan los mismos modelos de SPICE y el mecanismo para construirlos suele ser
similar, sobre todo en los grficos analgicos. Sucede que casi todos los modelos digitales de Proteus son
nativos.
Vamos previendo por tanto que podemos optar por grficos analgicos o digitales, aunque eso no significa
que un grfico analgico no pueda incluir una seal digital o viceversa.
Continuando con el diseo anterior en este ejercicio vamos a capturar las grficas de las tensiones de entrada y
salida del amplificador.
En este modo el nico archivo a cargar en el microcontrolador es el HEX; el archivo SDI solo tiene que
acompaarlo en su carpeta de destino. Para crear un archivo SDI Proteus tiene que ensamblar de nuevo todo el
programa por su cuenta. El procedimiento que debemos seguir es:
Vamos al menu Source Add/Remove Source Files... Nos aparecer una ventana como la siguiente. En
Target Processor estar seleccionado el nombre del microcontrolador presente en el diseo, es decir,
asumimos que a hacer esto partimos con al menos un microcontrolador en el circuito. De lo contrario habr
que seleccionarlo manualmente. Por otro lado, si hay ms de un microcontrolador en el circuito, aparecern
todos ellos y nosotros escogemos para cul ser el ensamblado actual (los programas se ensamblan por cada
microcontrolador a la vez).
Qu es el Studio 5
El Studio 5 es el Entorno de Desarrollo Integrado de Atmel para el desarrollo de proyectos con varios de sus
productos, relacionados con sus microcontroladores. Entre las herramientas que incluye nos deben interesar
las siguientes:
Un editor de cdigos, para editar los programas. Como todo gran editor permite mostrar los cdigos
fuente a colores, con nmeros de lnea, etc.
Un administrador de proyectos, que adems de trabajar con programas en ensamblador, le da un
completo soporte a los compiladores GCC AVR32 y GCC AVR (WinAVR). A diferencia de versiones
anteriores ahora es ms difcil la integracin con compiladores comerciales como CodeVision AVR o
ImageCraft AVR.
El ensamblador AVRASM, para trabajar con programas en ensamblador.
Los compiladores de software libre GCC AVR y GCC AVR32 en su versin para Windows
(WinAVR), para desarrollar programas en C para los AVR de 8 y 32 bits, como los ATtiny, ATmega,
ATxMega y AVR32. En versiones pasadas del Studio 5, este compilador se deba instalar por
separado.
El simulador AVR Simulator, para simular los programas de los AVR tanto si estn escritos en
lenguaje C o ensamblador.
El paquete AVR Software Framework o ASF, que es un conjunto de ms de 400 proyectos de ejemplo
en lenguaje C para los AVR de 8 y de 32 bits, desde el uso de puertos hasta el control del puerto USB.
Un completo sistema de ayuda integrado.
El Studio 5 tambin 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 ms de ellos, puedes
visitar la web de Atmel.
Los softwares de programacin como AVR Dragon, AVRISP MkII o AVR ONE!. stos son
programas que trabajan con dispositivos programadores comerciales del mismo nombre.
Un potente depurador llamado JTAGICE mkII. ICE significa In Circuit Emulator, y hace referencia a
un sistema de depuracin en el mismo circuito y con el mismo microcontrolador. Obviamente debe
trabajar con su propio adapatador hardware, que se conecta a la PC va la interface JTAG, conformada
por los pines TMS, TCK, TDI y TDO del AVR. Esta interface tambin permite la programacin serial
del AVR, es decir, el hardware es un depurador y programador. No todos los AVR tienen soporte
JTAG ICE (ejemplo los ATtiny).
El software JTAGICE mkII puede dirigir el programa del AVR en la misma aplicacin paso a paso,
como en cmara 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 cmo 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 simulacin de Proteus o del mismo Studio 5,
pero esto ser real.
Los elementos hardware de control y programacin del AVR tambin suelen estar disponibles en las
tarjetas de desarrollo que provee ATmel, como las tarjetas STK500 o STK600. Las utilidades software
correspondientes estn incluidas en el Studio 5.
Ahora que ya tienes una idea bien formada de lo que es y lo que puede hacer el Studio 5, puedes descargarlo
libremente desde www.atmel.com. Puede pesar hasta ms de 600 MB, as que tendrs que esperar un poquito.
Si ya tienes este programa, pero en su versin 4.x, ser mejor que te actualices. Hay sustanciales diferencias
entre la versin 5.x y las anteriores, en las cuales no perder tiempo citndolas. Solo espero que luego no te
quejes si encuentras cosas que no te salen igual ;).
En la pgina de descarga encontrars los siguientes paquetes.
AVR Studio 5 Installer (includes VSS and .NET 4.0). Como all se indica, esto incluye los paquetes Microsoft
Windows Framework .NET 4.0 y Visual Studio Shell (Isolated Mode) 2010. Ambos son prerrequisitos para la
instalacin del Studio 5 y probablemente ya los tengas instalados en tu PC. Si no ests seguro de ello o si
prefieres perder un poquito ms de tiempo en la descarga antes que averiguarlo, puedes descargar este paquete
completo. El instalador trae incluidos los compiladores GCC AVR y GCC AVR32 (WinAVR).
AVR Studio 5 Installer. Contiene nicamente el instalador del Studio 5 (incluyendo GCC AVR32 y GCC
AVR [WinAVR]). Descarga esta opcin si ya tienes Microsoft VSS y .NET 4.0.
AVR Studio 5 AVR Software Framework Update. Este paquete contiene la actualizacin de los ms de los
ms de 400 proyectos, ejemplos, libreras, etc., que conforman lo que se llama el AVR Software Framework o
simplemente ASF. El instalador del Studio 5 ya lo trae incluido solo que quiz no en su versin ms actual.
Por ejemplo, cuando descargu mi Studio 5.0 inclua el ASF 2.5.1 mientras que el actual era 2.7.0. La verdad
es que la diferencia en el contenido no se percibe. Descarga este archivo exe solo si quieres estar sper
actualizado e instlalo luego del Studio 5.
AVR Studio 5.0 - Part Support Pack for AVR XMEGA with USB. Es una extensin para que el Studio 5
tambin soporte los nuevos AVR ATxMega con USB, en este caso los ATxmega16A4U, ATxmega32A4U,
ATxmega64A3U, ATxmega128A3U, ATxmega192A3U, ATxmega256A3BU y ATxmega256A3U. La lista
debe cambiar con el tiempo. Descarga el instalador si trabajas con estos AVR.
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).
Ordenador 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 vdeo con soporte DirectX 9 y resolucin de 1024 x 768 o superiores.
Primero debo hacer una salvedad y es que antes de instalar el Studio 5 versin Release debes desinstalar la
versin Beta, si es que lo tenas. Y si tienes instalado AVR Studio 4 o AVR32 Studio, los puedes conservar
porque el Studio 5 puede trabajar en paralelo con ellos.
El Studio 5 ya no soporta los siguientes dispositivos, que an estn 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.
Bueno ahora s empezamos la instalacin como cualquier otro programa de Windows, siguiendo las
indicaciones presentadas. Aqu, algunos screenshots:
Terminada esta parte nos dirn que Microsoft Visual Studio 2010 Shell se instal con xito, a pesar de que
pueden aparecer esas X en rojo indicando que an no se ha instalado la documentacin respectiva (no es
necesario para el desempeo del Studio 5 pero si deseas la puedes descargar desde la web de Microsoft).
Tambin me recomienda actualizar mi PC con los parches de seguridad ms recientes de Windows.
Por supuesto que los drivers de Jungo son confiables, son de los mejores.
Creacin de un Proyecto en C
Solo hay una forma de crear un proyecto, esto para simplificar las cosas.
Abierto el Studio 5 vamos al men File
esta ventana para reconocer las memorias de cada dispositivo y para ver las herramientas que tienen
disponibles desde el Studio 5.
Las libreras en el lenguaje C se suelen dividir en dos archivos: uno (con extensin .c) que suele contener los
cdigos ejecutables de las funciones y otro (con extensin .h) donde se escriben las definiciones y los
prototipos de las funciones, bsicamente. As por ejemplo, en cursomicros.com se usan las libreras 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 cdigos fuente los archivos se invocan mediante la directiva include. Adicionalmente en los proyectos
de WinAVR o IAR C deben incluirse desde sus entornos de desarrollo. En esta ocasin veremos como hacer
esto con los archivos avr_compiler.h, usart.h y usart.c y el procediemiento descrito es el mismo que debes
seguir cuando aadas otros archivos.
Una vez ubicados los archivos avr_compiler.h, usart.h y usart.c, colcalos en la carpeta de tu proyecto. Esto
no es necesario porque que se le podra incluir dondequiera que est desde el Explorador de la Solucin. Sin
embargo habr proyectos en los que se quiera editar el archivo incluido y para que dichos cambios no afecten
a los dems proyectos lo ms 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 deberan estar en la carpeta del proyecto.
Ahora debemos indexar el archivo al proyecto. Para ello seleccionamos el proyecto en el Explorador de la
Solucin y vamos al men Project Add Existing Item Tambin puedes emplear mi camino preferido, que
se ilustra a continuacin.
Build Solution (construir la Solucin). Una Solucin puede estar compuesta por varios proyectos. De
ser el caso, con esta opcin se construirn todos los proyectos de la Solucin.
Rebuild Solucin (reconstruir la Solucin). Reconstruye todos los proyectos de la Solucin haciendo
previamente una limpieza de los archivos de salida generados antes.
Clean Solution (Limpiar Solucin). Para todos los proyectos de la Solucin actual, limpia o borra
todos los archivos de salida, como HEX, LSS, MAP, etc., que se crearon durante una construccin
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
accin 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 opcin 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 Solucin de ejemplo solo tiene un proyecto, dar lo mismo si elegimos construir el
proyecto o construir la Solucin.
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 disposicin pero como
aprendimos antes, eso es solo cuestin de reubicacin.
Resultados de la construccin.
Entre los archivos de salida puedes ver que aparecieron los esperados HEX (para grabar el microcontrolador)
y ELF (para la depuracin o simulacin en programas como Proteus). Todos estos archivos se depositan en la
carpeta Release del proyecto porque lo compilamos en ese modo.
(como se aprecia en el siguiente extracto de dicho archivo) porque en la gran mayora de mis diseos 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
Hay varias categoras desde Build hasta Advance y en muchas de ellas podremos elegir si la configuracin 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.
No optimizar (nivel O0). El cdigo no se optimiza nada. Por ejemplo, el programa del LED
parpadeante compilado con este nivel resulta en un cdigo 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 depuracin porque genera el mejor archivo ELF. No por nada es el nivel por defecto del modo
Debug. Adems, esa diferencia se desvanece cuando se trabaja con programas grandes. Por ejemplo un
programa que utiliza funciones matemticas con nmeros de punto flotante y la funcin Printf en su
mxima performance a m me result en 3366 bytes sin optimizacin y 3038 con optimizacin Os.
Optimizar (nivel O1). Optimiza regularmente; bastante, comparado con el nivel O0. Con este nivel mi
programa de prueba result en 3066 bytes.
Optimizar ms (nivel O2). Con este nivel mi programa de prueba result en 3060 bytes.
Optimizar al mximo (nivel O3). Este nivel es particular porque no optimiza para conseguir el menor
cdigo, sino el ms veloz, es decir, el cdigo que se ejecuta en el menor tiempo posible. Para esto el
compilador intentar incrustar en lnea 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 tamao (nivel Os). Optimiza para que la construccin genere el menor cdigo mquina
posible. En general el tamao del cdigo maquina se contrapone a su velocidad de ejecucin. No se
puede conseguir un cdigo mnimo y que al mismo tiempo sea el ms 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 opcin por defecto del modo Release.
AVR/GNU C
Recuerda que puedes elegir si la configuracin se aplica al modo Debug o Release y que cada modo hace que
los archivos de salida vayan a sus respectivas carpetas.
En fin, sea como fuere, para habilitar la opcin -mcall-prologues en el Studio 5 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.
nunca se te debera presentar. Nosotros no invocamos al archivo delay.h directamente, sino a travs de
avr_compiler.h.
Segn el manual de avr-libc, la versin minimizada de printf se consigue estableciendo las siguientes
opciones de linkador:
-Wl,-u,vfprintf -lprintf_min
La versin predeterminada de printf ofrece todas las caractersticas de la versin completa excepto las
relacionadas con los nmeros de punto flotante. En los megaAVR demanda cerca de 350 bytes ms
que la versin minimizada.
Es redundante decir que sta es la versin por defecto y no requiere de ninguna instruccin al linkador.
Pero si previamente se hubo establecido alguna de las otras versiones, entonces bastara con quitar las
siguientes opciones para regresar a la versin por defecto de printf.
-Wl,-u,vfprintf
La versin completa de printf brinda todo su potencial, incluyendo las conversiones de nmeros de
punto flotante. En los megaAVR requiere cerca de 380 bytes de memoria FLASH ms que la versin
predeterminada.
La versin completa de printf viene con las siguientes opciones de linkador:
Las preguntas son: y dnde se escriben esas cosas?, o hay que escribirlas? Por qu no se arregla
simplemente con un clic?, o por qu no es como en CCS C, o aunque sea como en CodeVisionAVR?
Y las respuestas son: porque WinAVR sigue siendo un husped del Studio 5 y todava no tienen una mejor
interaccin. Tal vez en el futuro. Por otro lado, de hecho CCS C tambin utiliza varias plantillas de su funcin
printf con diferente alcance cada una, solo que antes de compilar el programa detecta las caractersticas
mnimas necesarias segn el cdigo fuente y selecciona automticamente la plantilla ms adecuada. Genial
idea, verdad? Me quito el sombrero. Sospecho que CodeVisionAVR est cerca de conseguir esa
funcionalidad. Por su parte, las recientes versiones de IAR C ya cuentan con una opcin Auto para este
propsito..
Retomando el tema, lo que hacen esas cosas son dos cosas (como en El gato ;): primero, le dicen al linkador
que no utilice la versin 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 smbolo vfprintf (que
representa la funcin printf y similares) que se encuentra entre las libreras que usa por defecto y que ms bien
lo busque en otras libreras. Dnde? Para la versin minimizada, en la librera indicada por:
-lprintf_min
Y para la versin completa, en las libreras indicadas por:
-lprintf_flt -lm
Segn el manual de GNU GCC (que tambin se instal con tu WinAVR), la opcin genrica llibrary se
refiere a la librera liblibrary.a, por lo que en realidad las libreras mencionadas son libprintf_min.a ,
libprintf_flt.a y libm.a.
AVR/GNU C Compiler
La versin minimizada admite el ingreso de caracteres, cadenas de hasta 256 caracteres y nmeros
enteros de 16 bits positivos o negativos. No maneja nmeros de punto flotante, no admite el
especificador [, usado para filtrar el ingreso segn un conjunto de caracteres indicado por el usuario.
Tampoco permite usar el flag de conversin %.
Para su uso se requieren las siguientes opciones de linkador. All se indican inhabilitar la versin
predeterminada e incluir las libreras libscanf_min.a y libm.a.
-Wl,-u,vfscanf -lscanf_min -lm
La versin predeterminada provee casi todas caractersticas de scanf en versin completa. No ofrece
conversiones de nmeros de punto flotante y el ingreso de caracteres se limita a 256.
No hacen falta opciones de linkador. Si se estableci antes otra versin, habra que remover al menos
las opciones:
-Wl,-u,vfscanf
La versin completa s permite las conversiones de nmeros de punto flotante.
Para su uso debemos usar las siguientes opciones de linkador. All se indican inhabilitar la versin
predeterminada e incluir las libreras libscanf_flt.a y libm.a.
-Wl,-u,vfscanf -lscanf_flt -lm
Ya vimos que agregar libreras y poner las opciones Wl, u, vfscanf es muy sencillo. El punto ser cmo
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
Para esta seccin practicaremos con el pequeo secuenciador de LEDs ledflasher3. Todas las prcticas de
cursomicros.com son proyectos con Solucin separada, as que da lo mismo si abres el archivo del proyecto o
de la solucin. Puedes abrir el proyecto o solucin desde el Studio 5 o con doble clic en cualquiera de sus
archivos, *.avrgccproj o *.avrsln, respectivamente.
Con la accin indicada estamos iniciando el depurador, que ms que un simulador tambin 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 depuracin, entonces el Studio 5
las detectara y nos dara a elegir con cul depurador deseamos trabajar. Pero como no es el caso, la nica
opcin 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
simulacin correr directamente.
Windows.
La ventana Breakpoints muestra todos los breakpoints del programa, si es que existen. La forma ms cmoda
de poner breakpoints es haciendo doble clic en el margen izquierdo de la lnea deseada. Con ello aparecern
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 ejecucin de la depuracin cuando est en modo Run,
que es iniciado por el comando Continue..
La ventana Processor presenta informacin de los recursos asociados con el procesador del AVR, como el
Contador de Programa, el Puntero de Pila (Stack Pointer), el Registro de Estado, los Registros 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 cronmetro Stop Watch.
La ventana IO View muestra los Perifricos 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
mdulo del puerto D y el panel inferior lista sus registros relacionados, en este caso PIND, DDRD y PORTD.
Esta ventana tambin est disponible y ayuda muchsimo cuando se trabaja en modo de diseo. La diferencia
es que en modo de depuracin es posible modificar el valor de algunos de los Registros de E/S. Se podra
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 funcin 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 funcin main.
Por otro lado, la ventana Autos es ms selectiva an. Autos solo muestra las variables locales involucradas en
la operacin actual. Por ejemplo, en la figura mostrada Autos contiene a Effect porque est cerca la ejecucin
de la sentencia Effect = '1'; segn lo seala la flecha amarilla. El campo type seala el tipo de dato de la
variable, con el signo arroba @ indicando la localidad de RAM 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 pgina) comprobaremos que se hallan
implementadas en los Registros 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 vaca y la forma ms fcil de colocar las
variables en ella es seleccionar la variable en el cdigo y arrastrarla con el mouse hasta la ventana Watch, ms
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 tambin de los Registros de Trabajo (R0 a R31) y de los Registros de E/S vindolos mapeados en
la RAM.
La ventana Registry muestra exclusivamente todos los 32 Registros de Trabajo del AVR (R00 a R31). Vemos
en la siguiente figura que despus 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 fcilmente que la variable local Effect se almacena en el registro R18.
Ventana Stopwatch.
Observa que la frecuencia del procesador es por defecto de 1 MHz y no se actualiza al valor establecido en
algn archivo de configuracin. Lo primero que debemos hacer por tanto es modificarlo (si es necesario
claro). Y como en casi todas las prcticas de cursomicros.com se trabaja a 8 MHz, el cambio es obligado para
calibrar los tiempos correctamente. Solo selecciona el valor de Frecuency y edtalo.
Ahora hagamos un ejercicio midiendo el tiempo que toma la ejecucin de las sentencias puts, esto es, desde la
lnea 70 hasta la lnea 75 del programa secuenciador de LEDs ledflasher3. Para ello sigue los siguientes pasos.
Establece el flujo del programa en la primera sentencia puts (lnea 70); para esto puedes colocar el cursor en
esa lnea y luego presionar el botn Run to Cursor
Ahora resetea al valor del Stop Watch y si deseas medir los ciclos transcurridos tambin resetea el Contador
de Ciclos. Debera lucir ms o menos as.
El Stop Watch ha medido 107.144ms y el Contador de ciclos comput 857 152 ciclos.
Introduccin
Aprender a programar microcontroladores significa aprender a usar todos sus recursos para luego aplicarlos en
el diseo deseado. Es un proceso continuo, sistemtico y que demanda algo de paciencia.
En esta clase empezaremos por conocer el hardware interno de los AVR enfocndonos en los ATmega de las
series 8yy y 4yy, que son en la actualidad los mejores microcontroladores de 8 bits de Atmel disponibles en
encapsulados DIP de 28 y 40 pines respectivamente. Entre los miembros de estas familias estn el
ATmega48yy, ATmega88yy, ATmega168yy, ATmega328yy, ATmega164yy, ATmega324yy, ATmega644yy
y ATmega1284yy. Las letras yy pueden ser P, V, PA.
Existe mucha compatibilidad entre los microcontroladores de Atmel, por lo que la teora expuesta es aplicable
en gran parte a otras series de AVR como los ATtiny, los ATmega con USB o incluso a los AVR antiguos
como los clsicos ATmega8535, ATmega8515, ATmega16, ATmega32, etc. A decir verdad, los AVR que
estudiaremos son como las versiones mejoradas de los "clsicos" ATmega citados anteriormente.
En esta clase nos familiarizaremos con la nomenclatura de los AVR, aprenderemos a reconocer sus
capacidades de memoria FLASH y RAM a partir de sus nombres, y echaremos un vistazo a sus principales
recursos hardware. As que si no sabas con cul microcontrolador AVR empezar a trabajar, ste es un buen
punto de partida.
Tienen un repertorio de 131 instrucciones. Estn optimizadas para generar un mejor cdigo 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 ATxmega.
Tienen una velocidad de ejecucin 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 prctica no ser una velocidad sostenida, porque en el programa habr
instrucciones que se demoran 2 ms ciclos de instruccin. Sin embargo, siguen siendo bastante
rpidos si los comparamos por ejemplo con sus contrapartes de Microchip, los PIC18, los cuales tienen
un lmite de 10 o 12 MIPS.
Tienen un Timer0 de 8 bits que puede trabajar como Contador o Temporizador o Generador de ondas
PWM de 8 bits de resolucin.
Tienen un Timer1 de 16 bits que opera en modo Contador, Temporizador o como Generador de ondas
PWM con resolucin configurable de hasta 16 bits.
Los ATmega1284yy tienen adicionalmente un Timer3 idntico al Timer1.
Tienen un Timer2 de 8 bits, parecido al Timer0 pero con soporte para operar asncronamente con un
XTAL externo.
Tienen un Comparador Analgico.
Tienen un mdulo TWI (Two Wire Interface) para comunicaciones con el protocolo I2C en modos
Maestro y Esclavo.
Tienen un mdulo SPI programable. Soporta los modos Maestro y Esclavo.
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 Aritmtico Lgica) con el que realiza las operaciones de
suma, resta, AND lgica, OR lgica, 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 cul ser la siguiente instruccin
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 tambin variables temporales del programa cuando sea necesario.
Los perifricos del AVR son elementos que se pueden usar para una determinada tarea; por ejemplo, el
Timer0 sirve para temporizaciones. El USART para comunicaciones seriales RS232, etc. Casi todos
ellos sern estudiados en un captulo aparte.
Los puertos de E/S PORTA,..., PORTD son las lneas hacia/desde el exterior donde se pueden conectar
los dispositivos a controlar, como LEDs, transistores, LCDs, etc. Los ATmega de 40 pines tienen los 4
puertos completos, mientras que a los ATmega de 28 pines les falta PORTA y algunos pines en los
otros puertos.
Hay ms recursos presentes dentro de un AVR que tambin son imprescindibles pero cuyo trabajo queda en
segundo plano. Algunos de ellos sern 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 deberamos tanto. No
conozco a nadie que haya llegado a ese lmite con un solo microcontrolador. Lo ms probable es que, por ms
cuidado que tengas, llegues a frer tu AVR antes de tiempo en algn accidente. Eso es algo muy normal.
En los AVR las instrucciones de programa son de 16 de 32 bits. Pero siendo la gran mayora de 16 bits,
podemos decir que un AVR de N bytes de memoria FLASH puede almacenar hasta N/2 instrucciones de
cdigo ensamblador.
Antiguamente pareca sencillo reconocer la cantidad de memoria FLASH que tenan algunos AVR. Por
ejemplo, un ATmega32 tiene 32 k-bytes, un ATmega8L tiene 8 k-bytes. Los ATmega de ahora todava
conservan esa correspondencia entre el nmero que aparece en su nombre y la cantidad de k-bytes de FLASH,
solo que las nuevas series tambin llevan un nmero que puede entrar a confundir un poco.
En la siguiente tabla apreciamos los ATmega de las series 8yy, 4yy, 5yy y 50yy. (Las letras yy pueden ser P,
V, A o PA.) Personalmente, creo que al haber varias series, es ms fcil separar primero los nmeros que
representan la capacidad de FLASH porque deben ser nmeros redondos (digitalmente hablando), es decir,
deben ser potencias de 2, como 4, 8, 16, 32, 64 128. Por ejemplo, cunta memoria FLASH tendr un
ATmega3290P?, 3290 kbytes, 329 kbytes, 32 kbytes o 3 kbytes? Como el nico nmero redondo es 32, la
respuesta es 32 kbytes y deducimos que este ATmega es de la serie 90P. Se llega ms rpido a la respuesta si
leemos las opciones empezando por la izquierda.
AVR
ATmega48yy
ATmega88yy
ATmega168yy
ATmega328yy
ATmega164yy
ATmega324yy
ATmega644yy
ATmega1284yy
ATmega165yy
ATmega325yy
ATmega3250yy
ATmega645yy
ATmega6450yy
Memoria
FLASH
4K
8K
16 K
32 K
16 K
32 K
64 K
128 K
16 K
32 K
32 K
64 K
64 K
Memoria
RAM
512
1K
1K
2K
1K
2K
4K
16 K
1K
2K
2K
4K
4K
Memoria
EEPROM
256
512
512
1K
512
1K
2K
4K
512
1K
1K
2K
2K
Pines de E/S
23
23
23
23
32
32
32
32
54/69
54/69
54/69
54/69
54/69
La Seccin de Boot loader est diseada para almacenar el cdigo del Boot loader, que es un pequeo
programa para cargar el programa del AVR en la Seccin de Aplicacin, 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).
Adems, a diferencia de un SO para PC, un Boot loader debe ser el programa ms pequeo posible y se debe
ubicar al final de la memoria FLASH. No todos los ATmega tienen Seccin de Boot Loader, como el
ATmega48yy.
1
0
1
0
Los fuses BOOTSZ1 y BOOTSZ0 solo se pueden modificar al grabar el AVR. Su configuracin por defecto
siempre establece el tamao mayor elegible de la Seccin de Boot loader.
El hecho de que la Seccin de Boot Loader est diseada para alojar el programa cargador no significa que
est limitada a esa tarea. Si no la vamos a usar para su propsito primigenio, podemos emplearla como si fuera
parte de la Seccin de Aplicacin, o sea, como si no existiera la divisin entre estas dos Secciones y por ende
no tendr importancia el valor que pongamos en los fuses BOOTSZ1 y BOOTSZ0.
Memoria
FLASH
4K
Memoria RAM
Algunos Modelos
512
ATmega48A, ATmega48PA,
ATmega48P/V, ATmega48/V
AVR
Memoria
FLASH
ATmegaNNN
8K
ATmegaNNN
16 K
ATmegaNNN
32 K
ATmegaNNN
64 K
ATmegaNNN
128 K
Memoria RAM
Algunos Modelos
1K
ATmega88A, ATmega88PA,
ATmega88P/V, ATmega88/V
ATmega168A, ATmega168PA,
ATmega168P/V, ATmega168/V
ATmega164A, ATmega164PA
ATmega165A, ATmega165PA
ATmega169, ATmega169P
ATmega328, ATmega328P
ATmega324A, ATmega324PA
ATmega325A, ATmega325PA
ATmega3250A, ATmega3250PA
ATmega329P, ATmega3290P
ATmega644A, ATmega644PA
ATmega645A, ATmega645P
ATmega6450A, ATmega6450P
ATmega128, ATmega1281
ATmega1284, ATmega1284P
1K
2K
4K
4K, 8K o 16K
En los modelos listados y en general en todos los ATmega de las series ms recientes el espacio de la
memoria RAM (entendida en su concepto tradicional) empieza en la direccin 0x0100 y termina en 0x02FF,
0x04FF, 0x08FF, 0x10FF, 0x20FF o 0x40FF, segn el modelo. La verdad, no importa mucho saber dnde
termina como la cantidad misma. La direccin de inicio s es de consideracin pero cuando se programa en
lenguaje ensamblador.
Quiz alguien pudiera preguntar por qu la direccin de inicio no es 0x0000, como en otros
microcontroladores. Porque las primeras direcciones, desde 0x0000 hasta 0x00FF, estn 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
tambin se les puede acceder direccionndolos 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 algn modo, y tienen
funciones anlogas. Estn separados solo por tener diferente modo de acceso, pero eso se explicar mejor en
su seccin respectiva.
El direccionamiento y distincin de los espacios de la RAM solo son de preocupacin al trabajar en
ensamblador. Al programar en lenguajes de alto nivel los compiladores se encargan de toda la administracin
de la RAM, salvo que reciban directivas avanzadas.
El Contador de Programa, PC
El PC es un registro que indica la siguiente instruccin que debe ejecutar el CPU. Si vale 0x0000, ejecutar la
primera instruccin de la memoria; si vale 0x0002 ejecutar la tercera instruccin, y as... Al arrancar
microcontrolador, el PC vale 0x0000 y se va incrementando automticamente, con lo que el AVR debera
ejecutar una a una desde la primera hasta la ltima instruccin del programa. En realidad, en el cdigo habr
instrucciones que modifiquen el valor del PC de modo que el programa nunca termine.
Las que s son especiales son las instrucciones que trabajan con la Pila, como PUSH y POP. Estas
instrucciones no necesitan conocer la locacin en la Pila a/de donde guardarn/recuperarn 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 analoga 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 tendramos que quitar los platos que estn
encima.
Pero hasta ah llega la analoga 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 direccin inferior a la anterior. Esta
direccin 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.
SPHSP15SP14SP13SP12SP11SP10SP9SP8
SPLSP7SP6SP5SP4SP3SP2SP1SP0
El Puntero de Pila est representado por los registros de E/S SPH y SPL, que concatenados actan como un
registro de 16 bits. Como se dijo anteriormente, este registro tiene un comportamiento de auto-incremento y
auto-decremento. La nica intervencin por parte del usuario debera ser su inicializacin, esto es, cargarle la
ltima direccin de la RAM. Pero esta operacin solo es indispensable al programar en ensamblador. Los
compiladores como el C inicializan la Pila automticamente. 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 tamao del cdigo es pequeo y le puede caber sobradamente.
Pero quiz la participacin ms notable de Los Registros de Trabajo sea en el ALU (Unidad Aritmtico
Lgica) para computar las operaciones aritmticas y lgicas. Por ejemplo imaginemos que deseamos obtener
la raz cuadrada de un nmero de punto flotante ubicado en la RAM y almacenar el resultado de nuevo en la
RAM. En lenguaje C bastara con escribir una sola sentencia, pero el cdigo mquina generado involucra una
tarea ms compleja: al inicio el CPU mueve los 4 bytes del nmero 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 tambin estarn en los registros de trabajo para aprovechar su velocidad y eficacia. Solo al
terminar el cmputo el CPU depositar el resultado en la RAM.
Los compiladores de alto nivel tambin suelen emplear los Registros de Trabajo para pasar los argumentos de
sus funciones. Creo que con esos ejemplos debe quedar clara la razn 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 instruccin LDI, que sirve para cargar constantes al
registro (otro aspecto primordial de la programacin en ensamblador9. Los registros R26 a R31 tienen la
capacidad adicional de funcionar como punteros de 16 bits cada uno.
Los punteros pueden apuntar a (contener la direccin de) cualquier locacin del espacio de RAM. Esto junto
con las instrucciones adecuadas conforman el direccionamiento indirecto ms potente, muy til por ejemplo
para mover grandes bloques de datos.
Terminamos esta seccin explicando las direcciones que figuran en el mapa de los Registros de Trabajo. En
principio a los Registros de Trabajo no les debera hacer falta tener direcciones porque estn 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 estn diseadas para la RAM, como LD y ST. De esta manera se hacen ms 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 estn implementadas fsicamente all.
Espero que los mapas de memoria de los Registros de E/S que presento no te hagan pensar que estn divididos
en una suerte de bancos, como en los PICmicro. No, seor, nada de eso. Todos los espacios de memoria en los
AVR son lineales. Yo los tuve que subdividir para no presentar un culebrn.
Cada registro es de 8 bits y en total se cuentan 224 registros de E/S, aunque ni siquiera la mitad estn
implementados fsicamente. Las locaciones que aparecen sin nombre estn RESERVADAS y no nunca
deberan ser accedidas.
De modo similar, hay registros con algunos bits sin nombre ni funcionalidad que tambin 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 repitindolo a cada rato despus.
SMCR------------SM2SM1SM0SE
Los antiguos microcontroladores AVR solo tenan 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 tenan sus propias instrucciones de acceso, IN y OUT, que de hecho todava 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 parntesis.
Con la aparicin de nuevos perifricos en los AVR, aumentaron los registros de E/S y se sobrepasaba el
alcance de las instrucciones IN y OUT. As fue necesario disear los Registros de E/S para que tambin
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 parntesis.
A juzgar por sus direcciones, ya podrs deducir que la nica diferencia entre los Registros de E/S (estndar) 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 todava admiten el uso de las instrucciones clsicas como IN y
OUT.
El direccionamiento y distincin de los Registros de E/S estndar o extendidos son de especial preocupacin
al trabajar en ensamblador. Al programar en lenguajes de alto nivel los compiladores son quienes escogen el
modo de acceso y las instrucciones que consideren ms convenientes, salvo que reciban directivas contrarias.
Estos son registros de E/S que no estn relacionados con una parte exclusiva del microcontrolador. Por tanto
los volveremos a ver al mencionar en otras clases, aunque sea para referirnos a uno solo de sus bits.
El bit I se limpia por hardware despus de ocurrir una interrupcin, y se setea por la
instruccin para habilitar posteriores interrupciones. El bit I tambin se puede setear
o limpiar por la aplicacin con las instrucciones SEI y CLI.
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 operacin. Con la instruccin BST se puede copiar
un bit de un registro de trabajo al bit T y con la instruccin BLD se puede copiar el
bit T a un bit de un registro de trabajo.
Half Carry Flag
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 caractersticas del microcontrolador que se estudiaron en
esta clase. Ampliaremos la funcionalidad del bit PUD en la clase de puertos y entrada y salida generales y los
bits IVSEL junto con IVCE sern mejor expuestos en la clase de interrupciones.
MCUCRJTDBODSBODSEPUD------IVSELIVCE
JTD
JTAG Interface Disable
0. La interface JTAG estar habilitada si est programado el fuse JTAGEN.
1. la interface JTAG est deshabilitada.
BODS
BODSE
El bit BODS estar activo durante tres ciclos de reloj despus de haberse seteado.
Para apagar el circuito BOD para el modo Sleep actual se debe ejecutar la instruccin
Sleep mientras el bit BODS est activo. El bit BODS se limpia automticamente
despus de tres ciclos de reloj.
BOD Sleep Enable
PUD
El bit BODSE habilita la configuracin del bit de control BODS, como se explic en
la descripcin del bit BODS.
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.
IVSEL
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 deshabilitarn automticamente durante la ejecucin de esta
secuencia. Las interrupciones se deshabilitan en el ciclo en que se setea el bit IVCE y
permanecen deshabilitadas hasta despus de la instruccin 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 SREG no se afecta por la
deshabilitacin automtica.
IVCE
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.
MCUSR---------JTRFWDRFBORFEXTRFPORF
JTRF
JTAG Reset Flag
WDRF
Este bit se pone a uno si se produce un reset por un uno lgico en el Registro JTAG
Reset seleccionado por la instruccin de JTAG AVR_RESET. Este bit se pone a cero
por un Reset POR, o escribiendo un cero lgico en el flag.
Watchdog Reset Flag
BORF
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 lgico en el flag.
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 lgico en el flag.
EXTRF
PORF
Bit
7
6
5
4
3
2
1
Descripcin
Divide clock by 8
Clock output
Select start-up time
Select start-up time
Select Clock source
Select Clock source
Select Clock source
Bit
0
Bit
7
6
5
4
3
2
1
0
Bit
Extendido
BODLEVEL2
BODLEVEL1
BODLEVEL0
7
6
5
4
3
2
1
0
Descripcin
Select Clock source
Descripcin
Enable OCD
Enable JTAG
Enable Serial Programming
Watchdog Timer always on
EEPROM memory is preserved
Select Boot Size
Select Boot Size
Select Reset Vector
Descripcin
Pongo en relieve estos detalles porque hay muchos programadores, como el famoso WinPic800, en los que los
fuses se configuran bit a bit, como se ve abajo. Cmo haras si tuvieras que configurar el uso de un oscilador
de XTAL de 16 MHz en un ATmega3250P? Primero tendras que conocer los fuses correspondientes, cierto?
En este caso son CKSEL0, CKSEL1, CKSEL2 y CKSEL3. Luego tendras que descubrir cul es la
combinacin correcta para un XTAL de 16MHz. No te preocupes, todo esto lo estudiaremos ampliamente.
Reloj Externo. En este caso la fuente de reloj ser una seal 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 opcin no se necesitarn aadir componentes externos. El
reloj ser el oscilador RC Interno, el cual tiene una frecuencia cercana a 8MHz, y adicionalmente
ofrece al diseador la posibilidad de ajustar por software dicha calibracin hasta en 1%. Es til para
sistemas de bajo costo aunque de menor nivel de estabilidad. Es la opcin 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
estabilizacin opcionales. Es una configuracin diseada para aplicaciones de reloj.
En los ATmega 8xx y 4xx se establece poniendo los bits CKSEL3-0 = 0100 o 0101. En otros ATmega
puede variar.
Oscilador RC Interno de 128kHz. Este oscilador es parecido al RC de 8MHz, solo que ofrece menor
precisin y no se puede calibrar.
En los ATmega 8xx y 4xx se establece con los bits CKSEL3-0 iguales a 0011. En otros ATmega
puede no estar disponible.
XTAL Externo de Baja Potencia. Esta configuracin soporta cristales de 0.9 MHz hasta 16 MHz.
Estos cristales ahorran energa 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 estndar de alta frecuencia.
En los ATmega 8xx y 4xx los valores de los bits CKSEL3-0 dependern del rango de frecuencia del
XTAL, segn la siguiente tabla.
Rango del XTAL en
MHz
0.9 - 3.0
3.0 - 8.0
8.0 - 16.0
CKSEL3-0
1011 o 1010
1101 o 1100
1111 o 1110
XTAL Externo Estndar. Con esta configuracin el XTAL usado estar entre 0.4 MHz y 20 MHz (el
mximo 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 ATmega 8xx y 4xx se establece con los bits CKSEL3-0 = 0111 o 0110, dependiendo de la
frecuencia del cristal usado, segun la siguiente tabla..
Rango del XTAL en
MHz
0.4 - 16
0.4 - 20
CKSEL3-0
0111 o 0110
0111
Observa que con los 4 bits CKSEL3-0 es posible formar hasta 16 combinaciones. Entre las no citadas, algunas
estn reservadas y no deberan usarse, y otras corresponden a configuraciones relacionadas con el uso de un
Resonador Cermico en lugar del XTAL externo estndar o de baja potencia. Exclu las combinaciones del
resonador cermico para evitar una sobrecarga de nmeros innecesaria.
En la mayora de las prcticas de cursomicros.com se usa un XTAL de 8 MHz. Puede ser estndar o de baja
potencia. El XTAL de cuarzo brinda el reloj ms preciso y estable, lo que es ideal para aplicaciones que usan
los mdulos sncronos como el USART o TWI (I2C).
Debemos recordar que un 0 es un bit programado y un 1 es un bit sin programar. En los entornos de los
programadores 0 suele ser una casilla marcada. Por ejemplo, en WinPic800 la combinacin CKSEL3-0 =
0111 se vera as.
Pero ah no termina. Para asegurarse de que el oscilador del sistema tambin se haya estabilizado, habr un
Retardo de Arranque hasta que transcurran 1K o 16K ciclos de reloj CK. Durante ese lapso el AVR tambin
se mantiene en estado de reset y luego recin el procesador empezar a ejecutar el programa. El Retardo de
Arranque no solo se activa despus de un reset sino tambin despus de que el AVR salga de los estados
Power-save o Power-down, que son dos de los seis modos SLEEP que tienen los ATmega.
La configuraciones de los retardos de reset y de arranque varan de acuerdo con la fuente de reloj del sistema.
La siguiente tabla muestra solo las opciones admisibles para un XTAL estndar o de baja potencia.
CKSEL0
0
0
1
1
1
1
SUT1 SUT0
10
11
00
01
10
11
Retardo de
arranque
14CK 1K CK
4.1ms + 14CK 1K CK
65ms + 14CK 1K CK
14CK 16K CK
4.1ms + 14CK 16K CK
65ms + 14CK 16K CK
Retardo de reset
En esta tabla XTAL baja frecuencia no se refiere al XTAL de reloj de 32.768 kHz, sino a un XTAL estndar o
de baja potencia cuya frecuencia no est cercana a la mxima admisible por el AVR, que para los ATmega
8xx o 4xx es de 20MHz. Esta condicin concuerda con el valor del bit CKSEL0 dado en las tablas de los bits
CKSEL3-0 vistas anteriormente.
Como se ve algo confuso, vamos a poner un ejemplo de diseo. Supongamos que nuestra aplicacin utilizar
un ATmega644P a una frecuencia muy estable de 20 MHz. En primer lugar debemos utilizar un XTAL
estndar de 20MHz, para lo cual debemos programar los bits CKSEL3-0 con 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, tambin descartamos la configuracin de XTAL baja
frecuencia, slowly rising power. Ahora todava tenemos las tres opciones resaltadas en la siguiente tabla.
CKSEL0
0
0
1
1
1
1
SUT1 SUT0
10
11
00
01
10
11
Retardo de
arranque
14CK 1K CK
4.1ms + 14CK 1K CK
65ms + 14CK 1K CK
14CK 16K CK
4.1ms + 14CK 16K CK
65ms + 14CK 16K CK
Retardo de reset
Cul elegir? Vemos que los ciclos de reloj son todos iguales a 16K CK. Entonces nuestra decisin depender
del retardo de reset requerido. Si nuestro circuito tiene una alimentacin 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 rpidamente (fast rising power), podemos optar por el
retardo de 4.1ms con SUT1-0 = 10. Por supuesto que tambin 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 tensin de Vcc cae debajo de ciertos niveles. Tambin sirve para que el AVR no est trabajando con una
alimentacin 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 aplicacin trabaje con un circuito BOD externo o
interno. El BOD interno del AVR se configura con los bits BODLEVEL2-0.
Pero si programamos el bit RSTDISBL, el pin reset trabajar como PC6, o sea, como el sptimo 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.
Este fuse solo est disponible en los ATmega que no dividen su memoria en secciones de Aplicacin y de
Boot Loader, es decir, en los AVR que no soportan el Boot Loader convencional. Con el fuse programado se
podr utilizar la instruccin 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 recomendara que lo actives.
Byte de
Bits de Lock
BLB12
BLB11
BLB02
BLB01
LB2
LB1
Bit
7
6
5
4
3
2
1
0
Descripcin
Los bits de candado generales LB1 y LB2 establecen tres modos de proteccin que se describen en la tabla de
abajo. Debes tener especial cuidado con los modos 2 y 3. Si eliges el modo 3 tu AVR se quedar con su
programa actual y no podrs volver a programarlo. Con esta opcin tampoco se podr leer el cdigo de
programa del AVR desde un programador. Esto podra servir como una proteccin de cdigo, pero sin
marcha atrs.
En el modo 2 todava ser posible reprogramar el AVR, pero solo mediante la interface JTAG. Con esta
interface tambin se pueden programar los bits de fuses y de candado.
Modo de LB
1
2
Para comprender el uso de los 4 bits de candado de Boot Loader debemos conocer primero la funcin de las
Secciones de Aplicacin y de Boot Loader, adems de las instrucciones de ensamblador LPM y SPM, que
participan activamente en la auto-programacin 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.
Modo de BLB0
1
2
Modo de BLB0
Modo de BLB1
1
2
El valor predeterminado de todos bits de candado es 1 sin programar. En los softwares de programacin
como WinPic800 significa que las casillas respectivas estn desmarcadas y as se deberan dejar. En otros
software como AVRFLASH se indican las configuraciones por sus modos (1, 2, 3 4) y siendo los modos 1
los predeterminados, tambin deberan quedar as, a menos que estemos seguros de lo que hacemos.
Cuando actan como salidas, los pines pueden entregar tensiones de hasta Vcc. Cuando actan como entradas
pueden manejar niveles de hasta 0.5V por encima de Vcc. El diseo de los pines incluye diodos internos de
sujecin que les permiten soportar tensiones mucho mayores que Vcc o 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
lmites de corriente total que soportan los puertos en los ATmegaNN4YY:
La suma de las corrientes suministradas por lo pines PB0 - PB7, PD0 - PD7 y XTAL2 no debera
exceder los 100 mA.
La suma de las corrientes suministradas por los pines PA0 - PA3 y PC0 - PC7 no debera exceder los
100 mA.
La suma de las corrientes recibidas por los pines PB0 - PB7, PD0 - PD7 y XTAL2 no debera exceder
los 100 mA.
La suma de las corrientes recibidas por los pines PA0 - PA3 y PC0 - PC7 no debera exceder los 100
mA.
Para los ATmegaNN8YY la distribucin de lmites es un poco diferente. (Los pines ADC7 y ADC6 no estn
presentes en encapsulados DIP.)
La suma de las corrientes suministradas por lo pines PC0 - PC5, ADC7, ADC6 no debera exceder los
100 mA.
La suma de las corrientes suministradas por los pines PB0 - PB5, PD5 - PD7, XTAL1 y XTAL2 no
debera exceder los 100 mA.
La suma de las corrientes suministradas por los pines PD0 - PD4 y RESET no debera exceder los 100
mA.
La suma de las corrientes recibidas por los pines PC0 - PC5, PD0 - PD4, ADC7 y RESET no debera
exceder los 150 mA.
La suma de las corrientes recibidas por los pines PB0 - PB5, PD5 - PD7, ADC6, XTAL1 y XTAL2 no
debera exceder los 150 mA.
Existe un bit llamado PUD en el registro MCUCR cuya funcin 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 inters por setearlo puede ser
eliminar la pequea corriente que puede fluir por las pull-ps cuando los pines en cuestin se conectan a 0
lgico.
La siguiente figura muestra la conexin de un pulsador al AVR aprovechando la pull-up de un pin de E/S.
Fjate en que las pull-ups no se pueden usar como resistencias para excitar dispositivos como LEDs, rels, etc.
El registro PORTx es para escribir datos en los pines del puerto x que estn configurados como salida.
se es su uso habitual. Sin embargo, escribir en los bits de PORTx cuyos pines estn configurados
como entradas significa activar o desactivar las pull-ups de dichos pines (ver la tabla de abajo). sta es
la segunda funcin 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 estn
configurados como entradas o como salidas. La funcin 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 direccin del puerto x, es decir, para establecer cules pines
sern entradas y cules sern salidas. (Data Direction Register = Registro de Direccin de Datos).
Despus de un reset todos los puertos inician con sus pines configurados como entradas, pero se
pueden reconfigurar en cualquier punto del programa.
0 entrada
1 salida
Por ejemplo, si escribimos el valor 11110000 en DDRB, entonces los cuatro pines de menor peso del puerto B
sern entradas digitales y los cuatro pines superiores sern de salida.
Si escribimos 11111111 en DDRA, todos los pines del puerto A sern de salida, y si escribimos 00000000 en
DDRB todo el puerto B ser de entrada. La codificacin de lo expuesto sera as:
DDRA = 0xFF; // 0xFF = 0b11111111 DDRB = 0x00; // 0x00 = 0b00000000
Luego podremos leer y escribir en los puertos mediante PORTA y PINB, as.
unsigned char regval;
PORTA = 0x73; // Escribir 0b01110011 en el puerto A
regval = PINB; // Leer puerto B
Hasta aqu estuvo todo muy fcil porque los puertos completos estaban configurados para entrada o salida.
Ahora veremos casos de configuracin mixta y lo que sucede, por ejemplo, si escribimos en PINx o si leemos
de PORTx. Si adems trabajamos con el pin PUD para habilitar las resistencias de pull-up, tendremos que
valernos de una tabla para no enredarnos.
Caso
1
2
3
4
5
Bit en
DDRx
0
0
0
1
1
Bit en
PORTx
0
1
1
0
1
Bit PUD
(en MCUCR)
X
0
1
X
X
Direccin de
Pin
Entrada
Entrada
Entrada
Salida
Salida
Pull-up
Estado de Pin
No
S
No
No
No
Casos 4 y 5. Son los ms simples. Si un pin est configurado como salida, de ningn modo tendr pullup y su estado de salida ser 1 0 lgico, 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 lgico (por la pull-up), siempre que el bit PUD del registro
MCUCR valga 0.
Caso 3. Raramente se suele poner PUD a 1, pero si se hace, se deshabilitarn las pull-ups de todos los
pines. Por tanto los pines de entrada sern siempre de alta impedancia.
Ms adelante est una prctica de interface con teclado matricial donde se utiliza.
La notacin de nmeros binarios empleando el prefijo 0b no es parte del C Estndar y aunque AVR GCC la
admita, IAR C no la reconoce. Por tanto solo es recomendable el uso de la notacin hexadecimal.
Dado que los LEDs que parpadean dan miles de centelleos por segundo, nuestros ojos no lo podrn percibir
as, solo veremos una disminucin en su luminosidad.
Es el valor promedio de la corriente lo que cuenta para el brillo del LED. No obstante, tampoco hay una
proporcin directa entre estos dos parmetros. 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 ms adelante.
Circuito de la prctica.
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 separacin de los bloques en tres grupos, uno para cada efecto, tambin es una divisin lgica.
Cada uno de los bloques de la matriz Pattern es una posicin en el desplazamiento de los LEDs. Por ejemplo,
en la posicin 2 del primer efecto se ejecutar el bloque 2 por 100 veces cclicamente. El bloque 2 es el
tercero porque en el mundo digital se empieza desde 0 y su patrn es el conjunto de datos
/******************************************************************************
* FileName:
main.c
* Purpose:
LED Flasher 3. Secuenciador de 3 efectos seleccionables va USART
* Processor: ATmega164P
* Compiler:
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.
*
usart_init();
while(1)
{
start:
/* Establecer parmetros 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
* opcin vlida para cambiar de
*/
if(kbhit())
//
{
c = getchar();
//
if((c<='3') && (c>='1')) //
{
Effect = c;
//
goto start;
//
}
}
delay_us(150);
}
}
}
}
}
Delays Antirrebote
Cuando apretamos un pulsador o movemos un switch, la seal de tensin relacionada no cambiar su valor
establemente, sino que se darn pequeos rebotes en el contacto que generarn ondas irregulares. A veces se
puede implementar un sencillo filtro pasa-bajas para evadir estos rebotes. Como este circuito puede resultar
algo incmodo de armar, casi siempre se prefiere aadir una rutina antirrebote en el programa.
Circuito de la prctica.
/******************************************************************************
* FileName:
main.c
* Purpose:
Delays antirrebote
* Processor: ATmega164P
* Compiler:
IAR-C y AVR-GCC (WinAVR)
* Author:
Shawn Johnson. http://www.cursomicros.com.
*
* Copyright (C) 2008 - 2012 Shawn Johnson. All rights reserved.
*
* License:
Se permiten el uso y la redistribucin de este cdigo 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 = 0x0C;
DDRB = 0xFF;
PORTB = 0x00;
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
{
port >>= 1;
port &= 0x7F;
PORTB = port;
delay_us(30000);
while((PIND & 0x08)==0)
continue;
}
}
}
Circuito de la prctica.
En el programa se emplea el array bcdcodes para almacenar los cdigos 7 segmentos de los nmeros del 0 al
9.
// Matriz de cdigos hexadeximales. Formato: xgfedcba , x = don't care
const char 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
----0x3f
0x06
0x5b
0x4f
=
=
=
=
binario
----------00111111
00000110
01011011
01001111
//
//
//
//
7-segment
7-segment
7-segment
7-segment
code
code
code
code
of
of
of
of
0
1
2
3
0x66
0x6d
0x7d
0x07
0x07
0x7f
=
=
=
=
=
=
01100110
01101101
01111101
00000111
01111111
01101111
//
//
//
//
//
//
7-segment
7-segment
7-segment
7-segment
7-segment
7-segment
code
code
code
code
code
code
of
of
of
of
of
of
4
5
6
7
8
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, dont 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, dont care.
/******************************************************************************
* FileName:
main.c
* Overview:
Control de 4 displays 7 segmentos con interface USART
* Processor: ATmega164P
* Compiler:
IAR-C y AVR-GCC (WinAVR)
* Author:
Shawn Johnson. http://www.cursomicros.com.
*
* Copyright (C) 2008 - 2012 Shawn Johnson. All rights reserved.
*
* License:
Se permiten el uso y la redistribucin de este cdigo 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, unsigned char len);
// Prototipo de funcin
int main(void)
{
// Matriz de cdigos hexadeximales. Formato: xgfedcba , x = don't care
const char bcdcodes[] = {0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};
unsigned int d, digit;
signed char i;
char buff[10] = "1234";
char buff2[10];
DDRB = 0xFF;
DDRD = 0xF0;
usart_init();
puts("\r\n Escribe un nmero para el display 7 segmentos \r\n");
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 nmeroa
strcpy(buff, buff2);
// Actualizar cadena para visualizar
i = strlen(buff)-d-1;
if(i >= 0)
digit = buff[i];
else
digit = '0';
PORTD = 0xf0;
PORTB = bcdcodes[digit-'0'];
PORTD = PORTD & (~(0x10<<d));
delay_us(100);
}
}
}
//****************************************************************************
// Lee una cadena de texto de 'len' nmeros
// El tamao de 'buffer' debe ser mayor que 'len'.
//****************************************************************************
char GetNumStr(char * buffer, unsigned char len)
{
char c;
static unsigned char 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
}
else if((c=='\b')&&(i))
// Si c es RETROCESO y si i>0
{
i--;
//
putchar(c);
// Eco
}
else if((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
return 1;
// Retornar con 1
}
}
return 0;
// Retornar con 0
}
Pero aqu no termina la cosa. Con criterio de buen diseador deberamos prever lo que pasara si alguien
presionara un botn sin antes haber soltado el anterior. Eso depender de la aplicacin. En este programa yo
decid dejar sin efecto al segundo botn mientras el primero est pulsado. Est de ms decir que los rebotes
deben ser filtrados.
Circuito de la prctica.
La tarea de la funcin CheckButtons la puedes leer en su respectivo encabezado. All dice que si se llama
peridicamente cada 100us el retraso de repeticin ser de 600ms y la frecuencia de repeticin de 1/100ms =
10 incrementos/decrementos por segundo. Estos parmetros se pueden modificar editando los lmites de
conteo de las variables a y b.
Tras ser detectada la seal negativa (botn pulsado), el programa seguir testeando el pin continuamente
durante 25 ms. Solo se reconocer como vlido el pulso que permaneci estable en este lapso. Eso servir para
filtrar los pequeos pulsos de rebote.
Una caracterstica 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 aplicacin. En esta prctica ni se nota, pero si realmente llegara a interesar se puede ajustar o
superar con las interrupciones de algn timer.
/******************************************************************************
* FileName:
main.c
* Purpose:
Retraso de repeticin y velocidad de repeticin
* Processor: ATmega164P
* Compiler:
IAR-C y AVR-GCC (WinAVR)
* Author:
Shawn Johnson. http://www.cursomicros.com.
*
* Copyright (C) 2008 - 2012 Shawn Johnson. All rights reserved.
*
* License:
Se permiten el uso y la redistribucin de este cdigo 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 funcin */
void CheckButtons(void);
void IncCNT(void);
void DecCNT(void);
/* Definiciones */
#define MaxCNT
5000
#define ButtonI
( PIND & (1<<2) )
#define ButtonD
( PIND & (1<<3) )
/* Variables globales */
volatile unsigned int CNT;
// Contador
/*****************************************************************************
* Main function
****************************************************************************/
int main(void)
{
// Matriz de cdigos hexadeximales. Formato: xgfedcba , x = don't care
const char bcdcodes[] = {0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};
unsigned int i, j, digit;
signed char idx;
char buff[10];
DDRB = 0x7F;
PORTB = 0x80;
DDRD = 0xF0;
PORTD = 0x0C;
//
//
//
//
CNT = 123;
while(1)
{
for(j=0; j<100; j++)
{
for(i=0; i<4; i++)
// Inicializar contador
{
CheckButtons();
itoa(CNT, buff, 10);
idx = strlen(buff)-i-1;
if(idx >= 0)
digit = buff[idx];
else
digit = '0';
PORTD |= 0xf0;
PORTB = bcdcodes[digit-'0'];
PORTD &= (~(0x10<<i));
}
}
}
}
/*****************************************************************************
* Mide el tiempo que los botones I y D estn pulsados. Si dicho tiempo
* llega a 25ms llamar a IncCNT (o DecCNT) y luego de 600ms la llamar
* continuamente tras cada 100ms. Un botn no responder mientras el
* anterior no sea liberado.
* Para cumplir los tiempos citados debera ser llamada cada 100us.
****************************************************************************/
void CheckButtons(void)
{
static unsigned int a;
// Cuenta el tiempo que ButtonI est pulsado
static unsigned int 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
}
else if (a>6250)
// Si pasaron ms de 600 ms
{
IncCNT();
// Procesar por segunda y dems veces
a -= 1000;
// Siguientes incrementos sern 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
}
else if (b>6250)
// Si pasaron ms de 600 ms
{
DecCNT();
// Procesar por segunda y dems veces
b -= 1000;
// Siguientes incrementos sern 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)
CNT--;
}
// Si Duty > 0
// Decrementar CNT
Modo Full Step o de paso completo. Es cuando las bobinas se polarizan de dos en dos. En la siguiente
animacin los terminales medios estn unidos interna o externamente al crculo que representa la
alimentacin 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
polarizacin y a descubrir que no existen ms que 4 combinaciones de polarizacin aplicables, las
cuales deben repetirse cclicamente.
Los nmeros hexadecimales al lado de la tabla se obtienen asumiendo que los puntos azules son ceros
y los rojos unos. De hecho esa suposicin no importa mucho, como tampoco importa que los
terminales medios estn conectados a la alimentacin 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 direccin. Lo nico que har que cambie de direccin es que reciba la secuencia de pasos en
orden invertido.
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, vers que tambin 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 ms finos. La desventaja es que puede disminuir el torque, o
fuerza de giro del rotor.
El programa de esta prctica 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 de la prctica.
Puesto que la tabla de pasos del modo Half Step tambin 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 */
const char steps[] = {0x09,0x0D,0x0C,0x0E,0x06,0x07,0x03,0x0B};
/******************************************************************************
* FileName:
main.c
* Overview:
Control de motor PAP en modos Full step y Half step, va USART
* Processor: ATmega164P
* Compiler:
IAR-C y AVR-GCC (WinAVR)
* Author:
Shawn Johnson. http://www.cursomicros.com.
*
* Copyright (C) 2008 - 2012 Shawn Johnson. All rights reserved.
*
* License:
Se permiten el uso y la redistribucin de este cdigo 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 */
const char steps[] = {0x09,0x0D,0x0C,0x0E,0x06,0x07,0x03,0x0B};
signed char step;
char modo, c;
DDRB = 0x0F;
PORTB = 0x00;
step = 1;
modo = 0xff;
usart_init();
puts("\r\n
puts("\r\n
puts("\r\n
puts("\r\n
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("\r\n Full step");
else
puts("\r\n Half step");
break;
case '+':
step++;
if( modo && (step&0x01) )
step++;
if(step>7)
step = 0;
PORTB = steps[step];
break;
//
step--;
if( modo && (step&0x01) )
necesario...
case '-':
// Dar la vuelta
// Ejecutar el paso
necesario...
step--;
if(step<0)
{
step = 7;
//
// Dar la vuelta
if(modo)
step--;
}
PORTB = steps[step];
break;
}
}
}
}
La funcionalidad del programa no puede ser ms sencilla. El valor de cada tecla pulsada ser enviado a la
consola de puerto serie de la PC.
Circuito de la prctica.
La funcin de bajo nivel para el teclado es keypad_scan. En el interior de esta funcin 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.
Segn mi cdigo, el valor ledo en las columnas cuando no hay teclas pulsadas debera ser 1 lgico, y 0
cuando si las hay. Para eso es necesario que dichas lneas estn 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 funcin.
/******************************************************************************
* FileName:
main.c
* Purpose:
Control de teclado matricial de 4x4
* Processor: ATmega164P
* Compiler:
IAR-C y AVR-GCC (WinAVR)
* Author:
Shawn Johnson. http://www.cursomicros.com.
*
* Copyright (C) 2008 - 2012 Shawn Johnson. All rights reserved.
*
* License:
Se permiten el uso y la redistribucin de este cdigo 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
#define
#define
kpd_PORT
kpd_PIN
kpd_DDR
PORTB
PINB
DDRB
// Port write
// Port read
// Direccin de puerto
char keypad_read(void);
void keypad_released(void);
char keypad_scan(void);
int main(void)
{
char tecla;
usart_init();
}
//****************************************************************************
// 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 ledas son iguales
return c2;
// entonces aceptarla
}
return 0x00;
}
//****************************************************************************
// Espera hasta que el teclado quede libre.
//****************************************************************************
void keypad_released(void)
{
delay_us(10);
//
while(keypad_scan())
continue;
}
//****************************************************************************
// Escanea el teclado y retorna el valor ASCII de la primera tecla que
// encuentre pulsada. De otro modo retorna 0x00.
//****************************************************************************
char keypad_scan(void)
{
unsigned char Col, Row;
char RowMask, ColMask;
// Col0 Col1 Col2 Col3
const static char 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;
kpd_PORT = 0xF0;
RowMask = 0xFE;
for(Row=0; Row<4; Row++)
{
kpd_PORT = RowMask;
delay_us(10);
ColMask = 0x10;
//
// Para que se estabilice la seal
// Inicializar ColMask a 00010000
ColMask <<= 1;
RowMask <<= 1;
RowMask |= 0x01;
}
// Se llega aqu si no se hall ninguna tecla pulsada
kpd_DDR = 0x00;
// Todo puerto entrada otra vez
return 0x00;
// Retornar Cdigo de no tecla pulsada
}
El hardware
Sabemos que para encender un LED necesitamos de una seal de control, aparte de la alimentacin (Vcc o
GND), cierto? Con esto en mente deberamos suponer que para un letrero de 515 LEDs se necesitaran de
512 seales saliendo del microcontrolador, ms o menos como en la siguiente figura:
Pero no es as. Podemos resolver parte de este problema multiplicando las seales 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 que los primeros porque cuentan con
doble buffer. Uno para almacenar internamente los datos seriales que van ingresando al registro y otro que se
conecta al exterior. Ms adelante veremos los beneficios de esta arquitectura.
Todos estos registros son de 8 bits pero tienen la caracterstica de poder ser montados en cascada para
multiplicar sus salidas. Por ejemplo, en la siguiente figura se muestra cmo conectar varios registros 74595 en
cascada. Se pueden ir aadiendo tantos registros como salidas paralelas se desee.
Por otro lado, si nos basamos solo en este mecanismo para ampliar nuestras seales, para controlar los 512
LEDs tendramos que usar 512/8 = 64 registros de 8 bits, lo cual nos llevara a un circuito muy difcil de
implementar adems de bastante costoso. La tcnica 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
impresin de que estuvieran 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 y los ctodos se unen formando las filas (rows).
Tambin se puede armar una configuracin alternativa invirtiendo la polaridad de todos los LEDs. En ese caso
los transistores sern de tipo PNP.
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 estarn prendidos al 100 % sino
la octava parte (por las ocho filas) y tambin 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.
Por otro lado, los transistores deben tener la capacidad de controlar la corriente proveniente de todos los LEDs
de cada fila. En algunos casos bastar con usar el ULN2803.
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 vera as:
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 animacin se muestra como para visualizar la letra G se encienden los LEDs
correspondientes pero en tiempos diferentes. La primera figura muestra la secuencia del barrido en cmara
lenta pero en la prctica los barridos sern tan rpidos que los LEDs se vern como en la segunda figura.
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 segn un patrn establecido. Este patrn
corresponde a letras, figuras o nmeros que queramos y se puede estructurar de diversas formas.
Vamos a representar el patrn con una matriz 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 pequeo texto de arriba la matriz escrita en lenguaje C quedara algo as:
const char matrix[] = {0x00, 0xFF, 0x10, 0x28, 0x44, 0x82, 0x00, 0xFF, 0x11, 0x31, 0x51, 0x8E, 0x00, 0x00,
0x00};
Esta matriz puede tener cualquier nombre pero de aqu en adelante me referir a ella como matrix.
Generacin automtica de matrix
Ya vimos que para generar nuestra 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 diseado para componer patrones de caracteres o grficos para displays
LCD o GLCD, tambin nos servir para nuestro panel de LEDs. Su uso es bastante fcil de descubrir, as que
no entrar en muchos detalles. Los pasos que debemos seguir son:
Presiona el botn Choose font y escoge la fuente que desees. Yo escog Verdana-Negrita-11 porque he visto
que produce un tipo de letra que se ajusta bien a la altura del letrero. Puedes probar por tu cuenta para ver
otros resultados.
En Char input ingresa el texto que mostrar tu 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 patrn 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.
Ahora presiona el botn "Step 2: open the fonts dialog parameters" y en la ventana que se abre escoge los
parmetros que indica la siguiente figura.
Presiona el botn "Step 3: Making a single fonts with the current graphics" para generar la matriz
hexadecimal. El resultado aparecer como se muestra en la siguiente figura. Puedes seleccionarlo y copiarlo
manualmente o mediante el botn "Copy all".
Guarda la matriz generada para usarla en el cdigo fuente. Esta matriz est por defecto declarada como
"unsigned char code Bmp001" pero lo cambiaremos luego.
El cdigo fuente
La elaboracin del cdigo depender de varios factores, como el tamao del panel, la forma cmo se presenta
el texto (efecto), la longitud de los mensajes, de si los mensajes son estticos o si se programan en tiempo de
ejecucin, etc. En esta prctica el panel solo muestra un mensaje en desplazamiento. Por tanto el cdigo
fuente ser muy simple.
/******************************************************************************
* FileName:
main.c
* Overview:
LED sign. Letrero Matricial de LEDs.
* Processor: ATmega164P
* Compiler:
IAR-C y AVR-GCC (WinAVR)
* Author:
Shawn Johnson. http://www.cursomicros.com.
*
* Copyright (C) 2008 - 2012 Shawn Johnson. All rights reserved.
*
* License:
Se permiten el uso y la redistribucin de este cdigo 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()
#define SDATA_LOW()
PORTD |= (1<<5)
PORTD &= ~(1<<5)
PORTD |= (1<<7)
PORTD &= ~(1<<7)
#define WIDTH 64
PROGMEM char matrix[] =
{
/*---------------------------------------------------------------------------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
};
const unsigned int LEN = sizeof(matrix);
/*****************************************************************************
* Main function
****************************************************************************/
int main(void)
{
unsigned int bad;
// Base index
unsigned int idx;
// Index
unsigned char dato; // dato
// Fila
// Columna
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 (23288
ciclos)
{
for(col=WIDTH; col; col--) // Cada fila iene WIDTH columnas (2911
ciclos)
{
idx = bad + col - 1;
// Calcular ndice de elemento en matrix
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)
SDATA_HIGH();
else
SDATA_LOW();
nop();nop();
// Si el bit de row es 1
// colocar 1 en DS
SCLOCK_HIGH();
nop();nop();
SCLOCK_LOW();
}
PORTB = 0x00
// o, colocar 0
temporalmente
}
}
}
}
}
descargar
SOEN_HIGH();
nop();nop();
SOEN_LOW();
PORTB = row;
La simulacin
Dudo que todos los lectores consigan implementar en la prctica real un letrero matricial completo debido a la
relativa complejidad del hardware, pero creo que al menos podrn ver su diseo en una buena simulacin
gracias a Proteus.
Debemos notar que para la simulacin en Proteus no es necesario armar el circuito completamente. Para este
diseo por ejemplo he ignorado las resistencias y los transistores de las columnas y filas del panel.
Se puede (o debe) editar el parmetro Minimum Trigger Time de las matrices de LEDs para mejorar la
visualizacin de los LEDs sobre todo si se cambia la frecuencia del XTAL.
El realismo de simulacin tambin depender de la potencia de ordenador. En ordenadores lentos el contenido
del panel se desplaza ms lentamente, aunque se puede mejorar la animacin modificando algunos
parmetros, como la cantidad de barridos por frame en el cdigo fuente, solo para fines de la simulacin.
Introduccin
Este captulo est dedicado a los LCDs alfanumricos con controlador Hitachi HD44780 o compatible, es
decir, la mayora. Hay diversas firmas, como Optrex, Sharp, Crystalfontz America, Tianma, etc., que
producen muchsimos LCDs de este tipo. Los hay desde 1 a 4 lneas, desde 8 a 40 letras por lnea, algunos con
iluminacin de fondo, con diferente tecnologa de fabricacin, etc. Dada la compatibilidad en el control de
todos ellos, la eleccin de un modelo en particular queda a tu cargo. El LCD utilizado en este curso es de 2
lneas, de 16 letras cada una.
Nmero
de Pin
1
2
3
4
5
6
7...14
Smbolo
Vss
Vcc o Vdd
Vee o Vo
RS
R/W
E
DB0...DB7
Funcin
8 lneas de bus de datos. Para transferencia bidireccional de datos entre el C y el mdulo LCD.
DB7 tambin se puede usar como bit busy flag. En operacin de 4 bits solo se usa el nibble alto.
Enable Seal de inicio de operacin de lectura/escritura.
Seal para seleccionar operacin de lectura o escritura.
R/W
0 : Escribir en LCD
1 : Leer de LCD
Register Select
RS
Vee o Vo
Vdd o Vcc
Vss
Por lo tanto, podemos entender que siempre tenemos un LCD virtual de 240; aunque solo podamos ver 8, 16
20 letras por cada lnea. Los otros datos escritos en la DDRAM permanecen all aunque no se visualicen.
El Puntero de RAM
Llamado tambin 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 locacin de DDRAM (o CGRAM) de esa direccin.
Ahora bien, solo hay un puntero de RAM que trabaja con las dos RAMs del LCD, y para saber a cul de ellas
accede actualmente debemos ver la instruccin enviada ms 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 mayora de los casos, el Puntero de RAM estar apuntando a la DDRAM.
Adems, en este caso viene a representar la posicin del cursor (visible o no) del LCD en la pantalla.
Cdigo
Instrucciones
de comando
DB4
DB3
DB2
DB1
DB0
Clear Display
Return Home
0 0
0 0
0
0
0
0
0
0
0
0
0
0
0
0
0
1
0 0
I/D
0 0
0 0
0 0
0 0
0 0
0
1
1
Puntero de RAM (CGRAM)
Puntero de RAM (DDRAM)
Display ON/OFF
Control
Cursor or Display
Shift
Function Set
0 1
BF
DL
S/C
R/L
1 0
Escribir dato
1 1
Leer dato
Conviene saber que las instrucciones Clear Display y Return Home tienen un tiempo de ejecucin de cerca de
1.52 ms. Las dems 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 locacin de la DDRAM o
CGRAM, para operaciones de lectura y escritura de datos.
Con RS = 0 accedemos al registro de comandos para 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. Tambin retorna el cursor a su posicin inicial (cima izquierda), esto es,
designa el puntero de RAM a la direccin 0x00 de la DDRAM.
Return home: 0 0 0 0 0 0 1 x
Regresa el cursor a su posicin inicial pero sin alterar el texto del display, es decir, solo designa el puntero de
RAM a la direccin 0x00 de la DDRAM.
Entry mode set: 0 0 0 0 0 1 I/D S
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.
Designa el puntero de RAM a lanueva direccin AAAAAAA de la DDRAM. Digamos que sirve para
controlar la posicin del cursor del LCD.
Ejemplo, para escribir un texto en la segunda lnea del display (que tiene direccin inicial 0x40), primero
habra que enviar el comando Set DDRAM Address con el nmero 0x40 en el parmetro AAAAAAA.
Leer bit Busy Flag (BF) y el valor del puntero de RAM. BF = 1 indica que una operacin interna est en
progreso. El LCD no aceptar una nueva instruccin hasta que BF sea 0.
El valor de AAAAAAA ledo 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
instruccin.
Write data to CGRAM / DDRAM: DDDDDDDD
Escribe el dato de 8 bits DDDDDDDD en la DDRAM o CGRAM, dependiendo de cul de las dos est siendo
direccionada actualmente. Despus de la escritura el puntero de RAM se incrementa o decrementa, segn se
haya configurado el display. Ver instruccin Entry Mode Set.
Read data from CGRAM / DDRAM: DDDDDDDD
Lee un dato de 8 bits de la DDRAM o CGRAM, dependiendo de cul de ellas est siendo direccionada
actualmente. Despus de la lectura el puntero de RAM se incrementa o decrementa en uno, segn la
configuracin del display. Ver instruccin Entry Mode Set.
Aunque los LCDs parezcan simples de usar, para bien o para mal sus caractersticas abren puertas a diversos
modos de interface. Aqu, algunos puntos de consideracin.
Bus de datos. Estos LCDs ofrecen la posibilidad de ser controlados utilizando los 8 bits de su bus de
datos o solo 4.
Un modo de operacin del LCD (con ventajas y desventajas) le permite trabajar sin conectar el pin
R/W al microcontrolador. En ese modo pin R/W siempre debe plantarse a tierra y el programa deber
esperar los tiempos adecuados para que se ejecuten las instrucciones.
Los LCDs estn fabricados con tecnologa CMOS, lo que deriva en la sugerencia de conectar los pines
de entrada no usados a alguna seal estable para evitar que por ellos se filtre algn ruido que pueda
perturbar la operacin del LCD.
LCDs con iluminacin de fondo. Esta caracterstica se basa en diferentes tecnologas, siendo la ms
habitual el empleo de una matriz de LEDs colocados detrs de la pantalla. T sabes que hay todo tipo
de LEDs: algunos prenden a penas, mientras que otros, con la misma corriente, pueden servir de faros
(bueno, casi :). Creo que eso da cuenta de lo mucho que puede variar el uso de la iluminacin de un
modelo a otro.
La iluminacin suele activarse con los pines 15 y 16, pero su polaridad tambin vara entre modelos.
Sobra decir, por tanto, que sera mejor que chequees el datasheet de tu LCD si es uno de estos.
Como sea, los pines que activan la iluminacin suelen ser independientes de los 14 estndares y las
prcticas de este curso deberan funcionar con iluminacin o sin ella.
La librera utiliza por defecto el puerto B del AVR tanto para el bus de datos del LCD como para las lneas 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 podra ser una configuracin de cierta
recurrencia, en cambio, no deberamos tocar lcd.c, salvo que por alguna razn deseemos editar su cdigo.
lcd_init(). Obviamente es la primera funcin 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 automticamente tras cada letra que se escribe. Con
lcd_gotorc(r,c) podemos mover el cursor manualmente a la fila r y columna c. El parmetro 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 posicin actual del cursor. La cadena s es
almacenada en RAM. No se suelen mostrar grandes datos en un LCD, as que no implemente una funcin
anloga 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 posicin actual del cursor. lcd_data() tambin permite crear
caracteres grficos, como se muestra en una prctica ms adelante.
lcd_cmd(com). Ocasionalmente tambin usaremos esta funcin para enviar comandos al LCD, por ejemplo:
lcd_cmd(LCD_LINE2);
// Mover cursor al inicio de lnea 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 direccin 7 de la CGRAM
Las constantes LCD_CLEAR y algunas ms se hallan definidas en el archivo lcd.h. Por supuesto que tambin
se pueden formar cualesquiera cdigos de instrucciones de los estudiados antes.
/******************************************************************************
* FileName:
lcd.h
* Purpose:
Librera de funciones para controlar un display LCD con chip
*
Hitachi HD44780 o compatible. La interface es de 4 bits.
* Processor: ATmel AVR
* Compiler:
IAR-C y AVR-GCC (WinAVR)
* Author:
Shawn Johnson. http://www.cursomicros.com.
*
* Copyright (C) 2008 - 2012 Shawn Johnson. All rights reserved.
*
* License:
Se permiten el uso y la redistribucin de este cdigo con
*
modificaciones o sin ellas, siempre que se mantengan esta
*
licencia y las notas de autor y copyright de arriba.
*****************************************************************************/
#include "avr_compiler.h"
//****************************************************************************
//
CONFIGURACIN 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
// Registro PORT del puerto
#define lcd_DATAin
PINB
// Registro PIN del puerto
#define lcd_DATAddr
DDRB
// Registro DDR del puerto
/* Define el puerto a donde se conectarn las lneas de control del LCD
* E, RW y RS. Puede ser el mismo puerto del bus de datos.
*/
#define lcd_CTRLout
PORTB
// Registro PORT del puerto
#define lcd_CTRLin
PINB
// Registro PIN del puerto
#define lcd_CTRLddr
DDRB
// Registro DDR del puerto
/* Define los nmeros de los pines del puerto anterior que correspondern a
* las lneas E, RW y RS del LCD.
*/
#define lcd_E
3
// Pin Enable
#define lcd_RW
2
// Pin Read/Write
#define lcd_RS
1
// Pin Register Select
//****************************************************************************
//
CDIGOS DE COMANDO USUALES
//****************************************************************************
#define LCD_CLEAR
0x01
// Limpiar Display
#define LCD_RETHOM
0x02
// Cursor a inicio de lnea 1
#define LCD_LINE1
0x80
// Lnea 1 posicin 0
#define LCD_LINE2
0xC0
// Lnea 2 posicin 0
#define LCD_DDRAM
0x80
// Direccin 0x00 de DDRAM
#define LCD_CGRAM
0x40
// Direccin 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
//****************************************************************************
void lcd_init(void);
// Inicializa el LCD
void lcd_puts(char * s);
// Enva una cadena ram al LCD
void lcd_gotorc(char r, char c);
// Cursor a fila r, columna c
void lcd_clear(void);
// Limpia el LCD y regresa el cursor al inicio
void lcd_data(char dat);
// Enva una instruccin de dato al LCD
void lcd_cmd(char com);
// Enva una instruccin de comando al LCD
char lcd_read(char RS);
// Lee un dato del LCD
void lcd_write(char inst, char RS); // Escribe una instruccin en el LCD
void lcd_nibble(char nibble);
void ldelay_ms(unsigned char );
/******************************************************************************
* FileName:
lcd.c
* Purpose:
Librera de funciones para controlar un display LCD con chip
*
Hitachi HD44780 o compatible. La interface es de 4 bits.
* Processor: ATmel AVR
* Compiler:
IAR-C y AVR-GCC (WinAVR)
* Author:
Shawn Johnson. http://www.cursomicros.com.
*
* Copyright (C) 2008 - 2012 Shawn Johnson. All rights reserved.
*
* License:
Se permiten el uso y la redistribucin de este cdigo 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 inicializacin software completa del LCD. La configuracin es
// de: interface de 4 bits, despliegue de 2 lneas y caracteres de 5x7 puntos.
//****************************************************************************
void lcd_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 inicializacin 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, 47font
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 instruccin en el LCD:
// Si RS = 0 la instruccin es de comando (Function Set, Entry Mode set, etc).
// Si RS = 1 la instruccin es de dato y va a la DDRAM o CGRAM.
//****************************************************************************
void lcd_write(char inst, char RS)
{
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
}
//****************************************************************************
// Enva el nibble alto de 'nibble' al LCD.
//****************************************************************************
void lcd_nibble(char nibble)
{
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 locacin de DDRAM o CGRAM direccionada actualmente.
// Si RS = 0 se lee el 'bit de Busy Flag' + el 'Puntero de RAM'.
//****************************************************************************
char lcd_read(char RS)
{
char high, 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);
high = lcd_DATAin;
lcd_CTRLout &= ~(1<<lcd_E);
delay_us(2);
lcd_CTRLout |= (1<<lcd_E);
delay_us(2);
low = lcd_DATAin;
lcd_CTRLout &= ~(1<<lcd_E);
return (high&0xF0)|(low>>4);
//
//
//
//
//
//
//
//
//
}
//****************************************************************************
// Envan cadenas RAM terminadas en nulo al LCD.
//****************************************************************************
void lcd_puts(char * s)
{
unsigned char c, i=0;
while(c = s[i++])
lcd_write(c, 1);
// Instruccin 'Write Data to DDRAM/CGRAM'
}
//****************************************************************************
// Ubica el cursor del LCD en la columna c de la lnea r.
//****************************************************************************
void lcd_gotorc(char r, char c)
{
if(r==1)
r = LCD_LINE1;
else
r = LCD_LINE2;
lcd_write(r+c-1, 0);
// Instruccin 'Set DDRAM Address'
}
//****************************************************************************
// Limpia la pantalla del LCD y regresa el cursor a la primera posicin
// de la lnea 1.
//****************************************************************************
void lcd_clear(void)
{
lcd_write(LCD_CLEAR, 0);
// Instruccin 'Clear Display'
}
//****************************************************************************
// Envan instrucciones de comando y de datos al LCD.
//****************************************************************************
void lcd_cmd(char com)
{
lcd_write(com, 0);
// Cualquier instruccin de comando
}
void lcd_data(char dat)
{
lcd_write(dat, 1);
// Instruccin 'Write Data to DDRAM/CGRAM'
}
//****************************************************************************
// Genera un delay de n milisegundos
//****************************************************************************
void ldelay_ms(unsigned char n)
{
while(n--)
delay_us(1000);
}
Circuito de la prctica.
/******************************************************************************
* FileName:
main.c
* Purpose:
LCD - Visualizacin de texto
* Processor: ATmega164P
* Compiler:
IAR-C y AVR-GCC (WinAVR)
* Author:
Shawn Johnson. http://www.cursomicros.com.
*
// Inicializar LCD
//
//
//
//
// Pausa de 600 ms
// Limpiar pantalla
// ...
}
}
Visualizacin de Nmeros
Los LCDs solo entienden de caracteres alfanumricos y algunos otros, pero no saben reconocer nmeros. En
esta prctica veremos cmo hacerlo implementando un sencillo reloj. No ser el ms preciso, pero servir de
buen ejemplo parar formatear nmeros.
Para el circuito, de ahora en adelante, en vez del potencimetro, colocaremos un diodo 1N4148 en el pin VEE
para fijar la tensin (VDD-VEE) a cerca de 4.3 V. En la mayora de los LCDs este valor brinda un muy
aceptable nivel de contraste de la pantalla.
Circuito de la prctica.
La funcin lcd_puts recibe como parmetro un array de tipo char, que en su forma ms usada sera una cadena
texto.
Para visualizar nmeros en el LCD primero debemos convertirlos en cadenas de texto. La conocida funcin
sprintf (acrnimo de string print formatted) puede formatear cadenas y nmeros en diferentes bases y
colocarlas en el array que recibe como primer parmetro. Sprintf est basada en printf, as que tiene las
mismas caractersticas y limitaciones. En este programa solo se convierten nmeros enteros. Pero si deseas
utilizar nmeros de punto flotante tendrs que habilitar el uso de la librera que contiene printf en versin
completa. Para ms informacin puedes revisar la seccin Configuracin de printf de la clase de Studio5 y
WinAVR.
Sprintf soporta varios formatos de nmeros e incluso en su modo bsico requiere de cierta memoria que a
veces podra ser de consideracin. Para ese caso tambin se pueden usar otras funciones de la librera C
estndar stdlib.h, como itoa, por ejemplo. Normalmente no las uso porque tienen variaciones entre los
compiladores y al menos para las prcticas como sta prefiero no tocar esas divergencias.
/******************************************************************************
* FileName:
main.c
* Purpose:
LCD - Visualizacin de nmeros
* Processor: ATmega164P
* Compiler:
IAR-C y AVR-GCC (WinAVR)
* Author:
Shawn Johnson. http://www.cursomicros.com.
*
* Copyright (C) 2008 - 2012 Shawn Johnson. All rights reserved.
*
* License:
Se permiten el uso y la redistribucin de este cdigo 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(unsigned int t)
{
while(t--)
delay_us(1000);
}
int main(void)
{
char buff[17];
unsigned seg, min, hor;
seg = min = 0;
hor = 12;
lcd_init();
lcd_gotorc(1,4);
lcd_puts("easy clock");
// Inicializar LCD
// Cursor a fila 1 posicin 4
for(;;)
{
sprintf(buff, "%2d:%02d:%02d ", hor, min, seg); // Formatear
lcd_gotorc(2,5);
// Cursor a fila 2 posicin 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);
}
}
Hagamos un poco de memoria. Cuando enviamos el cdigo de un carcter alfanumrico a la DDRAM del
LCD, su chip interno buscar en la CGROM el patrn correspondiente y luego lo visualizar en la pantalla.
As se escriben todos textos (y as hemos trabajado hasta ahora).
Ahora bien, si el cdigo enviado vale entre 0x00 y 0x07 (o 0x08 y 0x0F), el chip interno buscar su patrn de
visualizacin en la CGRAM. Siendo sta una RAM de lectura/escritura, podemos programar en ella los
diseos que se nos ocurran.
El primer bloque de CGRAM, con direcciones desde 0b00000000 hasta 0b00000111, corresponde al cdigo
0x00 ( 0x80) de la DDRAM.
El segundo bloque CGRAM, con direcciones desde 0b00001000 hasta 0b00001111, corresponde al cdigo 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 habra que escribir un
cdigo as:
lcd_data(0x00);
lcd_data(0x01);
Sobre la prctica en s, como parte de su funcionalidad, el LCD tiene instrucciones para desplazar lo mostrado
en la pantalla hacia un lado u otro. Puede parecer interesante, pero sus limitaciones llevan a muchos a realizar
esos efectos mediante rutinas software. Pues es lo que haremos en esta prctica, mostrar por el LCD un
mensaje que pasa como una marquesina, y como nuevo carcter pondremos a un pacman glotn que en la
esquina inferior izquierda.
Circuito de la prctica.
Despus de iniciado el LCD, los datos que se le enven irn 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 direccin de CGRAM que queremos acceder.
La otra sentencia lcd_cmd(LCD_CGRAM+8) permitir que los siguientes datos vayan al segundo bloque (de
8 bytes) de la CGRAM. Fjate en que no era necesario porque el Puntero de RAM ya estaba apuntando a esta
direccin.
Como hemos creado los dos pacman en los dos primeros bloques (de 8 bytes) de la CGRAM, los cdigos para
accederlos sern 0 (PacOpen) y 1 (PacShut), respectivamente.
A continuacin se encuentra la sentencia lcd_clear(). Con ella no solo limpiamos la pantalla del LCD (que,
por cierto, ya estaba limpia) sino que volvemos a cambiar a la DDRAM.
Por si no qued claro cmo se forman los patrones de los dos pacman, aqu los tenemos solitos. (Los bits no
importan, pueden ser 1s o 0s.)
Pasando a otros temas: el texto de la pantalla se desplaza una posicin cada 400 ms. Si te parece que avanza
muy lento, puedes disminuir esta pausa. No obstante, podras empezar a ver como si hubiera dos letras por
casillero de la pantalla. Ello se debe a que el carcter enviado al LCD no se muestra ni se borra de inmediato.
Es lo que sus datasheets llaman tiempo de respuesta de visualizacin.
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.
/******************************************************************************
* FileName:
main.c
* Purpose:
LCD - Creacin de caracteres grficos personalizados
* Processor: ATmega164P
* Compiler:
IAR-C y AVR-GCC (WinAVR)
* Author:
Shawn Johnson. http://www.cursomicros.com.
*
* Copyright (C) 2008 - 2012 Shawn Johnson. All rights reserved.
*
* License:
Se permiten el uso y la redistribucin de este cdigo 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
#define
#define
LCD_LEN
PacOpen
PacShut
16
0x00
0x01
lcd_clear();
lcd_puts(" Hungry Pacman ");
while(1)
{
start:
i = 0;
for(;;)
{
lcd_cmd(LCD_LINE2);
if(i & 0x01)
lcd_data(PacOpen);
else
lcd_data(PacShut);
//
//
//
//
//
Cursor
Si bit
enviar
Si no,
enviar
a inicio de lnea 2
0 de i es 1,
pacman abierto
pacman cerrado
La tecla Escape, de cdigo 27, sirve pare limpiar la pantalla del LCD.
La tecla Retroceso o Backspace, de cdigo 0x08 = \b, lleva el cursor del LCD una posicin atrs.
Circuito de la prctica.
/******************************************************************************
* FileName:
main.c
* Purpose:
LCD - Acceso a LCD desde PC mediante AVR
* Processor: ATmega164P
* Compiler:
IAR-C y AVR-GCC (WinAVR)
* Author:
Shawn Johnson. http://www.cursomicros.com.
*
* Copyright (C) 2008 - 2012 Shawn Johnson. All rights reserved.
*
* License:
Se permiten el uso y la redistribucin de este cdigo 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"
int main(void)
{
lcd_init();
lcd_cmd(LCD_CURBLK);
usart_init();
// 9600 - 8N1
puts("\n\r Acceso a LCD desde PC \n\r");
puts("\n\r Escribe en el LCD... \n\r");
while(1)
{
if(kbhit())
// Si hay algn dato en el buffer de recepcin
{
char c = getchar();
// Obtener un dato
putchar(c);
// Devolver dato
switch(c)
{
case 0x1B: lcd_clear();
// Limpiar LCD
puts("\n\r");
break;
case 0x08: lcd_cmd(0x10);
// Cursor atrs
lcd_data(' ');
// Escribir espacio blanco
lcd_cmd(0x10);
// Cursor atrs
break;
default:
lcd_data(c);
// Escribir c en LCD
}
}
}
}
Introduccin
Hay una analoga que siempre recuerdo desde que la le en un buen libro de Turbo Pascal cuando aprenda 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
algn pulsador o algn teclado y actuar en consecuencia. Eso se conoce como tcnica Polling o de sondeo e
implica el desperdicio de recursos y ciclos de CPU.
En este captulo 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 pequea muestra de lo que se
puede conseguir con las interrupciones.
Num Direccin de
Nombre de
Vector Programa Vector de Interrupcin
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
0x0002
0x0004
0x0006
0x0008
0x000A
0x000C
0x000E
0x0010
0x0012
0x0014
0x0016
0x0018
0x001A
0x001C
0x001E
0x0020
0x0022
0x0024
0x0026
0x0028
0x002A
0x002C
0x002E
0x0030
0x0032
0x0034
0x0036
0x0038
0x003A
0x003C
0x003E
0x0040
0x0042
0x0044
INT0
INT1
INT2
PCINT0
PCINT1
PCINT2
PCINT3
WDT
TIMER2_COMPA
TIMER2_COMPB
TIMER2_OVF
TIMER1_CAPT
TIMER1_COMPA
TIMER1_COMPB
TIMER1_OVF
TIMER0_COMPA
TIMER0_COMPB
TIMER0_OVF
SPI_STC
USART0_RX
USART0_UDRE
USART0_TX
ANALOG_COMP
ADC
EE_READY
TWI
SPM_READY
USART1_RX
USART1_UDRE
USART1_TX
TIMER3_CAPT
TIMER3_COMPA
TIMER3_COMPB
TIMER3_OVF
Fuente de interrupcin
Watchdog Reset, and JTAG AVR Reset
External Interrupt Request 0
External Interrupt Request 1
External Interrupt Request 2
Pin Change Interrupt Request 0
Pin Change Interrupt Request 1
Pin Change Interrupt Request 2
Pin Change Interrupt Request 3
Watchdog Time-out Interrupt
Timer/Counter2 Compare Match A
Timer/Counter2 Compare Match B
Timer/Counter2 Overflow
Timer/Counter1 Capture Event
Timer/Counter1 Compare Match A
Timer/Counter1 Compare Match B
Timer/Counter1 Overflow
Timer/Counter0 Compare Match A
Timer/Counter0 Compare match B
Timer/Counter0 Overflow
SPI Serial Transfer Complete
USART0 Rx Complete
USART0 Data Register Empty
USART0 Tx Complete
Analog Comparator
ADC Conversion Complete
EEPROM Ready
2-wire Serial Interface
Store Program Memory Ready
USART1 Rx Complete
USART1 Data Register Empty
USART1 Tx Complete
Timer/Counter3 Capture Event
Timer/Counter3 Compare Match A
Timer/Counter3 Compare Match B
Timer/Counter3 Overflow
Para entender cmo 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 interrupcin el hardware guardar en la Pila el valor actual del Contador de Programa y lo
actualizar con el valor del Vector de Interrupcin respectivo, de modo que el CPU pasar a ejecutar el cdigo
que se encuentre a partir de esa direccin. Al final del cdigo de la interrupcin debe haber una instruccin de
retorno que restaure el Contador de Programa con el valor que se haba guardado en la Pila. La instruccin es
reti y la pone el compilador.
Si se llegara a producir el evento excepcional en que se disparen dos o ms interrupciones al mismo tiempo, se
ejecutarn las interrupciones en orden de prioridad. Tiene mayor prioridad la interrupcin cuyo Vector se
ubique ms abajo, es decir, entre todas, la interrupcin INT0 tiene siempre las de ganar.
La estructura y caractersticas de la Tabla de Vectores de Interrupcin pueden variar entre las diferentes
familias de ATmega y a veces entre diferentes partes de una misma serie. Por ejemplo, los ATmega de la serie
8yy no tienen la interrupcin externa INT2 y tampoco las interrupciones PCINT3 (porque les falta el puerto
A). Adems, el ATmega48yy no dispone de la funcionalidad de Boot Loader, as que este AVR no puede
desplazar su Tabla de Vectores de Interrupcin. 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 Interrupcin, que en la tabla de arriba se resaltan con enlaces en en azul.
Los nombres de los Vectores de Interrupcin presentados corresponden al datasheet y no necesariamente son
idnticos a los que utilizan los compiladores AVR GCC o IAR C. Estos nombres se encuentran definidos en
los archivos de dispositivo de cada AVR, ubicados en la carpeta include de cada compilador. La instalacin
por defecto de WinAVR con Studio 5 en Windows 7 marca la ruta C:\Program Files (x86)\Atmel\AVR Studio
5.0\AVR Toolchain\avr\include\avr. All los puedes ubicar, y de hecho es recomendable examinarlos que de
vez en cuando.
Pero si de momento deseas ahorrarte el trabajo te dir que la nica diferencia es el apndice _vect. Es decir, en
todos los archivos de dispositivo de IAR C y de AVR GCC (en sus versiones actuales) los nombres de los
Vectores de Interrupcin son los mismos que aparecen en el datasheet pero con el aadido _vect, como se
muestra en la siguiente tabla de ejemplo. Est de ms decir que en nuestros programas debemos usar la forma
con _vect.
Nombre de
Nombre de
Vector de Interrupcin Vector de Interrupcin
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 Interrupcin. 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 tendrn ms remedio que recurrir a los archivos de dispositivo de sus AVR, los cuales se hallan en la
fcilmente ubicable carpeta inc creada por CodeVisionAVR en su directorio de instalacin.
La Funcin de Interrupcin o ISR va siempre identificada por su Vector de Interrupcin, y su esquema vara
ligeramente entre un compilador y otro, puesto que no existe en el lenguaje C un formato estndar. Lo nico
seguro es que es una funcin que no puede recibir ni devolver ningn parmetro.
En el compilador AVR GCC (WinAVR) la funcin de interrupcin se escribe utilizando la palabra reservada
ISR. Recordemos que el Vector_de_Interrupcion debe tener la terminacin _vect, como se indic
anteriormente, y si tienes dudas puedes buscar en la carpeta include del directorio de instalacin de WinAVR.
ISR (Vector_de_Interrupcion)
{
// Cdigo de la funcin de interrupcin.
// No requiere limpiar el flag respectivo. El flag se limpia por hardware
}
Por otro lado, en IAR C y CodeVisionAVR la construccin es un poquito ms elaborada. Requiere el empleo
de la directiva #pragma vector y la palabra reservada __interrupt. El Nombre_de_Interrupcion queda a la
libertad del programador.
#pragma vector = Vector_de_interrupcion
__interrupt void Nombre_de_Interrupcion (void)
{
// Cdigo de la funcin de interrupcin.
// No requiere limpiar el flag respectivo. El flag se limpia por hardware
}
Por fortuna, estas divergencias entre IAR C y WinAVR solo se presentan en el encabezado de la funcin. La
implementacin del cuerpo de la funcin es idntica en ambos compiladores. Adems existe la posibilidad de
utilizar macros que adapten el esquema de la funcin de interrupcin de IAR C al formato de WinAVR. Estas
macros estn escritas en el archivo avr_compiler.h del Atmel Software Framework o ASF y que siempre se
usa en los programas de cursomicros.com.
En realidad, CodeVision AVR ofrece otras formas adicionales de implementar una funcin de interrupcin
pero, por ms que se parezcan en forma, lamentablemente ninguna de ellas es compatible con el cdigo de
IAR C o WinAVR, debido a las diferencias en los nombres de los Vectores de Interrupcin.
Cada interrupcin habilitada y disparada, saltar a su correspondiente Funcin de Interrupcin o ISR, de modo
que a diferencia de algunos otros microcontroladores no ser necesario sondear los flags de interrupcin para
conocer la fuente de interrupcin.
Los AVR van ms lejos y tienen un hardware que limpia automticamente el bit de Flag apenas se empiece a
ejecutar la funcin de interrupcin. Pero puesto que los flags se habilitan independientemente de si las
interrupciones estn habilitadas o no, en ocasiones ser necesario limpiarlos por software y en ese caso
debemos tener la especial consideracin de hacerlo escribiendo un uno y no un cero en su bit respectivo. S,
seor, dije, uno.
Al ejecutarse la funcin de interrupcin tambin se limpia por hardware el bit enable general I para evitar que
se disparen otras interrupciones cuando se est ejecutando la interrupcin 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 propsito, 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.
EIMSK---------------INT2INT1INT0
EIFR---------------INTF2INTF1INTF0
EICRA------ISC21ISC20ISC11ISC10ISC01ISC00
Para habilitar la interrupcin INTx hay que setear el bit INTx, del registro EIMSK, adems del bit enable
general I, del registro SREG.
Una vez producido el evento, el hardware setear el flag INTFx, del registro EIFR, y luego se disparar la
interrupcin. Este evento se debe configurar previamente en el registro EICRA y hay cuatro opciones
posibles.
Modo
ISCx1
ISCx0
Evento de la Interrupcin
Modo
0
1
2
3
ISCx1
ISCx0
0
1
1
Evento de la Interrupcin
Nivel bajo en el pin INTx.
EICRA------ISC21ISC20ISC11ISC10ISC01ISC00
Como la mayora de los registros, EICRA inicia con todos sus bits a cero lo que significa que por defecto la
interrupcin INTx habilitada se disparar cuando dicho pin est a nivel bajo.
La interrupcin externa INTx tiene la capacidad de despertar al AVR, es decir, de sacarlo del modo sleep.
sta es una caracterstica muy notable que veremos luego.
Circuito de la prctica.
El cdigo fuente
Cada aplicacin puede tener sus propias especificaciones, pero, en general, un buen hbito de programacin es
poner la sentencia sie(); que setea el bit I del registro SREG cuando ya todo est listo para atender a la
interrupcin.
Al analizar la estructura del programa, notamos que la funcin ISR es totalmente independiente de main, es
decir, no es referenciada desde ningn punto de main.
Una vez habilitada, la interrupcin se disparar cuando alguien presione el botn (en el flanco de bajada). En
ese preciso instante (quiz cuando se est ejecutando PINC = 0x02 o quiz en algn punto dentro de
delay_ms(600)) el CPU pasar a ejecutar la funcin ISR. Al salir de ISR, el CPU regresar a continuar la tarea
que estaba ejecutando antes de la interrupcin.
/******************************************************************************
* FileName:
main.c
* Purpose:
Uso de la interrupcin INTx
* Processor: ATmega AVR
* Compiler:
IAR-C y AVR-GCC (WinAVR)
* Author:
Shawn Johnson. http://www.cursomicros.com.
*
* Copyright (C) 2008 - 2012 Shawn Johnson. All rights reserved.
*
* License:
Se permiten el uso y la redistribucin de este cdigo con
*
modificaciones o sin ellas, siempre que se mantengan esta
*
licencia y las notas de autor y copyright de arriba.
*****************************************************************************/
#include "avr_compiler.h"
void delay_ms(unsigned int t)
{
while(t--)
delay_us(1000);
}
//****************************************************************************
// Interrupt Service Routine, ISR
// Esta funcin 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
}
//****************************************************************************
// Funcin principal
//****************************************************************************
int main(void)
{
DDRC = 0x03;
// Pines PC0 y PC1 para salida (LEDs)
PORTD = 0x04;
// Habilitar pull-up de pin PD2/INT0 (pulsador)
/* Habilitar y configurar la interrupcin 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();
while(1)
{
PINC = 0x02;
delay_ms(600);
}
// Bucle infinito
// Conmutar pin PC1
// 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 ejecucin del programa. Sin embargo los valores de todos los registros y puertos del
microcontrolador permanecern inalterables. En este estado se dice que el microcontrolador est durmiendo,
por el trmino sleep = sueo, en ingls.
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 botn. 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 muchsima energa que se perdera con el CPU y dems perifricos estando activos en
vano. Esto es clave sobre todo en circuitos alimentados por bateras.
Los microcontroladores AVR tienen un sistema oscilador sofisticado que divide el reloj en varias
ramificaciones que van a los diferentes mdulos 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 extendera tanto el tema que movera 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 instruccin 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
instruccin sleep. Cada compilador la implementa a su modo. En el archivo avr_compiler.h original se utiliza
la macro sleep_enter(), pero yo le aad otra definida como sleep(), a secas.
Tocamos el modo sleep ahora porque el evento por excelencia que puede despertar al microcontrolador es el
disparo de una interrupcin 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 sueo ms profundo, o
sea del modo Power-down.
Cuando se dispare una interrupcin lo primero que har el CPU al despertar es ejecutar la primera instruccin
de ensamblador que sigue a sleep, e inmediatamente despus pasar a ejecutar la funcin de interrupcin ISR.
Si an recuerdas el retardo de arranque, te dir que es aqu donde entra en accin: esto es, despus 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, recin, el CPU reiniciar
su trabajo. De los 6 modos sleep del AVR, el retardo de arranque solo acta en los niveles Power- down y
Power-save porque en los dems niveles, el oscilador del AVR no est del todo detenido.
Circuito de la prctica.
El cdigo fuente
/******************************************************************************
* FileName:
main.c
* Purpose:
Uso de las interrupciones INTx + Modo Sleep
* Processor: ATmega164P
* Compiler:
IAR-C y AVR-GCC (WinAVR)
* Author:
Shawn Johnson. http://www.cursomicros.com.
*
* Copyright (C) 2008 - 2012 Shawn Johnson. All rights reserved.
*
* License:
Se permiten el uso y la redistribucin de este cdigo con
*
modificaciones o sin ellas, siempre que se mantengan esta
*
licencia y las notas de autor y copyright de arriba.
*****************************************************************************/
#include "avr_compiler.h"
void delay_ms(unsigned int t)
{
while(t--)
delay_us(1000);
}
//****************************************************************************
// Gestor de Interrupcin INT0
// Esta funcin 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 Interrupcin INT1
// Esta funcin 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 Interrupcin INT2
// Esta funcin 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
}
//****************************************************************************
// Funcin principal
//****************************************************************************
int main(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 estn 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();
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 interrupcin, ejecutar nop() (que tambin equivale a una instruccin
de ensamblador) y luego llamar a la funcin ISR respectiva. Lo dems es historia conocida.
Aunque en algunos casos el nop() es recomendable, en esta ocasin lo puse solo para esta explicacin.
Y para los ATmega 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
PCICR---------------PCIE2PCIE1PCIE0
Luego se deben setear los bits enable que identifican individualmente los pines de los puertos. Estos bits se
encuentran en los registros de mscara PCMSK (Pin Change Mask Register). Hay un registro de mscara por
cada puerto del AVR aunque la relacin vara segn el nmero de puertos del AVR, como se indica en la
siguiente tabla.
ATmega de 4 puertos
Registro de mscara
Puerto
PCMSK0
PORTA
PCMSK1
PORTB
PCMSK2
PORTC
PCMSK3
PORTD
ATmega de 3 puertos
Registro de mscara
Puerto
PCMSK0
PORTB
PCMSK1
PORTC
PCMSK2
PORTD
Cada bit del registro de mscara 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 interrupcin es ubicndolos directamente por sus nombres PCINTx.
Para esto tambin debes estar revisando el diagrama de pines del AVR.
PCMSK0PCINT7PCINT6PCINT5PCINT4PCINT3PCINT2PCINT1PCINT0
PCMSK1PCINT15PCINT14PCINT13PCINT12PCINT11PCINT10PCINT9PCINT8
PCMSK2PCINT23PCINT22PCINT21PCINT20PCINT19PCINT18PCINT17PCINT16
PCMSK3PCINT31PCINT30PCINT29PCINT28PCINT27PCINT26PCINT25PCINT24
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 interrupcin, se activar el
flag respectivo PCIF (Pin Change Interrupt Flag) del registro PCIFR y luego se llamar a la funcin de
interrupcin ISR. As como hay un bit enable para cada puerto, en este nivel tambin 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.
PCIFR------------PCIF3PCIF2PCIF1PCIF0
Por supuesto, en los AVR de 3 puertos hay variacin en el mapa del registro PCIFR (Pin Change Interrupt
Flag Register).
PCIFR---------------PCIF2PCIF1PCIF0
Como de costumbre, el flag PCIF ser limpiado por el hardware al ejecutarse el gestor de interrupcin 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 situacin es recomendable habilitar la Interrupcin de Cambio de Pin despus 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 Interrupcin [de Cambio de Pin] por cada puerto
de AVR, llamados PCINT0_vect, PCINT1_vect, PCINT2_vect y PCINT3_vect. La activacin del flag PCIFx
conducir a la ejecucin de la funcin ISR identificada por el vector PCINTx_vect.
Circuito de la prctica.
El cdigo fuente
/******************************************************************************
* FileName:
main.c
* Purpose:
Uso de las Interrupciones de Cambio de Pin, PCINTx
* Processor: ATmega AVR
* Compiler:
IAR-C y AVR-GCC (WinAVR)
* Author:
Shawn Johnson. http://www.cursomicros.com.
*
* Copyright (C) 2008 - 2012 Shawn Johnson. All rights reserved.
*
* License:
Se permiten el uso y la redistribucin de este cdigo 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 funcin se ejecuta cuando se detectan cambios de nivel (flancos de
// subida o de bajada) en los pines PD5, PD6 o PD7.
//****************************************************************************
ISR (PCINT3_vect)
{
char dato = ~(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 ms de un pin
}
delay_us(40000);
// 40ms para pasar los rebotes
}
//****************************************************************************
// Funcin principal
//****************************************************************************
int main(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();
while(1)
// Bucle infinito
{
/* Entrar en modo sleep (Power-Down mode) */
SMCR = (1<<SM1)|(1<<SE);
sleep();
}
}
Circuito de la prctica.
El cdigo fuente
/******************************************************************************
* FileName:
main.c
* Purpose:
Control de Teclado mediante Interrupciones PCINTx
* Processor: ATmega164P
* Compiler:
IAR-C y AVR-GCC (WinAVR)
* Author:
Shawn Johnson. http://www.cursomicros.com.
*
* Copyright (C) 2008 - 2012 Shawn Johnson. All rights reserved.
*
* License:
Se permiten el uso y la redistribucin de este cdigo 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"
void SetupInt(void);
//****************************************************************************
// Interrupt Service Routine
// Esta funcin se ejecuta cuando se detectan cambios de nivel (flancos de
// subida o de bajada) en los pines PB4...PB7.
//****************************************************************************
ISR (PCINT1_vect)
{
char dato = keypad_read(); // Leer teclado
if(dato)
// Si fue tecla vlida
{
/* Esperar a que haya espacio en el buffer de transmisin */
while ((UCSR0A & (1<<UDRE0)) == 0 );
/* Colocar dato en el buffer de transmisin */
UDR0 = dato;
/* Esperar a que el teclado quede libre */
keypad_released();
}
SetupInt();
}
//****************************************************************************
// Funcin principal
//****************************************************************************
int main(void)
{
usart_init();
// Setup USART0 @ 9600-8N1
puts("\r\n Control de Teclado mediante Interrupciones PCINTx \r\r\n");
SetupInt();
/* Habilitar las interrupciones PCINT de PORTB en los pines PB4...PH7 */
PCICR = (1<<PCIE1);
PCMSK1 = 0xF0;
sei();
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 tensin producido al
// presionar una tecla.
//****************************************************************************
void SetupInt(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;
}
Segn el circuito y la librera del teclado, cuando no hay teclas pulsadas las lneas Col (nibble alto de PORTB)
se leen como 1 lgico (gracias a las pull-ups), y as deberan permanecer mientras el AVR est soando.
Por tanto, para que haya un cambio de nivel al pulsar una tecla, las lneas de Row (nibble bajo de PORTB)
deberan sacar 0 lgico. De esto se encarga la funcin SetupInt.
//****************************************************************************
// Prepara el puerto B para que detecte un cambio de tensin producido al
// presionar una tecla.
//****************************************************************************
void SetupInt(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;
}
Introduccin
La interface con un ordenador se puede realizar por cualquiera de sus puertos externos ms conocidos: serie,
paralelo o el USB. El paralelo casi ni se encuentra en los ordenadores de hoy y de 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 del ordenador (data logging), as
como a enviar datos mediante el teclado del PC hacia el microcontrolador.
El Estndar RS-232
Toda comunicacin elaborada entre dos dispositivos requiere conocer el protocolo que la gobierna a nivel
hardware y software. Para el puerto serie se trata del Estndar RS-232, o ms 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 ordenadores y equipos de mdem
de la poca (hace ms de 40 aos). Con el tiempo han surgido otras versiones como RS-232-C, RS-232-D,
RS-232-E, etc., una ms reciente que la otra, pero con variaciones inapreciables por ser uno de los estndares
menos estrictos. Despus de todo, es solo un Estndar Recomendado o Recommended Standard; de ah la
RS.
En la literatura tcnica se acostumbra mucho utilizar los trminos DTE y DCE para referir a los dispositivos
que se comunican segn el Estndar RS-232. DTE (Data Terminal Equipment) suele representar al ordenador
y DCE (Data Circuit-terminating Equipment) designa a cualquier dispositivo conectado al ordenador (un
mdem se sobrentenda antes).
Sin embargo, estos conceptos no quedan del todo claros en redes del tipo ordenador-ordenador o
microcontrolador-microcontrolador usando el puerto serie. As que por comodidad en adelante hablaremos de
ordenador y mdem, viendo como mdem 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 estndar.
Un 1 lgico se expresa por una tensin de 5V a 15V. Este estado se llama spacing.
Un 0 lgico se da cuando la tensin en cualquiera de las lneas es de +5V hasta +15V. Este estado se
conoce como marking
Bit Start. Es la transicin de 1 a 0 e indica el inicio de una transferencia. En la lgica RS-232 podra
significar una transicin de -15V a +15V y en lgica TTL es una transicin 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 despus 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 podra detectar errores, no corregirlos) y, segundo, porque hay mejores formas de
tratamiento de errores.
Bits Stop. Los bits de Stop son estados de 1 lgico. El Estndar dice que puede haber 1, 1.5 2 bits de
Stop al final de los datos (o del bit de paridad si lo hubiera).
Ahora bien, cuando vamos a conectar el ordenador a un microcontrolador nuestro inters se puede reducir a
tres lneas: TD, RD y SG. Las dems: o pueden ignorarse, o pueden conectarse al mismo puerto serie
artificiosamente para evitar problemas de comunicacin, o pueden usarse para implementar un Control del
Flujo de Datos (Handshaking) hardware, con el microcontrolador emulando algunas funciones de mdem. En
cualquiera de los tres casos, eso depender del software de ordenador usado para controlar el puerto serie.
En el conector hembra la posicin de los pines se puede ver diferente a fin de establecer una conexin
cruzada, es decir, para que el TD de un dispositivo se una con el RD del otro y viceversa. Lo mismo debe
pasar con los pares RTS-CTS y DTR-DSR.
Requerimientos Hardware
Requerimientos Software
Por un lado necesitamos unas rutinas para el microcontrolador que gestionen las funciones del Estndar RS232. stas pueden implementarse tranquilamente a nivel software debido a su simplicidad o mediante el
mdulo USART, el cual por supuesto ofrecer mucha ms eficiencia y flexibilidad.
Por otro lado, necesitaremos un programa de ordenador que se encargue de controlar su puerto serie. A su vez,
este programa puede ser uno desarrollado por nosotros mismos, que nos permitira tener el control total del
puerto serie y podramos transmitir y recibir todo tipo de datos (binarios o de texto). Tambin podramos
implementar tcnicas 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
tambin requiere de conocimientos a mediano nivel sobre programacin en lenguajes como Visual C++,
Delphi o Visual Basic.
Como alternativa prctica, 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 irn enviando hacia nuestro
microcontrolador. As mismo, los caracteres enviados desde el microcontrolador se irn mostrando en la
consola, todo en tiempo real.
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 instalacin te pedir aceptar los trminos y la
configuracin de cada aplicacin por separado.
Suponiendo que seguiste mi recomendacin, la instalacin 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 opcin
Serial y en Port debemos seleccionar el puerto serie COM a usar. Normalmente los ordenadores actuales solo
tienen un puerto COM disponible, el COM1. Despus 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 aplicacin que lo est
utilizando.
Y as de rpido estaremos al frente de la siguiente ventana, que ya podemos utilizar para nuestras
comunicaciones seriales, ya que su configuracin por defecto suele ser la ms habitual. La barra de ttulo
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 configuracin de comunicacin que usa Tera Term por defecto.
Para ello debemos ir al men Setup Serial Port El parmetro Transmit delay es el retardo que habr entre
cada uno de los datos que se enven y/o entre cada lnea de caracteres. Quiz interese incrementar este retardo
cuando el microcontrolador no etnga la capacidad de recibir los datos con la prestancia necesaria. En cuando a
los otros parmetros, ya los discutimos de sobra en el Estndar RS232.
Otra ventana donde encontrar algunas opciones tiles est en men Setup Terminal New-line establece si
el cursor pasar a la siguiente lnea 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 despus el color de fuente (Text) a verde
como se indica en la figura.
Ya te habrs dado cuenta de que la ventana del terminal se limpia cada vez que cambias su tamao. Para evitar
que lo haga debemos ir al men Setup Aditional settings y desmarcar la opcin 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
opcin 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 numrico. 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 funcionarn como en un teclado convencional.
Bueno, para ir directo al grano, seguimos el directorio de instalacin 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 lnea 29 como se ve abajo.
Las lneas precedidas de punto y coma (;) son comentarios y no cuentan. Son los nmeros mostrados en
naranja los que debemos editar: debemos cambiarlos todos por la palabra off, 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 tambin puedes ir al
men Setup Load key map y recargar manualmente el archivo KEYBOARD.CNF.
Finalmente, puesto que usaremos este programa con tanta frecuencia y no querremos estar configurndolo
cada vez que lo ejecutemos, debemos guardar la configuracin 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 configuracin actual.
Asncronamente pueden trabajar en modo full-duplex, esto es transmitir y recibir datos, al mismo tiempo
inclusive.
Pueden generar interrupciones al recibir datos o despus de enviarlos.
Puede operar en modo SPI maestro. Esta opcin no est disponible en los viejos AVR.
Operan en background (detrs 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 adems de aceptar el
bit paridad puede computar a nivel hardware su valor para el dato actual.
UDR0. El USART tiene dos buffers para las transferencias de datos: un buffer de transmisin (donde se cargan
los datos a trasmitir) y un buffer de recepcin (donde se almacenan los datos recibidos). Mediante el registro
UDR0 se accede a ambos buffers. Esto es, se accede al buffer de transmisin (en las operaciones de escritura)
y al buffer de recepcin (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 an usan los viejos AVR, deben saber que los
registros UCSRC y UBRRH comparten la misma locacin 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 podran aceptar
cualesquiera valores. Los registros de control y estado tienen los siguientes mapas de bits:
UCSR0ARXC0
TXC0
UDRE0
UCSR0BRXCIE0
TXCIE0
UDRIE0
UCSR0CUMSEL01 UMSEL00 UPM01
FE0
RXEN0
UPM00
DOR0
TXEN0
USBS0
UPE0
UCSZ02
UCSZ01
U2X0
RXB80
UCSZ00
MPCM0
TXB80
UCPOL0
En lo sucesivo iremos describiendo las funciones de estos dos registros y de cada uno de sus bits. Algunos bits
estn relacionados con la operacin del USART en modo sncrono, tema que de momento no nos compete, y
sern ignorados.
UBRR0 est conformado por la unin de dos registros de E/S: UBRR0H y UBRR0L. Con su capacidad de 16
bits y la frmula de U2X0 = 1 se pueden obtener los suficientes valores de baud rates como para hacernos
olvidar de la frmula con U2X0 = 0. De hecho, analizando las frmulas se deduce que cualquier valor de baud
con U2X = 0 tambin se puede conseguir con la frmula de U2X0 = 1, aunque a veces esto devendr en una
menor performance en el muestreo de la seal de recepcin.
En la prctica no interesa tanto el hecho de que U2X0 = 1 incremente la velocidad de las transferencias,
sino que se pueden obtener baud rates ms precisos.
En consecuencia, la mejor decisin en la mayora de los casos ser escoger la primera frmula, con U2X = 1.
De all debemos despejar UBRR0 para calcular su valor. Esto nos da siguiente expresin, que escrita en forma
lineal sera UBRR0 = F_CPU/(8*BAUD) 1.
El valor generado para UBRR0 raras veces ser exacto y en tales casos se deber escoger el ms cercano y
recalcular el valor del baud rate. Debemos tener cuidado con los resultados obtenidos puesto que hay valores
impuestos por el estndar 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 funcin de inicializacin del USART0. Es irnico
que tanta teora se condense en tan poco cdigo, pero es ms gracioso saber que la configuracin establecida
se puede resumir con la notacin BAUD 8N1, que ledo en orden significa Baud rate = BAUD, formato de
datos = 8 bits, No (sin) bit de paridad y 1 bit de Stop. Deberemos recordar esos parmetros a la hora de
configurar el software terminal del lado del ordenador.
//****************************************************************************
// Inicializa el USART0.
//****************************************************************************
void usart_init(void)
{
/* Configurar baud rate
*/
UCSR0A |= (1<<U2X0);
UBRR0 = F_CPU/(8*USART_BAUD)-1;
/* Configurar modo de operacin Asncrono y formato de frame a
* 8 bits de datos, 1 bit de stop y sin bit de paridad. */
UCSR0C = (1<<UCSZ01)|(1<<UCSZ00);
/* Habilitar mdulos Receptor y Transmisor
UCSR0B = (1<<RXEN0)|(1<<TXEN0);
*/
Una aclaracin para quienes usen los viejos AVR: en dichos AVR los registros UBRRH y UBRRL se
encuentran bastante lejos uno del otro en el espacio de los registros de E/S. Como consecuencia la sentencia
de asignacin UBRR = F_CPU/(8*USART_BAUD)-1; no ser vlida. Tendrn que escribirlos por separado
Transmisin 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 Transmisin si es que est vaco. 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 en UDR0 hasta que el registro de desplazamiento quede disponible. En ese lapso de tiempo
el registro UDR0 no podr aceptar un nuevo dato.
Una conclusin 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 en UDR0. As podemos decir que el
USART0 tiene un buffer FIFO de transmisin de dos niveles.
Con lo expuesto ya se puede codificar la siguiente funcin putchar. El encabezado de esta funcin est de
acuerdo con su definicin en la librera stdio.h del compilador C. All se indica que putchar debe tener un
parmetro de entrada y uno de salida, ambos de tipo int, aunque no en la prctica 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 transmisin */
while ((UCSR0A & (1<<UDRE0)) == 0 );
/* Colocar dato en el buffer de transmisin */
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 tambin tiene la capacidad de disparar una interrupcin ante dicho evento. En ese caso el flag
TXDC se limpiar automticamente al ejecutarse la funcin ISR; pero siendo de lectura/escritura tambin se
puede limpiar por software escribindole uno sobre l.
Recepcin 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 Recepcin. 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 ledos por el procesador, el nuevo dato permanecer en el registro de
desplazamiento hasta que haya espacio en UDR0.
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 ledo debemos comprobar el bit RXC0. Est flag es de solo lectura y se limpiar
automticamente cuando hayamos terminado de leer todos los datos del buffer UDR0. La activacin del flag
RXC0 tambin puede generar una interrupcin si est previamente habilitada.
No debemos confundir el UDR0 de recepcin con el UDR0 de transmisin. Aunque tengan el mismo nombre,
corresponden a dos registros diferentes. Es ms, el UDR0 de recepcin 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 UDR0 y 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 perdera y como seal se activara el flag de
desbordamiento DOR0 (Data OverRun).
Como no querremos que ocurra dicho evento trgico ser recomendable que leamos de inmediato cada nuevo
que llegue al USART. Siguiendo esta recomendacin la funcin de recepcin tendr el siguiente aspecto. Esta
implementacin tambin toma como referencia la definicin 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 recepcin */
while ((UCSR0A & (1<<RXC0)) == 0 );
/* Leer y retornar el dato menos reciente del buffer de recepcin */
return UDR0;
}
TXC0
FE0
DOR0
UPE0
U2X0
Este bit de flag vale uno cuando hay datos no ledos en el buffer de recepcin y vale cero
cuando el buffer de recepcin est vaco (esto es, no contiene datos por leer). Si el
mdulo receptor est deshabilitado, el buffer de recepcin ser liberado y
consecuentemente el bit RXC0 se pondr a cero. E flag RXC0 se puede usar para generar
una Interrupcin de Recepcin Completada (ver la descripcin del bit RXCIE0).
USART Transmit Complete
Este bit de flag se pone a uno cuando un dato completo ha terminado de salir del
Registro de Desplazamiento de Transmisin y no hay ningn dato presente en el registro
UDR0. El bit TXC0 se limpia automticamente cuando se ejecuta la funcin de
interrupcin, o se puede limpiar por software escribiendo uno sobre l. El flag TXC0
puede generar la Interrupcin de Transmisin Completada (ver la descripcin del bit
TXCIE0).
MPCM0
UCSR0ARXC0
TXC0
UDRE0
UDRE0
USART Data Register Empty
FE0
DOR0
UPE0
U2X0
FE0
El flag UDRE0 indica si el buffer de transmisin (UDR0) est listo para recibir un nuevo
dato. Si el bit UDRE0 vale uno, el buffer est vaco, y por tanto est listo para ser escrito.
El flag UDRE0 puede generar una Interrupcin de Registro de Dato Vaco (ver
descripcin del bit UDRIE0). El bit UDRE0 se pone a uno despus de un reset para
indicar que el Transmisor est listo.
Frame Error
DOR0
Este bit se pone a uno si el siguiente carcter en el buffer de recepcin tuvo un error de
frame en la recepcin del dato. Esto es, cuando el primer bit Stop del siguiente carcter
en el buffer de recepcin es cero. Este bit ser vlido hasta que se lea el buffer de
recepcin 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.
Data OverRun
UPE0
Este bit se pone a uno cuando se detecta una condicin de desbordamiento de dato.
Ocurre un desbordamiento de dato (Data OverRun) cuando el buffer de recepcin est
lleno (con dos caracteres), contiene un nuevo carcter esperando en su Registro de
Desplazamiento, y se detecta un nuevo bit Start. Este bit es vlido hasta que se lea el
buffer UDR0. Siempre que se escriba en el registro UCSR0A este bit se debe mantener
en cero.
USART Parity Error
U2X0
MPCM0
Este bit solo tiene efecto en el modo de operacin asncrono. Se debe escribir cero en
este bit cuando se usa el modo de operacin sncrono.
MPCM0
En las comunicaciones asncronas, si se escribe uno en este bit se reducir el divisor del
generador de baud rate de 16 a 8, dando como resultado la multiplicacin por dos de la
velocidad de transferencia de datos.
Multi-processor Communication Mode
Este bit habilita el modo de Comunicacin Multiprocesador. Cuando se escribe uno en el
bit MPCM0, sern ignorados todos los frames recibidos por el USART que no contengan
informacin de direccin. El mdulo Transmisor no queda afectado por la configuracin
de los bits MPCM0.
El Registro UCSR0B
UCSR0BRXCIE0
TXCIE0
UDRIE0
RXCIE0 RX Complete Interrupt Enable
RXEN0
TXEN0
UCSZ02
RXB80
Al escribir uno en este bit se habilita la Interrupcin de Recepcin Completada cada vez
TXB80
UCSR0BRXCIE0
TXCIE0
UDRIE0
RXEN0
TXEN0
UCSZ02
RXB80
TXB80
que se active el flag RXC0. Para que se genere la interrupcin ser necesario que tambin
el bit enable general I de SREG valga uno.
TXCIE0
TX Complete Interrupt Enable
UDRIE0
Al escribir uno en este bit se habilita la Interrupcin de Transmisin Completada cada vez
que se active el flag TXC0. Para que se genere la interrupcin ser necesario que tambin
el bit enable general I de SREG valga uno.
USART Data Register Empty Interrupt Enable
Al escribir uno en este bit se habilita la interrupcin al activarse el flag UDRE0. Se generar una
Interrupcin de Dato de Registro Vaco 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
TXEN0
Al escribir uno en este bit se habilita el mdulo Receptor del USART. El receptor tomar
el control del pin RXD0. Al deshabilitar el receptor se liberar el buffer de recepcin
invalidando los flags FE0, DOR0 y UPE0.
Transmitter Enable
UCSZ02
Al escribir uno en este bit se habilita el mdulo Transmisor del USART. El transmisor
tomar el control del pin TXD0. La des-habilitacin 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 estn vacos.
Cuando se deshabilite el Transmisor el pin TXD0 quedar en libertad.
Character Size
RXB80
El bit UCSZ02 combinado con los bits UCSZ01 y UCSZ00 del registro UCSRC establece
el nmero de los bits de datos (tamao del carcter) en los frames que usarn el
Transmisor y el Receptor.
Receive Data Bit 8
TXB80
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.
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
UCSR0C UMSEL01 UMSEL00
UPM01
UPM00
USBS0
UCSZ01
UCSZ00
UCPOL0
UMSEL01
USART Mode Select
UMSEL00
Estos bits seleccionan el modo de operacin del USART como se muestra en
la siguiente tabla.
UMSEL01 UMSEL00
Mode
0
0
USART Asncrono
USBS0
UPM01 UPM00
0
0
0
1
1
0
1
1
Stop Bit Select
Modo de Paridad
Deshabilitado
Reservado
Habilitado, Paridad Par
Habilitado, Paridad Impar
UCSZ01:
UCSZ00
USBS0
0
1
Character Size
Bits Stop
1 bit
2 bits
Estos bits se combinan con el bit UCSZ02 del registro UCSR0B para
establecer el nmero de bits de los datos (tamao de carcter) que utilizarn el
Transmisor y el Receptor.
UCPOL0
UCPOL0
--Bit 6
--Bit 5
--Bit 4
Bit 11
Bit 3
Bit 10
Bit 9
Bit 8
Bit 2
Bit 1
Bit 0
Estos bits estn reservados para usos futuros. Cuando se escriba en UBRR0H este bit debe
permanecer en cero por compatibilidad con futuros dispositivos.
Bit 11:0 UBRR11:0: USART Baud Rate Register
Este es un registro de 12 bits que contiene el baud rate del USART. El registro UCRR0H
contiene los 4 bits ms 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 sern estropeadas. La escritura en UBRR0L actualizar de
inmediato el prescaler del baud rate.
Prctica: Hello
Circuito de la prctica.
El cdigo fuente
/******************************************************************************
* FileName:
main.c
* Purpose:
Comunicacin bsica por USART0
* Processor: ATmel AVR con mdulo TWI
* Compiler:
IAR-C y AVR-GCC (WinAVR)
* Author:
Shawn Johnson. http://www.cursomicros.com.
*
* Copyright (C) 2008 - 2012 Shawn Johnson. All rights reserved.
*
* License:
Se permiten el uso y la redistribucin de este cdigo 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)
{
char name[20];
usart_init();
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 seccin Uso de Tera Term, o en el archivo
usart.h, como se indic en la seccin Libreras para el USART. De lo contrario la funcin scanf no mostrar el
texto que vayas ingresando por el teclado.
Seguiremos considerando que todo lo expuesto es igualmente vlido para el USART1 e incluso para el
USART de los viejos AVR. Con eso aclarado El USART0 puede generar tres interrupciones, a citar:
Interrupcin de Recepcin Completada. Cuando se acaba de recibir un nuevo dato, esto es, cuando un dato
recin llegado acaba de pasar del Registro de desplazamiento al buffer del registro UDR0 se activar el flag
RXC0 el cual podr disparar la interrupcin, si es que est habilitada.
Esta interrupcin 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 automticamente al ejecutarse la funcin
de interrupcin ISR, sino nicamente cuando el buffer de recepcin est vaco. Esto significa que si la
interrupcin est habilitada se disparar sucesivamente mientras haya uno o ms datos por leer.
UCSR0ARXC0
UCSR0BRXCIE0
TXC0
TXCIE0
UDRE0
UDRIE0
FE0
RXEN0
DOR0
TXEN0
UPE0
UCSZ02
U2X0
RXB80
MPCM0
TXB80
Interrupcin de Registro de Datos Vaco. Se refiere al registro de datos UDR0 del mdulo transmisor. Esta
interrupcin se habilita seteando el bit UDRIE0 (aparte de I), y se disparar cuando se active a uno el flag
UDRE0. Este evento ocurre cuando el registro UDR0 est vaco, sea despus de un reset o despus de
depositar su contenido en el Registro de desplazamiento de modo que est dispuesto a aceptar un nuevo
dato.
El flag UDRE0 se limpia automticamente al ejecutarse la funcin de interrupcin ISR. Tambin se puede
limpiar escribindole un uno.
Interrupcin de Transmisin Completada. Esta interrupcin se habilita seteando el bit TXEN0 (adems 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 Interrupcin de Registro de Dato Vaco. En la prctica significa que los
datos se envan ms rpido usando la Interrupcin de Registro de Datos Vaco porque los espacios entre datos
se reducen al mnimo, de modo que la Interrupcin de Transmisin completada ser raramente usada.
El flag TXC0 se limpia automticamente al ejecutarse la funcin de interrupcin ISR o puede limpiarse por
software escribindole un uno.
Circuito de la prctica.
El cdigo fuente
/******************************************************************************
* FileName:
main.c
* Purpose:
Uso de las interrupciones del USART
* Processor: ATmel AVR con mdulo TWI
* Compiler:
IAR-C y AVR-GCC (WinAVR)
* Author:
Shawn Johnson. http://www.cursomicros.com.
*
* Copyright (C) 2008 - 2012 Shawn Johnson. All rights reserved.
*
* License:
Se permiten el uso y la redistribucin de este cdigo 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"
Observa que en la funcin principal main no se transfiere ningn dato. Todos viajan por interrupciones. La
interrupcin de recepcin se usa con mucha frecuencia y es la ms fcil de captar.
//****************************************************************************
// Gestor de interrupciones (Interrupcin de Recepcin Completada).
// La interrupcin de recepcin se dispara cuando haya llegado algn dato.
//****************************************************************************
ISR (USART0_RX_vect)
{
char c = UDR0;
// Leer dato
UDR0 = c;
// Devolver dato
Cada nuevo dato a guardar en el buffer ser depositado en la casilla actualmente apuntada por
Inpointer. A continuacin 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 continuacin Outpointer se incrementa en 1. (Outpointer para datos Out = salida.)
Con todo lo expuesto ya puedes ensayar cmo funciona el buffer circular. Descubrirs que tiene un
comportamiento FIFO: los primeros datos en entrar sern los primeros en salir; que en tanto haya espacio en
el buffer siempre se podrn meter ms datos sin importar en qu posiciones vayan, evitando el riesgo de
sobrescribir posiciones ya ocupadas. Podras hacer eso con un buffer lineal? Muy difcil, verdad? Ahora
pasemos de los ensayos a la prctica real.
Para saber si en el buffer hay espacio para meter ms 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 extrado.
Circuito de la prctica.
El cdigo fuente
/******************************************************************************
* FileName:
main.c
* Purpose:
Uso de buffers circulares con las interrupciones del USART
* Processor: ATmel AVR con mdulo TWI
* Compiler:
IAR-C y AVR-GCC (WinAVR)
* Author:
Shawn Johnson. http://www.cursomicros.com.
*
* Copyright (C) 2008 - 2012 Shawn Johnson. All rights reserved.
*
* License:
Se permiten el uso y la redistribucin de este cdigo 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"
char
char
void
void
GetFromTXBuffer(void);
GetFromRXBuffer(void);
PutToTXBuffer(char data);
PutToRXBuffer(char data);
#define
#define
TXBufferSize
RXBufferSize
50
50
volatile
volatile
volatile
volatile
volatile
volatile
volatile
volatile
volatile
volatile
char TXBuffer[TXBufferSize];
// Buffer circular de Tx
char RXBuffer[TXBufferSize];
// Buffer circular de Rx
unsigned char TXInpointer = 0;
unsigned char RXInpointer = 0;
unsigned char TXOutpointer = 0;
unsigned char RXOutpointer = 0;
unsigned char TXBufferData = 0;
unsigned char RXBufferData = 0;
unsigned char TXBufferSpace = TXBufferSize;
unsigned char RXBufferSpace = RXBufferSize;
//****************************************************************************
// Gestor de interrupciones (Interrupcin de Recepcin Completada).
// La interrupcin de recepcin se dispara cuando haya llegado algn dato.
//****************************************************************************
ISR (USART0_RX_vect)
{
char c = UDR0;
// Leer dato
if(RXBufferSpace)
PutToRXBuffer(c);
else
nop();
}
//****************************************************************************
// Gestor de interrupciones (Interrupcin de Registro de Datos Vaco).
// La interrupcin se dispara cuando se pueda enviar un nuevo dato.
//****************************************************************************
ISR (USART0_UDRE_vect)
{
char c;
if(TXBufferData)
{
c = GetFromTXBuffer();
UDR0 = c;
}
}
int main(void)
{
char c;
unsigned char i=0;
const char *text = "\n\r Escribe en el LCD... \n\r";
usart_init();
lcd_init();
lcd_cmd(LCD_CURBLK);
while(c = text[i++])
// Cargar TXBuffer
{
if(TXBufferSpace)
PutToTXBuffer(c);
else
nop();
}
/* Habilitar las interrupciones de 'Recepcin Completada' y
* de 'Registro de Datos Vaco'.
*/
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)
{
case 0x1B: lcd_clear();
// Limpiar LCD
break;
case 0x08: lcd_cmd(0x10);
// Cursor atrs
lcd_data(' ');
// Escribir espacio blanco
lcd_cmd(0x10);
// Cursor atrs
break;
default:
lcd_data(c);
// Escribir c
}
}
// Algunas otras tareas...
nop();
}
}
//****************************************************************************
// Extrae un dato de TXBuffer.
// Antes de llamar se debe comprobar si hay algn dato con if(TXBufferData)
//****************************************************************************
char GetFromTXBuffer(void)
{
char c = TXBuffer[TXOutpointer];
// Extraer dato
if(++TXOutpointer >= TXBufferSize) // Al pasar el lmite
TXOutpointer = 0;
// Dar la vuelta
TXBufferData--;
// Un dato menos
TXBufferSpace++;
// Un espacio ms
return c;
//
}
//****************************************************************************
// Extrae un dato de RXBuffer.
// Antes de llamar se debe comprobar si hay algn dato con if(RXBufferData)
//****************************************************************************
char GetFromRXBuffer(void)
{
char c = RXBuffer[RXOutpointer];
// Extraer dato
if(++RXOutpointer >= RXBufferSize) // Al pasar el lmite
RXOutpointer = 0;
// Dar la vuelta
RXBufferData--;
// Un dato menos
RXBufferSpace++;
// Un espacio ms
return c;
//
}
//****************************************************************************
// Ingresa un dato en TXBuffer
// Antes de llamar se debe comprobar si hay espacio con if(TXBufferSpace)
//****************************************************************************
void PutToTXBuffer(char data)
{
TXBuffer[TXInpointer] = data;
// Ingresar dato
if(++TXInpointer >= TXBufferSize)
// Al pasar el lmite
TXInpointer = 0;
// Dar la vuelta
TXBufferData++;
// Un dato ms
TXBufferSpace--;
// Un espacio menos
}
//****************************************************************************
// Ingresa un dato en RXBuffer
// Antes de llamar se debe comprobar si hay espacio con if(RXBufferSpace)
//****************************************************************************
void PutToRXBuffer(char data)
{
RXBuffer[RXInpointer] = data;
// Ingresar dato
if(++RXInpointer >= RXBufferSize)
// Al pasar pasar el lmite
RXInpointer = 0;
// Dar la vuelta
RXBufferData++;
// Un dato ms
RXBufferSpace--;
// Un espacio menos
}
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 rstico se utilizan
funciones como putchar para depositar un dato en el registro de transmisin, y getchar para leer del minibuffer
de recepcin de 2 datos.
En cambio, con los buffers circulares podemos usar las funciones como PutToTXBuffer o GetFromRXBuffer
para depositar/leer en/de sus megabuffers de transmisin y recepcin. Se usan las variables como
RXBufferData o TXBufferSpace para comprobar si hay datos o espacios para ellos en los buffers.