Está en la página 1de 42

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES

-----------------------------------------------------------------------------------------------------------------------------

UNIVERSIDAD NACIONAL DEL CALLAO

FACULTAD DE INGENIERA ELCTRICA Y ELECTRNICA


ESCUELA PROFESIONAL ACADMICO DE INGENIERA
ELECTRNICA

CURSO: MICROCONTROLADORES

LENGUAJE C PARA ATMEGA8


PROFESOR: MSc ING. ASTOCONDOR VILLAR JACOB

CALLAO, 2014V

-----------------------------------------------------------------------------------------------------------------------------M.S.c Ing. Jacob Astocondor Villar

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES


-----------------------------------------------------------------------------------------------------------------------------

PROGRAMACION EN C DEL ATMEGA8-AVR


INTRODUCCIN
Un curso de microcontroladores como ste implica abarcar tres reas:
Conocer el microcontrolador. Un microcontrolador es un circuito integrado genrico
cuyas partes debemos adaptar para que funcionen segn los requerimientos de nuestro
diseo. Obviamente no podramos programar lo que no conocemos.
Conocer los perifricos externos. Un micro CONTROLADOR no sera muy til si no
tiene qu controlar. Muchos dispositivos a controlar o mediante los cuales se va a
controlar son comunes de la electrnica analgica, como transistores, rels, diodos
LED, registros de desplazamiento e incluso los motores, y se da por hecho que el lector
ya conoce lo suficiente de ellos. Tambin estn los perifricos que difcilmente 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 perifricos externos el que se cubre en un curso de
microcontrolador como ste.
Conocer un lenguaje de programacin. Conocer un lenguaje de programacin 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 ms potentes que el ensamblador aunque su aprendizaje
demanda un mayor esfuerzo.
Para empezar a programar en ensamblador nos puede bastar con aprender unas 50 palabras (las
instrucciones bsicas).
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. Adems, los procesadores no son como las personas: si en un
cdigo de 100 lneas te olvidaste de una sola coma, los compiladores no te lo pasarn por alto.

ESTRUCTURA DE UN PROGRAMA EN C
Tomaremos en cuenta este sencillsimo 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 "avr_compiler.h"
//****************************************************************************
// delay_ms
//****************************************************************************
void delay_ms(unsigned int t)
{
while(t--)
delay_us(1000);
-----------------------------------------------------------------------------------------------------------------------------M.S.c Ing. Jacob Astocondor Villar

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES


-----------------------------------------------------------------------------------------------------------------------------

}
//****************************************************************************
// Funcin principal
//****************************************************************************
int main(void)
{
DDRB = 0x01;
// Configurar pin PB0 como salida
for( ;; )
{
PORTB |= 0x01; // Poner 1 en pin PB0
delay_ms(400); //
PORTB &= 0xFE; // Poner 0 en pin PB0
delay_ms(300);
}
}
No hay que ser muy perspicaz para descubrir lo que hace este programa: configura el
pin PB0 como salida y luego lo setea y lo limpia tras pausas. Es como hacer parpadear
un LED conectado al pin PB0. Parpadea porque el bloque de while se ejecuta
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 as como una mega instruccin, que hace lo que varias instrucciones del
ensamblador.
Salvo casos particulares, donde su uso es opcional, una sentencia debe finalizar con un punto
y coma (;).
As que tambin podemos entender que los; sirven para separar las sentencias. Alguna vez le
que el compilador C lee el cdigo como si lo absorbiera con una caita, lnea por lnea, una a
continuacin de otra (evadiendo los comentarios por supuesto).
-----------------------------------------------------------------------------------------------------------------------------M.S.c Ing. Jacob Astocondor Villar

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES


-----------------------------------------------------------------------------------------------------------------------------

Por ejemplo, la funcin 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 funciones y algunas sentencias mediante llaves
({}).
Como ves en el ejemplo de arriba, las funciones main y pausa tienen sus bloques, as como los
bucles while y for. Creo que exager con los comentarios, pero sirven para mostrarnos dnde
empieza y termina cada bloque. Podrs ver cmo las tabulaciones ayudan a distinguir unos
bloques de otros. Afortunadamente, los editores de los buenos compiladores C pueden resaltar
cules son las llaves de inicio y de cierre de cada bloque. Te ser fcil acostumbrarte a usarlas.
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 s
mismas no son cdigo ejecutable. Suelen ser indicaciones sobre cmo se compilar el cdigo.
Entre las pocas directivas del C estndar que tambin son soportadas por los compiladores C
para AVR estn;
#include (para incluir archivos, parecido al Assembler),
#define (mejor que el #define del ensamblador)
y las #if, #elif, #endif y similares. Fuera de ellas, cada compilador maneja sus propias directivas
y sern tratadas por separado.
5.- LAS FUNCIONES
-----------------------------------------------------------------------------------------------------------------------------M.S.c Ing. Jacob Astocondor Villar

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES


-----------------------------------------------------------------------------------------------------------------------------

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
funciones son muchsimo ms potentes y, por cierto, algo ms complejas de aprender. Por eso ni
siquiera el gran espacio que se les dedica ms adelante puede ser suficiente para entenderlas
plenamente. Pero, no te preocupes, aprenderemos de a poco.
En un programa en C puede haber las funciones que sean posibles, pero nunca debe faltar la
funcin principal, llamada main.
Donde quiera que se encuentre, la funcin main siempre ser la primera en ser ejecutada. De
hecho, all empieza y no debera salir de ella.

6.- Variables y Tipos de Datos


En ensamblador todas las variables de programa suelen ser registros de la RAM crudos, es
decir, datos de 8 bits sin formato. En los lenguajes de alto nivel estos registros son tratados de
acuerdo con formatos que les permiten representar nmeros de 8, 16 32 bits (a veces ms
grandes), con signo o sin l, nmeros enteros o decimales. Esos son los tipos de datos bsicos.
Las variables de los compiladores pueden incluso almacenar matrices de datos del mismo tipo
(llamadas arrays) o de tipos diferentes (llamadas estructuras). Estos son los tipos de datos
complejos.
Los siguientes son los principales tipos de datos bsicos del lenguaje C. Observa que la tabla los
separa en dos grupos, los tipos enteros y los tipos de punto flotante.
Tabla de variables y tipos de datos del lenguaje C
Tipo de dato

Tamao en bits Rango de valores que puede adoptar

char

0 a 255 -128 a 127

signed char

-128 a 127

unsigned char

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

-263 a 263 - 1

unsigned long long (int) 64

0 a 264 - 1

float

32

1.18E-38 a 3.39E+38

double

32

1.18E-38 a 3.39E+38

double

64

2.23E-308 a 1.79E+308

Afortunadamente, a diferencia de los compiladores para PIC, los compiladores para AVR suelen
respetar bastante los tipos establecidos por el ANSI C. Algunos compiladores tambin manejan
tipos de un bit como bool (o boolean) o bit, pero con pequeas divergencias que pueden afectar
la portabilidad de los cdigos adems de confundir a los programadores. Esos tipos son
raramente usados.

-----------------------------------------------------------------------------------------------------------------------------M.S.c Ing. Jacob Astocondor Villar

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES


-----------------------------------------------------------------------------------------------------------------------------

Por defecto el tipo double es de 32 bits en los microcontroladores. En ese caso es equivalente al
tipo float. Los compiladores ms potentes como AVR IAR C y AVR GCC sin embargo ofrecen
la posibilidad de configurarlo para que sea de 64 bits y poder trabajar con datos ms grandes y
de mayor precisin.
Los especificadores signed (con signo) mostrados entre parntesis son opcionales. Es decir, da
lo mismo poner int que signed int, por ejemplo. Es una redundancia que se suele usar para
reforzar su condicin o para que se vea ms ilustrativo.
El tipo char est pensado para almacenar caracteres ASCII como las letras. Puesto que estos
datos son a fin de cuentas nmeros tambin, es comn usar este tipo para almacenar nmeros de
8 bits. Es decir es equivalente a signed char o unsigned char, dependiendo de la configuracin
establecida por el entorno compilador. Y como es preferible dejar de lado estas cuestiones, si
vamos a trabajar con nmeros lo mejor es poner el especificador signed o unsigned en el cdigo.
Quiz te preguntes cul es la diferencia 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 fue y siempre debera ser de 16 bits, en tanto que int fue
concebido para adaptarse al bus de datos del procesador. Esto todava se cumple en la
programacin 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 debera 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
tradicin y en regla de facto para los microcontroladores. Actualmente solo en CCS C el tipo int
es de 8 bits. Es irnico para ser el compilador que menos respeta los tipos de datos del ANSI C.
A pesar de todo, se nota que todava pueden aparecer ciertas imprecisiones en los tipos de datos
que pueden perturbar la portabilidad de los programas entre los diferentes compiladores. Es por
esto que el lenguaje C/C++ provee la librera stdint.h para definir tipos enteros que sern de un
tamao especfico independientemente de los procesadores y de la plataforma software en que
se trabaje.
Tabla de variables y tipos de datos del lenguaje C
Tipo de dato Tamao en bits Rango de valores que puede adoptar
int8_t

-128 a 127

uint8_t

0 a 255

int16_t

16

-32,768 a 32,767

uint16_t

16

0 a 65,536

int32_t

32

-2,147,483,648 a 2,147,483,647

uint32_t

32

0 a 4,294,967,295

int64_t

64

-263 a 263 - 1

uint64_t

64

0 a 264 - 1

Es fcil descubrir la estructura de estos tipos para familiarizarse con su uso. Para ello debemos
en primer lugar incluir en nuestro programa el archivo stdint.h con la siguiente directiva.
#include <stdint.h>
Esta inclusin ya est hecha en el archivo avr_compiler.h que se usa en todos los programas de
curso, as que no es necesario volverlo a hacer. Aunque el objetivo de este archivo es permitir la
compatibilidad de cdigos entre los compiladores AVR IAR C y AVR GCC, debemos saber que
en AVR IAR C el archivo avr_compiler.h solo est disponible al usar la librera DLIB. Como
-----------------------------------------------------------------------------------------------------------------------------M.S.c Ing. Jacob Astocondor Villar

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES


