You are on page 1of 42

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES

-----------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------
M.S.c Ing. Iacob Astocondor V|||ar 1
UNIVERSIDAD NACIONAL DEL CALLAO
FACULTAD DE INGENIERIA ELCTRICA Y ELECTRNICA
ESCUELA PROFESIONAL ACADMICO DE INGENIERIA
ELECTRNICA
CURSO: MICROCONTROLADORES
LENGUAJE C PARA ATMEGA8
PROFESOR: MSc ING. ASTOCONDOR VILLAR 1ACOB
CALLAO, 2014V
UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES
-----------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------
M.S.c Ing. Iacob Astocondor V|||ar 2
PROGRAMACION EN C DEL ATMEGA8-AVR
INTRODUCCIN
Un curso de microcontroladores como este implica abarcar tres areas:
Conocer el microcontrolador. Un microcontrolador es un circuito integrado generico
cuyas partes debemos adaptar para que Iuncionen segun los requerimientos de nuestro
diseo. Obviamente no podriamos programar lo que no conocemos.
Conocer los perifricos externos. Un micro CONTROLADOR no seria muy util si no
tiene que controlar. Muchos dispositivos a controlar o mediante los cuales se va a
controlar son comunes de la electronica analogica, como transistores, reles, diodos
LED, registros de desplazamiento e incluso los motores, y se da por hecho que el lector
ya conoce lo suIiciente de ellos. Tambien estan los periIericos que diIicilmente pudo el
alumno haber operado antes sin ayuda de un microcontrolador o una computadora,
como por ejemplo, LCDs, los motores de pasos, los sensores de temperatura digitales,
etc. Es todo este segundo grupo de periIericos externos el que se cubre en un curso de
microcontrolador como este.
Conocer un lenguaje de programacin. Conocer un lenguaje de programacion es un
mundo aparte y es raro que una persona trate de conocer un microcontrolador al mismo
tiempo que va aprendiendo el lenguaje.
El lenguaje C en particular es un tema que normalmente se aprende por separado.
Los lenguajes de alto nivel son mucho mas potentes que el ensamblador aunque su aprendizaje
demanda un mayor esIuerzo.
Para empezar a programar en ensamblador nos puede bastar con aprender unas 50 palabras (las
instrucciones basicas).
En cambio dominar un lenguaje de alto nivel como el C es como aprender a hablar en un
nuevo idioma. No basta con memorizar palabras nuevas, sino que debemos aprender a manejar
una nueva estructura gramatical. Ademas, los procesadores no son como las personas: si en un
codigo de 100 lineas te olvidaste de una sola coma, los compiladores no te lo pasaran por alto.
ESTRUCTURA DE UN PROGRAMA EN C
Tomaremos en cuenta este sencillisimo ejemplo, escrito para los compiladores AVR IAR C y
AVR GCC.
/************************************************************************
* FileName: main.c
* Purpose: LED parpadeantwe
* Processor: ATmel AVR
* Compiler: AVR IAR C & AVR GCC (WinAVR)
* Author:
*************************************************************************/
#include "avrcompiler.h"
//****************************************************************************
// delayms
//****************************************************************************
void delayms(unsigned int t)

while(t--)
delayus(1000);
UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES
-----------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------
M.S.c Ing. Iacob Astocondor V|||ar 3
}
//****************************************************************************
// Funcion principal
//****************************************************************************
int main(void)

DDRB 0x01; // ConIigurar pin PB0 como salida