-----------------------------------------------------------------------------------------------------------------------------

las prcticas de curso trabajan sobre la librera CLIB, he evitado recurrir a los tipos extendidos
de stdint.h.
Finalmente, existen adems de los vistos arriba otros tipos y especificadores de datos que no son
parte del lenguaje C pero que fueron introducidos por los compiladores pensando en las
caractersticas especiales de los microcontroladores. Muchos de ellos son redundantes o simples
alias y algunos que s son de utilidad como el tipo PGM_P los veremos en su momento.

7.-Declaracin de variables
Esta parte es comparable, aunque lejanamente a cuando se identifican las variables del
ensamblador con la directiva .def. No se puede usar una variable si antes no se ha declarado. La
forma general ms simple de hacerlo es la siguiente:
data_type myvar;
Donde data_type es un tipo de dato bsico o complejo, del compilador o definido por el usuario
y myvar es un identificador cualquiera, siempre que no sea palabra reservada.

Ejemplos.

unsigned char d;
char b;
signed char c;
int i;
signed int j;
unsigned int k;

//
//
//
//
//
//
//

Variable para enteros de 8 bits sin signo


Variable de 8 bits (para almacenar
caracteres ascii)
Variable para enteros de 8 bits con signo
i es una variable int, con signo
j tambin es una variable int con signo
k es una variable int sin signo

Tambin es posible declarar varias variables del mismo tipo, separndolas con comas. As 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 declaracin de una variable se le puede aadir un especificador de tipo como const, static,
volatile, extern, register, etc. Dichos especificadores tienen diversas funciones y, salvo const, se
suelen usar en programas ms elaborados. Como no queremos enredarnos tan pronto, lo
dejaremos para otro momento.
Una variable const debe ser inicializada en su declaracin. Despus de eso el compilador solo
permitir su lectura mas no su escritura. Ejemplos:
const int a = 100;
int b;

// Declarar constante a
// Declarar variable b

//...
b = a;
b = 150;

// Vlido
// Vlido

a = 60;
a = b;

// Error! a es constante
// Error! a es constante

-----------------------------------------------------------------------------------------------------------------------------M.S.c Ing. Jacob Astocondor Villar

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES


-----------------------------------------------------------------------------------------------------------------------------

Por ms que las variables constantes sean de solo lectura, ocuparn posiciones en la RAM del
microcontrolador. En CodeVisionAVR es posible configurar para que s residan en FLASH
pero por compatibilidad se usa muy poco.
Por eso muchas veces es preferible definir las constantes del programa con las clsicas
directivas #define (como se hace en el ensamblador).
#define a 100

// Definir constante a

9.-SENTENCIAS SELECTIVAS
Llamadas tambin sentencias de bifurcacin, sirven para redirigir el flujo de un programa segn
la evaluacin de alguna condicin lgica.
Las sentencias if e ifelse son casi estndar en todos los lenguajes de programacin. Adems de
ellas estn las sentencias ifelse escalonadas y switchcase.

9.1 La sentencia if
La sentencia if (si condicional, en ingls) hace que un programa ejecute una sentencia o un
grupo de ellas si una expresin es cierta. Esta lgica se describe en el siguiente esquema.

Diagrama de flujo de la sentencia if.


La forma codificada sera as:
sentenciaA;
if ( expression )
{

sentenciaB;
sentenciaC;

}
sentenciaX;

// Si expression es verdadera,
// ejecutar el siguiente bloque
// apertura de bloque
// algunas otras sentencias
// cierre de bloque

Despus de ejecutar sentenciaA el programa evala expresin. Si resulta ser verdadera, se


ejecutan todas las sentencias de su bloque y luego se ejecutar la sentenciaX.
-----------------------------------------------------------------------------------------------------------------------------M.S.c Ing. Jacob Astocondor Villar

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES


-----------------------------------------------------------------------------------------------------------------------------

En cambio, si expression es falsa, el programa se saltear el bloque de if y ejecutar sentenciaX.

9.2 LA SENTENCIA IF ELSE


La sentencia if brinda una rama que se ejecuta cuando una condicin lgica es verdadera.
Cuando el programa requiera dos ramas, una que se ejecute si cierta expression es cierta y otra
si es falsa, entonces se debe utilizar la sentecia if else. Tiene el siguiente esquema.

Diagrama de flujo de la sentencia if else.


Expresando lo descrito en cdigo C, tenemos: (Se lee como indican los comentarios.)
SentenciaA;
if ( expression )
{
sentenciaB;
sentenciaC;
// ...
}
else
{
sentenciaM;
sentenciaN;
// ...
}
sentenciaX;
// ...

//
//

Si expression es verdadera, ejecutar


este bloque

// En caso contrario, ejecutar este bloque

Como ves, es bastante fcil, dependiendo del resultado se ejecutar uno de los dos bloques de la
sentencia if else, pero nunca los dos a la vez.

9.3 LA SENTENCIA IF ELSE IF ESCALONADA


Es la versin ampliada de la sentencia if else.
En el siguiente boceto se comprueban tres condiciones lgicas, aunque podra haber ms. Del
mismo modo, se han puesto dos sentencias por bloque solo para simplificar el esquema.
if ( expression_1 )
{
sentencia1;
sentencia2;
}

// Si expression_1 es verdadera ejecutar


// este bloque

-----------------------------------------------------------------------------------------------------------------------------M.S.c Ing. Jacob Astocondor Villar

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES


----------------------------------------------------------------------------------------------------------------------------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 evalan de arriba abajo. Cuando alguna de ellas sea verdadera, se ejecutar
su bloque correspondiente y los dems bloques sern salteados. El bloque final (de else) se
ejecuta si ninguna de las expresiones es verdadera. Adems, si dicho bloque est vaco, puede
ser omitido junto con su else.

9.4 LA SENTENCIA SWITCH


La sentencia switch brinda una forma ms elegante de bifurcacin mltiple. Podemos
considerarla como una forma ms estructurada de la sentencia if else if escalonada, aunque
tiene algunas restricciones en las condiciones lgicas a evaluar, las cuales son comparaciones de
valores enteros.
Para elaborar el cdigo en C se usan las palabras reservadas switch, case, break y default.
El siguiente esquema presenta tres cases pero podra haber ms, as como cada bloque tambin
podra tener ms sentencias.
switch ( expression )
{
case constante1: // Si expression = constante1, ejecutar este bloque
sentencia1;
sentencia2;
break;
case constante2: // Si expression = constante2, ejecutar este bloque
sentencia3;
sentencia4;
break;
case constante3: // Si expression = constante3, ejecutar este bloque
sentencia5;
sentencia6;
break;
default:
//Si expression no fue igual a ninguna de las
// constantes anteriores, ejecutar este bloque
sentencia7;
sentencia8;
break;
}
sentenciaX;
// todo...

donde constante1, constante2 y constante3 deben ser constantes enteras, por ejemplo, 2, 0x45,
a, etc. (a tiene cdigo ASCII 165, que es, a fin de cuentas, un entero.)
-----------------------------------------------------------------------------------------------------------------------------M.S.c Ing. Jacob Astocondor Villar

10

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES


-----------------------------------------------------------------------------------------------------------------------------

Expresin puede ser una variable compatible con entero. No es una expresin que conduce a
una condicin lgica como en los casos anteriores.
El programa solo ejecutar uno de los bloques dependiendo de qu constante coincida con
expression. Usualmente los bloques van limitados por llaves, pero en este caso son opcionales,
dado que se pueden distinguir fcilmente. Los bloques incluyen la sentencia break. Qu es eso?
La sentencia break hace que el programa salga del bloque de switch y ejecute la sentencia que
sigue (en el boceto, sentenciaX). Atento!: de no poner break, tambin se ejecutar el bloque del
siguiente case, sin importar si su constante coincida con expression o no.
No sera necesario poner el default si su bloque estuviera vaco.
10. SENTENCIAS ITERATIVAS
Las sentencias de control iterativas sirven para que el programa ejecute una sentencia o un
grupo de ellas un nmero determinado o indeterminado de veces. As es, esta seccin no habla
de otra cosa que de los bucles en C.
El lenguaje C soporta tres tipos de bucles, las cuales se construyen con las sentencias while, do
while y for. El segundo es una variante del primero y el tercero es una versin ms compacta
e intuitiva del bucle while.

10.1 La sentencia while

El cuerpo o bloque de este bucle se ejecutar una y otra vez mientras (while, en ingls) una
expresin sea verdadera.

Diagrama de flujo de las sentencia while.

El bucle while en C tiene la siguiente sintaxis y se lee as: mientras (while) expression sea
verdadera, ejecutar el siguiente bloque.
sentenciaA;
while ( expression )
{

sentenciaB;
sentenciaC;
// ...

};
sentenciaX;

// Mientras expression sea verdadera, ejecutar el


// siguiente bloque

// Este ; es opcional

-----------------------------------------------------------------------------------------------------------------------------M.S.c Ing. Jacob Astocondor Villar

11

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES


----------------------------------------------------------------------------------------------------------------------------// ...

Nota que en este caso primero se evala expression. Por lo tanto, si desde el principio
expression es falsa, el bloque de while no se ejecutar nunca. Por otro lado, si expression no
deja de ser verdadera, el programa se quedar dando vueltas para siempre.

10.2 LA SENTENCIA DO WHILE


Como dije antes, es una variacin de la sentencia while simple. La principal diferencia es que la
condicin lgica (expression) de este bucle se presenta al final. Como se ve en la siguiente
figura, esto implica que el cuerpo o bloque de este bucle se ejecutar al menos una vez.

Diagrama de flujo de las sentencia do while.


La sintaxis para la sentencia do while es la siguiente y se lee: Ejecutar (do) el siguiente
bloque, mientras (while) expression sea verdadera.
sentenciaA;
do
{
sentenciaB;
sentenciaC;
// ...
} while ( expression ); // Este ; es mandatorio
sentenciaX;
// ...

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 forma de conteo finito es preferible emplear la sentencia for. (Inversamente, al
ver un for en un programa, debemos suponer que estamos frente a algn bucle de ese tipo.)
sta es la sintaxis general de la sentencia for en C:
for ( expression_1 ; expression_2 ; expression_3 )
{
sentencia1;
sentencia2;
// ...
};
// Este ; es opcional

Ahora veamos por partes cmo funciona:


-----------------------------------------------------------------------------------------------------------------------------M.S.c Ing. Jacob Astocondor Villar

12

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES


-----------------------------------------------------------------------------------------------------------------------------

expression_1 suele ser una sentencia de inicializacin.


expression_2 se evala como condicin lgica para que se ejecute el bloque.
expression_3 es una sentencia que debera poner coto a expression_2.

Por la forma y orden en que se ejecutan estas expresiones, el bucle for es equivalente a la
siguiente construccin, utilizando la sentencia while. Primero se ejecuta expression_1 y luego se
ejecuta el bloque indicado tantas veces mientras expression_2 sea verdadera.
expression_1;
while ( expression_2 )
{
sentencia1;
sentencia2;
// ...
}

expression_3;

No obstante, de esa forma se ve ms rara an; as que, mejor, veamos estos ejemplos, que son
sus presentaciones ms clsicas. (i es una variable y a y b son constantes o variables):
for ( i = 0 ; i < 10 ; i++ )
{
sentencias;
}

Se lee: para (for) i igual a 0 hasta que sea menor que 10 ejecutar sentencias. La sentencia i++
indica que i se incrementa tras cada ciclo. As, el bloque de for se ejecutar 10 veces, desde que
i valga 0 hasta que valga 9.
En este otro ejemplo las sentencias se ejecutan desde que i valga 10 hasta que valga 20. Es
decir, el bucle dar 11 vueltas en total.
for ( i = 10 ; i <= 20 ; i++ )
{
sentencias;
}

El siguiente bucle for empieza con i inicializado a 100 y su bloque se ejecutar mientras i sea
mayor o igual a 0. Por supuesto, en este caso i se decrementa tras cada ciclo.
for ( i = 100 ; i >= 0 ; i-- )
{
sentencias;
}

Se pueden hacer muchas ms construcciones, todas coincidentes con la primera plantilla, pero
tambin son menos frecuentes.

11.- SENTENCIAS CON BLOQUES SIMPLES


Cuando las sentencias selectivas (como if) o de bucles (como while o for) tienen cuerpos o
bloques que constan de solo una sentencia, se pueden omitir las llaves. Aun as, es aconsejable
seguir manteniendo las tabulaciones para evitarnos confusiones.
Por ejemplo, las siguientes sentencias:
-----------------------------------------------------------------------------------------------------------------------------M.S.c Ing. Jacob Astocondor Villar

13

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES


----------------------------------------------------------------------------------------------------------------------------if(a > b)
{
a = 0;
}
if(a == b)
{
a++;
}
else
{
b--;
}
while( a >= b)
{
a = a + b;
}
for(i=0; i<=10; i++)
{
a = a*2;
}

bien se pueden escribir de la siguiente forma:


if(a > b)
a = 0;
if(a == b)
a++;
else
b--;
while( a >= b)
a = a + b;
for(i=0; i<=10; i++)
a = a*2;

12.- LOS OPERADORES

Sirven para realizar operaciones aritmticas, lgicas, comparativas, etc. Segn esa funcin se
clasifican en los siguientes grupos.

12.1 Operadores aritmticos


Adems de los tpicos operadores de suma, resta, multiplicacin y divisin, estn los operadores
de mdulo, incremento y decremento.
Tabla de Operadores aritmticos
Operador

Accin

Suma

Resta

Multiplicacin

Divisin

Mdulo. Retorna el residuo de una divisin entera. Solo se debe usar con nmeros
enteros.

-----------------------------------------------------------------------------------------------------------------------------M.S.c Ing. Jacob Astocondor Villar

14

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES


-----------------------------------------------------------------------------------------------------------------------------

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 ab a c
a++;
// Incrementar a en 1
b--;
// Decrementar b en 1
++c;
// Incrementar c en 1
--b;
// Decrementar b en 1

Te recordaron a tus clases de lgebra del colegio? A diferencia de esas matemticas, estas
expresiones no son ecuaciones; significan las operaciones que indican sus comentarios.

Por lo visto, los operadores ++ y -- funcionan igual si estn antes o despus de una variable en
una expresin simple. Sin embargo, hay una forma (tal vez innecesaria y confusa para un
novato, pero muy atractiva para los que ya estamos acostumbrados a su uso) que permite
escribir cdigo ms compacto, es decir, escribir dos sentencias en una.

Si ++ o -- estn antes del operando, primero se suma o resta 1 al operando y luego se


evala la expresin.
Si ++ o -- estn despus del operando, primero se evala la expresin y luego se suma o
resta 1 al operando.
int a, b;

// Declarar variables enteras a y b

a = b++;
a = ++b;

// Lo mismo que a = b; y luego b = b + 1;


// Lo mismo que b = b + 1; y luego a = b;

if (a++ < 10)


{
// algn cdigo
}

// Primero comprueba si a < 10 y luego


// incrementa a en 1

if (++a < 10)


{
// algn cdigo
}

// Primero incrementa a en 1 y luego


// comprueba si a < 10

12.2 OPERADORES DE BITS


Se aplican a operaciones lgicas con variables a nivel binario. Aqu tenemos las clsicas
operaciones AND, OR inclusiva, OR exclusiva y la NEGACIN. Adicionalmente, he incluido
en esta categora los operaciones de desplazamiento a la derecha y la izquierda.

-----------------------------------------------------------------------------------------------------------------------------M.S.c Ing. Jacob Astocondor Villar

15

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES


-----------------------------------------------------------------------------------------------------------------------------

Si bien son operaciones que producen resultados anlogos a los de las instrucciones de
ensamblador los operadores lgicos del C pueden operar sobre variables de distintos tamaos,
ya sean de 1, 8, 16 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;
int n;
m
m
m
m
n
n
m
m
m

=
=
=
=
=
=
=
=
=

0x48;
m & 0x0F;
m | 0x24;
m & 0b11110000;
0xFF00;
~n;
m | 0b10000001;
m & 0xF0;
m ^ 0b00110000;

m = 0b00011000;
m = m >> 2;
n = 0xFF1F;
n = n << 12;
m = m << 8;

// variable de 8 bits
// variable de 16 bits
//
//
//
//
//
//
//
//
//

m ser 0x48
Despus de esto m ser 0x08
Despus de esto m ser 0x2F
Despus de esto m ser 0x20
n ser 0xFF00
n ser 0x00FF
Setear bits 0 y 7 de variable m
Limpiar nibble bajo de variable m
Invertir bits 4 y 5 de variable m

// Cargar m con 0b00011000


// Desplazar m 2 posiciones a la derecha
// Ahora m ser 0b00000110
// Desplazar n 12 posiciones a la izquierda
// Ahora n ser 0xF000;
// Despus de esto m ser 0x00

Fjate en la semejanza entre las operaciones de desplazamiento con >> y << y las operaciones
del rotacin del ensamblador. Cuando una variable se desplaza hacia un lado, los bits que salen
por all se pierden y los bits que entran por el otro lado son siempre ceros. Es por esto que en la
ltima sentencia, m = m << 8, el resultado es 0x00. Por cierto, en el lenguaje C no existen
operadores de rotacin. Hay formas alternativas de realizarlas.

Desplazamientos producidos por los operadores << y >>.

12-3 OPERADORES RELACIONALES

-----------------------------------------------------------------------------------------------------------------------------M.S.c Ing. Jacob Astocondor Villar

16

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES


-----------------------------------------------------------------------------------------------------------------------------

Se emplean para construir las condiciones lgicas de las sentencias de control selectivas e
iterativas, como ya hemos podido apreciar en las secciones anteriores. La siguiente tabla
muestra los operadores relacionales disponibles.
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 ms condiciones lgicas simples. Por suerte, estos
operadores solo son tres y sern explicados en las prcticas del curso.
Tabla de Operadores lgicos
Operador

Accin

&&

AND lgica

||

OR lgica

Negacin lgica

Ejemplos:
if( !(a==0) )
{
// sentencias
}

// Si a igual 0 sea falso

if( (a<b) && (a>c) )


{
// sentencias
}

// Si a<b y a>c son verdaderas

while( (a==0) || (b==0) )


{
// sentencias
}

// Mientras a sea 0 b sea 0

12.5 COMPOSICIN DE OPERADORES


Se utiliza en las operaciones de asignacin y nos permite escribir cdigo ms abreviado. La
forma general de escribir una sentencia de asignacin mediante los operadores compuestos es:
obtect op= expression;
que es equivalente a la sentencia
object = object op expression;
-----------------------------------------------------------------------------------------------------------------------------M.S.c Ing. Jacob Astocondor Villar

17

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES


-----------------------------------------------------------------------------------------------------------------------------

op puede ser cualquiera de los operadores aritmticos o de bit estudiados arriba. O sea, op puede
ser +, - , *, /, %, &, | , ^, ~, << >>. Nota: no debe haber ningn espacio entre el operador y el
signo igual.
Ejemplos:
int a;
a += 50;
a += 20;
a *= 2;
a &= 0xF0;
a <<= 1;

//
//
//
//
//
//

Declarar a
Es lo mismo que a
Tambin significa
Es lo mismo que a
Es lo mismo que a
Es lo mismo que a

= a + 50;
sumarle 20 a a
= a * 2;
= a & 0xF0;
= a << 1;

12.6 PRECEDENCIA DE OPERADORES


Una expresin puede contener varios operadores, de esta forma:
b = a * b + c / b;

// a, b y c son variables

A diferencia del lenguaje Basic, donde la expresin se evala de izquierda a derecha, en esta
sentencia no queda claro en qu orden se ejecutarn las operaciones indicadas. Hay ciertas
reglas que establecen dichas prioridades; por ejemplo, las multiplicaciones y divisiones siempre
se ejecutan antes que las sumas y restas. Pero es ms prctico emplear los parntesis, los cuales
ordenan que primero se ejecuten las operaciones de los parntesis ms internos. Eso es como en
el lgebra elemental de la escuela, as que no profundizar.
Por ejemplo, las tres siguientes sentencias son diferentes.
b = (a * b) + (c / b);
b = a * (b + (c / b));
b = ((a * b) + c)/ b);