Ior( ;; )
{
PORTB |= 0x01; // Poner 1 en pin PB0
delay_ms(400); //
PORTB &= 0xFE; // Poner 0 en pin PB0
delay_ms(300);
}
}
No hay que ser muy perspicaz para descubrir lo que hace este programa: configura el
pin PB0 como salida y luego lo setea y lo limpia tras pausas. Es como hacer parpadear
un LED conectado al pin PB0. Parpadea porque el bloque de while se ejecuta
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.
1.-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.
*/
2.- LAS SENTENCIAS
Un programa en C, en lugar de instrucciones, se ejecuta por sentencias.
Una sentencia es algo asi como una mega instruccion, 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 (;).
Asi que tambien podemos entender que los; sirven para separar las sentencias. Alguna vez lei
que el compilador C lee el codigo como si lo absorbiera con una caita, linea por linea, una a
continuacion de otra (evadiendo los comentarios por supuesto).
UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES
-----------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------
M.S.c Ing. Iacob Astocondor V|||ar 4
Por ejemplo, la Iuncion main del programa de arriba bien puede escribirse del siguiente modo.
//****************************************************************************
// Funcin principal
//****************************************************************************
int main(void)
{
DDRB = 0x01;
for( ;; )
{
PORTB |= 0x01;
delay_ms(400);
PORTB &= 0xFE;
delay_ms(300);
}
}
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,
sentenciasde llamadas de funcin, etc.
Las describiremos ms adelante.
3.- LOS BLOQUES
Un bloque establece y delimita el cuerpo de las Iunciones y algunas sentencias mediante llaves
(]).
Como ves en el ejemplo de arriba, las funciones main y pausa tienen sus bloques, asi como los
bucles while y for. Creo que exagere con los comentarios, pero sirven para mostrarnos donde
empieza y termina cada bloque. Podras ver como las tabulaciones ayudan a distinguir unos
bloques de otros. AIortunadamente, los editores de los buenos compiladores C pueden resaltar
cuales son las llaves de inicio y de cierre de cada bloque. Te sera Iacil acostumbrarte a usarlas.
4.-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 si
mismas no son codigo ejecutable. Suelen ser indicaciones sobre como se compilara el codigo.
Entre las pocas directivas del C estandar que tambien son soportadas por los compiladores C
para AVR estan;
#include (para incluir archivos, parecido al Assembler),
#define (mejor que el #deIine del ensamblador)
y las #if, #elif, #endif y similares. Fuera de ellas, cada compilador maneja sus propias directivas
y seran tratadas por separado.
5.- LAS FUNCIONES
UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES
-----------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------
M.S.c Ing. Iacob Astocondor V|||ar S
Si un programa en ensamblador se puede dividir en varias subrutinas para su mejor
estructuracin, un programa en C se puede componer de funciones. Por supuesto que las
Iunciones son muchisimo mas potentes y, por cierto, algo mas complejas de aprender. Por eso ni
siquiera el gran espacio que se les dedica mas adelante puede ser suIiciente para entenderlas
plenamente. Pero, no te preocupes, aprenderemos de a poco.
En un programa en C puede haber las Iunciones que sean posibles, pero nunca debe faltar la
funcin principal, llamada main.
Donde quiera que se encuentre, la funcin main siempre sera la primera en ser ejecutada. De
hecho, alli empieza y no deberia salir de ella.
.- Variables y Tipus de Datus
En ensamblador todas las variables de programa suelen ser registros de la RAM crudos, es
decir, datos de 8 bits sin Iormato. En los lenguajes de alto nivel estos registros son tratados de
acuerdo con Iormatos que les permiten representar numeros de 8, 16 o 32 bits (a veces mas
grandes), con signo o sin el, numeros enteros o decimales. Esos son los tipos de datos basicos.
Las variables de los compiladores pueden incluso almacenar matrices de datos del mismo tipo
(llamadas arrays) o de tipos diIerentes (llamadas estructuras). Estos son los tipos de datos
complejos.
Los siguientes son los principales tipos de datos basicos del lenguaje C. Observa que la tabla los
separa en dos grupos, los tipos enteros y los tipos de punto Ilotante.
Tabla de variables y tipos de datos del lenguaje C
Tipo de dato Tamao en bits Rango de valores que puede adoptar
char 8 0 a 255 o -128 a 127
signed char 8 -128 a 127
unsigned char 8 0 a 255
(signed) int 16 -32,768 a 32,767
unsigned int 16 0 a 65,536
(signed) short 16 -32,768 a 32,767
unsigned short 16 0 a 65,536
(signed) long 32 -2,147,483,648 a 2,147,483,647
unsigned long 32 0 a 4,294,967,295
(signed) long long (int) 64 -2
63
a 2
63
- 1
unsigned long long (int) 64 0 a 2
64
- 1
Iloat 32 +1.18E-38 a +3.39E38
double 32 +1.18E-38 a +3.39E38
double 64 +2.23E-308 a +1.79E308
AIortunadamente, a diIerencia de los compiladores para PIC, los compiladores para AVR suelen
respetar bastante los tipos establecidos por el ANSI C. Algunos compiladores tambien manejan
tipos de un bit como bool (o boolean) o bit, pero con pequeas divergencias que pueden aIectar
la portabilidad de los codigos ademas de conIundir a los programadores. Esos tipos son
raramente usados.
UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES
-----------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------
M.S.c Ing. Iacob Astocondor V|||ar 6
Por deIecto el tipo double es de 32 bits en los microcontroladores. En ese caso es equivalente al
tipo Iloat. Los compiladores mas potentes como AVR IAR C y AVR GCC sin embargo oIrecen
la posibilidad de conIigurarlo para que sea de 64 bits y poder trabajar con datos mas grandes y
de mayor precision.
Los especificadores signed (con signo) mostrados entre parentesis son opcionales. Es decir, da
lo mismo poner int que signed int, por ejemplo. Es una redundancia que se suele usar para
'reIorzar su condicion o para que se vea mas ilustrativo.
El tipo char esta pensado para almacenar caracteres ASCII como las letras. Puesto que estos
datos son a Iin de cuentas numeros tambien, es comun usar este tipo para almacenar numeros de
8 bits. Es decir es equivalente a signed char o unsigned char, dependiendo de la conIiguracion
establecida por el entorno compilador. Y como es preIerible dejar de lado estas cuestiones, si
vamos a trabajar con numeros lo mejor es poner el especiIicador signed o unsigned en el codigo.
Quiza te preguntes cual es la diIerencia entre los tipos de datos int y short si aparentemente
tienen el mismo tamao y aceptan el mismo rango de valores. Esa apariencia es real en el
entorno de los microcontroladores AVR. Es decir, al compilador le da lo mismo si ponemos int
o short. Sucede que el tipo short Iue y siempre deberia ser de 16 bits, en tanto que int Iue
concebido para adaptarse al bus de datos del procesador. Esto todavia se cumple en la
programacion de las computadoras, por ejemplo, un dato int es de 32 bits en un Pentium IV y es
de 64 bits en un procesador Core i7. De acuerdo con este diseo un tipo int deberia ser de 8 bits
en un megaAVR y de 32 bits en un AVR32. Sin embargo, la costumbre de relacionar el tipo int
con los 16 bits de las primeras computadoras como las legendarias 286 se ha convertido en
tradicion y en regla de Iacto para los microcontroladores. Actualmente solo en CCS C el tipo int
es de 8 bits. Es ironico para ser el compilador que menos respeta los tipos de datos del ANSI C.
A pesar de todo, se nota que todavia pueden aparecer ciertas imprecisiones en los tipos de datos
que pueden perturbar la portabilidad de los programas entre los diIerentes compiladores. Es por
esto que el lenguaje C/C provee la libreria stdint.h para deIinir tipos enteros que seran de un
tamao especiIico independientemente de los procesadores y de la plataIorma soItware en que
se trabaje.
Tabla de variables y tipos de datos del lenguaje C
Tipo de dato Tamao en bits Rango de valores que puede adoptar
int8t 8 -128 a 127
uint8t 8 0 a 255
int16t 16 -32,768 a 32,767
uint16t 16 0 a 65,536
int32t 32 -2,147,483,648 a 2,147,483,647
uint32t 32 0 a 4,294,967,295
int64t 64 -2
63
a 2
63
- 1
uint64t 64 0 a 2
64
- 1
Es Iacil descubrir la estructura de estos tipos para Iamiliarizarse con su uso. Para ello debemos
en primer lugar incluir en nuestro programa el archivo stdint.h con la siguiente directiva.
#include stdint.h~
Esta inclusion ya esta hecha en el archivo avr_compiler.h que se usa en todos los programas de
curso, asi que no es necesario volverlo a hacer. Aunque el objetivo de este archivo es permitir la
compatibilidad de codigos entre los compiladores AVR IAR C y AVR GCC, debemos saber que
en AVR IAR C el archivo avr_compiler.h solo esta disponible al usar la libreria DLIB. Como
UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES
-----------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------
M.S.c Ing. Iacob Astocondor V|||ar 7
las practicas de curso trabajan sobre la libreria CLIB, he evitado recurrir a los tipos extendidos
de stdint.h.
Finalmente, existen ademas de los vistos arriba otros tipos y especiIicadores de datos que no son
parte del lenguaje C pero que Iueron introducidos por los compiladores pensando en las
caracteristicas especiales de los microcontroladores. Muchos de ellos son redundantes o simples
alias y algunos que si son de utilidad como el tipo PGMP los veremos en su momento.
7.-Declaracin de variables
Esta parte es comparable, aunque lejanamente a cuando se identiIican las variables del
ensamblador con la directiva .def. No se puede usar una variable si antes no se ha declarado. La
Iorma general mas simple de hacerlo es la siguiente:
data_type myvar;
Donde datatype es un tipo de dato basico o complejo, del compilador o deIinido por el usuario
y myvar es un identiIicador cualquiera, siempre que no sea palabra reservada.
Ejemplos.
unsigned char d; // Variable para enteros de 8 bits sin signo
char b; // Variable de 8 bits (para almacenar
// caracteres ascii)
signed char c; // Variable para enteros de 8 bits con signo
int i; // i es una variable int, con signo
signed int j; // j tambin es una variable int con signo
unsigned int k; // k es una variable int sin signo
Tambien es posible declarar varias variables del mismo tipo, separandolas con comas. Asi nos
ahorramos algo de tipeo. Por ejemplo:
float area, side; // Declarar variables area y side de tipo float
unsigned char a, b, c; //Declarar variables a, b y c como unsigned char
8.-Especificadores de tipo de datos
A la declaracion de una variable se le puede aadir un especiIicador de tipo como const, static,
volatile, extern, register, etc. Dichos especiIicadores tienen diversas Iunciones y, salvo const, se
suelen usar en programas mas elaborados. Como no queremos enredarnos tan pronto, lo
dejaremos para otro momento.
Una variable const debe ser inicializada en su declaracion. Despues de eso el compilador solo
permitira su lectura mas no su escritura. Ejemplos:
const int a = 100; // Declarar constante a
int b; // Declarar variable b
//...
b = a; // Vlido
b = 150; // Vlido
a = 60; // Error! a es constante
a = b; // Error! a es constante
UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES
-----------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------
M.S.c Ing. Iacob Astocondor V|||ar 8
Por mas que las variables constantes sean de solo lectura, ocuparan posiciones en la RAM del
microcontrolador. En CodeVisionAVR es posible conIigurar para que si residan en FLASH
pero por compatibilidad se usa muy poco.
Por eso muchas veces es preIerible deIinir las constantes del programa con las clasicas
directivas #deIine (como se hace en el ensamblador).
#define a l00 // Definir constante a
9.-SENTENCIAS SELECTIVAS
Llamadas tambien sentencias de biIurcacion, sirven para redirigir el Ilujo de un programa segun
la evaluacion de alguna condicion logica.
Las sentencias iI e iI-else son casi estandar en todos los lenguajes de programacion. Ademas de
ellas estan las sentencias iI-else escalonadas y switch-case.
9.1 La sentencia if
La sentencia if (si condicional, en ingles) hace que un programa ejecute una sentencia o un
grupo de ellas si una expresion es cierta. Esta logica se describe en el siguiente esquema.
Diagrama de Ilujo de la sentencia iI.
La Iorma codiIicada seria asi:
sentenciaA;
if ( expression ) // Si expression es verdadera,
// ejecutar el siguiente bloque
{ // apertura de bloque
sentenciaB;
sentenciaC;
// algunas otras sentencias
} // cierre de bloque
sentenciaX;
Despues de ejecutar sentenciaA el programa evalua expresion. Si resulta ser verdadera, se
ejecutan todas las sentencias de su bloque y luego se ejecutara la sentenciaX.
UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES
-----------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------
M.S.c Ing. Iacob Astocondor V|||ar 9
En cambio, si expression es Ialsa, el programa se salteara el bloque de iI y ejecutara sentenciaX.
9.2 LA SENTENCIA IF - ELSE
La sentencia if brinda una rama que se ejecuta cuando una condicion logica es verdadera.
Cuando el programa requiera dos ramas, una que se ejecute si cierta expression es cierta y otra
si es Ialsa, entonces se debe utilizar la sentecia if - else. Tiene el siguiente esquema.
Diagrama de Ilujo de la sentencia iI - else.
Expresando lo descrito en codigo C, tenemos: (Se lee como indican los comentarios.)
SentenciaA;
if ( expression ) // Si expression es verdadera, ejecutar
{ // este bloque
sentenciaB;
sentenciaC;
// ...
}
else // En caso contrario, ejecutar este bloque
{
sentenciaM;
sentenciaN;
// ...
}
sentenciaX;
// ...
Como ves, es bastante Iacil, dependiendo del resultado se ejecutara uno de los dos bloques de la
sentencia iI - else, pero nunca los dos a la vez.
9.3 LA SENTENCIA IF - ELSE - IF ESCALONADA
Es la version ampliada de la sentencia if - else.
En el siguiente boceto se comprueban tres condiciones logicas, aunque podria haber mas. Del
mismo modo, se han puesto dos sentencias por bloque solo para simpliIicar el esquema.
if ( expression_1 ) // Si expression_1 es verdadera ejecutar
{ // este bloque
sentencia1;
sentencia2;
}
UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES
-----------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------
M.S.c Ing. Iacob Astocondor V|||ar 10
else if ( expression_2 ) // En caso contrario y si expression_2 es
{ // verdadera, ejecutar este bloque
sentencia3;
sentencia4;
}
else if ( expression_3 ) // En caso contrario y si expression_3 es
{ // verdadera, ejecutar este bloque
sentencia5;
sentencia6;
}
else // En caso contrario, ejecutar este bloque
{
sentencia7;
sentencia8;
}; // ; opcional
// todo...
Las 'expresiones se evaluan de arriba abajo. Cuando alguna de ellas sea verdadera, se ejecutara
su bloque correspondiente y los demas bloques seran salteados. El bloque Iinal (de else) se
ejecuta si ninguna de las expresiones es verdadera. Ademas, si dicho bloque esta vacio, puede
ser omitido junto con su else.
9.4 LA SENTENCIA SWITCH
La sentencia switch brinda una Iorma mas elegante de biIurcacion multiple. Podemos
considerarla como una Iorma mas estructurada de la sentencia if else if escalonada, aunque
tiene algunas restricciones en las condiciones logicas a evaluar, las cuales son comparaciones de
valores enteros.
Para elaborar el codigo en C se usan las palabras reservadas switch, case, break y deIault.
El siguiente esquema presenta tres case`s pero podria haber mas, asi como cada bloque tambien
podria tener mas 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 codigo ASCII 165, que es, a Iin de cuentas, un entero.)
UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES
-----------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------
M.S.c Ing. Iacob Astocondor V|||ar 11
Expresion puede ser una variable compatible con entero. No es una expresion que conduce a
una condicion logica como en los casos anteriores.
El programa solo ejecutara uno de los bloques dependiendo de que constante coincida con
expression. Usualmente los bloques van limitados por llaves, pero en este caso son opcionales,
dado que se pueden distinguir Iacilmente. Los bloques incluyen la sentencia break. Que 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, tambien se ejecutara el bloque del
siguiente case, sin importar si su constante coincida con expression o no.
No seria necesario poner el deIault si su bloque estuviera vacio.
10. SENTENCIAS ITERATIVAS
Las sentencias de control iterativas sirven para que el programa ejecute una sentencia o un
grupo de ellas un numero determinado o indeterminado de veces. Asi es, esta seccion 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 version mas compacta
e intuitiva del bucle while.
10.1 La sentencia while
El cuerpo o bloque de este bucle se ejecutara una y otra vez mientras (while, en ingles) una
expresion sea verdadera.
Diagrama de Ilujo de las sentencia while.
El bucle while en C tiene la siguiente sintaxis y se lee asi: mientras (while) expression sea
verdadera, ejecutar el siguiente bloque.
sentenciaA;
while ( expression ) // Mientras expression sea verdadera, ejecutar el
// siguiente bloque
{
sentenciaB;
sentenciaC;
// ...
}; // Este ; es opcional
sentenciaX;
UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES
-----------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------
M.S.c Ing. Iacob Astocondor V|||ar 12
// ...
Nota que en este caso primero se evalua expression. Por lo tanto, si desde el principio
expression es Ialsa, el bloque de while no se ejecutara nunca. Por otro lado, si expression no
deja de ser verdadera, el programa se quedara dando vueltas 'para siempre.
10.2 LA SENTENCIA DO - WHILE
Como dije antes, es una variacion de la sentencia while simple. La principal diIerencia es que la
condicion logica (expression) de este bucle se presenta al Iinal. Como se ve en la siguiente
Iigura, esto implica que el cuerpo o bloque de este bucle se ejecutara al menos una vez.
Diagrama de Ilujo de las sentencia do - while.
La sintaxis para la sentencia do - while es la siguiente y se lee: Ejecutar (do) el siguiente
bloque, mientras (while) expression sea verdadera.
sentenciaA;
do
{
sentenciaB;
sentenciaC;
// ...
} while ( expression ); // Este ; es mandatorio
sentenciaX;
// ...
10.3 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 Iorma de conteo Iinito es preIerible emplear la sentencia Ior. (Inversamente, al
ver un Ior en un programa, debemos suponer que estamos Irente a algun bucle de ese tipo.)
Esta es la sintaxis general de la sentencia Ior en C:
for ( expression_1 ; expression_2 ; expression_3 )
{
sentencia1;
sentencia2;
// ...
}; // Este ; es opcional
Ahora veamos por partes como Iunciona:
UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES
-----------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------
M.S.c Ing. Iacob Astocondor V|||ar 13
expresslon_1 suele ser una senLencla oe lnlclallzacln.
expresslon_2 se evalua como conolcln lglca para que se e[ecuLe el bloque.
expresslon_3 es una senLencla que oeberla poner coLo a expresslon_2.
Por la Iorma y orden en que se ejecutan estas expresiones, el bucle Ior es equivalente a la
siguiente construccion, utilizando la sentencia while. Primero se ejecuta expression1 y luego se
ejecuta el bloque indicado tantas veces mientras expression2 sea verdadera.
expression_1;
while ( expression_2 )
{
sentencia1;
sentencia2;
// ...
expression_3;
}
No obstante, de esa Iorma se ve mas rara aun; asi que, mejor, veamos estos ejemplos, que son
sus presentaciones mas clasicas. (i es una variable y a y b son constantes o variables):
for ( i = 0 ; i < 10 ; i++ )
{
sentencias;
}
Se lee: para (Ior) i igual a 0 hasta que sea menor que 10 ejecutar sentencias. La sentencia i
indica que i se incrementa tras cada ciclo. Asi, el bloque de Ior se ejecutara 10 veces, desde que
i valga 0 hasta que valga 9.
En este otro ejemplo las se ejecutan desde que i valga 10 hasta que valga 20. Es
decir, el bucle dara 11 vueltas en total.
for ( i = 10 ; i <= 20 ; i++ )
{
sentencias;
}
El siguiente bucle Ior empieza con i inicializado a 100 y su bloque se ejecutara 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 mas construcciones, todas coincidentes con la primera plantilla, pero
tambien son menos Irecuentes.
11.- SENTENCIAS CUN BLUQUES SIMPLES
Cuando las sentencias selectivas (como iI) o de bucles (como while o Ior) tienen cuerpos o
bloques que constan de solo una sentencia, se pueden omitir las llaves. Aun asi, es aconsejable
seguir manteniendo las tabulaciones para evitarnos conIusiones.
Por ejemplo, las siguientes sentencias:
UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES
-----------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------
M.S.c Ing. Iacob Astocondor V|||ar 14
if(a > b)
{
a = 0;
}
if(a == b)
{
a++;
}
else
{
b--;
}
while( a >= b)
{
a = a + b;
}
for(i=0; i<=10; i++)
{
a = a*2;
}
bien se pueden escribir de la siguiente Iorma:
if(a > b)
a = 0;
if(a == b)
a++;
else
b--;
while( a >= b)
a = a + b;
for(i=0; i<=10; i++)
a = a*2;
12.- LOS OPERADORES
Sirven para realizar operaciones aritmeticas, logicas, comparativas, etc. Segun esa Iuncion se
clasiIican en los siguientes grupos.
12.1 Operadores aritmticos
Ademas de los tipicos operadores de suma, resta, multiplicacion y division, estan los operadores
de modulo, incremento y decremento.
Tabla de Operadores aritmticos
Operador Accin
Suma
- Resta
* Multiplicacion
/ Division

Modulo. Retorna el residuo de una division entera. Solo se debe usar con numeros
enteros.
UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES
-----------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------
M.S.c Ing. Iacob Astocondor V|||ar 1S
Tabla de Operadores aritmticos
Operador Accin
Incrementar en uno
-- Decrementar en uno
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 a-b a c
a++; // Incrementar a en 1
b--; // Decrementar b en 1
++c; // Incrementar c en 1
--b; // Decrementar b en 1
Te recordaron a tus clases de algebra del colegio? A diIerencia de esas matematicas, estas
expresiones no son ecuaciones; signiIican las operaciones que indican sus comentarios.
Por lo visto, los operadores y -- Iuncionan igual si estan antes o despues de una variable en
una expresion simple. Sin embargo, hay una Iorma (tal vez innecesaria y conIusa para un
novato, pero muy atractiva para los que ya estamos acostumbrados a su uso) que permite
escribir codigo mas compacto, es decir, escribir dos sentencias en una.
Si o -- estan antes del operando, primero se suma o resta 1 al operando y luego se
evalua la expresion.
Si o -- estan despues del operando, primero se evalua la expresion y luego se suma o
resta 1 al operando.
int a, b; // Declarar variables enteras a y b
a = b++; // Lo mismo que a = b; y luego b = b + 1;
a = ++b; // Lo mismo que b = b + 1; y luego a = b;
if (a++ < 10) // Primero comprueba si a < 10 y luego
{ // incrementa a en 1
// algn cdigo
}
if (++a < 10) // Primero incrementa a en 1 y luego
{ // comprueba si a < 10
// algn cdigo
}
12.2 OPERADORES DE BITS
Se aplican a operaciones logicas con variables a nivel binario. Aqui tenemos las clasicas
operaciones AND, OR inclusiva, OR exclusiva y la NEGACION. Adicionalmente, he incluido
en esta categoria los operaciones de desplazamiento a la derecha y la izquierda.
UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES
-----------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------
M.S.c Ing. Iacob Astocondor V|||ar 16
Si bien son operaciones que producen resultados analogos a los de las instrucciones de
ensamblador los operadores logicos del C pueden operar sobre variables de distintos tamaos,
ya sean de 1, 8, 16 o 32 bits.
Tabla de operadores de 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; // variable de 8 bits
int n; // variable de 16 bits
m = 0x48; // m ser 0x48
m = m & 0x0F; // Despus de esto m ser 0x08
m = m | 0x24; // Despus de esto m ser 0x2F
m = m & 0b11110000; // Despus de esto m ser 0x20
n = 0xFF00; // n ser 0xFF00
n = ~n; // n ser 0x00FF
m = m | 0b10000001; // Setear bits 0 y 7 de variable m
m = m & 0xF0; // Limpiar nibble bajo de variable m
m = m ^ 0b00110000; // Invertir bits 4 y 5 de variable m
m = 0b00011000; // Cargar m con 0b00011000
m = m >> 2; // Desplazar m 2 posiciones a la derecha
// Ahora m ser 0b00000110
n = 0xFF1F;
n = n << 12; // Desplazar n 12 posiciones a la izquierda
// Ahora n ser 0xF000;
m = m << 8; // Despus de esto m ser 0x00
Fijate en la semejanza entre las operaciones de desplazamiento con ~~ y y las operaciones
del rotacion del ensamblador. Cuando una variable se desplaza hacia un lado, los bits que salen
por alli se pierden y los bits que entran por el otro lado son siempre ceros. Es por esto que en la
ultima sentencia, m m 8, el resultado es 0x00. Por cierto, en el lenguaje C no existen
operadores de rotacion. Hay Iormas alternativas de realizarlas.
Desplazamientos producidos por los operadores y ~~.
12-3 OPERADORES RELACIONALES
UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES
-----------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------
M.S.c Ing. Iacob Astocondor V|||ar 17
Se emplean para construir las condiciones logicas de las sentencias de control selectivas e
iterativas, como ya hemos podido apreciar en las secciones anteriores. La siguiente tabla
muestra los operadores relacionales disponibles.
Tabla de Operadores relacionales
Operador Accin
Igual
! No igual
~ Mayor que
Menor que
~ Mayor o igual que
Menor o igual que
12.4 OPERADORES LGICOS
Generalmente se utilizan para enlazar dos o mas condiciones logicas simples. Por suerte, estos
operadores solo son tres y seran explicados en las practicas del curso.
Tabla de Operadores lgicos
Operador Accin
&& AND lgica
[[ OR lgica
! Negacin lgica
Ejemplos:
if( !(a==0) ) // Si a igual 0 sea falso
{
// sentencias
}
if( (a<b) && (a>c) ) // Si a<b y a>c son verdaderas
{
// sentencias
}
while( (a==0) || (b==0) ) // Mientras a sea 0 b sea 0
{
// sentencias
}
12.5 COMPOSICIN DE OPERADORES
Se utiliza en las operaciones de asignacion y nos permite escribir codigo mas abreviado. La
Iorma general de escribir una sentencia de asignacion mediante los operadores compuestos es:
obtect op= expression;
que es equivalente a la sentencia
object = object op expression;
UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES
-----------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------
M.S.c Ing. Iacob Astocondor V|||ar 18
op puede ser cualquiera de los operadores aritmeticos o de bit estudiados arriba. O sea, op puede
ser , - , *, /, , &, , , `, ~, o ~~. Nota: no debe haber ningun espacio entre el operador y el
signo igual.
Ejemplos:
int a; // Declarar a
a += 50; // Es lo mismo que a = a + 50;
a += 20; // Tambin significa sumarle 20 a a
a *= 2; // Es lo mismo que a = a * 2;
a &= 0xF0; // Es lo mismo que a = a & 0xF0;
a <<= 1; // Es lo mismo que a = a << 1;
12.6 PRECEDENCIA DE OPERADORES
Una expresion puede contener varios operadores, de esta Iorma:
b = a * b + c / b; // a, b y c son variables
A diIerencia del lenguaje Basic, donde la expresion se evalua de izquierda a derecha, en esta
sentencia no queda claro en que orden se ejecutaran 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 mas practico emplear los parentesis, los cuales
ordenan que primero se ejecuten las operaciones de los parentesis mas internos. Eso es como en
el algebra elemental de la escuela, asi que no proIundizare.
Por ejemplo, las tres siguientes sentencias son diIerentes.
b = (a * b) + (c / b);
b = a * (b + (c / b));
b = ((a * b) + c)/ b);
Tambien se pueden construir expresiones condicionales, asi:
if ( (a > b) && ( b < c) ) // Si a>b y b<c, ...
{
// ...
}
13. LAS FUNCIONES
Una Iuncion es un bloque de sentencias identiIicado por un nombre y puede recibir y devolver
datos. En bajo nivel, en general, las Iunciones operan como las subrutinas de Assembler, es
decir, al ser llamadas, se guarda en la Pila el valor actual del PC (Program Counter), despues se
ejecuta todo el codigo de la Iuncion y Iinalmente se recobra el PC para regresar de la Iuncion.
Dada su relativa complejidad, no es tan simple armar una plantilla general que represente a
todas las Iunciones. El siguiente esquema es una buena aproximacion.
data_type1 function_name (data_type2 arg1, data_type3 arg2, ... )
{
// Cuerpo de la funcin
// ...
return SomeData; // Necesario solo si la funcin retorna algn valor
}
UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES
-----------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------
M.S.c Ing. Iacob Astocondor V|||ar 19
Donde:
function_name es el nombre de la Iuncion. Puede ser un identiIicador cualquiera.
data_type1 es un tipo de dato que identiIica el parametro de salida. Si no lo hubiera, se
debe poner la palabra reservada void (vacio, en ingles).
arg1 y arg2 (y puede haber mas) son las variables de tipos datatype1, datatype2...,
respectivamente, que recibiran los datos que se le pasen a la Iuncion. Si no hay ningun
parametro de entrada, se pueden dejar los parentesis vacios o escribir un void entre
ellos.
13.1 FUNCIONES SIN PARMETROS
Para una Iuncion que no recibe ni devuelve ningun valor, la plantilla de arriba se reduce al
siguiente esquema:
void function_name ( void )
{
// Cuerpo de la funcin
}
Y se llama escribiendo su nombre seguido de parentesis vacios, asi:
function_name();
La Iuncion principal main es otro ejemplo de Iuncion sin parametros. Dondequiera que se
ubique, siempre deberia ser la primera en ejecutarse; de hecho, no deberia terminar.
void main (void)
{
// Cuerpo de la funcin
}
13.2 FUNCIONES CON PARMETROS (POR VALOR)
Por el momento, solo estudiaremos las Iunciones que pueden tener varios parametros de entrada
pero solo uno de salida.
Si la Iuncion no tiene parametros de entrada o de salida, debe escribirse un void en su lugar. El
valor devuelto por una Iuncion se indica con la palabra reservada return.
Segun el comportamiento de los parametros de entrada de la Iuncion, estos se dividen en
parametros por valor y parametros por reIerencia. Lo expuesto en este apartado corresponde al
primer grupo porque es el caso mas ampliamente usado. Con esto en mente podemos seguir.
Para llamar a una Iuncion con parametros es importante respetar el orden y el tipo de los
parametros que ella recibe. El primer valor pasado corresponde al primer parametro de entrada;
el segundo valor, al segundo parametro; y asi sucesivamente si hubiera mas.
Cuando una variable es entregada a una Iuncion, en realidad se le entrega una copia suya. De
este modo, el valor de la variable original no sera alterado. Mejor, plasmemos todo esto en el
siguiente ejemplo.
int minor ( int arg1, int arg2, int arg3 )
{
int min; // Declarar variable min
min = arg1; // Asumir que el menor es arg1
UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES
-----------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------
M.S.c Ing. Iacob Astocondor V|||ar 20
if ( arg2 < min ) // Si arg2 es menor que min
min = arg2; // Cambiar a arg2
if ( arg3 < min ) // Si arg3 es menor que min
min = arg3; // Cambiar a arg3
return min; // Retornar valor de min
}
void main (void)
{
int a, b, c, d; // Declarar variables a, b, c y d
/* Aqu asignamos algunos valores iniciales a 'a', 'b' y 'c'
*/
/* ... */
d = minor(a,b,c); // Llamar a minor
// En este punto 'd' debera ser el menor entre 'a', 'b' y 'c'
while (1); // Bucle infinito
}
En el programa mostrado la Iuncion minor recibe tres parametros de tipo int y devuelve uno,
tambien de tipo int, que sera el menor de los numeros recibidos.
El mecanismo Iunciona asi: siempre respetando el orden, al llamar a minor el valor de a se
copiara a la variable arg1; el valor de b, a arg2 y el valor de c, a arg3. Despues de ejecutarse el
codigo de la Iuncion el valor de retorno (min en este caso) sera copiado a una variable temporal
y de alli pasara a d.
Aunque el C no es tan implacable con la comprobacion de tipos de datos como Pascal, siempre
deberiamos revisar que los datos pasados sean compatibles con los que la Iuncion espera, asi
como los datos recibidos, con los que la Iuncion devuelve. Por ejemplo, estaria mal llamar a la
Iuncion minor del siguiente modo:
d = minor(-15, 100, 5.124); // Llamar a minor
Aqui los dos primeros parametros estan bien, pero el tercero es un numero decimal (de 32 bits),
no compatible con el tercer parametro que la Iuncion espera (entero de 16 bits). En estos casos
el compilador nos mostrara mensajes de error, o cuando menos de advertencia.
13.3 PARMETROS POR REFERENCIA
La Iuncion que recibe un parametro por reIerencia puede cambiar el valor de la variable pasada.
La Iorma clasica de estos parametros se puede identiIicar por el uso del simbolo &, tal como se
ve en el siguiente boceto de Iuncion.
int minor ( int & arg1, int & arg2, int & arg3 )
{
// Cuerpo de la funcin.
// arg1, arg2 y arg3 son parmetros por referencia.
// Cualquier cambio hecho a ellos desde aqu afectar a las
variables
// que fueron entregadas a esta funcin al ser llamada.
}
No voy proIundizar al respecto porque he visto que muchos compiladores C no soportan esta
Iorma. Otra Iorma de pasar un parametro por reIerencia es mediante los punteros, pero eso lo
dejamos para el Iinal porque no es nada nada Iacil para un novato.
UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES
-----------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------
M.S.c Ing. Iacob Astocondor V|||ar 21
13.4 PROTOTIPOS DE FUNCIONES
El prototipo de una Iuncion le inIorma al compilador las caracteristicas que tiene, como su tipo
de retorno, el numero de parametros que espera recibir, el tipo y orden de dichos parametros.
Por eso se deben declarar al inicio del programa.
El prototipo de una Iuncion es muy parecido a su encabezado, se pueden diIerenciar 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 Iunciones main, Iunc1 y
Iunc2 declaradas al inicio del archivo permitiran que dichas Iunciones sean accedidas desde
cualquier parte del programa. Ademas, sin importar donde se ubique la Iuncion main, ella
siempre sera la primera en ejecutarse. Por eso su prototipo de Iuncion es opcional.
#include <avr.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 Iunciones no tienen prototipos, el acceso a ellas sera restringido. El compilador solo vera
las Iunciones que estan implementadas encima de la Iuncion llamadora o, de lo contrario,
mostrara errores de 'Iuncion no deIinida. El siguiente boceto ilustra este hecho. (Atiende a los
comentarios.)
#include <avr.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
// Desde aqu se puede acceder a main pero no a func2
}
char func2(int a)
{
// Cuerpo de la funcin
UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES
-----------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------
M.S.c Ing. Iacob Astocondor V|||ar 22
// Desde aqu se puede acceder a func1 y main
}
Para terminar, dado que los nombres de las variables en los parametros de entrada son
opcionales, los prototipos de Iunc1 y Iunc2 tambien se pueden escribir asi
void func1(char, long);
char func2(int );
14. VARIABLES LOCALES Y VARIABLES GLOBALES
Los lenguajes de alto nivel como el C Iueron diseados para desarrollar los programas mas
grandes y complejos que se puedan imaginar, programas donde puede haber cientos de
variables, entre otras cosas. Imaginas lo que signiIicaria buscar nombres para cada variable si
todos tuvieran que ser diIerentes? Pues bien, para simpliIicar las cosas, el C permite tener varias
variables con el mismo nombre.
Asi es. Esto es posible gracias a que cada variable tiene un ambito, un area desde donde sera
accesible. Hay diversos tipos de ambito, pero empezaremos por Iamiliarizarnos con los dos mas
usados, que corresponden a las variables globales y variables locales.
Las variables declaradas Iuera de todas las Iunciones y antes de sus implementaciones
tienen caracter global y podran ser accedidas desde todas las Iunciones.
Las variables declaradas dentro de una Iuncion, incluyendo las variables del
encabezado, tienen ambito local. Ellas solo podran ser accedidas desde el cuerpo de
dicha Iuncion.
De este modo, puede haber dos o mas variables con el mismo nombre, siempre y cuando esten
en diIerentes Iunciones. Cada variable pertenece a su Iuncion y no tiene nada que ver con las
variables de otra Iuncion, por mas que tengan el mismo nombre.
En la mayoria de los compiladores C para microcontroladores las variables locales deben
declararse al principio de la Iuncion.
Por ejemplo, en el siguiente boceto de programa hay dos variables globales (speed y limit) y
cuatro variables locales, tres de las cuales se llaman count. Atiende a los comentarios.
char foo(long ); // 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++; // Acceso a variable global speed
vari = 0; // Esto dar ERROR porque vari solo pertenece
// a la funcin foo. No compilar.
}
void main(void)
{
int count; // Variable local count
/* Este count no tiene nada que ver con el count
de las funciones inter o foo */
UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES
-----------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------
M.S.c Ing. Iacob Astocondor V|||ar 23
count = 0; // Acceso a count local
speed = 0; // Acceso a variable global speed
}
char foo(long count) // Variable local count
{
int vari; // Variable local vari
}
Algo muy importante: a diIerencia de las variables globales, las variables locales tienen
almacenamiento temporal, es decir, se crean al ejecutarse la Iuncion y se destruyen al salir de
ella. Que signiIica eso? Lo explico en el siguiente apartado.
Si dentro de una Iuncion hay una variable local con el mismo nombre que una variable global, la
precedencia en dicha Iuncion la tiene la variable local. Si te conIunde, no uses variables
globales y locales con el mismo nombre.
14.1 Variables static
Antes de nada debemos aclarar que una variable static local tiene diIerente signiIicado que una
variable static global. Ahora vamos a enIocarnos al primer caso por ser el mas comun.
Cuando se llama a una Iuncion sus variables locales se crearan en ese momento y cuando se
salga de la Iuncion se destruiran. Se entiende por destruir al hecho de que la locacion de
memoria que tenia una variable sera luego utilizada por el compilador para otra variable local
(asi se economiza la memoria). Como consecuencia, el valor de las variables locales no sera el
mismo entre llamadas de Iuncion.
Por ejemplo, revisa la siguiente Iuncion, donde a es una variable local ordinaria.
void increm()
{
int a; // Declarar variable a
a++; // Incrementar a
}
Cualquiera que haya sido su valor inicial, crees que despues de llamar a esta Iuncion 10 veces,
el valor de a se habra 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 Iuncion, se destruye.
Para que una variable tenga una locacion de memoria independiente y su valor no cambie entre
llamadas de Iuncion tenemos dos caminos: o la declaramos como global, o la declaramos como
local estatica. Los buenos programadores siempre eligen el segundo.
Una variable se hace estatica anteponiendo a su declaracion el especiIicador static. Por deIecto
las variables estaticas se auto inicializan a 0, pero se le puede dar otro valor en la misma
declaracion (dicha inicializacion solo se ejecuta la primera vez que se llama a la Iuncion), asi:
static int var1; // Variable static (inicializada a 0 por defecto)
static int var2 = 50; // Variable static inicializada a 50
Ejemplos.
void increm()
{
static int a = 5; //Variable local esttica inicializada a 5
a++; // Incrementar a
UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES
-----------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------
M.S.c Ing. Iacob Astocondor V|||ar 24
}
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
}
14.2 Variables volatile
A diIerencia de los ensambladores, los compiladores tienen cierta 'inteligencia. Es decir,
piensan un poco antes de traducir el codigo Iuente en codigo ejecutable. Por ejemplo, veamos el
siguiente pedazo de codigo para saber lo que suele pasar con una variable ordinaria:
int var; // Declarar variable var
//...
var = var; // Asignar var a var
El compilador creera (probablemente como nosotros) que la sentencia var var no tiene sentido
(y quiza tenga razon) y no la tendra en cuenta, la ignorara. Esta es solo una muestra de lo que
signiIica optimizacion del codigo. Luego descubriras mas Iormas de ese trabajo.
El ejemplo anterior Iue algo burdo, pero habra codigos con redundancias aparentes y mas
diIiciles de localizar, cuya optimizacion puede ser contraproducente. El caso mas notable que
destacan los manuales de los compiladores C para microcontroladores es el de las variables
globales que son accedidas por la Iuncion de interrupcion y por cualquier otra Iuncion.
Para que un compilador no intente 'pasarse de listo con una variable debemos declararla como
volatile, anteponiendole dicho caliIicador a su declaracion habitual.
Por ejemplo, en el siguiente boceto de programa la variable count debe ser accedida desde la
Iuncion interrupt como desde la Iuncion main; por eso se le declara como volatile. Nota: el
esquema de las Iunciones de interrupcion suele variar de un compilador a otro. Este es solo un
ejemplo.
volatile int count; // count es variable global voltil
void interrupt(void) // Funcin de interrupcin
{
// Cdigo que accede a count
}
void main(void) // Funcin principal
{
// Cdigo que accede a count
}
15. ARRAYS Y PUNTEROS
Probablemente este sea el tema que a todos nos ha dado mas de un dolor de cabeza y que mas
hemos releido para captarlo a cabalidad. Hablo mas bien de los punteros. Si ellos el C no seria
UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES
-----------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------
M.S.c Ing. Iacob Astocondor V|||ar 2S
nada, perderia la potencia por la que las mejores empresas lo eligen para crear sus soItwares de
computadoras.
Pero bueno, regresando a lo nuestro, estos temas se pueden complicar muchisimo mas de lo que
veremos aqui. Solo veremos los arrays unidimensionales y los punteros (que en principio
pueden apuntar a todo tipo de cosas) los abocaremos a los datos basicos, incluyendo los mismos
arrays. Aun asi, te sugiero que tengas un par de aspirinas al lado.
15.1 Los arrays o matrices
Un array es una mega variable compuesto de un conjunto de variables simples del mismo tipo y
ubicadas en posiciones contiguas de la memoria. Con los arrays podemos hacer todos lo que
haciamos con las tablas (de busqueda) del ensamblador y muchisimo mas.
Un array completo tiene un nombre y para acceder a cada uno de sus elementos se utilizan
indices entre corchetes (| |). Los indices pueden estar indicados por variables o constantes. En el
siguiente esquema se ve que el primer elemento de un array tiene indice 0 y el ultimo, N-1,
siendo N la cantidad de elementos del array.
Estructura de un array unidimensional de N elementos.
15.2 Declaracin de arrays
Para declarar un array unidimensional se utiliza la siguiente sintaxis:
data_type identifier[ NumElementos ];
Donde datatype es un tipo de dato cualquiera, identiIier es el nombre del array y
NumElementos es la cantidad de elementos que tendra (debe ser un valor constante).
De este modo, el indice del primer elemento es 0 y el del ultimo es NumElements - 1.
Por ejemplo, las siguientes lineas declaran tres arrays.
char letters10]; // letters es un array de 10 elementos de tipo char
long HexTable[16]; // HexTable es un array de 16 elementos de tipo long
int address[100]; //address es un array de 100 elementos de tipo int
Para el array letters el primer elemento es letters|0| y el ultimo, letters|9|. Asi, tenemos 10
elementos en total. Si quisieramos asignar a cada uno de los elementos de letters los caracteres
desde la a` hasta la j`, lo podriamos hacer individualmente asi:
letters[0] = 'a'; // Aqu el ndice es 0
letters[1] = 'b'; // Aqu el ndice es 1
letters[2] = 'c'; // ...
letters[3] = 'd'; //
letters[4] = 'e';
letters[5] = 'f';
letters[6] = 'g';
letters[7] = 'h';
letters[8] = 'i';
UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES
-----------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------
M.S.c Ing. Iacob Astocondor V|||ar 26
letters[9] = 'j'; // Aqu el ndice es 9
Pero asi no tiene gracia utilizar arrays. En este caso lo mejor es utilizar un bucle, asi: (Nota: los
caracteres son, al Iin y al cabo, numeros en codigos ASCII y se les puede comparar.)
char c;
for ( c = 'a'; c <= 'j'; c++ )
letters[i] = c;
15.3 Inicializacin de arrays
Los elementos de un array se pueden inicializar junto con su declaracion. 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 inicializacion solo esta
permitido en la declaracion 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
Tambien es posible inicializar un array sin especiIicar en su declaracion el tamao que tendra,
dejando los corchetes vacios. El tamao sera pre calculado y puesto por el compilador. Esta es
una Iorma bastante usada en los arrays de texto, donde puede resultar muy incomodo 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 que el ultimo array tiene 31 elementos si solo se ven 30 letras? Lo sabremos luego.
15.4 Cadenas de texto terminadas en nulo
Son arrays de tipo de dato char. Hay dos caracteristicas que distinguen a estas cadenas de los
demas arrays. Primero: su inicializacion se hace empleando comillas dobles y segundo, el
ultimo termino del array es un caracter NULL (simplemente un 0x00). De ahi su nombre.
Ejemplos:
char Greet[10] = "Hello"; // Un array de 10 elementos
char msg[] = "Hello"; // Un array de 6 elementos
El array Greet tiene espacio para 10 elementos, de los cuales solo los 5 primeros han sido
llenados con las letras de Hello, el resto se rellena con ceros.
El array msg tiene 6 elementos porque ademas de las 5 letras de 'Hello se le ha aadido un
Null (0x00) al Iinal (claro que no se nota). Es decir, la inicializacion de msg es equivalente a:
char msg[] = { 'H', 'e', 'l', 'l', 'o', 0x00}; // Un array de 6 elementos
Visto graIicamente, msg tendria la siguiente representacion:
UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES
-----------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------
M.S.c Ing. Iacob Astocondor V|||ar 27
Estructura de una cadena de texto.
16 LOS PUNTEROS
Los punteros suelen ser el tema que mas cuesta entender en programacion. Pero si ya llegaste
aqui, es el momento menos indicado para detenerte.
Los punteros son un tipo de variables muy especial. Son variables que almacenan las
direcciones Iisicas de otras variables. Si tenemos la direccion de una variable, tenemos acceso a
esa variable de manera indirecta y podemos hacer con ellas todo lo que queramos.
16.1 Declaracin de punteros
Los punteros pueden apuntar a todo tipo de variables, pero no a todas al mismo tiempo. La
declaracion de un puntero es un tanto peculiar. En realidad, se parece a la declaracion 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 inIluenciadas por los
caliIicadores const, static, etc. Todas excepto los arrays; por que?
La Iorma general de declarar un puntero es la siguiente:
data_type * PointerName;
Los siguientes ejemplos muestran lo Iacil que es Iamiliarizarse con la declaracion de los
punteros:
int * ip; // ip es un puntero a variable de tipo int
char * ucp; // cp es un puntero a variable de tipo char
unsigned char * ucp; // Puntero a variable de tipo unsigned char
const long * clp; // Puntero a constante de tipo long
float * p1, *p2; // Declara dos punteros a variable de tipo float
16.2 Apuntando a variables
Decimos que una variable puntero 'apunta a una variable x si contiene la direccion de dicha
variable. Para ello se utiliza el operador &, el cual extrae la direccion de la variable a la que
acompaa. Un puntero siempre deberia apuntar a una variable cuyo tipo coincida con el tipo del
puntero.
En los siguientes ejemplos vemos como apuntar a variables de tipo basico, como int, char o
Iloat. Mas adelante veremos como apuntar a arrays.
void main (void)
{
int height, width;
char a, b, c;
float max;
int * ip; // ip es un puntero a variable tipo int
UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES
-----------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------
M.S.c Ing. Iacob Astocondor V|||ar 28
char * cp; // cp es un puntero a variable tipo char
float * fp; // Puntero a variable tipo float
ip = &height; // Con esto ip tendr la direccin de height
ip = &width; // Ahora ip apunta a width
cp = &a; // cp apunta a a
cp = &c; // Ahora cp apunta a c
cp = &a; // Ahora cp apunta a a otra vez
fp = &max; // fp apunta a max
fp = &height; // Error! height no es una variable float
//...
}
16.3 Asignaciones indirectas mediante punteros
Una vez que un puntero apunte a una variable cualquiera, se puede acceder a dicha variable
utilizando el nombre del puntero precedido por un asterisco, de esta Iorma:
void main (void)
{
int height, width, n; // Variables ordinarias
int * p, * q; // p y q son punteros a variables de tipo int
p = &height; // p apunta a height
*p = 10; // Esto es como height = 10
p = &width; // p apunta a width
*p = 50; // Esto es como width = 50
height = *p; // Esto es como height = width
q = &height; // q apunta a height
n = (*p + *q)/2; // Esto es como n = (height + width)/2
//...
}
La expresion *p se deberia leer: 'la variable apuntada por p. Eso tambien 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 inIinito, por eso quiero ir con cuidado y poco a
poco para que nadie se pierda.
16.4 PUNTEROS Y ARRAYS
Cmo se oeclara un punLero a un array? un punLero a un array es slmplemenLe un punLero al
Llpo oe oaLo oel array. Cuanoo se aslgna un punLero a un array, en realloao el punLero Loma la
olreccln oe su prlmer elemenLo, a menos que se especlflque oLro elemenLo.
Luego, bastaria con modiIicar el valor del puntero para que apunte a los otros elementos del
array. Todo lo indicado se reIleja en el siguiente codigo:
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
UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES
-----------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------
M.S.c Ing. Iacob Astocondor V|||ar 29
p = &mat; // p apunta a mat (a su primer elemento)
n = *p; // Esto da n = 78
p++; // Incrementar p para apuntar a siguiente elemento
n = *p; // Esto da n = 98
p++; // Incrementar p para apuntar a siguiente element
n = *p; // Esto da n = 26
*p = 10; // Con esto mat[3] valdr 10
p--; // Decrementar p para apuntar a elemento anterior
*p = 100; // Con esto mat[2] valdr 100
p = mat; // p apunta a mat. Es lo mismo que p = &mat
p = NULL; // Desasignar p. Lo mismo que p = 0x0000
// ...
}
En el Iondo los arrays y los punteros trabajan de la misma Iorma, por lo menos cuando
reIerencian a variables almacenadas en la RAM del microcontrolador. La unica diIerencia es
que los arrays no pueden direccionar a datos diIerentes de su contenido; por eso tambien se les
llama punteros estaticos. En la practica esto signiIica 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 demas, las operaciones de asignacion son similares
para punteros y arrays, tal como se puede apreciar en el siguiente codigo. (Por si las moscas,
str1 es el array y str2, el puntero.)
void main(void)
{
char str1[] = { 'A', 'r', 'r', 'a', 'y' };
char * str2 = { 'P', 'o', 'i', 'n', 't', 'e', 'r' };
char a;
a = str1[0]; // Esto da a = 'A'
a = str1[3]; // Esto da a = 'a'
a = str2[0]; // Esto da a = 'P'
a = str2[3]; // Esto da a = 'n'
str1 += 2; // Error! Str1 es esttico
str2 += 2; // Correcto. Ahora str2 apunta a 'i'
str1++; // Error otra vez! Str1 es esttico
str2++; // Correcto. Ahora str2 apunta a 'n'
a = *str2; // Esto da a = 'n'
// ...
}
16.5 Paso de punteros y arrays a funciones
Recuerdas el paso de variables por valor y por reIerencia? Pues aqui vamos de nuevo.
Bien, recordemos: una variable pasada por valor a una Iuncion, en realidad le entrega una copia
suya; por lo que la variable original no tiene por que ser aIectada por el codigo de la Iuncion.
Ahora bien, pasar una variable por reIerencia signiIica que se pasa la direccion de dicha
variable. Como consecuencia, la Iuncion tendra acceso a la variable original y podra modiIicar
su contenido. Esto podria resultar riesgoso, pero, bien usada, la tecnica es una potente arma.
UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES
-----------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------
M.S.c Ing. Iacob Astocondor V|||ar 30
Ya que los punteros operan con direcciones de variables, son el medio ideal para trabajar con
parametros por reIerencia. Hay dos casos de particular interes: uno, cuando deseamos en serio
que la variable pasada a la Iuncion cambie a su regreso; y dos, cuando la variable pasada es
demasiado grande (un array) como para trabajar con copias. De hecho, los arrays siempre se
pasan por reIerencia ya que tambien son punteros al Iin.
La sintaxis de los punteros en el encabezado de la Iuncion no es nada nuevo, teniendo en cuenta
que tambien tienen la Iorma de declaraciones de variables.
En el siguiente ejemplo la Iuncion interchange intercambia los valores de las dos variables
recibidas. En seguida explicare por que varia un poco la Iorma en que se llama a la Iuncion.
void interchange( int * p1, int * p2 )
{
int tmp = *p1; //Guardar valor inicial de variable apuntada por p1.
*p1 = *p2; // Pasar valor de variable apuntada por p2 a...
// variable apuntada por p1.
*p2 = tmp; // Variable apuntada por p2 valdr tmp.
}
void main (void)
{
int i, j;
/* Hacer algunas asignaciones */
i = 10;
j = 15;
/* Llamar a funcin interchange pasando las direcciones de i y j */
interchange( &i, &j );
// En este punto i vale 15 y j vale 10
// ...
}
Al llamar a interchange le entregamos &i y &j, es decir, las direcciones de i y j. Por otro lado, la
Iuncion interchange recibira dichos valores en p1 y p2, respectivamente. De ese modo, p1 y p2
estaran apuntando a i y j, y podremos modiIicar sus valores.
Ten presente que se mantiene la Iorma de asignacion 'puntero &variable (puntero igual a
direccion de variable).
Ahora veamos ejemplos donde la Iorma de asignacion 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 Iuncion prom, la cual devuelve el valor
promedio de los elementos del array recibido. Como para ese calculo se necesita conocer la
cantidad de elementos que tiene el array, prom recibe dicho valor en el parametro 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
UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES
-----------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------
M.S.c Ing. Iacob Astocondor V|||ar 31
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 Iunciones auxiliares: mayus convierte la cadena recibida en
mayusculas, y lon calcula la longitud del texto almacenado en el array recibido. Ambas
Iunciones 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
// minscula
*p = *p - 32; // Hacerlo mayscula
p++; // 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 texto */
L = lon(song1); // Debera dar L = 9
L = lon(song2); // Debera dar L = 15
L = lon(song3); // Debera dar L = 13
/* Convertir cadenas en maysculas */
mayus(song1 ); // Es lo mismo que mayus(&song1);
// Ahora song1 debera valer "DARK BLUE"
mayus(song2 ); // Es lo mismo que mayus(&song2);
// Ahora song2 debera valer "STARING PROBLEM"
mayus(song3 ); // Es lo mismo que mayus(&song3);
// Ahora song3 debera valer "EX-GIRLFRIEND"
while(1); // Bucle infinito
UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES
-----------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------
M.S.c Ing. Iacob Astocondor V|||ar 32
}
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 caracter 0x00.
Segun la tabla de caracteres ASCII, las letras mayusculas estan ubicadas 32 posiciones por
debajo de las minusculas. Por eso basta con sumarle o restarle ese valor a un caracter ASCII
para pasarlo a mayuscula o minuscula.
En ambas Iunciones el puntero p navega por los elementos del array apuntado hasta que
encuentra el Iinal, indicado por un caracter nulo (0x00).
17. ARRAYS CONSTANTES
No es que me haya atrasado con el tema, es solo que los arrays constantes son uno de los temas
cuyo tratamiento varia mucho entre los distintos compiladores. Veamos en que.
Un array constante es uno cuyos elementos solo podran ser leidos pero no escritos; tan simple
como eso.
En principio, para que un array sea constante a su clasica declaracion con inicializacion de un
array se le debe anteponer el caliIicador const. No es posible declarar un array constante vacio y
llenar sus elementos despues pues eso equivaldria a modiIicar sus elementos. Enseguida
tenemos ejemplos de declaracion de arrays constantes:
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 seran de solo lectura, y sus elementos podran ser
leidos, mas no escritos. El compilador mostrara mensajes de error si en lo que resta del
programa encuentra intentos de cambio, por ejemplo, como
a[4] = 100; // Esto generar un error en tiempo de compilacin
vocals[0] = 'a';//Error! no se puede escribir por mas que sea el mismo dato
Ahora bien, que los datos no cambien durante la ejecucion del programa no necesariamente
signiIica los arrays constantes esten ubicados en la memoria FLASH. En algunos compiladores
de PICs, como CCS C y Hitech C, si ocurre asi, pero el lenguaje C solo dice que estos datos son
inmodiIicables, no dice donde deben residir. Recordemos que las variables en un programa de
computadora, constantes o no, van siempre en la RAM. Para las computadoras no es problema
porque les "sobra" la RAM, cosa que no sucede en los microcontroladores.
18. Variables PROGMEM y su acceso
Por otro lado, los compiladores de AVR oIrecen metodos alternos para declarar arrays, o
variables en general, que puedan residir en la memoria FLASH. Con eso no solo se destina la
preciada RAM a otras variables sino que se pueden usar grandes arrays constantes que
simplemente no podrian caber en la RAM. Como todo esto va al margen de lo que diga el ANSI
C, cada compilador ha establecido su propia sintaxis para hacerlo.
UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES
-----------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------
M.S.c Ing. Iacob Astocondor V|||ar 33
Empecemos por examinar el estilo de AVR GCC. Por ejemplo, si queremos que los tres
primeros arrays de esta pagina se almacenen en la FLASH debemos declararlas e inicializarlas
de esta Iorma
PROGMEM const int a[5] = { 20, 56, 87, -58, 5000 };// Array constante
PROGMEM const char vocals[5] = { 'a', 'e', 'i', 'o', 'u' }; // Array constante
PROGMEM const char text[] = "Este es un array constante de caracteres";
Observa que Iue tan simple como aadirles al inicio la palabra reservada PROGMEM. El
caliIicador const era opcional en las versiones pasadas de AVR GCC como la que viene con
AVR Studio 5, pero es necesaria en las versiones recientes como la que trae Atmel Studio 6. De
todos modos es sencillo. Lo complicado viene despues. Para acceder a los elementos de estos
arrays hay que emplear una Iorma un tanto exotica. Se deben usar algunas macros propias del
compilador, todas proveidas por la libreria pgmspace.h. Las principales son estas cuatro
pgmreadbyte. Lee de un array residente en FLASH un entero compuesto por un byte
de dato. En el lenguaje C los unicos datos de este tamao son del tipo char y sus
derivados signed char y unsigned char.
pgmreadword. Lee de un array residente en FLASH un entero compuesto por 2 bytes.
En el C serian arrays de tipo (signed) int, unsigned int, (signed) short y unsigned short.
pgmreaddword. Lee de un array residente en FLASH un entero compuesto por 4
bytes. En el C serian arrays de tipo (signed) long y unsigned long.
pgmreadIloat. Lee de un array residente en FLASH un numero de punto Ilotante
compuesto por 4 bytes. En el lenguaje C serian arrays de tipo Iloat y double operando
en modo de 32 bits.
Estas macros reciben como argumento la direccion de un elemento del array en FLASH. Como
la direccion de una variable cualquiera en el C se obtiene al aplicarle el operador &, las macros
citadas trabajan de esta Iorma.
PROGMEM const int a[5] = { 20, 56, 87, -58, 5000 }; // Array constante
PROGMEM const char vocals[5] = { 'a', 'e', 'i', 'o', 'u' }; // Array constante
PROGMEM const char text[] = "Este es un array constante de caracteres";
int var;
char c;
var = pgm_read_word(&a[1]); // Con esto var valdr 56
c = pgm_read_byte(&vocals[3]); // Con esto c valdr 'o'
c = pgm_read_byte(&text[11]); // Con esto c valdr 'a'
El manual de AVR GCC nos presenta una Iorma que puede resultar mas Iacil de asimilar el
acceso a los elementos de estos arrays: dice que primero asumamos acceder al elemento como si
perteneciera a un array ordinario (residente en RAM), por ejemplo:
var = a[1];
luego le aplicamos el operador &
var = &a[1];
y Iinalmente le aplicamos la macro respectiva.
var = pgm_read_word( &a[1] ); // Con esto var valdr 56
Por supuesto que en nuestro programa deberemos poner solo la ultima expresion. Es muy
importante recordar esto puesto que las expresiones previas son tambien validas para el
UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES
-----------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------
M.S.c Ing. Iacob Astocondor V|||ar 34
compilador y no generara errores. Solo nos daremos con la sorpresa de ver nuestro programa
Iuncionando desastrosamente. Me pasa con Irecuencia porque en mis codigos tengo la
costumbre de ubicar primero los arrays en la RAM para luego de obtener buenos resultados
mudarla a la memoria FLASH. Si eres nuevo te recomiendo seguir la misma practica. Trabajar
con datos en FLASH desde el inicio requiere de mucha experiencia. Hay otras macros y tipos de
datos que debemos saber usar, y si no estamos seguros de lo que hacemos, repito, el compilador
no nos ayudara.
Los parametros que reciben como argumento las macros pgmreadbyte, pgmreadword,
pgmreaddword y pgmreadIloat son direcciones de 16 bits. Esto quiere decir que mediante
ellas podemos acceder a los arrays cuyos elementos cubren un espacio de 2
16
65536 bytes de
la memoria FLASH. En la gran mayoria de los casos es mucho mas de lo que se necesita,
considerando que solo hay dos megaAVR que superan esa memoria, los ATmega128 y los
ATmega256. Pero si se presentara la descomunal situacion donde tengamos que trabajar con
arrays de mas de 64 KB, la libreria pgmspace.h nos provee de otras macros ad hoc. Retomamos
este aspecto al Iinal de la pagina.
18.1 Variables PROGMEM locales
Las variables PROGMEM tienen un espacio asignado en la memoria que no cambia durante la
ejecucion del programa. Parece una perogrullada pero los lectores perspicaces saben que ese
comportamiento es propio de dos tipos de variables del C: las variables globales y las variables
static locales. Todos los otros tipos de variables tienen posiciones dinamicas.
Mas que una coincidencia, lo dicho arriba es una condicion necesaria para todas las variables
almacenadas en la FLASH para AVR IAR C y AVR GCC. Es decir, en estos compiladores las
variables PROGMEM deben o ser globales o static locales. Todos los ejemplos mostrados
arriba Iuncionan bien asumiendo que estan declaradas a nivel global. Si los colocamos dentro de
una Iuncion habra problemas.
Por ejemplo, el siguiente extracto de Iuncion no dara errores en AVR GCC pero el programa
Iuncionara deIectuosamente, a pesar de que los arrays estan declarados e inicializados conIorme
a lo estudiado previamente.
/****************************************************************
* Toca las notas del ringtone apuntado por pRingtone.
***********************************************************/
void Tune(PGM_P pRingtone)
{ // C C# D D# E F F#
PROGMEM const unsigned int NoteFreqs[] = {262,277,294,311,330,349,370};
PROGMEM const unsigned char Octaves[] = {6,7,5};
PROGMEM const unsigned int Bpms[] = {0,812,406,270,203,162,135};
PROGMEM const unsigned char Durations[] = {4,8,1,2};
/* ... */
}
Sucede que los arrays estan declarados como si Iueran locales ordinarias. Si los hubieramos
declarado globalmente estaria bien. Pero como son locales es necesario que sean ademas de tipo
static. Como sabemos, estas variables en C se Iorman aadiendoles la palabra reservada static a
su declaracion habitual. Con esto aclarado, el codigo anterior trabajara perIectamente si lo
escribimos de esta Iorma.
/************************************************************
* Toca las notas del ringtone apuntado por pRingtone.
**********************************************************/
UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES
-----------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------
M.S.c Ing. Iacob Astocondor V|||ar 3S
void Tune(PGM_P pRingtone)
{ // C C# D D# E F F#
static PROGMEM const unsigned int NoteFreqs[] = {262,277,294,311,330,349,370};
static PROGMEM const unsigned char Octaves[]= {6,7,5};
static PROGMEM const unsigned int Bpms[]= {0,812,406,270,203,162,135};
static PROGMEM const unsigned char Durations[] = {4,8,1,2};
/* ... */
}
Ese trozo de codigo pertenece al programa de la practica reproductor de ringtones PICAXE. Si
deseas comprobar lo expuesto puedes descargarlo y recompilarlo haciendo las modiIicaciones
explicadas.
18.2 Los punteros PGM_P y PGM_VOID_P
Justo en el ejemplo anterior aparece el tipo de dato PGMP en una de sus Iunciones que es
permitir el paso de variables a Iunciones. Ese tema lo proIundizaremos luego.
El tipo de dato PGMP es un puntero a una variable residente en la memoria FLASH. Su
deIinicion en el archivo pgmspace.h de AVR GCC es
#define PGM_P const PROGMEM char *
pero el archivo pgmspace.h de AVR IAR C lo deIine como
#define PGM_P const char __flash *
Con el Iin de que los programas de cursomicros sean lo mas transparentes posible trato de evitar
el uso excesivo de los #deIines que conducen a terminos innecesarios. El hecho de estar
estudiando PGMP sugiere que se trata de una excepcion. Notemos en primer lugar que el
archivo avrcompiler.h que se usa en esta web deIine PROGMEM como Ilash con lo cual las
dos expresiones de arriba serian identicas asumiendo que en AVR GCC const PROGMEM char
equivale a const char PROGMEM y tambien a PROGMEM const char, siendo esta ultima
presentacion la Iorma en que hemos venido trabajando. Debido a ello en muchas ocasiones
podremos prescindir de PGMP, pero surgiran algunos casos en que AVR IAR C muestre su
disconIormidad por ese reacomodo de terminos. PGMP no solo termina de arreglar estos
desajustes sino que Iacilita notablemente la escritura del codigo ante la aparicion de
construcciones mas complejas como las que veremos despues.
Si PGMP deIine un tipo puntero que apunta a variables char (o de un byte en general), alguien
podria preguntar cuales son los punteros para las variables de tipo int, short, Iloat, etc. No hay
deIiniciones especiales para esos casos. Podemos crearlas por cuenta propia si deseamos pero
sera raramente necesario porque a Iin de cuentas el puntero seguira siendo de 16 bits. Solo suele
interesar la direccion de un dato. Para leer ese dato con el Iormato deseado habra que usar la
macro adecuada, entre pgmreadbyte, pgmreadword, pgmreaddword y pgmreadIloat,
junto a las conversiones de datos respectivos.
Ejemplo, en el siguiente programa que esta escrito para los compiladores AVR IAR C y AVR
GCC Notes es un array de enteros de 16 bits y Octaves un array de enteros de 8 bits, ambos
residentes en la FLASH.
#include "avr_compiler.h"
PROGMEM const unsigned int Notes[] = {262, 277, 294, 311, 494};
PROGMEM const unsigned char Octaves[] = {6, 7, 5};
int main(void)
UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES
-----------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------
M.S.c Ing. Iacob Astocondor V|||ar 36
{
PGM_P p; // Declarar el puntero p
unsigned char c;
unsigned int n;
p = (PGM_P)Octaves; // Apuntar al array Octaves
c = pgm_read_byte(p + 1); // Obtener el elemento Octaves[1]
p = (PGM_P)Notes; // Apuntar al array Notes
n = pgm_read_word((PROGMEM int*)p + 3); // Obtener Notes[3]
while(1);
}
Como los arrays pueden ser entendidos como punteros tambien, en principio se podrian hacer
las asignaciones a p directamente como p Notes, pero para evitar protestas del compilador se
deben usar conversiones de tipo, poniendo al lado izquierdo y entre parentesis el tipo de la
variable que recibe el valor, en este caso PGMP porque p es de ese tipo. De ese modo p podra
apuntar a arrays de cualquier tipo.
Por otro lado, para leer los elementos del array Octaves usamos la macro pgmreadbyte porque
es un array de bytes y para Notes usamos pgmreadword porque es un array de enteros de 2
bytes.
A pgmreadbyte se le envia el puntero p mas el indice del elemento accedido. Recordemos que
estas macros reciben direcciones y como los punteros contienen direcciones, no es necesario
sacar direcciones mediante el operador &. Este caso Iue sencillo porque el tipo de Octave se
acoplaba Iacilmente a PGMP.
pgmreadword tambien espera recibir una direccion pero no se le puede enviar a p
directamente como en el caso previo. Es preciso hacer una conversion de tipo para que el valor
de p sea entendido como una direccion de variables de 2 bytes. Por eso colocamos al lado
izquierdo de p la expresion (PROGMEM int *) que se adapta al tipo del array Notes. Tambien
se pudo haber puesto (PROGMEM const unsigned int *) para una mejor claridad en la
correspondencia. Lo que cuenta es que implique un tipo de 2 bytes.
El hecho de usar el tipo PGMP hace presuponer que solo se trabajara con variables de bytes
que son accedidas mediante la macro pgmreadbyte. De hecho es asi en la gran mayoria de los
casos y todo queda bien. La legibilidad se pierde en programas como el ejemplo previo donde el
mismo puntero se usa tambien para acceder a un array de enteros de 2 bytes. Si de todos modos
vamos a estar haciendo conversiones de tipos lo mas recomendable seria usar un puntero
"neutro" lo cual deja por sentado que trabajara sobre variables de distintos tipos.
Ese tipo de puntero existe y se llama PGMVOIDP. Es aceptado asi en los compiladores AVR
IAR C y AVR GCC. Es un puntero a void deIinido en AVR GCC como const void PROGMEM
* y en AVR IAR C como const void Ilash *. Lo importante es que su empleo es similar al
puntero PGMP, asi que lo asimilaremos de inmediato. El programa anterior por ejemplo se
puede reescribir de la siguiente Iorma. (El codigo quedo con mejor acabado y con una linda
simetria.)
#include "avr_compiler.h"
PROGMEM const unsigned int Notes[] = {262, 277, 294, 311, 494};
PROGMEM const unsigned char Octaves[] = {6, 7, 5};
int main(void)
{
PGM_VOID_P p; // Declarar el puntero p
UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES
-----------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------
M.S.c Ing. Iacob Astocondor V|||ar 37
unsigned char c;
unsigned int n;
p = (PGM_VOID_P)Octaves; // Apuntar al array Octaves
c = pgm_read_byte((PROGMEM char*)p + 1); // Obtener el elemento Octaves[1]
p = (PGM_VOID_P)Notes; // Apuntar al array Notes
n = pgm_read_word((PROGMEM int*)p + 3); // Obtener Notes[3]
while(1);
}
Los punteros PGMP y PGMVOIDP tambien pueden actuar sobre variables de tipo
complejo. Estamos hablando por ejemplo de estructuras deIinidas por el usuario. Por el mismo
hecho de ser variables complejas poner aqui un programa de demostracion abarcaria demasiado
espacio. PreIiero remitirme a la libreria para USB que distribuye Atmel. Me parece un perIecto
ejemplo. Puedes encontrarla en varias notas de aplicacion como AVR270, AVR271, AVR272 y
AVR273, por citar algunas. En el archivo usbstandarrequest.c se declara y usa el puntero
pbuIIer de tipo PGMVOIDP para acceder a los descriptores de USB que por su tamao
residen en la FLASH.
Esa libreria USB se vale de un archivo llamado compiler.h para guardar la compatibilidad de
codigos entre los compiladores AVR IAR C y AVR GCC para los que esta escrita. Contiene
varias imprecisiones que, imagino, se deben a los deIectos que AVR GCC presentaba
antiguamente, cuando se escribio la libreria. Igual vale la pena revisarla.
18.3 Variables PROGMEM como argumentos de funciones
En primer lugar recordemos que los argumentos de las Iunciones deben ser del mismo tipo que
las variables que se le envian. Si las variables son residentes en la FLASH, lo cual deja suponer
que son arrays o estructuras complejas, el metodo a usar son los punteros, no solo por el tamao
de esas variables sino por la capacidad de adaptacion de los punteros que estudiamos en la
seccion anterior.
Alli se describio la operacion de los punteros PGMP y PGMVOIDP y se explico lo
imprescindibles que serian. No es recomendable que los argumentos de las Iunciones sean
punteros con tipos diIerentes de PGMP o PGMVOIDP. Solo ellos garantizan que nuestros
programas Iuncionaran bien en los dos compiladores AVR IAR C y AVR GCC. Dicho eso,
podemos pasar al ejemplo.
En el siguiente programa la Iuncion print imprime un mensaje por el puerto serie, similar a puts
de la libreria stdio.h del compilador. La Iuncion puts solo trabaja con mensajes en la RAM a
diIerencia de print que recibe arrays residentes en la FLASH. Los dos compiladores que usamos
tambien oIrecen Iunciones de FLASH y a eso pretendemos llegar: a su uso.
#include "avr_compiler.h"
#include "usart.h"
PROGMEM const char rt01[] = "\r Deck the halls";
PROGMEM const char rt02[] = "\r Jingle bells";
PROGMEM const char rt03[] = "\r We wish you a merry christmas";
PROGMEM const char rt04[] = "\r Silent night";
/*************************************************************
* Enva por el USART el texto pasado en p
*******************************************************/
void print(PGM_P p)
{
UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES
-----------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------
M.S.c Ing. Iacob Astocondor V|||ar 38
char c;
while( (c = pgm_read_byte(p++)) != 0x00 )
putchar(c);
}
/*******************************************************
* Main function
***********************************************************/
int main(void)
{
usart_init(); // Inicializar USART
print(rt01); // Imprimir mensaje de rt01
print(rt02); // ...
print(rt03); // ...
print(rt04); // ...
while (1);
}
Creo que el codigo esta bastante claro. Como los arrays son de texto (de caracteres de 1 byte), se
opto por el puntero PGMP y por la macro pgmreadbyte para la que no Iue necesaria una
conversion de tipo. La conversion de tipo para p es opcional, por ejemplo, tambien se pudo
escribir print((PGMP)rt01).
Y ahora la pregunta que nos trae aqui: Se puede enviar a una Iuncion una variable en Ilash
directamente? Es decir, que pasa si en vez de declarar los arrays por separado, los escribimos
directamente en el argumento de la siguiente Iorma.
print("\r Deck the halls"); // Imprimir este mensaje
print("\r Jingle bells"); // ...
print("\r We wish you a merry christmas"); // ...
print("\r Silent night"); // ...
El compilador AVR GCC todavia acepta las sentencias y construye el programa limpiamente,
sin presentar errores, ni siquiera advertencias. Pero el resultado es un programa mostrando
mamarrachos en vez de los villancicos esperados. El compilador AVR IAR C, por su parte,
simplemente no admite el codigo Iuente. Que paso?
Como variables locales ordinarias que son, los compiladores tratan de implementar las cadenas
pasadas en la memoria RAM. AVR IAR C nota la incompatibilidad de tipos y rechaza el codigo
de plano, en tanto que AVR GCC si cumple el cometido pasando por alto la divergencia de
tipos porque, recordemos, este compilador hace la diIerencia en el momento de acceder a las
variables usando sus macros como pgmreadbyte. Esa macro recibe en el programa una
direccion RAM (tambien de 16 bits) y la usa para leer de la memoria FLASH como si las
cadenas de texto estuvieran alli. Lee "cualquier cosa" menos las cadenas.
Alguien mas avezado podria decir que eso se puede arreglar con conversiones de tipos por
ejemplo reescribiendo las sentencias asi
print((PGM_P)("\r Deck the halls")); // Imprimir este mensaje
print((PGM_P)("\r Jingle bells")); // ...
print((PGM_P)("\r We wish you a merry christmas")); // ...
print((PGM_P)("\r Silent night")); // ...
El codigo vuelve a compilarse limpiamente. Hasta AVR IAR C es engaado. Pero cuando
vemos el programa en accion descubrimos que solo nos hemos engaado a nosotros mismos. En
este programa los garabatos que se visualizan en el terminal serial nos quitaron la venda de los
ojos rapidamente, Ielizmente. En otras circunstancias detectar el error hubiera costado mas. Las
UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES
-----------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------
M.S.c Ing. Iacob Astocondor V|||ar 39
cadenas siguen siendo colocadas en la RAM. Recordemos que para que las variables residan en
la FLASH deben o ser globales o static locales. Lo primero es obviamente un imposible; y lo
segundo, que sean static, solo es posible en el compilador AVR GCC gracias a una macro
llamada PSTR que inicializa el array como static y toma su direccion automaticamente. La
siguiente construccion entonces Iuncionara como se desea pero solo en este compilador.
print(PSTR("\r Deck the halls")); // Imprimir este mensaje
print(PSTR("\r Jingle bells")); // ...
print(PSTR("\r We wish you a merry christmas")); // ...
print(PSTR("\r Silent night")); // ...
Solo por curiosidad, la macro PSTR tiene la siguiente deIinicion. PSTR es ampliamente usada
cuando se trabaja con las Iunciones P del compilador AVR GCC. Asi que hablaremos mas de
ella en adelante.
#define PSTR(s) (__extension__({static const char __c[] PROGMEM = (s);
&__c[0];}))
18.4 Arrays de cadenas PROGMEM
Este es un tema recurrente en programacion.
Para crear un array de cadenas en FLASH primero se declaran e inicializan las cadenas de la
Iorma ya conocida y luego se construye el array con los nombres de las cadenas. Esta regla es
unica, inIlexible, limitante e igualmente valida para los dos compiladores que usamos, AVR
IAR C y AVR GCC. Con un ejemplo lo vamos a entender mejor.
El objeto del siguiente programa es identico al ejemplo de la seccion anterior: el programa debe
mostrar los mismos mensajes almacenados en la FLASH solo que esta vez se les desea acceder
mediante un indice, por eso los mensajes estan agrupados en un array.
#include "avr_compiler.h"
#include "usart.h"
PROGMEM const char ringt01[] = "\r Deck the halls";
PROGMEM const char ringt02[] = "\r Jingle bells";
PROGMEM const char ringt03[] = "\r We wish you a merry christmas";
PROGMEM const char ringt04[] = "\r Silent night";
PGM_P ringtones[] =
{
ringt01,
ringt02,
ringt03,
ringt04
};
/****************************************************************
* Main function
***************************************************************/
int main(void)
{
usart_init(); // Inicializar USART
puts_P(ringtones[0]); // Imprimir Deck the halls
puts_P(ringtones[1]); // Imprimir Jingle bells
puts_P(ringtones[2]); // Imprimir We wish you a merry christmas
puts_P(ringtones[3]); // Imprimir Silent night
UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES
-----------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------
M.S.c Ing. Iacob Astocondor V|||ar 40
while (1);
}
La Iuncion putsP es proveida por los compiladores. Es similar a la Iuncion puts pero las
cadenas que recibe deben ubicarse en la FLASH. En otras palabras, putsP es similar a la
Iuncion print que creamos en el ejemplo previo.
Se nota que el array ringtones ha sido declarado para ubicarse en la RAM, por eso accedemos a
sus elementos de Iorma regular y no empleando macros. Se hizo asi porque cada elemento es un
puntero de 2 bytes y como solo son 4 punteros, no ocupan mucho espacio. Si hubiera muchos
mas elementos en el array ringtones, la situacion cambiaria y seria mejor que tambien residiera
en la FLASH. Eso lo veremos al Iinal.
Elaborar el array y el contenido de sus elementos por separado es incomodo por el hecho de
tener que poner nombres a cada elemento, nombres que no son necesarios en otra parte del
programa, pero no hay otro camino. Quisieramos que Iuera posible implementar el array por
ejemplo como se muestra abajo donde cada elemento se inicializa directamente, pero eso solo es
Iactible en otros compiladores como CodeVisionAVR o SDCC. Por lo que dicen sus manuales,
en los compiladores AVR IAR C y AVR GCC en el mejor de los casos esto solo ubicara los
elementos en la RAM. En AVR GCC ni siquiera la macro PSTR, que sirve para inicializar en
linea datos residentes en FLASH, podra ayudarnos esta vez. Y, lo olvidaba, no intentes Iorzar el
destino del array o de sus elementos utilizando conversiones de tipos. Solo evadiras los errores
y advertencias, pero los datos seguiran yendo a la RAM y el programa Iuncionara
incorrectamente. (Haz clic aqui si quieres ver la version CodeVisionAVR de este programa.)
PGM_P ringtones[] =
{
"\r Deck the halls",
"\r Jingle bells",
"\r We wish you a merry christmas",
"\r Silent night"
};
Esa presentacion coincide con la Iorma en que hemos estado trabajando antes: primero
PROGMEM, luego const y al Iinal el tipo de dato. En AVR IAR C una permutacion a veces
producira incompatibilidades. Por tanto, el uso de PGMP mas que una cuestion de
simpliIicacion es una necesidad que Iacilitara la compatibilidad de codigos.
Como dijimos antes, cada elemento del array ringtones es un puntero de 2 bytes y en conjunto
no ocupan mucha RAM en este programa. Ahora bien si nuestro codigo requiriera incluso ese
espacio para otros datos o si el array es bastante mas grande, entonces el mismo array ringtones
tambien deberia almacenarse en la memoria FLASH. Solo hay un arreglo para esto y, como ya
discutimos demasiado, vamos directamente a poner la Iorma que debe tener el programa en ese
caso.
#include "avr_compiler.h"
#include "usart.h"
PROGMEM const char ringt01[] = "\r Deck the halls";
PROGMEM const char ringt02[] = "\r Jingle bells";
PROGMEM const char ringt03[] = "\r We wish you a merry christmas";
PROGMEM const char ringt04[] = "\r Silent night";
PROGMEM PGM_P const ringtones[] =
{
ringt01,
ringt02,
ringt03,
UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES
-----------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------
M.S.c Ing. Iacob Astocondor V|||ar 41
ringt04
};
/*******************************************************
* Main function
************************************************/
int main(void)
{
usart_init(); // Inicializar USART
puts_P((PGM_P)pgm_read_word(&ringtones[0]));//Imprimir 'Deck the halls'
puts_P((PGM_P)pgm_read_word(&ringtones[1])); // Imprimir 'Jingle bells'
puts_P((PGM_P)pgm_read_word(&ringtones[2]));//Imprimir 'We wish you a ...
puts_P((PGM_P)pgm_read_word(&ringtones[3])); // Imprimir 'Silent night'
while (1);
}
La conversion de tipo con (PGMP) no es necesaria para AVR IAR C y para AVR GCC sirve
para evitar warnings aunque el programa Iunciona igual.
Cambiando de tema, en AVR GCC el programa se compila en 528 bytes de memoria FLASH y
20 bytes de RAM. En CodeVisionAVR habia tomado 490 bytes de FLASH y solo 8 bytes de
RAM. Es uno de los inusuales casos donde gana CodeVisionAVR, normalmente ni se le acerca.
Pero AVR IAR C lo hizo en 375 bytes de FLASH y 70 bytes de RAM. Parece que AVR IAR C
hubiera puesto los datos en la RAM, pero no. Es el estilo de este compilador tomar un poco mas
de RAM para ahorrar mas FLASH. En los tres casos la compilacion se realizo con la
optimizacion a maximo nivel.
18.5 Arrays ms que grandes
Los parametros que reciben como argumento las macros pgmreadbyte, pgmreadword,
pgmreaddword y pgmreadIloat son direcciones de 16 bits. Esto quiere decir que mediante
ellas podemos acceder a los arrays cuyos elementos cubren un espacio de 2
16
65536 bytes de
la memoria FLASH. En la gran mayoria de los casos es mucho mas de lo que se necesita,
considerando que solo hay dos megaAVR que superan esa memoria, los ATmega128 y los
ATmega256. Pero si se presentara la descomunal situacion donde tengamos que trabajar con
arrays de mas de 64 KB, la libreria pgmspace.h nos provee de otras macros ad hoc.
pgmreadbyteIar. Accede a arrays de enteros de 1 byte.
pgmreadwordIar. Accede a arrays de enteros de 2 bytes.
pgmreaddwordIar. Accede a arrays de enteros de 4 bytes.
pgmreadIloatIar. Accede a arrays de decimales 4 bytes.
Sin ser muy observadores notamos que sus nombres provienen de las macros anteriores. Ahora
llevan el suIijo Iar. Estas macros reciben como argumento direcciones de 32 bits con lo que
teoricamente tienen un alcance de hasta 4 GB de datos en FLASH. Como en la practica los
punteros X, Y y Z de los AVR de 8 bits solo llegan a ser de 24 bits, su alcance es en realidad de
16 MB. El uso de estas macros es completamente igual al de sus pares de 16 bits, por eso se ven
como redundantes los siguientes ejemplos. Las variables sobre las que actuan tambien deben ser
declaradas con PROGMEM.
PROGMEM const int a[5] = { 20, 56, 87, -58, 5000 }; // Array constante
PROGMEM const char vocals[5] = { 'a', 'e', 'i', 'o', 'u' }; // Array constante
PROGMEM const char text[] = "Este es un array constante de caracteres";
int var;
char c;
UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES
-----------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------
M.S.c Ing. Iacob Astocondor V|||ar 42
var = pgm_read_word_far(&a[1]); // Con esto var valdr 56
c = pgm_read_byte_far(&vocals[3]); // Con esto c valdr 'o'
c = pgm_read_byte_far(&text[11]); // Con esto c valdr 'a'
Podemos entender que los unicos AVR que aceptan estas macros son los que tienen mas de 64
KB de memoria FLASH. Con esos AVR, es posible usar los dos tipos de macros, las de 16 bits
y las de 32 bits, sin embargo no siempre seran igual de eIicientes. Si el codigo de arriba, por
ejemplo, estuviera escrito para un ATmega1284P, el acceso se dilataria ligeramente al tenerse
que trabajar con 32 bits. Puede ser un detalle insigniIicante pero a veces servira para optimizar
procesos.
Para complementar el tema, diremos que si existen macros con Iar (lejos, en ingles), en la
libreria pgmspace.h tambien hay macros con el apendice near (cerca). Estas nuevas macros son
pgmreadbytenear, pgmreadwordnear, pgmreaddwordnear y pgmreadIloatnear.
Pero no te preocupes si crees que el tema se va recargando demasiado. No se tratan mas que de
alias de las primeras macros de 16 bits que estudiamos arriba, por ejemplo,
pgmreadbytenear es lo mismo que pgmreadbyte, y asi con las demas. Es bueno saberlo
para no quedar sorprendidos por los codigos de quienes preIieren usar la una u otra Iorma.

You might also like