Tambin se pueden construir expresiones condicionales, as:


if ( (a > b) && ( b < c) )
{
// ...
}

// Si a>b

b<c, ...

13. LAS FUNCIONES


Una funcin es un bloque de sentencias identificado por un nombre y puede recibir y devolver
datos. En bajo nivel, en general, las funciones operan como las subrutinas de Assembler, es
decir, al ser llamadas, se guarda en la Pila el valor actual del PC (Program Counter), despus se
ejecuta todo el cdigo de la funcin y finalmente se recobra el PC para regresar de la funcin.
Dada su relativa complejidad, no es tan simple armar una plantilla general que represente a
todas las funciones. El siguiente esquema es una buena aproximacin.
data_type1 function_name (data_type2 arg1, data_type3 arg2, ... )
{
// Cuerpo de la funcin
// ...
return SomeData;
// Necesario solo si la funcin retorna algn valor
}

-----------------------------------------------------------------------------------------------------------------------------M.S.c Ing. Jacob Astocondor Villar

18

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES


-----------------------------------------------------------------------------------------------------------------------------

Donde:

function_name es el nombre de la funcin. Puede ser un identificador cualquiera.


data_type1 es un tipo de dato que identifica el parmetro de salida. Si no lo hubiera, se
debe poner la palabra reservada void (vaco, en ingls).
arg1 y arg2 (y puede haber ms) son las variables de tipos data_type1, data_type2...,
respectivamente, que recibirn los datos que se le pasen a la funcin. Si no hay ningn
parmetro de entrada, se pueden dejar los parntesis vacos o escribir un void entre
ellos.

13.1 FUNCIONES SIN PARMETROS


Para una funcin que no recibe ni devuelve ningn 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 parntesis vacos, as:


function_name();

La funcin principal main es otro ejemplo de funcin sin parmetros. Dondequiera que se
ubique, siempre debera ser la primera en ejecutarse; de hecho, no debera terminar.
void main (void)
{
// Cuerpo de la funcin
}

13.2 FUNCIONES CON PARMETROS (POR VALOR)


Por el momento, solo estudiaremos las funciones que pueden tener varios parmetros de entrada
pero solo uno de salida.
Si la funcin no tiene parmetros de entrada o de salida, debe escribirse un void en su lugar. El
valor devuelto por una funcin se indica con la palabra reservada return.
Segn el comportamiento de los parmetros de entrada de la funcin, estos se dividen en
parmetros por valor y parmetros por referencia. Lo expuesto en este apartado corresponde al
primer grupo porque es el caso ms ampliamente usado. Con esto en mente podemos seguir.
Para llamar a una funcin con parmetros es importante respetar el orden y el tipo de los
parmetros que ella recibe. El primer valor pasado corresponde al primer parmetro de entrada;
el segundo valor, al segundo parmetro; y as sucesivamente si hubiera ms.
Cuando una variable es entregada a una funcin, en realidad se le entrega una copia suya. De
este modo, el valor de la variable original no ser alterado. Mejor, plasmemos todo esto en el
siguiente ejemplo.
int minor ( int arg1, int arg2, int arg3 )
{
int min;
// Declarar variable min
min = arg1;
// Asumir que el menor es arg1

-----------------------------------------------------------------------------------------------------------------------------M.S.c Ing. Jacob Astocondor Villar

19

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES


----------------------------------------------------------------------------------------------------------------------------if ( arg2 < min )
min = arg2;

// Si arg2 es menor que min


// Cambiar a arg2

if ( arg3 < min )


min = arg3;

// Si arg3 es menor que min


// Cambiar a arg3

return min;
}
void main (void)
{
int a, b, c, d;

// Retornar valor de min

// 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 funcin minor recibe tres parmetros de tipo int y devuelve uno,
tambin de tipo int, que ser el menor de los nmeros recibidos.
El mecanismo funciona as: siempre respetando el orden, al llamar a minor el valor de a se
copiar a la variable arg1; el valor de b, a arg2 y el valor de c, a arg3. Despus de ejecutarse el
cdigo de la funcin el valor de retorno (min en este caso) ser copiado a una variable temporal
y de all pasar a d.
Aunque el C no es tan implacable con la comprobacin de tipos de datos como Pascal, siempre
deberamos revisar que los datos pasados sean compatibles con los que la funcin espera, as
como los datos recibidos, con los que la funcin devuelve. Por ejemplo, estara mal llamar a la
funcin minor del siguiente modo:
d = minor(-15, 100, 5.124); // Llamar a minor

Aqu los dos primeros parmetros estn bien, pero el tercero es un nmero decimal (de 32 bits),
no compatible con el tercer parmetro que la funcin espera (entero de 16 bits). En estos casos
el compilador nos mostrar mensajes de error, o cuando menos de advertencia.
13.3 PARMETROS POR REFERENCIA
La funcin que recibe un parmetro por referencia puede cambiar el valor de la variable pasada.
La forma clsica de estos parmetros se puede identificar por el uso del smbolo &, tal como se
ve en el siguiente boceto de funcin.
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 profundizar al respecto porque he visto que muchos compiladores C no soportan esta
forma. Otra forma de pasar un parmetro por referencia es mediante los punteros, pero eso lo
dejamos para el final porque no es nada nada fcil para un novato.
-----------------------------------------------------------------------------------------------------------------------------M.S.c Ing. Jacob Astocondor Villar

20

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES


-----------------------------------------------------------------------------------------------------------------------------

13.4 PROTOTIPOS DE FUNCIONES


El prototipo de una funcin le informa al compilador las caractersticas que tiene, como su tipo
de retorno, el nmero de parmetros que espera recibir, el tipo y orden de dichos parmetros.
Por eso se deben declarar al inicio del programa.
El prototipo de una funcin es muy parecido a su encabezado, se pueden diferenciar tan solo por
terminar en un punto y coma (;). Los nombres de las variables de entrada son opcionales.
Por ejemplo, en el siguiente boceto de programa los prototipos de las funciones main, func1 y
func2 declaradas al inicio del archivo permitirn que dichas funciones sean accedidas desde
cualquier parte del programa. Adems, sin importar dnde se ubique la funcin main, ella
siempre ser la primera en ejecutarse. Por eso su prototipo de funcin es opcional.
#include <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 funciones no tienen prototipos, el acceso a ellas ser restringido. El compilador solo ver
las funciones que estn implementadas encima de la funcin llamadora o, de lo contrario,
mostrar errores de funcin no definida. El siguiente boceto ilustra este hecho. (Atiende a los
comentarios.)
#include <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

-----------------------------------------------------------------------------------------------------------------------------M.S.c Ing. Jacob Astocondor Villar

21

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES


----------------------------------------------------------------------------------------------------------------------------}

// Desde aqu se puede acceder a func1 y main

Para terminar, dado que los nombres de las variables en los parmetros de entrada son
opcionales, los prototipos de func1 y func2 tambin se pueden escribir asi
void func1(char, long);
char func2(int );

14. VARIABLES LOCALES Y VARIABLES GLOBALES


Los lenguajes de alto nivel como el C fueron diseados para desarrollar los programas ms
grandes y complejos que se puedan imaginar, programas donde puede haber cientos de
variables, entre otras cosas. Imaginas lo que significara buscar nombres para cada variable si
todos tuvieran que ser diferentes? Pues bien, para simplificar las cosas, el C permite tener varias
variables con el mismo nombre.
As es. Esto es posible gracias a que cada variable tiene un mbito, un rea desde donde ser
accesible. Hay diversos tipos de mbito, pero empezaremos por familiarizarnos con los dos ms
usados, que corresponden a las variables globales y variables locales.

Las variables declaradas fuera de todas las funciones y antes de sus implementaciones
tienen carcter global y podrn ser accedidas desde todas las funciones.
Las variables declaradas dentro de una funcin, incluyendo las variables del
encabezado, tienen mbito local. Ellas solo podrn ser accedidas desde el cuerpo de
dicha funcin.

De este modo, puede haber dos o ms variables con el mismo nombre, siempre y cuando estn
en diferentes funciones. Cada variable pertenece a su funcin y no tiene nada que ver con las
variables de otra funcin, por ms que tengan el mismo nombre.
En la mayora de los compiladores C para microcontroladores las variables locales deben
declararse al principio de la funcin.
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 */

-----------------------------------------------------------------------------------------------------------------------------M.S.c Ing. Jacob Astocondor Villar

22

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES


----------------------------------------------------------------------------------------------------------------------------count = 0;
speed = 0;

}
char foo(long count)
{
int vari;
}

// Acceso a count local


// Acceso a variable global speed
// Variable local count
// Variable local vari

Algo muy importante: a diferencia de las variables globales, las variables locales tienen
almacenamiento temporal, es decir, se crean al ejecutarse la funcin y se destruyen al salir de
ella. Qu significa eso? Lo explico en el siguiente apartado.
Si dentro de una funcin hay una variable local con el mismo nombre que una variable global, la
precedencia en dicha funcin la tiene la variable local. Si te confunde, no uses variables
globales y locales con el mismo nombre.

14.1 Variables static


Antes de nada debemos aclarar que una variable static local tiene diferente significado que una
variable static global. Ahora vamos a enfocarnos al primer caso por ser el ms comn.
Cuando se llama a una funcin sus variables locales se crearn en ese momento y cuando se
salga de la funcin se destruirn. Se entiende por destruir al hecho de que la locacin de
memoria que tena una variable ser luego utilizada por el compilador para otra variable local
(as se economiza la memoria). Como consecuencia, el valor de las variables locales no ser el
mismo entre llamadas de funcin.
Por ejemplo, revisa la siguiente funcin, donde a es una variable local ordinaria.
void increm()
{
int a;
a++;
}

// Declarar variable a
// Incrementar a

Cualquiera que haya sido su valor inicial, crees que despus de llamar a esta funcin 10 veces,
el valor de a se habr incrementado en 10?... Pues, no necesariamente. Cada vez que se llame a
increm se crea a, luego se incrementa y, al terminar de ejecutarse la funcin, se destruye.
Para que una variable tenga una locacin de memoria independiente y su valor no cambie entre
llamadas de funcin tenemos dos caminos: o la declaramos como global, o la declaramos como
local esttica. Los buenos programadores siempre eligen el segundo.
Una variable se hace esttica anteponiendo a su declaracin el especificador static. Por defecto
las variables estticas se auto inicializan a 0, pero se le puede dar otro valor en la misma
declaracin (dicha inicializacin solo se ejecuta la primera vez que se llama a la funcin), as:
static int var1;
// 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

-----------------------------------------------------------------------------------------------------------------------------M.S.c Ing. Jacob Astocondor Villar

23

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES


----------------------------------------------------------------------------------------------------------------------------}
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 diferencia de los ensambladores, los compiladores tienen cierta inteligencia. Es decir,
piensan un poco antes de traducir el cdigo fuente en cdigo ejecutable. Por ejemplo, veamos el
siguiente pedazo de cdigo para saber lo que suele pasar con una variable ordinaria:
int var;
//...
var = var;

// Declarar variable var


// Asignar var a var

El compilador creer (probablemente como nosotros) que la sentencia var = var no tiene sentido
(y quiz tenga razn) y no la tendr en cuenta, la ignorar. sta es solo una muestra de lo que
significa optimizacin del cdigo. Luego descubrirs ms formas de ese trabajo.
El ejemplo anterior fue algo burdo, pero habr cdigos con redundancias aparentes y ms
difciles de localizar, cuya optimizacin puede ser contraproducente. El caso ms notable que
destacan los manuales de los compiladores C para microcontroladores es el de las variables
globales que son accedidas por la funcin de interrupcin y por cualquier otra funcin.
Para que un compilador no intente pasarse de listo con una variable debemos declararla como
volatile, anteponindole dicho calificador a su declaracin habitual.
Por ejemplo, en el siguiente boceto de programa la variable count debe ser accedida desde la
funcin interrupt como desde la funcin main; por eso se le declara como volatile. Nota: el
esquema de las funciones de interrupcin suele variar de un compilador a otro. ste es solo un
ejemplo.
volatile int count;

// 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 ste sea el tema que a todos nos ha dado ms de un dolor de cabeza y que ms
hemos reledo para captarlo a cabalidad. Hablo ms bien de los punteros. Si ellos el C no sera
-----------------------------------------------------------------------------------------------------------------------------M.S.c Ing. Jacob Astocondor Villar

24

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES


-----------------------------------------------------------------------------------------------------------------------------

nada, perdera la potencia por la que las mejores empresas lo eligen para crear sus softwares de
computadoras.
Pero bueno, regresando a lo nuestro, estos temas se pueden complicar muchsimo ms de lo que
veremos aqu. Solo veremos los arrays unidimensionales y los punteros (que en principio
pueden apuntar a todo tipo de cosas) los abocaremos a los datos bsicos, incluyendo los mismos
arrays. Aun as, te sugiero que tengas un par de aspirinas al lado.
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
hacamos con las tablas (de bsqueda) del ensamblador y muchsimo ms.
Un array completo tiene un nombre y para acceder a cada uno de sus elementos se utilizan
ndices entre corchetes ([ ]). Los ndices pueden estar indicados por variables o constantes. En el
siguiente esquema se ve que el primer elemento de un array tiene ndice 0 y el ltimo, N-1,
siendo N la cantidad de elementos del array.

Estructura de un array unidimensional de N elementos.

15.2 Declaracin de arrays


Para declarar un array unidimensional se utiliza la siguiente sintaxis:
data_type identifier[ NumElementos ];

Donde data_type es un tipo de dato cualquiera, identifier es el nombre del array y


NumElementos es la cantidad de elementos que tendr (debe ser un valor constante).
De este modo, el ndice del primer elemento es 0 y el del ltimo es NumElements - 1.
Por ejemplo, las siguientes lneas declaran tres arrays.
char letters10];

long HexTable[16];
int address[100];

// letters es un array de 10 elementos de tipo char


// HexTable es un array de 16 elementos de tipo long
//address es un array de 100 elementos de tipo int

Para el array letters el primer elemento es letters[0] y el ltimo, letters[9]. As, tenemos 10
elementos en total. Si quisiramos asignar a cada uno de los elementos de letters los caracteres
desde la a hasta la j, lo podramos hacer individualmente as:
letters[0]
letters[1]
letters[2]
letters[3]
letters[4]
letters[5]
letters[6]
letters[7]
letters[8]

=
=
=
=
=
=
=
=
=

'a';
'b';
'c';
'd';
'e';
'f';
'g';
'h';
'i';

// Aqu el ndice es 0
// Aqu el ndice es 1
// ...
//

-----------------------------------------------------------------------------------------------------------------------------M.S.c Ing. Jacob Astocondor Villar

25

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES


----------------------------------------------------------------------------------------------------------------------------letters[9] = 'j';

// Aqu el ndice es 9

Pero as no tiene gracia utilizar arrays. En este caso lo mejor es utilizar un bucle, as: (Nota: los
caracteres son, al fin y al cabo, nmeros en cdigos 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 declaracin. Para ello se le asigna
una lista ordenada de valores encerrados por llaves y separados por comas. Por supuesto, los
valores deben ser compatibles con el tipo de dato del array. Este tipo de inicializacin solo est
permitido en la declaracin del array.

Ejemplos:
unsigned char mask[3] = { 0xF0, 0x0F, 0x3C };
// Ok
int a[5] = { 20, 56, 87, -58, 5000 };
// Ok
char vocals[5] = { 'a', 'e', 'i', 'o', 'u' };
// Ok
int c[4] = { 5, 6, 0, -5, 0, 4 };
// Error, demasiados
inicializadores

Tambin es posible inicializar un array sin especificar en su declaracin el tamao que tendr,
dejando los corchetes vacos. El tamao ser pre calculado y puesto por el compilador. sta es
una forma bastante usada en los arrays de texto, donde puede resultar muy incmodo estar
contando las letras de una cadena. Por ejemplo:
int a[] = { 70, 1, 51 };
// Un array de 3 elementos
char vocals[] = { 'a', 'e', 'i', 'o', 'u' };// Un array de 5 elementos
char msg[] = "Este es un array de caracteres"; // Un array of 31 elementos

Por qu el ltimo array tiene 31 elementos si solo se ven 30 letras? Lo sabremos luego.

15.4 Cadenas de texto terminadas en nulo


Son arrays de tipo de dato char. Hay dos caractersticas que distinguen a estas cadenas de los
dems arrays. Primero: su inicializacin se hace empleando comillas dobles y segundo, el
ltimo trmino del array es un carcter NULL (simplemente un 0x00). De ah su nombre.
Ejemplos:
char Greet[10] = "Hello";
char msg[] = "Hello";

// Un array de 10 elementos
// Un array de 6 elementos

El array Greet tiene espacio para 10 elementos, de los cuales solo los 5 primeros han sido
llenados con las letras de Hello, el resto se rellena con ceros.
El array msg tiene 6 elementos porque adems de las 5 letras de Hello se le ha aadido un
Null (0x00) al final (claro que no se nota). Es decir, la inicializacin de msg es equivalente a:
char msg[] = { 'H', 'e', 'l', 'l', 'o', 0x00};

// Un array de 6 elementos

Visto grficamente, msg tendra la siguiente representacin:


-----------------------------------------------------------------------------------------------------------------------------M.S.c Ing. Jacob Astocondor Villar

26

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES


-----------------------------------------------------------------------------------------------------------------------------

Estructura de una cadena de texto.


16 LOS PUNTEROS
Los punteros suelen ser el tema que ms cuesta entender en programacin. Pero si ya llegaste
aqu, es el momento menos indicado para detenerte.
Los punteros son un tipo de variables muy especial. Son variables que almacenan las
direcciones fsicas de otras variables. Si tenemos la direccin de una variable, tenemos acceso a
esa variable de manera indirecta y podemos hacer con ellas todo lo que queramos.
16.1 Declaracin de punteros
Los punteros pueden apuntar a todo tipo de variables, pero no a todas al mismo tiempo. La
declaracin de un puntero es un tanto peculiar. En realidad, se parece a la declaracin de una
variable ordinaria solo que se pone un asterisco de por medio. En este punto debes recordar las
declaraciones de todo tipo de variables que hemos visto, incluyendo las influenciadas por los
calificadores const, static, etc. Todas excepto los arrays; por qu?
La forma general de declarar un puntero es la siguiente:
data_type * PointerName;

Los siguientes ejemplos muestran lo fcil que es familiarizarse con la declaracin de los
punteros:
int * ip;
char * ucp;
unsigned char * ucp;
const long * clp;
float * p1, *p2;

// ip es un puntero a variable de tipo int


// cp es un puntero a variable de tipo char
// Puntero a variable de tipo unsigned char
// Puntero a constante de tipo long
// 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 direccin de dicha
variable. Para ello se utiliza el operador &, el cual extrae la direccin de la variable a la que
acompaa. Un puntero siempre debera apuntar a una variable cuyo tipo coincida con el tipo del
puntero.
En los siguientes ejemplos vemos cmo apuntar a variables de tipo bsico, como int, char o
float. Ms adelante veremos cmo apuntar a arrays.
void main (void)
{
int height, width;
char a, b, c;
float max;
int * ip;

// ip es un puntero a variable tipo int

-----------------------------------------------------------------------------------------------------------------------------M.S.c Ing. Jacob Astocondor Villar

27

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES


----------------------------------------------------------------------------------------------------------------------------char * cp;
float * fp;

// cp es un puntero a variable tipo char


// Puntero a variable tipo float

ip = &height;
ip = &width;

// Con esto ip tendr la direccin de height


// Ahora ip apunta a width

cp = &a;
cp = &c;
cp = &a;
fp = &max;
fp = &height;
}

// cp apunta a a
// Ahora cp apunta a c
// Ahora cp apunta a a otra vez
// fp apunta a max
// 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 forma:
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 = 10;

// p apunta a height
// Esto es como height = 10

p = &width;
*p = 50;

// p apunta a width
// Esto es como width = 50

height = *p;

// Esto es como height = width

q = &height;
n = (*p + *q)/2;
//...

// q apunta a height
// Esto es como n = (height + width)/2

La expresin *p se debera leer: la variable apuntada por p. Eso tambin ayuda mucho a
comprender a los punteros.
Y para esto se inventaron los punteros? Yo me preguntaba lo mismo en mis inicios. El tema de
los punteros se puede complicar casi hasta el infinito, por eso quiero ir con cuidado y poco a
poco para que nadie se pierda.
16.4 PUNTEROS Y ARRAYS
Cmo se declara un puntero a un array? Un puntero a un array es simplemente un puntero al
tipo de dato del array. Cuando se asigna un puntero a un array, en realidad el puntero toma la
direccin de su primer elemento, a menos que se especifique otro elemento.
Luego, bastara con modificar el valor del puntero para que apunte a los otros elementos del
array. Todo lo indicado se refleja en el siguiente cdigo:
void main (void)
{
int * p;
// Declara p como puntero a int
int n;
// Alguna variable
int mat[3] = { 78, 98, 26 };
// Array de variables int

-----------------------------------------------------------------------------------------------------------------------------M.S.c Ing. Jacob Astocondor Villar

28

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES


-----------------------------------------------------------------------------------------------------------------------------

p = &mat;
n = *p;
p++;
n = *p;
p++;
n = *p;
*p = 10;
p--;
*p = 100;
p = mat;
p = NULL;
// ...

//
//
//
//

// p apunta a mat (a su primer elemento)


// Esto da n = 78
Incrementar p para apuntar a siguiente elemento
// Esto da n = 98
Incrementar p para apuntar a siguiente element
// Esto da n = 26
// Con esto mat[3] valdr 10
Decrementar p para apuntar a elemento anterior
// Con esto mat[2] valdr 100
p apunta a mat. Es lo mismo que p = &mat
// Desasignar p. Lo mismo que p = 0x0000

En el fondo los arrays y los punteros trabajan de la misma forma, por lo menos cuando
referencian a variables almacenadas en la RAM del microcontrolador. La nica diferencia es
que los arrays no pueden direccionar a datos diferentes de su contenido; por eso tambin se les
llama punteros estticos. En la prctica esto significa que un array es siempre compatible con un
puntero, pero un puntero no siempre es compatible con un array.
Por ejemplo, a un array no se le puede asignar otro array ni se le pueden sumar o restar valores
para que apunten a otros elementos. Por lo dems, las operaciones de asignacin son similares
para punteros y arrays, tal como se puede apreciar en el siguiente cdigo. (Por si las moscas,
str1 es el array y str2, el puntero.)
void main(void)
{
char str1[] = { 'A', 'r', 'r', 'a', 'y' };
char * str2 = { 'P', 'o', 'i', 'n', 't', 'e', 'r' };
char a;

a = str1[0];
a = str1[3];

// Esto da a = 'A'
// Esto da a = 'a'

a = str2[0];
a = str2[3];

// Esto da a = 'P'
// Esto da a = 'n'

str1 += 2;
str2 += 2;

// Error! Str1 es esttico


// Correcto. Ahora str2 apunta a 'i'

str1++;
str2++;

// Error otra vez! Str1 es esttico


// 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 referencia? Pues aqu vamos de nuevo.
Bien, recordemos: una variable pasada por valor a una funcin, en realidad le entrega una copia
suya; por lo que la variable original no tiene por qu ser afectada por el cdigo de la funcin.
Ahora bien, pasar una variable por referencia significa que se pasa la direccin de dicha
variable. Como consecuencia, la funcin tendr acceso a la variable original y podr modificar
su contenido. Esto podra resultar riesgoso, pero, bien usada, la tcnica es una potente arma.
-----------------------------------------------------------------------------------------------------------------------------M.S.c Ing. Jacob Astocondor Villar

29

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES


-----------------------------------------------------------------------------------------------------------------------------

Ya que los punteros operan con direcciones de variables, son el medio ideal para trabajar con
parmetros por referencia. Hay dos casos de particular inters: uno, cuando deseamos en serio
que la variable pasada a la funcin 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 referencia ya que tambin son punteros al fin.
La sintaxis de los punteros en el encabezado de la funcin no es nada nuevo, teniendo en cuenta
que tambin tienen la forma de declaraciones de variables.
En el siguiente ejemplo la funcin interchange intercambia los valores de las dos variables
recibidas. En seguida explicar por qu vara un poco la forma en que se llama a la funcin.
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
funcin interchange recibir dichos valores en p1 y p2, respectivamente. De ese modo, p1 y p2
estarn apuntando a i y j, y podremos modificar sus valores.
Ten presente que se mantiene la forma de asignacin puntero = &variable (puntero igual a
direccin de variable).
Ahora veamos ejemplos donde la forma de asignacin cambia a puntero = puntero. Esto
incluye a los arrays porque, recordemos, un puntero siempre puede ser tratado como un array,
aunque lo contrario no siempre es posible.
En el siguiente programa array1 y array2 se pasan a la funcin prom, la cual devuelve el valor
promedio de los elementos del array recibido. Como para ese clculo se necesita conocer la
cantidad de elementos que tiene el array, prom recibe dicho valor en el parmetro size.
float prom ( int * p, int size )
{
int i; float tmp = 0;
for ( i=0; i<size;i++ )//Bucle para contar i desde 0 hasta size-1.
tmp += p[i];
// Sumar elemento p[i] a tmp.
return ( tmp/size );
// Retornar valor promediado.
}
void main (void)
{
int array1[4] = { 51, 14, 36, 78 }; // Un array de 4 elementos

-----------------------------------------------------------------------------------------------------------------------------M.S.c Ing. Jacob Astocondor Villar

30

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES


----------------------------------------------------------------------------------------------------------------------------int array2[] = { -85, 4, 66, 47, -7, 85 }; // Un array de 6 elementos
float avrg;
// Una variable tipo float, para decimales
avrg = prom (array1, 8);
// Ahora avrg debera valer (51 + 14 + 36 + 78 )/8 = 44.75
avrg = prom (array2, 6);
// Ahora avrg debera valer (-85 + 4 + 66 + 47 - 7 + 85 )/6 = 18.3333
}

while( 1 );

// Bucle infinito

Finalmente, veamos un programa donde se utilizan las Cadenas de texto terminadas en nulo.
Este programa tiene dos funciones auxiliares: mayus convierte la cadena recibida en
maysculas, y lon calcula la longitud del texto almacenado en el array recibido. Ambas
funciones reciben el array pasado en un puntero p dado que son compatibles.
void mayus( char * p )
{
while( *p )
// Mientras carcter apuntado sea diferente de 0x00
{
if( ( *p >= 'a' ) && ( *p <= 'z' ) ) // Si carcter apuntado es
// 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
L = lon(song1);
// Debera dar L =
L = lon(song2);
// Debera dar L =
L = lon(song3);
// Debera dar L =

texto */
9
15
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

-----------------------------------------------------------------------------------------------------------------------------M.S.c Ing. Jacob Astocondor Villar

31

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES


----------------------------------------------------------------------------------------------------------------------------}

En el programa se crean tres arrays de texto de 20 elementos (song1, song2 y song3), pero el
texto almacenado en ellos termina en un carcter 0x00.
Segn la tabla de caracteres ASCII, las letras maysculas estn ubicadas 32 posiciones por
debajo de las minsculas. Por eso basta con sumarle o restarle ese valor a un carcter ASCII
para pasarlo a mayscula o minscula.
En ambas funciones el puntero p navega por los elementos del array apuntado hasta que
encuentra el final, indicado por un carcter nulo (0x00).
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 vara mucho entre los distintos compiladores. Veamos en qu.
Un array constante es uno cuyos elementos solo podrn ser ledos pero no escritos; tan simple
como eso.
En principio, para que un array sea constante a su clsica declaracin con inicializacin de un
array se le debe anteponer el calificador const. No es posible declarar un array constante vaco y
llenar sus elementos despus pues eso equivaldra a modificar sus elementos. Enseguida
tenemos ejemplos de declaracin 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 sern de solo lectura, y sus elementos podrn ser
ledos, mas no escritos. El compilador mostrar 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 ejecucin del programa no necesariamente
significa los arrays constantes estn ubicados en la memoria FLASH. En algunos compiladores
de PICs, como CCS C y Hitech C, s ocurre as, pero el lenguaje C solo dice que estos datos son
inmodificables, no dice dnde 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 ofrecen mtodos 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 podran 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.

-----------------------------------------------------------------------------------------------------------------------------M.S.c Ing. Jacob Astocondor Villar

32

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES


-----------------------------------------------------------------------------------------------------------------------------

Empecemos por examinar el estilo de AVR GCC. Por ejemplo, si queremos que los tres
primeros arrays de esta pgina se almacenen en la FLASH debemos declararlas e inicializarlas
de esta forma
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 fue tan simple como aadirles al inicio la palabra reservada PROGMEM. El
calificador 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 despus. Para acceder a los elementos de estos
arrays hay que emplear una forma un tanto extica. Se deben usar algunas macros propias del
compilador, todas provedas por la librera pgmspace.h. Las principales son estas cuatro

pgm_read_byte. Lee de un array residente en FLASH un entero compuesto por un byte


de dato. En el lenguaje C los nicos datos de este tamao son del tipo char y sus
derivados signed char y unsigned char.
pgm_read_word. Lee de un array residente en FLASH un entero compuesto por 2 bytes.
En el C seran arrays de tipo (signed) int, unsigned int, (signed) short y unsigned short.
pgm_read_dword. Lee de un array residente en FLASH un entero compuesto por 4
bytes. En el C seran arrays de tipo (signed) long y unsigned long.
pgm_read_float. Lee de un array residente en FLASH un nmero de punto flotante
compuesto por 4 bytes. En el lenguaje C seran arrays de tipo float y double operando
en modo de 32 bits.

Estas macros reciben como argumento la direccin de un elemento del array en FLASH. Como
la direccin de una variable cualquiera en el C se obtiene al aplicarle el operador &, las macros
citadas trabajan de esta forma.
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]);
c = pgm_read_byte(&vocals[3]);
c = pgm_read_byte(&text[11]);

// Con esto var valdr 56


// Con esto c valdr 'o'
// Con esto c valdr 'a'

El manual de AVR GCC nos presenta una forma que puede resultar ms fcil 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 finalmente 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 ltima expresin. Es muy
importante recordar esto puesto que las expresiones previas son tambin vlidas para el
-----------------------------------------------------------------------------------------------------------------------------M.S.c Ing. Jacob Astocondor Villar

33

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES


-----------------------------------------------------------------------------------------------------------------------------

compilador y no generar errores. Solo nos daremos con la sorpresa de ver nuestro programa
funcionando desastrosamente. Me pasa con frecuencia porque en mis cdigos 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 prctica. 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 ayudar.
Los parmetros que reciben como argumento las macros pgm_read_byte, pgm_read_word,
pgm_read_dword y pgm_read_float son direcciones de 16 bits. Esto quiere decir que mediante
ellas podemos acceder a los arrays cuyos elementos cubren un espacio de 216 = 65536 bytes de
la memoria FLASH. En la gran mayora de los casos es mucho ms 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 situacin donde tengamos que trabajar con
arrays de ms de 64 KB, la librera pgmspace.h nos provee de otras macros ad hoc. Retomamos
este aspecto al final de la pgina.

18.1 Variables PROGMEM locales


Las variables PROGMEM tienen un espacio asignado en la memoria que no cambia durante la
ejecucin 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 dinmicas.
Ms que una coincidencia, lo dicho arriba es una condicin 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 funcionan bien asumiendo que estn declaradas a nivel global. Si los colocamos dentro de
una funcin habr problemas.
Por ejemplo, el siguiente extracto de funcin no dar errores en AVR GCC pero el programa
funcionar defectuosamente, a pesar de que los arrays estn declarados e inicializados conforme
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 estn declarados como si fueran locales ordinarias. Si los hubiramos
declarado globalmente estara bien. Pero como son locales es necesario que sean adems de tipo
static. Como sabemos, estas variables en C se forman aadindoles la palabra reservada static a
su declaracin habitual. Con esto aclarado, el cdigo anterior trabajar perfectamente si lo
escribimos de esta forma.
/************************************************************
* Toca las notas del ringtone apuntado por pRingtone.
**********************************************************/

-----------------------------------------------------------------------------------------------------------------------------M.S.c Ing. Jacob Astocondor Villar

34

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES


----------------------------------------------------------------------------------------------------------------------------void Tune(PGM_P pRingtone)
{

// C

C#

D#

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 cdigo pertenece al programa de la prctica reproductor de ringtones PICAXE. Si


deseas comprobar lo expuesto puedes descargarlo y recompilarlo haciendo las modificaciones
explicadas.

18.2 Los punteros PGM_P y PGM_VOID_P


Justo en el ejemplo anterior aparece el tipo de dato PGM_P en una de sus funciones que es
permitir el paso de variables a funciones. Ese tema lo profundizaremos luego.
El tipo de dato PGM_P es un puntero a una variable residente en la memoria FLASH. Su
definicin 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 define como


#define

PGM_P

const char __flash *

Con el fin de que los programas de cursomicros sean lo ms transparentes posible trato de evitar
el uso excesivo de los #defines que conducen a trminos innecesarios. El hecho de estar
estudiando PGM_P sugiere que se trata de una excepcin. Notemos en primer lugar que el
archivo avr_compiler.h que se usa en esta web define PROGMEM como __flash con lo cual las
dos expresiones de arriba seran idnticas asumiendo que en AVR GCC const PROGMEM char
equivale a const char PROGMEM y tambin a PROGMEM const char, siendo esta ltima
presentacin la forma en que hemos venido trabajando. Debido a ello en muchas ocasiones
podremos prescindir de PGM_P, pero surgirn algunos casos en que AVR IAR C muestre su
disconformidad por ese reacomodo de trminos. PGM_P no solo termina de arreglar estos
desajustes sino que facilita notablemente la escritura del cdigo ante la aparicin de
construcciones ms complejas como las que veremos despus.
Si PGM_P define un tipo puntero que apunta a variables char (o de un byte en general), alguien
podra preguntar cules son los punteros para las variables de tipo int, short, float, etc. No hay
definiciones especiales para esos casos. Podemos crearlas por cuenta propia si deseamos pero
ser raramente necesario porque a fin de cuentas el puntero seguir siendo de 16 bits. Solo suele
interesar la direccin de un dato. Para leer ese dato con el formato deseado habr que usar la
macro adecuada, entre pgm_read_byte, pgm_read_word, pgm_read_dword y pgm_read_float,
junto a las conversiones de datos respectivos.
Ejemplo, en el siguiente programa que est 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)

-----------------------------------------------------------------------------------------------------------------------------M.S.c Ing. Jacob Astocondor Villar

35

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES


----------------------------------------------------------------------------------------------------------------------------{

PGM_P p;
unsigned char c;
unsigned int n;

// Declarar el puntero p

p = (PGM_P)Octaves;
c = pgm_read_byte(p + 1);

// Apuntar al array Octaves


// 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 tambin, en principio se podran 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 parntesis el tipo de la
variable que recibe el valor, en este caso PGM_P porque p es de ese tipo. De ese modo p podr
apuntar a arrays de cualquier tipo.
Por otro lado, para leer los elementos del array Octaves usamos la macro pgm_read_byte porque
es un array de bytes y para Notes usamos pgm_read_word porque es un array de enteros de 2
bytes.
A pgm_read_byte se le enva el puntero p ms el ndice 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 fue sencillo porque el tipo de Octave se
acoplaba fcilmente a PGM_P.
pgm_read_word tambin espera recibir una direccin pero no se le puede enviar a p
directamente como en el caso previo. Es preciso hacer una conversin de tipo para que el valor
de p sea entendido como una direccin de variables de 2 bytes. Por eso colocamos al lado
izquierdo de p la expresin (PROGMEM int *) que se adapta al tipo del array Notes. Tambin
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 PGM_P hace presuponer que solo se trabajar con variables de bytes
que son accedidas mediante la macro pgm_read_byte. De hecho es as en la gran mayora de los
casos y todo queda bien. La legibilidad se pierde en programas como el ejemplo previo donde el
mismo puntero se usa tambin para acceder a un array de enteros de 2 bytes. Si de todos modos
vamos a estar haciendo conversiones de tipos lo ms recomendable sera usar un puntero
"neutro" lo cual deja por sentado que trabajar sobre variables de distintos tipos.
Ese tipo de puntero existe y se llama PGM_VOID_P. Es aceptado as en los compiladores AVR
IAR C y AVR GCC. Es un puntero a void definido en AVR GCC como const void PROGMEM
* y en AVR IAR C como const void __flash *. Lo importante es que su empleo es similar al
puntero PGM_P, as que lo asimilaremos de inmediato. El programa anterior por ejemplo se
puede reescribir de la siguiente forma. (El cdigo qued con mejor acabado y con una linda
simetra.)
#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

-----------------------------------------------------------------------------------------------------------------------------M.S.c Ing. Jacob Astocondor Villar

36

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES


----------------------------------------------------------------------------------------------------------------------------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;
n = pgm_read_word((PROGMEM int*)p + 3);
}

// Apuntar al array Notes


// Obtener Notes[3]

while(1);

Los punteros PGM_P y PGM_VOID_P tambin pueden actuar sobre variables de tipo
complejo. Estamos hablando por ejemplo de estructuras definidas por el usuario. Por el mismo
hecho de ser variables complejas poner aqu un programa de demostracin abarcara demasiado
espacio. Prefiero remitirme a la librera para USB que distribuye Atmel. Me parece un perfecto
ejemplo. Puedes encontrarla en varias notas de aplicacin como AVR270, AVR271, AVR272 y
AVR273, por citar algunas. En el archivo usb_standar_request.c se declara y usa el puntero
pbuffer de tipo PGM_VOID_P para acceder a los descriptores de USB que por su tamao
residen en la FLASH.
Esa librera USB se vale de un archivo llamado compiler.h para guardar la compatibilidad de
cdigos entre los compiladores AVR IAR C y AVR GCC para los que est escrita. Contiene
varias imprecisiones que, imagino, se deben a los defectos que AVR GCC presentaba
antiguamente, cuando se escribi la librera. Igual vale la pena revisarla.
18.3 Variables PROGMEM como argumentos de funciones
En primer lugar recordemos que los argumentos de las funciones deben ser del mismo tipo que
las variables que se le envan. Si las variables son residentes en la FLASH, lo cual deja suponer
que son arrays o estructuras complejas, el mtodo a usar son los punteros, no solo por el tamao
de esas variables sino por la capacidad de adaptacin de los punteros que estudiamos en la
seccin anterior.
All se describi la operacin de los punteros PGM_P y PGM_VOID_P y se explic lo
imprescindibles que seran. No es recomendable que los argumentos de las funciones sean
punteros con tipos diferentes de PGM_P o PGM_VOID_P. Solo ellos garantizan que nuestros
programas funcionarn bien en los dos compiladores AVR IAR C y AVR GCC. Dicho eso,
podemos pasar al ejemplo.
En el siguiente programa la funcin print imprime un mensaje por el puerto serie, similar a puts
de la librera stdio.h del compilador. La funcin puts solo trabaja con mensajes en la RAM a
diferencia de print que recibe arrays residentes en la FLASH. Los dos compiladores que usamos
tambin ofrecen funciones de FLASH y a eso pretendemos llegar: a su uso.
#include "avr_compiler.h"
#include "usart.h"
PROGMEM
PROGMEM
PROGMEM
PROGMEM

const
const
const
const

char
char
char
char

rt01[]
rt02[]
rt03[]
rt04[]

=
=
=
=

"\r
"\r
"\r
"\r

Deck the halls";


Jingle bells";
We wish you a merry christmas";
Silent night";

/*************************************************************
* Enva por el USART el texto pasado en p
*******************************************************/
void print(PGM_P p)
{

-----------------------------------------------------------------------------------------------------------------------------M.S.c Ing. Jacob Astocondor Villar

37

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES


-----------------------------------------------------------------------------------------------------------------------------

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 cdigo est bastante claro. Como los arrays son de texto (de caracteres de 1 byte), se
opt por el puntero PGM_P y por la macro pgm_read_byte para la que no fue necesaria una
conversin de tipo. La conversin de tipo para p es opcional, por ejemplo, tambin se pudo
escribir print((PGM_P)rt01).
Y ahora la pregunta que nos trae aqu: Se puede enviar a una funcin una variable en flash
directamente? Es decir, qu pasa si en vez de declarar los arrays por separado, los escribimos
directamente en el argumento de la siguiente forma.
print("\r
print("\r
print("\r
print("\r

Deck the halls");


// Imprimir este mensaje
Jingle bells");
// ...
We wish you a merry christmas"); // ...
Silent night");
// ...

El compilador AVR GCC todava 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 cdigo fuente. Qu pas?
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 cdigo
de plano, en tanto que AVR GCC s cumple el cometido pasando por alto la divergencia de
tipos porque, recordemos, este compilador hace la diferencia en el momento de acceder a las
variables usando sus macros como pgm_read_byte. Esa macro recibe en el programa una
direccin RAM (tambin de 16 bits) y la usa para leer de la memoria FLASH como si las
cadenas de texto estuvieran all. Lee "cualquier cosa" menos las cadenas.
Alguien ms avezado podra decir que eso se puede arreglar con conversiones de tipos por
ejemplo reescribiendo las sentencias as
print((PGM_P)("\r
print((PGM_P)("\r
print((PGM_P)("\r
print((PGM_P)("\r

Deck the halls"));


// Imprimir este mensaje
Jingle bells"));
// ...
We wish you a merry christmas")); // ...
Silent night"));
// ...

El cdigo vuelve a compilarse limpiamente. Hasta AVR IAR C es engaado. Pero cuando
vemos el programa en accin 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 rpidamente, felizmente. En otras circunstancias detectar el error hubiera costado ms. Las
-----------------------------------------------------------------------------------------------------------------------------M.S.c Ing. Jacob Astocondor Villar

38

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES


-----------------------------------------------------------------------------------------------------------------------------

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 direccin automticamente. La
siguiente construccin entonces funcionar como se desea pero solo en este compilador.
print(PSTR("\r
print(PSTR("\r
print(PSTR("\r
print(PSTR("\r

Deck the halls"));


// Imprimir este mensaje
Jingle bells"));
// ...
We wish you a merry christmas")); // ...
Silent night"));
// ...

Solo por curiosidad, la macro PSTR tiene la siguiente definicin. PSTR es ampliamente usada
cuando se trabaja con las funciones _P del compilador AVR GCC. As que hablaremos ms 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 programacin.
Para crear un array de cadenas en FLASH primero se declaran e inicializan las cadenas de la
forma ya conocida y luego se construye el array con los nombres de las cadenas. Esta regla es
nica, inflexible, limitante e igualmente vlida 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 idntico al ejemplo de la seccin anterior: el programa debe
mostrar los mismos mensajes almacenados en la FLASH solo que esta vez se les desea acceder
mediante un ndice, por eso los mensajes estn agrupados en un array.
#include "avr_compiler.h"
#include "usart.h"
PROGMEM
PROGMEM
PROGMEM
PROGMEM

const
const
const
const

char
char
char
char

ringt01[]
ringt02[]
ringt03[]
ringt04[]

=
=
=
=

"\r
"\r
"\r
"\r

Deck the halls";


Jingle bells";
We wish you a merry christmas";
Silent night";

PGM_P ringtones[] =
{
ringt01,
ringt02,
ringt03,
ringt04
};
/****************************************************************
* Main function
***************************************************************/
int main(void)
{
usart_init();
// Inicializar USART
puts_P(ringtones[0]);
puts_P(ringtones[1]);
puts_P(ringtones[2]);
puts_P(ringtones[3]);

// Imprimir Deck the halls


// Imprimir Jingle bells
// Imprimir We wish you a merry christmas
// Imprimir Silent night

-----------------------------------------------------------------------------------------------------------------------------M.S.c Ing. Jacob Astocondor Villar

39

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES


----------------------------------------------------------------------------------------------------------------------------}

while (1);

La funcin puts_P es proveda por los compiladores. Es similar a la funcin puts pero las
cadenas que recibe deben ubicarse en la FLASH. En otras palabras, puts_P es similar a la
funcin 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 forma regular y no empleando macros. Se hizo as porque cada elemento es un
puntero de 2 bytes y como solo son 4 punteros, no ocupan mucho espacio. Si hubiera muchos
ms elementos en el array ringtones, la situacin cambiara y sera mejor que tambin residiera
en la FLASH. Eso lo veremos al final.
Elaborar el array y el contenido de sus elementos por separado es incmodo 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. Quisiramos que fuera posible implementar el array por
ejemplo como se muestra abajo donde cada elemento se inicializa directamente, pero eso solo es
factible 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 ubicar los
elementos en la RAM. En AVR GCC ni siquiera la macro PSTR, que sirve para inicializar en
lnea datos residentes en FLASH, podr ayudarnos esta vez. Y, lo olvidaba, no intentes forzar el
destino del array o de sus elementos utilizando conversiones de tipos. Solo evadirs los errores
y advertencias, pero los datos seguirn yendo a la RAM y el programa funcionar
incorrectamente. (Haz clic aqu si quieres ver la versin 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 presentacin coincide con la forma en que hemos estado trabajando antes: primero
PROGMEM, luego const y al final el tipo de dato. En AVR IAR C una permutacin a veces
producir incompatibilidades. Por tanto, el uso de PGM_P ms que una cuestin de
simplificacin es una necesidad que facilitar la compatibilidad de cdigos.
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 cdigo requiriera incluso ese
espacio para otros datos o si el array es bastante ms grande, entonces el mismo array ringtones
tambin debera almacenarse en la memoria FLASH. Solo hay un arreglo para esto y, como ya
discutimos demasiado, vamos directamente a poner la forma que debe tener el programa en ese
caso.
#include "avr_compiler.h"
#include "usart.h"
PROGMEM
PROGMEM
PROGMEM
PROGMEM

const
const
const
const

char
char
char
char

ringt01[]
ringt02[]
ringt03[]
ringt04[]

=
=
=
=

"\r
"\r
"\r
"\r

Deck the halls";


Jingle bells";
We wish you a merry christmas";
Silent night";

PROGMEM PGM_P const ringtones[] =


{
ringt01,
ringt02,
ringt03,

-----------------------------------------------------------------------------------------------------------------------------M.S.c Ing. Jacob Astocondor Villar

40

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES


----------------------------------------------------------------------------------------------------------------------------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 conversin de tipo con (PGM_P) no es necesaria para AVR IAR C y para AVR GCC sirve
para evitar warnings aunque el programa funciona igual.
Cambiando de tema, en AVR GCC el programa se compila en 528 bytes de memoria FLASH y
20 bytes de RAM. En CodeVisionAVR haba 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 ms
de RAM para ahorrar ms FLASH. En los tres casos la compilacin se realiz con la
optimizacin a mximo nivel.

18.5 Arrays ms que grandes


Los parmetros que reciben como argumento las macros pgm_read_byte, pgm_read_word,
pgm_read_dword y pgm_read_float son direcciones de 16 bits. Esto quiere decir que mediante
ellas podemos acceder a los arrays cuyos elementos cubren un espacio de 216 = 65536 bytes de
la memoria FLASH. En la gran mayora de los casos es mucho ms 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 situacin donde tengamos que trabajar con
arrays de ms de 64 KB, la librera pgmspace.h nos provee de otras macros ad hoc.

pgm_read_byte_far. Accede a arrays de enteros de 1 byte.


pgm_read_word_far. Accede a arrays de enteros de 2 bytes.
pgm_read_dword_far. Accede a arrays de enteros de 4 bytes.
pgm_read_float_far. Accede a arrays de decimales 4 bytes.

Sin ser muy observadores notamos que sus nombres provienen de las macros anteriores. Ahora
llevan el sufijo _far. Estas macros reciben como argumento direcciones de 32 bits con lo que
tericamente tienen un alcance de hasta 4 GB de datos en FLASH. Como en la prctica 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 actan tambin 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;

-----------------------------------------------------------------------------------------------------------------------------M.S.c Ing. Jacob Astocondor Villar

41

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES


----------------------------------------------------------------------------------------------------------------------------var = pgm_read_word_far(&a[1]);
c = pgm_read_byte_far(&vocals[3]);
c = pgm_read_byte_far(&text[11]);

// Con esto var valdr 56


// Con esto c valdr 'o'
// Con esto c valdr 'a'

Podemos entender que los nicos AVR que aceptan estas macros son los que tienen ms 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 sern igual de eficientes. Si el cdigo de arriba, por
ejemplo, estuviera escrito para un ATmega1284P, el acceso se dilatara ligeramente al tenerse
que trabajar con 32 bits. Puede ser un detalle insignificante pero a veces servir para optimizar
procesos.
Para complementar el tema, diremos que si existen macros con _far (lejos, en ingls), en la
librera pgmspace.h tambin hay macros con el apndice _near (cerca). Estas nuevas macros son
pgm_read_byte_near, pgm_read_word_near, pgm_read_dword_near y pgm_read_float_near.
Pero no te preocupes si crees que el tema se va recargando demasiado. No se tratan ms que de
alias de las primeras macros de 16 bits que estudiamos arriba, por ejemplo,
pgm_read_byte_near es lo mismo que pgm_read_byte, y as con las dems. Es bueno saberlo
para no quedar sorprendidos por los cdigos de quienes prefieren usar la una u otra forma.

-----------------------------------------------------------------------------------------------------------------------------M.S.c Ing. Jacob Astocondor Villar

42

También podría gustarte