programacion C AVR

42
UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES ----------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------ M.S.c Ing. Jacob Astocondor Villar 1 UNIVERSIDAD NACIONAL DEL CALLAO FACULTAD DE INGENIERヘA ELノCTRICA Y ELECTRモNICA ESCUELA PROFESIONAL ACADノMICO DE INGENIERヘA ELECTRモNICA CURSO: MICROCONTROLADORES LENGUAJE C PARA ATMEGA8 PROFESOR: MSc ING. ASTOCONDOR VILLAR JACOB CALLAO, 2014V

Transcript of programacion C AVR

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES-----------------------------------------------------------------------------------------------------------------------------

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

UNIVERSIDAD NACIONAL DEL CALLAOFACULTAD DE INGENIERÍA ELÉCTRICA Y ELECTRÓNICA

ESCUELA PROFESIONAL ACADÉMICO DE INGENIERÍAELECTRÓNICA

CURSO: MICROCONTROLADORES

LENGUAJE C PARA ATMEGA8

PROFESOR: MSc ING. ASTOCONDOR VILLAR JACOB

CALLAO, 2014V

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES-----------------------------------------------------------------------------------------------------------------------------

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

PROGRAMACION EN C DEL ATMEGA8-AVR

INTRODUCCIÓN

Un curso de microcontroladores como éste implica abarcar tres áreas: Conocer el microcontrolador. Un microcontrolador es un circuito integrado genérico

cuyas partes debemos adaptar para que funcionen según los requerimientos de nuestrodiseño. Obviamente no podríamos programar lo que no conocemos.

Conocer los periféricos externos. Un micro CONTROLADOR no sería muy útil si notiene qué controlar. Muchos dispositivos a controlar o mediante los cuales se va acontrolar son comunes de la electrónica analógica, como transistores, relés, diodosLED, registros de desplazamiento e incluso los motores, y se da por hecho que el lectorya conoce lo suficiente de ellos. También están los periféricos que difícilmente pudo elalumno haber operado antes sin ayuda de un microcontrolador o una computadora,como por ejemplo, LCDs, los motores de pasos, los sensores de temperatura digitales,etc. Es todo este segundo grupo de periféricos externos el que se cubre en un curso demicrocontrolador como éste.

Conocer un lenguaje de programación. Conocer un lenguaje de programación es unmundo aparte y es raro que una persona trate de conocer un microcontrolador al mismotiempo que va aprendiendo el lenguaje.

El lenguaje C en particular es un tema que normalmente se aprende por separado.

Los lenguajes de alto nivel son mucho más potentes que el ensamblador aunque su aprendizajedemanda un mayor esfuerzo.

Para empezar a programar en ensamblador nos puede bastar con aprender unas 50 palabras (lasinstrucciones básicas).

En cambio dominar un lenguaje de alto nivel como el C es como aprender a hablar en unnuevo idioma. No basta con memorizar palabras nuevas, sino que debemos aprender a manejaruna nueva estructura gramatical. Además, los procesadores no son como las personas: si en uncódigo de 100 líneas te olvidaste de una sola coma, los compiladores no te lo pasarán por alto.

ESTRUCTURA DE UN PROGRAMA EN C

Tomaremos en cuenta este sencillísimo ejemplo, escrito para los compiladores AVR IAR C yAVR 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);

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES-----------------------------------------------------------------------------------------------------------------------------

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

}//****************************************************************************// Función principal//****************************************************************************int main(void){

DDRB = 0x01; // Configurar pin PB0 como salidafor( ;; ){

PORTB |= 0x01; // Poner 1 en pin PB0delay_ms(400); //PORTB &= 0xFE; // Poner 0 en pin PB0delay_ms(300);

}}

No hay que ser muy perspicaz para descubrir lo que hace este programa: configura elpin PB0 como salida y luego lo setea y lo limpia tras pausas. Es como hacer parpadearun LED conectado al pin PB0. Parpadea porque el bloque de while se ejecutacíclicamente.

Los elementos más notables de un programa en C son; las sentencias, lasfunciones, las directivas, los comentarios y los bloques. A continuación, una brevedescripción de ellos.

1.-LOS COMENTARIOS

Los comentarios tienen el mismo propósito que en ensamblador: documentar y“adornar” el código. Es todo es texto que sigue a las barritas // y todo lo que está entrelos signos /* y */. Se identifican fácilmente porque suelen aparecer en color verde.

Ejemplos.

// Éste es un comentario simple

/*Ésta es una forma de comentar varias líneas a la vez.Sirve mucho para enmascarar bloques de código.

*/

2.- LAS SENTENCIAS

Un programa en C, en lugar de instrucciones, se ejecuta por sentencias.

Una sentencia es algo así como una mega instrucción, que hace lo que varias instrucciones delensamblador.

Salvo casos particulares, donde su uso es opcional, una sentencia debe finalizar con un puntoy coma (;).

Así que también podemos entender que los; sirven para separar las sentencias. Alguna vez leíque el compilador C lee el código como si lo absorbiera con una cañita, línea por línea, una acontinuación de otra (evadiendo los comentarios por supuesto).

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES-----------------------------------------------------------------------------------------------------------------------------

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

Por ejemplo, la función main del programa de arriba bien puede escribirse del siguiente modo.

//****************************************************************************// Función principal//****************************************************************************int main(void)

{DDRB = 0x01;for( ;; )

{PORTB |= 0x01;delay_ms(400);PORTB &= 0xFE;delay_ms(300);

}}

¿Sorprendido? Podrás deducir que los espacios y las tabulaciones solo sirven paradarle un aspecto ordenado al código. Es una buena práctica de programaciónaprender a acomodarlas.

Las sentencias se pueden clasificar en; sentencias de asignación, sentencias selectivas, sentencias iterativas, sentenciasde llamadas de función, etc.

Las describiremos más 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 losbucles while y for. Creo que exageré con los comentarios, pero sirven para mostrarnos dóndeempieza y termina cada bloque. Podrás ver cómo las tabulaciones ayudan a distinguir unosbloques de otros. Afortunadamente, los editores de los buenos compiladores C pueden resaltarcuáles son las llaves de inicio y de cierre de cada bloque. Te será fácil acostumbrarte a usarlas.

4.-LAS DIRECTIVAS

Son conocidas en el lenguaje C como directivas de preprocesador, de preprocesador porque sonevaluadas antes de compilar el programa. Como pasaba en el ensamblador, las directivas por símismas no son código ejecutable. Suelen ser indicaciones sobre cómo se compilará el código.

Entre las pocas directivas del C estándar que también son soportadas por los compiladores Cpara AVR están;

#include (para incluir archivos, parecido al Assembler),#define (mejor que el #define del ensamblador)y las #if, #elif, #endif y similares. Fuera de ellas, cada compilador maneja sus propias directivasy serán tratadas por separado.

5.- LAS FUNCIONES

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES-----------------------------------------------------------------------------------------------------------------------------

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

Si un programa en ensamblador se puede dividir en varias subrutinas para su mejorestructuración, un programa en C se puede componer de funciones. Por supuesto que lasfunciones son muchísimo más potentes y, por cierto, algo más complejas de aprender. Por eso nisiquiera el gran espacio que se les dedica más adelante puede ser suficiente para entenderlasplenamente. Pero, no te preocupes, aprenderemos de a poco.

En un programa en C puede haber las funciones que sean posibles, pero nunca debe faltar lafunción principal, llamada main.

Donde quiera que se encuentre, la función main siempre será la primera en ser ejecutada. Dehecho, allí empieza y no debería salir de ella.

6.- Variables y Tipos de Datos

En ensamblador todas las variables de programa suelen ser registros de la RAM crudos, esdecir, datos de 8 bits sin formato. En los lenguajes de alto nivel estos registros son tratados deacuerdo con formatos que les permiten representar números de 8, 16 ó 32 bits (a veces másgrandes), con signo o sin él, números enteros o decimales. Esos son los tipos de datos básicos.

Las variables de los compiladores pueden incluso almacenar matrices de datos del mismo tipo(llamadas arrays) o de tipos diferentes (llamadas estructuras). Estos son los tipos de datoscomplejos.

Los siguientes son los principales tipos de datos básicos del lenguaje C. Observa que la tabla lossepara en dos grupos, los tipos enteros y los tipos de punto flotante.

Tabla de variables y tipos de datos del lenguaje CTipo de dato Tamaño en bits Rango de valores que puede adoptar

char 8 0 a 255 ó -128 a 127signed char 8 -128 a 127unsigned char 8 0 a 255(signed) int 16 -32,768 a 32,767unsigned int 16 0 a 65,536(signed) short 16 -32,768 a 32,767unsigned short 16 0 a 65,536(signed) long 32 -2,147,483,648 a 2,147,483,647unsigned long 32 0 a 4,294,967,295(signed) long long (int) 64 -263 a 263 - 1unsigned long long (int) 64 0 a 264 - 1

float 32 ±1.18E-38 a ±3.39E+38double 32 ±1.18E-38 a ±3.39E+38double 64 ±2.23E-308 a ±1.79E+308

Afortunadamente, a diferencia de los compiladores para PIC, los compiladores para AVR suelenrespetar bastante los tipos establecidos por el ANSI C. Algunos compiladores también manejantipos de un bit como bool (o boolean) o bit, pero con pequeñas divergencias que pueden afectarla portabilidad de los códigos además de confundir a los programadores. Esos tipos sonraramente usados.

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES-----------------------------------------------------------------------------------------------------------------------------

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

Por defecto el tipo double es de 32 bits en los microcontroladores. En ese caso es equivalente altipo float. Los compiladores más potentes como AVR IAR C y AVR GCC sin embargo ofrecenla posibilidad de configurarlo para que sea de 64 bits y poder trabajar con datos más grandes yde mayor precisión.

Los especificadores signed (con signo) mostrados entre paréntesis son opcionales. Es decir, dalo mismo poner int que signed int, por ejemplo. Es una redundancia que se suele usar para“reforzar” su condición o para que se vea más ilustrativo.

El tipo char está pensado para almacenar caracteres ASCII como las letras. Puesto que estosdatos son a fin de cuentas números también, es común usar este tipo para almacenar números de8 bits. Es decir es equivalente a signed char o unsigned char, dependiendo de la configuraciónestablecida por el entorno compilador. Y como es preferible dejar de lado estas cuestiones, sivamos a trabajar con números lo mejor es poner el especificador signed o unsigned en el código.

Quizá te preguntes cuál es la diferencia entre los tipos de datos int y short si aparentementetienen el mismo tamaño y aceptan el mismo rango de valores. Esa apariencia es real en elentorno de los microcontroladores AVR. Es decir, al compilador le da lo mismo si ponemos into short. Sucede que el tipo short fue y siempre debería ser de 16 bits, en tanto que int fueconcebido para adaptarse al bus de datos del procesador. Esto todavía se cumple en laprogramación de las computadoras, por ejemplo, un dato int es de 32 bits en un Pentium IV y esde 64 bits en un procesador Core i7. De acuerdo con este diseño un tipo int debería ser de 8 bitsen un megaAVR y de 32 bits en un AVR32. Sin embargo, la costumbre de relacionar el tipo intcon los 16 bits de las primeras computadoras como las legendarias 286 se ha convertido entradición y en regla de facto para los microcontroladores. Actualmente solo en CCS C el tipo intes de 8 bits. Es irónico para ser el compilador que menos respeta los tipos de datos del ANSI C.

A pesar de todo, se nota que todavía pueden aparecer ciertas imprecisiones en los tipos de datosque pueden perturbar la portabilidad de los programas entre los diferentes compiladores. Es poresto que el lenguaje C/C++ provee la librería stdint.h para definir tipos enteros que serán de untamaño específico independientemente de los procesadores y de la plataforma software en quese trabaje.

Tabla de variables y tipos de datos del lenguaje CTipo de dato Tamaño en bits Rango de valores que puede adoptarint8_t 8 -128 a 127uint8_t 8 0 a 255int16_t 16 -32,768 a 32,767uint16_t 16 0 a 65,536int32_t 32 -2,147,483,648 a 2,147,483,647uint32_t 32 0 a 4,294,967,295int64_t 64 -263 a 263 - 1uint64_t 64 0 a 264 - 1

Es fácil descubrir la estructura de estos tipos para familiarizarse con su uso. Para ello debemosen primer lugar incluir en nuestro programa el archivo stdint.h con la siguiente directiva.

#include <stdint.h>

Esta inclusión ya está hecha en el archivo avr_compiler.h que se usa en todos los programas decurso, así que no es necesario volverlo a hacer. Aunque el objetivo de este archivo es permitir lacompatibilidad de códigos entre los compiladores AVR IAR C y AVR GCC, debemos saber queen AVR IAR C el archivo avr_compiler.h solo está disponible al usar la librería DLIB. Como

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES-----------------------------------------------------------------------------------------------------------------------------

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

las prácticas de curso trabajan sobre la librería CLIB, he evitado recurrir a los tipos extendidosde stdint.h.

Finalmente, existen además de los vistos arriba otros tipos y especificadores de datos que no sonparte del lenguaje C pero que fueron introducidos por los compiladores pensando en lascaracterísticas especiales de los microcontroladores. Muchos de ellos son redundantes o simplesalias y algunos que sí son de utilidad como el tipo PGM_P los veremos en su momento.

7.-Declaración de variables

Esta parte es comparable, aunque lejanamente a cuando se identifican las variables delensamblador con la directiva .def. No se puede usar una variable si antes no se ha declarado. Laforma general más simple de hacerlo es la siguiente:

data_type myvar;

Donde data_type es un tipo de dato básico o complejo, del compilador o definido por el usuarioy myvar es un identificador cualquiera, siempre que no sea palabra reservada.

Ejemplos.unsigned char d; // Variable para enteros de 8 bits sin signochar b; // Variable de 8 bits (para almacenar

// caracteres ascii)signed char c; // Variable para enteros de 8 bits con signoint i; // i es una variable int, con signosigned int j; // j también es una variable int con signounsigned int k; // k es una variable int sin signo

También es posible declarar varias variables del mismo tipo, separándolas con comas. Así nosahorramos algo de tipeo. Por ejemplo:

float area, side; // Declarar variables area y side de tipo floatunsigned char a, b, c; //Declarar variables a, b y c como unsigned char

8.-Especificadores de tipo de datos

A la declaración de una variable se le puede añadir un especificador de tipo como const, static,volatile, extern, register, etc. Dichos especificadores tienen diversas funciones y, salvo const, sesuelen usar en programas más elaborados. Como no queremos enredarnos tan pronto, lodejaremos para otro momento.

Una variable const debe ser inicializada en su declaración. Después de eso el compilador solopermitirá su lectura mas no su escritura. Ejemplos:

const int a = 100; // Declarar constante aint b; // Declarar variable b

//...b = a; // Válidob = 150; // Válido

a = 60; // Error! a es constantea = b; // Error! a es constante

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES-----------------------------------------------------------------------------------------------------------------------------

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

Por más que las variables constantes sean de solo lectura, ocuparán posiciones en la RAM delmicrocontrolador. En CodeVisionAVR es posible configurar para que sí residan en FLASHpero por compatibilidad se usa muy poco.

Por eso muchas veces es preferible definir las constantes del programa con las clásicasdirectivas #define (como se hace en el ensamblador).

#define a 100 // Definir constante a

9.-SENTENCIAS SELECTIVAS

Llamadas también sentencias de bifurcación, sirven para redirigir el flujo de un programa segúnla evaluación de alguna condición lógica.

Las sentencias if e if–else son casi estándar en todos los lenguajes de programación. Además deellas están las sentencias if–else escalonadas y switch–case.

9.1 La sentencia if

La sentencia if (si condicional, en inglés) hace que un programa ejecute una sentencia o ungrupo de ellas si una expresión es cierta. Esta lógica se describe en el siguiente esquema.

Diagrama de flujo de la sentencia if.

La forma codificada sería así:

sentenciaA;if ( expression ) // Si expression es verdadera,

// ejecutar el siguiente bloque{ // apertura de bloque

sentenciaB;sentenciaC;

// algunas otras sentencias} // cierre de bloquesentenciaX;

Después de ejecutar sentenciaA el programa evalúa expresión. Si resulta ser verdadera, seejecutan todas las sentencias de su bloque y luego se ejecutará la sentenciaX.

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES-----------------------------------------------------------------------------------------------------------------------------

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

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 condición lógica es verdadera.Cuando el programa requiera dos ramas, una que se ejecute si cierta expression es cierta y otrasi es falsa, entonces se debe utilizar la sentecia if – else. Tiene el siguiente esquema.

Diagrama de flujo de la sentencia if – else.

Expresando lo descrito en código C, tenemos: (Se lee como indican los comentarios.)

SentenciaA;if ( expression ) // Si expression es verdadera, ejecutar{ // este bloque

sentenciaB;sentenciaC;// ...

}else // En caso contrario, ejecutar este bloque{

sentenciaM;sentenciaN;// ...

}sentenciaX;// ...

Como ves, es bastante fácil, dependiendo del resultado se ejecutará uno de los dos bloques de lasentencia if – else, pero nunca los dos a la vez.

9.3 LA SENTENCIA IF – ELSE – IF ESCALONADA

Es la versión ampliada de la sentencia if – else.

En el siguiente boceto se comprueban tres condiciones lógicas, aunque podría haber más. Delmismo modo, se han puesto dos sentencias por bloque solo para simplificar el esquema.

if ( expression_1 ) // Si expression_1 es verdadera ejecutar{ // este bloque

sentencia1;sentencia2;

}

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES-----------------------------------------------------------------------------------------------------------------------------

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

else if ( expression_2 ) // En caso contrario y si expression_2 es{ // verdadera, ejecutar este bloque

sentencia3;sentencia4;

}else if ( expression_3 ) // En caso contrario y si expression_3 es{ // verdadera, ejecutar este bloque

sentencia5;sentencia6;

}else // En caso contrario, ejecutar este bloque{

sentencia7;sentencia8;

}; // ; opcional// todo...

Las “expresiones” se evalúan de arriba abajo. Cuando alguna de ellas sea verdadera, se ejecutarásu bloque correspondiente y los demás bloques serán salteados. El bloque final (de else) seejecuta si ninguna de las expresiones es verdadera. Además, si dicho bloque está vacío, puedeser omitido junto con su else.

9.4 LA SENTENCIA SWITCH

La sentencia switch brinda una forma más elegante de bifurcación múltiple. Podemosconsiderarla como una forma más estructurada de la sentencia if – else – if escalonada, aunquetiene algunas restricciones en las condiciones lógicas a evaluar, las cuales son comparaciones devalores enteros.

Para elaborar el código en C se usan las palabras reservadas switch, case, break y default.

El siguiente esquema presenta tres case’s pero podría haber más, así como cada bloque tambiénpodría tener más sentencias.

switch ( expression ){

case constante1: // Si expression = constante1, ejecutar este bloquesentencia1;sentencia2;break;

case constante2: // Si expression = constante2, ejecutar este bloquesentencia3;sentencia4;break;

case constante3: // Si expression = constante3, ejecutar este bloquesentencia5;sentencia6;break;

default: //Si expression no fue igual a ninguna de las// constantes anteriores, ejecutar este bloque

sentencia7;sentencia8;break;

}sentenciaX;// todo...

donde constante1, constante2 y constante3 deben ser constantes enteras, por ejemplo, 2, 0x45,‘a’, etc. (‘a’ tiene código ASCII 165, que es, a fin de cuentas, un entero.)

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES-----------------------------------------------------------------------------------------------------------------------------

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

Expresión puede ser una variable compatible con entero. No es una expresión que conduce auna condición lógica como en los casos anteriores.

El programa solo ejecutará uno de los bloques dependiendo de qué constante coincida conexpression. Usualmente los bloques van limitados por llaves, pero en este caso son opcionales,dado que se pueden distinguir fácilmente. Los bloques incluyen la sentencia break. ¿Qué es eso?

La sentencia break hace que el programa salga del bloque de switch y ejecute la sentencia quesigue (en el boceto, sentenciaX). ¡Atento!: de no poner break, también se ejecutará el bloque delsiguiente case, sin importar si su constante coincida con expression o no.

No sería necesario poner el default si su bloque estuviera vacío.

10. SENTENCIAS ITERATIVAS

Las sentencias de control iterativas sirven para que el programa ejecute una sentencia o ungrupo de ellas un número determinado o indeterminado de veces. Así es, esta sección no hablade otra cosa que de los bucles en C.

El lenguaje C soporta tres tipos de bucles, las cuales se construyen con las sentencias while, do– while y for. El segundo es una variante del primero y el tercero es una versión más compactae intuitiva del bucle while.

10.1 La sentencia whileEl cuerpo o bloque de este bucle se ejecutará una y otra vez mientras (while, en inglés) unaexpresión sea verdadera.

Diagrama de flujo de las sentencia while.

El bucle while en C tiene la siguiente sintaxis y se lee así: mientras (while) expression seaverdadera, ejecutar el siguiente bloque.

sentenciaA;while ( expression ) // Mientras expression sea verdadera, ejecutar el

// siguiente bloque{

sentenciaB;sentenciaC;// ...

}; // Este ; es opcionalsentenciaX;

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES-----------------------------------------------------------------------------------------------------------------------------

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

// ...Nota que en este caso primero se evalúa expression. Por lo tanto, si desde el principioexpression es falsa, el bloque de while no se ejecutará nunca. Por otro lado, si expression nodeja de ser verdadera, el programa se quedará dando vueltas “para siempre”.

10.2 LA SENTENCIA DO – WHILE

Como dije antes, es una variación de la sentencia while simple. La principal diferencia es que lacondición lógica (expression) de este bucle se presenta al final. Como se ve en la siguientefigura, 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 siguientebloque, mientras (while) expression sea verdadera.

sentenciaA;do{

sentenciaB;sentenciaC;// ...

} while ( expression ); // Este ; es mandatoriosentenciaX;// ...

10.3 La sentencia for

Las dos sentencias anteriores, while y do – while, se suelen emplear cuando no se sabe deantemano la cantidad de veces que se va a ejecutar el bucle. En los casos donde el bucleinvolucra alguna forma de conteo finito es preferible emplear la sentencia for. (Inversamente, alver un for en un programa, debemos suponer que estamos frente a algún bucle de ese tipo.)

Ésta es la sintaxis general de la sentencia for en C:

for ( expression_1 ; expression_2 ; expression_3 ){

sentencia1;sentencia2;// ...

}; // Este ; es opcional

Ahora veamos por partes cómo funciona:

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES-----------------------------------------------------------------------------------------------------------------------------

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

expression_1 suele ser una sentencia de inicialización. expression_2 se evalúa como condición lógica para que se ejecute el bloque. expression_3 es una sentencia que debería poner coto a expression_2.

Por la forma y orden en que se ejecutan estas expresiones, el bucle for es equivalente a lasiguiente construcción, utilizando la sentencia while. Primero se ejecuta expression_1 y luego seejecuta el bloque indicado tantas veces mientras expression_2 sea verdadera.

expression_1;while ( expression_2 ){

sentencia1;sentencia2;// ...

expression_3;}

No obstante, de esa forma se ve más rara aún; así que, mejor, veamos estos ejemplos, que sonsus presentaciones más clásicas. (i es una variable y a y b son constantes o variables):

for ( i = 0 ; i < 10 ; i++ ){

sentencias;}

Se lee: para (for) i igual a 0 hasta que sea menor que 10 ejecutar sentencias. La sentencia i++indica que i se incrementa tras cada ciclo. Así, el bloque de for se ejecutará 10 veces, desde quei valga 0 hasta que valga 9.

En este otro ejemplo las sentencias se ejecutan desde que i valga 10 hasta que valga 20. Esdecir, 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 seamayor o igual a 0. Por supuesto, en este caso i se decrementa tras cada ciclo.

for ( i = 100 ; i >= 0 ; i-- ){

sentencias;}

Se pueden hacer muchas más construcciones, todas coincidentes con la primera plantilla, perotambién son menos frecuentes.

11.- SENTENCIAS CON BLOQUES SIMPLES

Cuando las sentencias selectivas (como if) o de bucles (como while o for) tienen cuerpos obloques que constan de solo una sentencia, se pueden omitir las llaves. Aun así, es aconsejableseguir manteniendo las tabulaciones para evitarnos confusiones.

Por ejemplo, las siguientes sentencias:

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES-----------------------------------------------------------------------------------------------------------------------------

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

if(a > b){

a = 0;}

if(a == b){

a++;}else{

b--;}

while( a >= b){

a = a + b;}

for(i=0; i<=10; i++){

a = a*2;}

bien se pueden escribir de la siguiente forma:

if(a > b)a = 0;

if(a == b)a++;

elseb--;

while( a >= b)a = a + b;

for(i=0; i<=10; i++)a = a*2;

12.- LOS OPERADORESSirven para realizar operaciones aritméticas, lógicas, comparativas, etc. Según esa función seclasifican en los siguientes grupos.

12.1 Operadores aritméticos

Además de los típicos operadores de suma, resta, multiplicación y división, están los operadoresde módulo, incremento y decremento.

Tabla de Operadores aritméticosOperador Acción+ Suma- Resta* Multiplicación/ División

% Módulo. Retorna el residuo de una división entera. Solo se debe usar con númerosenteros.

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES-----------------------------------------------------------------------------------------------------------------------------

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

Tabla de Operadores aritméticosOperador Acción++ 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 cb = b * c; // Multiplicar b por c. Resultado en bb = a / c; // Dividir a entre c. Colocar resultado en ba = a + c – b; // Sumar a y c y restarle b. Resultado en ac = (a + b) / c; // Dividir a+b entre c. Resultado en cb = a + b / c + b * b; // Sumar a más b/c más b×b. Resultado en bc = a % b; // Residuo de dividir a÷b a ca++; // Incrementar a en 1b--; // Decrementar b en 1++c; // Incrementar c en 1--b; // Decrementar b en 1

¿Te recordaron a tus clases de álgebra del colegio? A diferencia de esas matemáticas, estasexpresiones no son ecuaciones; significan las operaciones que indican sus comentarios.

Por lo visto, los operadores ++ y -- funcionan igual si están antes o después de una variable enuna expresión simple. Sin embargo, hay una forma (tal vez innecesaria y confusa para unnovato, pero muy atractiva para los que ya estamos acostumbrados a su uso) que permiteescribir código más compacto, es decir, escribir dos sentencias en una.

Si ++ o -- están antes del operando, primero se suma o resta 1 al operando y luego seevalúa la expresión.

Si ++ o -- están después del operando, primero se evalúa la expresión y luego se suma oresta 1 al operando.

int a, b; // Declarar variables enteras a y b

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

if (a++ < 10) // Primero comprueba si a < 10 y luego{ // incrementa a en 1

// algún código}

if (++a < 10) // Primero incrementa a en 1 y luego{ // comprueba si a < 10

// algún código}

12.2 OPERADORES DE BITS

Se aplican a operaciones lógicas con variables a nivel binario. Aquí tenemos las clásicasoperaciones AND, OR inclusiva, OR exclusiva y la NEGACIÓN. Adicionalmente, he incluidoen esta categoría los operaciones de desplazamiento a la derecha y la izquierda.

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES-----------------------------------------------------------------------------------------------------------------------------

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

Si bien son operaciones que producen resultados análogos a los de las instrucciones deensamblador los operadores lógicos del C pueden operar sobre variables de distintos tamaños,ya sean de 1, 8, 16 ó 32 bits.

Tabla de operadores de bitsOperador Acción& AND a nivel de bits| OR inclusiva a nivel de bits^ OR exclusiva a nivel de bits~ Complemento a uno a nivel de bits<< Desplazamiento a la izquierda>> Desplazamiento a la derecha

Ejemplos:

char m; // variable de 8 bitsint n; // variable de 16 bits

m = 0x48; // m será 0x48m = m & 0x0F; // Después de esto m será 0x08m = m | 0x24; // Después de esto m será 0x2Fm = m & 0b11110000; // Después de esto m será 0x20n = 0xFF00; // n será 0xFF00n = ~n; // n será 0x00FFm = m | 0b10000001; // Setear bits 0 y 7 de variable mm = m & 0xF0; // Limpiar nibble bajo de variable mm = m ^ 0b00110000; // Invertir bits 4 y 5 de variable m

m = 0b00011000; // Cargar m con 0b00011000m = m >> 2; // Desplazar m 2 posiciones a la derecha

// Ahora m será 0b00000110n = 0xFF1F;n = n << 12; // Desplazar n 12 posiciones a la izquierda

// Ahora n será 0xF000;m = m << 8; // Después de esto m será 0x00

Fíjate en la semejanza entre las operaciones de desplazamiento con >> y << y las operacionesdel rotación del ensamblador. Cuando una variable se desplaza hacia un lado, los bits que salenpor 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 existenoperadores de rotación. Hay formas alternativas de realizarlas.

Desplazamientos producidos por los operadores << y >>.

12-3 OPERADORES RELACIONALES

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES-----------------------------------------------------------------------------------------------------------------------------

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

Se emplean para construir las condiciones lógicas de las sentencias de control selectivas eiterativas, como ya hemos podido apreciar en las secciones anteriores. La siguiente tablamuestra los operadores relacionales disponibles.

Tabla de Operadores relacionalesOperador Acción

== Igual!= No igual> Mayor que< Menor que>= Mayor o igual que<= Menor o igual que

12.4 OPERADORES LÓGICOS

Generalmente se utilizan para enlazar dos o más condiciones lógicas simples. Por suerte, estosoperadores solo son tres y serán explicados en las prácticas del curso.

Tabla de Operadores lógicosOperador Acción&& AND lógica|| OR lógica! Negación lógica

Ejemplos:

if( !(a==0) ) // Si a igual 0 sea falso{

// sentencias}

if( (a<b) && (a>c) ) // Si a<b y a>c son verdaderas{

// sentencias}

while( (a==0) || (b==0) ) // Mientras a sea 0 ó b sea 0{

// sentencias}

12.5 COMPOSICIÓN DE OPERADORES

Se utiliza en las operaciones de asignación y nos permite escribir código más abreviado. Laforma general de escribir una sentencia de asignación mediante los operadores compuestos es:

obtect op= expression;

que es equivalente a la sentencia

object = object op expression;

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES-----------------------------------------------------------------------------------------------------------------------------

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

op puede ser cualquiera de los operadores aritméticos o de bit estudiados arriba. O sea, op puedeser +, - , *, /, %, &, | , ^, ~, << ó >>. Nota: no debe haber ningún espacio entre el operador y elsigno igual.

Ejemplos:

int a; // Declarar aa += 50; // Es lo mismo que a = a + 50;a += 20; // También significa sumarle 20 a aa *= 2; // Es lo mismo que a = a * 2;a &= 0xF0; // Es lo mismo que a = a & 0xF0;a <<= 1; // Es lo mismo que a = a << 1;

12.6 PRECEDENCIA DE OPERADORES

Una expresión puede contener varios operadores, de esta forma:

b = a * b + c / b; // a, b y c son variables

A diferencia del lenguaje Basic, donde la expresión se evalúa de izquierda a derecha, en estasentencia no queda claro en qué orden se ejecutarán las operaciones indicadas. Hay ciertasreglas que establecen dichas prioridades; por ejemplo, las multiplicaciones y divisiones siemprese ejecutan antes que las sumas y restas. Pero es más práctico emplear los paréntesis, los cualesordenan que primero se ejecuten las operaciones de los paréntesis más internos. Eso es como enel álgebra elemental de la escuela, así que no profundizaré.

Por ejemplo, las tres siguientes sentencias son diferentes.

b = (a * b) + (c / b);b = a * (b + (c / b));b = ((a * b) + c)/ b);

También se pueden construir expresiones condicionales, así:

if ( (a > b) && ( b < c) ) // Si a>b y b<c, ...{

// ...}

13. LAS FUNCIONES

Una función es un bloque de sentencias identificado por un nombre y puede recibir y devolverdatos. En bajo nivel, en general, las funciones operan como las subrutinas de Assembler, esdecir, al ser llamadas, se guarda en la Pila el valor actual del PC (Program Counter), después seejecuta todo el código de la función y finalmente se recobra el PC para regresar de la función.

Dada su relativa complejidad, no es tan simple armar una plantilla general que represente atodas las funciones. El siguiente esquema es una buena aproximación.

data_type1 function_name (data_type2 arg1, data_type3 arg2, ... ){

// Cuerpo de la función// ...

return SomeData; // Necesario solo si la función retorna algún valor}

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES-----------------------------------------------------------------------------------------------------------------------------

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

Donde:

function_name es el nombre de la función. Puede ser un identificador cualquiera. data_type1 es un tipo de dato que identifica el parámetro de salida. Si no lo hubiera, se

debe poner la palabra reservada void (vacío, en inglés). arg1 y arg2 (y puede haber más) son las variables de tipos data_type1, data_type2...,

respectivamente, que recibirán los datos que se le pasen a la función. Si no hay ningúnparámetro de entrada, se pueden dejar los paréntesis vacíos o escribir un void entreellos.

13.1 FUNCIONES SIN PARÁMETROS

Para una función que no recibe ni devuelve ningún valor, la plantilla de arriba se reduce alsiguiente esquema:

void function_name ( void ){

// Cuerpo de la función}

Y se llama escribiendo su nombre seguido de paréntesis vacíos, así:

function_name();

La función principal main es otro ejemplo de función sin parámetros. Dondequiera que seubique, siempre debería ser la primera en ejecutarse; de hecho, no debería terminar.

void main (void){

// Cuerpo de la función}

13.2 FUNCIONES CON PARÁMETROS (POR VALOR)

Por el momento, solo estudiaremos las funciones que pueden tener varios parámetros de entradapero solo uno de salida.

Si la función no tiene parámetros de entrada o de salida, debe escribirse un void en su lugar. Elvalor devuelto por una función se indica con la palabra reservada return.

Según el comportamiento de los parámetros de entrada de la función, estos se dividen enparámetros por valor y parámetros por referencia. Lo expuesto en este apartado corresponde alprimer grupo porque es el caso más ampliamente usado. Con esto en mente podemos seguir.

Para llamar a una función con parámetros es importante respetar el orden y el tipo de losparámetros que ella recibe. El primer valor pasado corresponde al primer parámetro de entrada;el segundo valor, al segundo parámetro; y así sucesivamente si hubiera más.

Cuando una variable es entregada a una función, en realidad se le entrega una copia suya. Deeste modo, el valor de la variable original no será alterado. Mejor, plasmemos todo esto en elsiguiente ejemplo.

int minor ( int arg1, int arg2, int arg3 ){

int min; // Declarar variable minmin = arg1; // Asumir que el menor es arg1

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES-----------------------------------------------------------------------------------------------------------------------------

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

if ( arg2 < min ) // Si arg2 es menor que minmin = arg2; // Cambiar a arg2

if ( arg3 < min ) // Si arg3 es menor que minmin = arg3; // Cambiar a arg3

return min; // Retornar valor de min}void main (void){

int a, b, c, d; // Declarar variables a, b, c y d

/* Aquí asignamos algunos valores iniciales a 'a', 'b' y 'c'*/

/* ... */d = minor(a,b,c); // Llamar a minor// En este punto 'd' debería ser el menor entre 'a', 'b' y 'c'while (1); // Bucle infinito

}

En el programa mostrado la función minor recibe tres parámetros de tipo int y devuelve uno,también de tipo int, que será el menor de los números recibidos.

El mecanismo funciona así: siempre respetando el orden, al llamar a minor el valor de a secopiará a la variable arg1; el valor de b, a arg2 y el valor de c, a arg3. Después de ejecutarse elcódigo de la función el valor de retorno (min en este caso) será copiado a una variable temporaly de allí pasará a d.

Aunque el C no es tan implacable con la comprobación de tipos de datos como Pascal, siempredeberíamos revisar que los datos pasados sean compatibles con los que la función espera, asícomo los datos recibidos, con los que la función devuelve. Por ejemplo, estaría mal llamar a lafunción minor del siguiente modo:

d = minor(-15, 100, 5.124); // Llamar a minor

Aquí los dos primeros parámetros están bien, pero el tercero es un número decimal (de 32 bits),no compatible con el tercer parámetro que la función espera (entero de 16 bits). En estos casosel compilador nos mostrará mensajes de error, o cuando menos de advertencia.

13.3 PARÁMETROS POR REFERENCIA

La función que recibe un parámetro por referencia puede cambiar el valor de la variable pasada.La forma clásica de estos parámetros se puede identificar por el uso del símbolo &, tal como seve en el siguiente boceto de función.

int minor ( int & arg1, int & arg2, int & arg3 ){

// Cuerpo de la función.// arg1, arg2 y arg3 son parámetros por referencia.// Cualquier cambio hecho a ellos desde aquí afectará a las

variables// que fueron entregadas a esta función al ser llamada.

}

No voy profundizar al respecto porque he visto que muchos compiladores C no soportan estaforma. Otra forma de pasar un parámetro por referencia es mediante los punteros, pero eso lodejamos para el final porque no es nada nada fácil para un novato.

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES-----------------------------------------------------------------------------------------------------------------------------

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

13.4 PROTOTIPOS DE FUNCIONES

El prototipo de una función le informa al compilador las características que tiene, como su tipode retorno, el número de parámetros que espera recibir, el tipo y orden de dichos parámetros.Por eso se deben declarar al inicio del programa.

El prototipo de una función es muy parecido a su encabezado, se pueden diferenciar tan solo porterminar en un punto y coma (;). Los nombres de las variables de entrada son opcionales.

Por ejemplo, en el siguiente boceto de programa los prototipos de las funciones main, func1 yfunc2 declaradas al inicio del archivo permitirán que dichas funciones sean accedidas desdecualquier parte del programa. Además, sin importar dónde se ubique la función main, ellasiempre será la primera en ejecutarse. Por eso su prototipo de función es opcional.

#include <avr.h>

void func1(char m, long p); // Prototipo de función "func1"char func2(int a); // Prototipo de función "func2"void main(void); // Prototipo de función "main". Es

opcional

void main(void){

// Cuerpo de la función// Desde aquí se puede acceder a func1 y func2

}void func1(char m, long p){

// Cuerpo de la función// Desde aquí se puede acceder a func2 y main

}char func2(int a){

// Cuerpo de la función// Desde aquí se puede acceder a func1 y main

}

La llamada a main, por supuesto, no tiene sentido; solo lo pongo para ilustrar.

Si las funciones no tienen prototipos, el acceso a ellas será restringido. El compilador solo verálas funciones que están implementadas encima de la función llamadora o, de lo contrario,mostrará errores de “función no definida”. El siguiente boceto ilustra este hecho. (Atiende a loscomentarios.)

#include <avr.h>

void main(void){

// Cuerpo de la función// Desde aquí no se puede acceder a func1 ni func2 porque están abajo

}void func1(char m, long p){

// Cuerpo de la función// Desde aquí se puede acceder a main pero no a func2

}char func2(int a){

// Cuerpo de la función

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES-----------------------------------------------------------------------------------------------------------------------------

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

// Desde aquí se puede acceder a func1 y main}

Para terminar, dado que los nombres de las variables en los parámetros de entrada sonopcionales, los prototipos de func1 y func2 también 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 diseñados para desarrollar los programas másgrandes y complejos que se puedan imaginar, programas donde puede haber cientos devariables, entre otras cosas. ¿Imaginas lo que significaría buscar nombres para cada variable sitodos tuvieran que ser diferentes? Pues bien, para simplificar las cosas, el C permite tener variasvariables con el mismo nombre.

Así es. Esto es posible gracias a que cada variable tiene un ámbito, un área desde donde seráaccesible. Hay diversos tipos de ámbito, pero empezaremos por familiarizarnos con los dos másusados, que corresponden a las variables globales y variables locales.

Las variables declaradas fuera de todas las funciones y antes de sus implementacionestienen carácter global y podrán ser accedidas desde todas las funciones.

Las variables declaradas dentro de una función, incluyendo las variables delencabezado, tienen ámbito local. Ellas solo podrán ser accedidas desde el cuerpo dedicha función.

De este modo, puede haber dos o más variables con el mismo nombre, siempre y cuando esténen diferentes funciones. Cada variable pertenece a su función y no tiene nada que ver con lasvariables de otra función, por más que tengan el mismo nombre.

En la mayoría de los compiladores C para microcontroladores las variables locales debendeclararse al principio de la función.

Por ejemplo, en el siguiente boceto de programa hay dos variables globales (speed y limit) ycuatro variables locales, tres de las cuales se llaman count. Atiende a los comentarios.

char foo(long ); // Prototipo de función

int speed; // Variable globalconst 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 función foo. No compilará.

}void main(void){

int count; // Variable local count/* Este count no tiene nada que ver con el count

de las funciones inter o foo */

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES-----------------------------------------------------------------------------------------------------------------------------

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

count = 0; // Acceso a count localspeed = 0; // Acceso a variable global speed

}char foo(long count) // Variable local count{

int vari; // Variable local vari}

Algo muy importante: a diferencia de las variables globales, las variables locales tienenalmacenamiento temporal, es decir, se crean al ejecutarse la función y se destruyen al salir deella. ¿Qué significa eso? Lo explico en el siguiente apartado.

Si dentro de una función hay una variable local con el mismo nombre que una variable global, laprecedencia en dicha función la tiene la variable local. Si te confunde, no uses variablesglobales y locales con el mismo nombre.

14.1 Variables static

Antes de nada debemos aclarar que una variable static local tiene diferente significado que unavariable static global. Ahora vamos a enfocarnos al primer caso por ser el más común.

Cuando se llama a una función sus variables locales se crearán en ese momento y cuando sesalga de la función se destruirán. Se entiende por destruir al hecho de que la locación dememoria que tenía una variable será luego utilizada por el compilador para otra variable local(así se economiza la memoria). Como consecuencia, el valor de las variables locales no será elmismo entre llamadas de función.

Por ejemplo, revisa la siguiente función, donde a es una variable local ordinaria.

void increm(){

int a; // Declarar variable aa++; // Incrementar a

}

Cualquiera que haya sido su valor inicial, ¿crees que después de llamar a esta función 10 veces,el valor de a se habrá incrementado en 10?... Pues, no necesariamente. Cada vez que se llame aincrem se crea a, luego se incrementa y, al terminar de ejecutarse la función, se destruye.

Para que una variable tenga una locación de memoria independiente y su valor no cambie entrellamadas de función tenemos dos caminos: o la declaramos como global, o la declaramos comolocal estática. Los buenos programadores siempre eligen el segundo.

Una variable se hace estática anteponiendo a su declaración el especificador static. Por defectolas variables estáticas se auto inicializan a 0, pero se le puede dar otro valor en la mismadeclaración (dicha inicialización solo se ejecuta la primera vez que se llama a la función), 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 estática inicializada a 5a++; // Incrementar a

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES-----------------------------------------------------------------------------------------------------------------------------

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

}void main(){

int i; // Declarar variable i

// El siguiente código llama 10 veces a incremfor(i=0; i<10; i++)

increm();

// Ahora la variable a sí debería valer 15while(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 código fuente en código ejecutable. Por ejemplo, veamos elsiguiente pedazo de código para saber lo que suele pasar con una variable ordinaria:

int var; // Declarar variable var//...var = var; // Asignar var a var

El compilador creerá (probablemente como nosotros) que la sentencia var = var no tiene sentido(y quizá tenga razón) y no la tendrá en cuenta, la ignorará. Ésta es solo una muestra de lo quesignifica optimización del código. Luego descubrirás más formas de ese trabajo.

El ejemplo anterior fue algo burdo, pero habrá códigos con redundancias aparentes y másdifíciles de localizar, cuya optimización puede ser contraproducente. El caso más notable quedestacan los manuales de los compiladores C para microcontroladores es el de las variablesglobales que son accedidas por la función de interrupción y por cualquier otra función.

Para que un compilador no intente “pasarse de listo” con una variable debemos declararla comovolatile, anteponiéndole dicho calificador a su declaración habitual.

Por ejemplo, en el siguiente boceto de programa la variable count debe ser accedida desde lafunción interrupt como desde la función main; por eso se le declara como volatile. Nota: elesquema de las funciones de interrupción suele variar de un compilador a otro. Éste es solo unejemplo.

volatile int count; // count es variable global volátil

void interrupt(void) // Función de interrupción{

// Código que accede a count}

void main(void) // Función principal{

// Código que accede a count}

15. ARRAYS Y PUNTEROS

Probablemente éste sea el tema que a todos nos ha dado más de un dolor de cabeza y que máshemos releído para captarlo a cabalidad. Hablo más bien de los punteros. Si ellos el C no sería

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES-----------------------------------------------------------------------------------------------------------------------------

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

nada, perdería la potencia por la que las mejores empresas lo eligen para crear sus softwares decomputadoras.

Pero bueno, regresando a lo nuestro, estos temas se pueden complicar muchísimo más de lo queveremos aquí. Solo veremos los arrays unidimensionales y los punteros (que en principiopueden apuntar a todo tipo de cosas) los abocaremos a los datos básicos, incluyendo los mismosarrays. 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 yubicadas en posiciones contiguas de la memoria. Con los arrays podemos hacer todos lo quehacíamos con las tablas (de búsqueda) del ensamblador y muchísimo más.

Un array completo tiene un nombre y para acceder a cada uno de sus elementos se utilizaníndices entre corchetes ([ ]). Los índices pueden estar indicados por variables o constantes. En elsiguiente 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 Declaración 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 yNumElementos es la cantidad de elementos que tendrá (debe ser un valor constante).

De este modo, el índice del primer elemento es 0 y el del último es NumElements - 1.

Por ejemplo, las siguientes líneas declaran tres arrays.char letters10]; // letters es un array de 10 elementos de tipo charlong HexTable[16]; // HexTable es un array de 16 elementos de tipo longint address[100]; //address es un array de 100 elementos de tipo int

Para el array letters el primer elemento es letters[0] y el último, letters[9]. Así, tenemos 10elementos en total. Si quisiéramos asignar a cada uno de los elementos de letters los caracteresdesde la ‘a’ hasta la ‘j’, lo podríamos hacer individualmente así:

letters[0] = 'a'; // Aquí el índice es 0letters[1] = 'b'; // Aquí el índice es 1letters[2] = 'c'; // ...letters[3] = 'd'; //letters[4] = 'e';letters[5] = 'f';letters[6] = 'g';letters[7] = 'h';letters[8] = 'i';

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES-----------------------------------------------------------------------------------------------------------------------------

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

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: loscaracteres son, al fin y al cabo, números en códigos ASCII y se les puede comparar.)

char c;for ( c = 'a'; c <= 'j'; c++ )

letters[i] = c;

15.3 Inicialización de arrays

Los elementos de un array se pueden inicializar junto con su declaración. Para ello se le asignauna lista ordenada de valores encerrados por llaves y separados por comas. Por supuesto, losvalores deben ser compatibles con el tipo de dato del array. Este tipo de inicialización solo estápermitido en la declaración del array.

Ejemplos:

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

inicializadores

También es posible inicializar un array sin especificar en su declaración el tamaño que tendrá,dejando los corchetes vacíos. El tamaño será pre calculado y puesto por el compilador. Ésta esuna forma bastante usada en los arrays de texto, donde puede resultar muy incómodo estarcontando las letras de una cadena. Por ejemplo:

int a[] = { 70, 1, 51 }; // Un array de 3 elementoschar vocals[] = { 'a', 'e', 'i', 'o', 'u' };// Un array de 5 elementoschar 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 características que distinguen a estas cadenas de losdemás arrays. Primero: su inicialización se hace empleando comillas dobles y segundo, elúltimo término del array es un carácter NULL (simplemente un 0x00). De ahí su nombre.

Ejemplos:

char Greet[10] = "Hello"; // Un array de 10 elementoschar msg[] = "Hello"; // Un array de 6 elementos

El array Greet tiene espacio para 10 elementos, de los cuales solo los 5 primeros han sidollenados con las letras de Hello, el resto se rellena con ceros.

El array msg tiene 6 elementos porque además de las 5 letras de “Hello” se le ha añadido unNull (0x00) al final (claro que no se nota). Es decir, la inicialización de msg es equivalente a:

char msg[] = { 'H', 'e', 'l', 'l', 'o', 0x00}; // Un array de 6 elementos

Visto gráficamente, msg tendría la siguiente representación:

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES-----------------------------------------------------------------------------------------------------------------------------

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

Estructura de una cadena de texto.

16 LOS PUNTEROS

Los punteros suelen ser el tema que más cuesta entender en programación. Pero si ya llegasteaquí, es el momento menos indicado para detenerte.

Los punteros son un tipo de variables muy especial. Son variables que almacenan lasdirecciones físicas de otras variables. Si tenemos la dirección de una variable, tenemos acceso aesa variable de manera indirecta y podemos hacer con ellas todo lo que queramos.

16.1 Declaración de punteros

Los punteros pueden apuntar a todo tipo de variables, pero no a todas al mismo tiempo. Ladeclaración de un puntero es un tanto peculiar. En realidad, se parece a la declaración de unavariable ordinaria solo que se pone un asterisco de por medio. En este punto debes recordar lasdeclaraciones de todo tipo de variables que hemos visto, incluyendo las influenciadas por loscalificadores const, static, etc. Todas excepto los arrays; ¿por qué?

La forma general de declarar un puntero es la siguiente:

data_type * PointerName;

Los siguientes ejemplos muestran lo fácil que es familiarizarse con la declaración de lospunteros:

int * ip; // ip es un puntero a variable de tipo intchar * ucp; // cp es un puntero a variable de tipo charunsigned char * ucp; // Puntero a variable de tipo unsigned charconst long * clp; // Puntero a constante de tipo longfloat * p1, *p2; // Declara dos punteros a variable de tipo float

16.2 Apuntando a variables

Decimos que una variable puntero “apunta” a una variable x si contiene la dirección de dichavariable. Para ello se utiliza el operador &, el cual extrae la dirección de la variable a la queacompaña. Un puntero siempre debería apuntar a una variable cuyo tipo coincida con el tipo delpuntero.

En los siguientes ejemplos vemos cómo apuntar a variables de tipo básico, como int, char ofloat. Más adelante veremos cómo apuntar a arrays.

void main (void){

int height, width;char a, b, c;float max;

int * ip; // ip es un puntero a variable tipo int

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES-----------------------------------------------------------------------------------------------------------------------------

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

char * cp; // cp es un puntero a variable tipo charfloat * fp; // Puntero a variable tipo float

ip = &height; // Con esto ip tendrá la dirección de heightip = &width; // Ahora ip apunta a width

cp = &a; // cp apunta a acp = &c; // Ahora cp apunta a ccp = &a; // Ahora cp apunta a a otra vez

fp = &max; // fp apunta a maxfp = &height; // Error! height no es una variable float

//...}

16.3 Asignaciones indirectas mediante punteros

Una vez que un puntero apunte a una variable cualquiera, se puede acceder a dicha variableutilizando el nombre del puntero precedido por un asterisco, de esta forma:

void main (void){

int height, width, n; // Variables ordinariasint * p, * q; // p y q son punteros a variables de tipo int

p = &height; // p apunta a height*p = 10; // Esto es como height = 10

p = &width; // p apunta a width*p = 50; // Esto es como width = 50

height = *p; // Esto es como height = width

q = &height; // q apunta a heightn = (*p + *q)/2; // Esto es como n = (height + width)/2//...

}

La expresión *p se debería leer: “la variable apuntada por p”. Eso también ayuda mucho acomprender a los punteros.

¿Y para esto se inventaron los punteros? Yo me preguntaba lo mismo en mis inicios. El tema delos punteros se puede complicar casi “hasta el infinito”, por eso quiero ir con cuidado y poco apoco para que nadie se pierda.

16.4 PUNTEROS Y ARRAYS

¿Cómo se declara un puntero a un array? Un puntero a un array es simplemente un puntero altipo de dato del array. Cuando se asigna un puntero a un array, en realidad el puntero toma ladirección de su primer elemento, a menos que se especifique otro elemento.

Luego, bastaría con modificar el valor del puntero para que apunte a los otros elementos delarray. Todo lo indicado se refleja en el siguiente código:

void main (void){

int * p; // Declara p como puntero a intint n; // Alguna variableint mat[3] = { 78, 98, 26 }; // Array de variables int

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES-----------------------------------------------------------------------------------------------------------------------------

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

p = &mat; // p apunta a mat (a su primer elemento)n = *p; // Esto da n = 78p++; // Incrementar p para apuntar a siguiente elementon = *p; // Esto da n = 98p++; // Incrementar p para apuntar a siguiente elementn = *p; // Esto da n = 26*p = 10; // Con esto mat[3] valdrá 10p--; // Decrementar p para apuntar a elemento anterior*p = 100; // Con esto mat[2] valdrá 100p = mat; // p apunta a mat. Es lo mismo que p = &matp = NULL; // Desasignar p. Lo mismo que p = 0x0000// ...

}

En el fondo los arrays y los punteros trabajan de la misma forma, por lo menos cuandoreferencian a variables almacenadas en la RAM del microcontrolador. La única diferencia esque los arrays no pueden direccionar a datos diferentes de su contenido; por eso también se lesllama punteros estáticos. En la práctica esto significa que un array es siempre compatible con unpuntero, 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 valorespara que apunten a otros elementos. Por lo demás, las operaciones de asignación son similarespara punteros y arrays, tal como se puede apreciar en el siguiente código. (Por si las moscas,str1 es el array y str2, el puntero.)

void main(void){

char str1[] = { 'A', 'r', 'r', 'a', 'y' };char * str2 = { 'P', 'o', 'i', 'n', 't', 'e', 'r' };

char a;

a = str1[0]; // Esto da a = 'A'a = str1[3]; // Esto da a = 'a'

a = str2[0]; // Esto da a = 'P'a = str2[3]; // Esto da a = 'n'

str1 += 2; // Error! Str1 es estáticostr2 += 2; // Correcto. Ahora str2 apunta a 'i'

str1++; // Error otra vez! Str1 es estáticostr2++; // 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 función, en realidad le entrega una copiasuya; por lo que la variable original no tiene por qué ser afectada por el código de la función.Ahora bien, pasar una variable por referencia significa que se pasa la dirección de dichavariable. Como consecuencia, la función tendrá acceso a la variable original y podrá modificarsu contenido. Esto podría resultar riesgoso, pero, bien usada, la técnica es una potente arma.

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES-----------------------------------------------------------------------------------------------------------------------------

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

Ya que los punteros operan con direcciones de variables, son el medio ideal para trabajar conparámetros por referencia. Hay dos casos de particular interés: uno, cuando deseamos en serioque la variable pasada a la función cambie a su regreso; y dos, cuando la variable pasada esdemasiado grande (un array) como para trabajar con copias. De hecho, los arrays siempre sepasan por referencia ya que también son punteros al fin.

La sintaxis de los punteros en el encabezado de la función no es nada nuevo, teniendo en cuentaque también tienen la forma de declaraciones de variables.

En el siguiente ejemplo la función interchange intercambia los valores de las dos variablesrecibidas. En seguida explicaré por qué varía un poco la forma en que se llama a la función.

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 función 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, lafunción interchange recibirá dichos valores en p1 y p2, respectivamente. De ese modo, p1 y p2estarán apuntando a i y j, y podremos modificar sus valores.

Ten presente que se mantiene la forma de asignación “puntero = &variable” (puntero igual adirección de variable).

Ahora veamos ejemplos donde la forma de asignación cambia a “puntero = puntero”. Estoincluye 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 función prom, la cual devuelve el valorpromedio de los elementos del array recibido. Como para ese cálculo se necesita conocer lacantidad de elementos que tiene el array, prom recibe dicho valor en el parámetro size.

float prom ( int * p, int size ){

int i; float tmp = 0;for ( i=0; i<size;i++ )//Bucle para contar i desde 0 hasta size-1.

tmp += p[i]; // Sumar elemento p[i] a tmp.return ( tmp/size ); // Retornar valor promediado.

}

void main (void){int array1[4] = { 51, 14, 36, 78 }; // Un array de 4 elementos

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES-----------------------------------------------------------------------------------------------------------------------------

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

int array2[] = { -85, 4, 66, 47, -7, 85 }; // Un array de 6 elementosfloat avrg; // Una variable tipo float, para decimales

avrg = prom (array1, 8);// Ahora avrg debería valer (51 + 14 + 36 + 78 )/8 = 44.75

avrg = prom (array2, 6);// Ahora avrg debería 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 enmayúsculas, y lon calcula la longitud del texto almacenado en el array recibido. Ambasfunciones reciben el array pasado en un puntero p dado que son compatibles.

void mayus( char * p ){

while( *p ) // Mientras carácter apuntado sea diferente de 0x00{if( ( *p >= 'a' ) && ( *p <= 'z' ) ) // Si carácter apuntado es

// minúscula*p = *p - 32; // Hacerlo mayúscula

p++; // Incrementar p para apuntar sig. carácter}

}int lon( char * p){

int i = 0; // Declarar variable i e iniciarla a 0.while( *p ) // Mientras carácter apuntado sea diferente de 0x00{

i++; // Incrementar contador.p++; // Incrementar p para apuntar sig. carácter

}return i; // Retornar i

}void main (void){

int L;char song1[20] = "Dark Blue";char song2[20] = "Staring Problem";char song3[20] = "Ex-Girlfriend";

/* Obtener longitudes de los arrays de texto */L = lon(song1); // Debería dar L = 9L = lon(song2); // Debería dar L = 15L = lon(song3); // Debería dar L = 13

/* Convertir cadenas en mayúsculas */

mayus(song1 ); // Es lo mismo que mayus(&song1);// Ahora song1 debería valer "DARK BLUE"

mayus(song2 ); // Es lo mismo que mayus(&song2);// Ahora song2 debería valer "STARING PROBLEM"

mayus(song3 ); // Es lo mismo que mayus(&song3);// Ahora song3 debería valer "EX-GIRLFRIEND"

while(1); // Bucle infinito

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES-----------------------------------------------------------------------------------------------------------------------------

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

}

En el programa se crean tres arrays de texto de 20 elementos (song1, song2 y song3), pero eltexto almacenado en ellos termina en un carácter 0x00.

Según la tabla de caracteres ASCII, las letras mayúsculas están ubicadas 32 posiciones pordebajo de las minúsculas. Por eso basta con sumarle o restarle ese valor a un carácter ASCIIpara pasarlo a mayúscula o minúscula.

En ambas funciones el puntero p navega por los elementos del array apuntado hasta queencuentra el final, indicado por un carácter nulo (0x00).

17. ARRAYS CONSTANTES

No es que me haya atrasado con el tema, es solo que los arrays constantes son uno de los temascuyo tratamiento varía mucho entre los distintos compiladores. Veamos en qué.

Un array constante es uno cuyos elementos solo podrán ser leídos pero no escritos; tan simplecomo eso.

En principio, para que un array sea constante a su clásica declaración con inicialización de unarray se le debe anteponer el calificador const. No es posible declarar un array constante vacío yllenar sus elementos después pues eso equivaldría a modificar sus elementos. Enseguidatenemos ejemplos de declaración de arrays constantes:

const int a[5] = { 20, 56, 87, -58, 5000 }; // Arrayconstante

const char vocals[5] = { 'a', 'e', 'i', 'o', 'u' }; // Arrayconstante

const char text[] = "Este es un array constante de caracteres";

De este modo, los arrays a, vocals y text serán de solo lectura, y sus elementos podrán serleídos, mas no escritos. El compilador mostrará mensajes de error si en lo que resta delprograma encuentra intentos de cambio, por ejemplo, como

a[4] = 100; // Esto generará un error en tiempo de compilaciónvocals[0] = 'a';//Error! no se puede escribir por mas que sea el mismo dato

Ahora bien, que los datos no cambien durante la ejecución del programa no necesariamentesignifica los arrays constantes estén ubicados en la memoria FLASH. En algunos compiladoresde PICs, como CCS C y Hitech C, sí ocurre así, pero el lenguaje C solo dice que estos datos soninmodificables, no dice dónde deben residir. Recordemos que las variables en un programa decomputadora, constantes o no, van siempre en la RAM. Para las computadoras no es problemaporque 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 métodos alternos para declarar arrays, ovariables en general, que puedan residir en la memoria FLASH. Con eso no solo se destina lapreciada RAM a otras variables sino que se pueden usar grandes arrays constantes quesimplemente no podrían caber en la RAM. Como todo esto va al margen de lo que diga el ANSIC, cada compilador ha establecido su propia sintaxis para hacerlo.

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES-----------------------------------------------------------------------------------------------------------------------------

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

Empecemos por examinar el estilo de AVR GCC. Por ejemplo, si queremos que los tresprimeros arrays de esta página se almacenen en la FLASH debemos declararlas e inicializarlasde esta forma

PROGMEM const int a[5] = { 20, 56, 87, -58, 5000 };// Array constantePROGMEM const char vocals[5] = { 'a', 'e', 'i', 'o', 'u' }; // Array constantePROGMEM const char text[] = "Este es un array constante de caracteres";

Observa que fue tan simple como añadirles al inicio la palabra reservada PROGMEM. Elcalificador const era opcional en las versiones pasadas de AVR GCC como la que viene conAVR Studio 5, pero es necesaria en las versiones recientes como la que trae Atmel Studio 6. Detodos modos es sencillo. Lo complicado viene después. Para acceder a los elementos de estosarrays hay que emplear una forma un tanto exótica. Se deben usar algunas macros propias delcompilador, todas proveídas por la librería pgmspace.h. Las principales son estas cuatro

pgm_read_byte. Lee de un array residente en FLASH un entero compuesto por un bytede dato. En el lenguaje C los únicos datos de este tamaño son del tipo char y susderivados signed char y unsigned char.

pgm_read_word. Lee de un array residente en FLASH un entero compuesto por 2 bytes.En el C serían 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 4bytes. En el C serían arrays de tipo (signed) long y unsigned long.

pgm_read_float. Lee de un array residente en FLASH un número de punto flotantecompuesto por 4 bytes. En el lenguaje C serían arrays de tipo float y double operandoen modo de 32 bits.

Estas macros reciben como argumento la dirección de un elemento del array en FLASH. Comola dirección de una variable cualquiera en el C se obtiene al aplicarle el operador &, las macroscitadas trabajan de esta forma.

PROGMEM const int a[5] = { 20, 56, 87, -58, 5000 }; // Array constantePROGMEM const char vocals[5] = { 'a', 'e', 'i', 'o', 'u' }; // Array constantePROGMEM const char text[] = "Este es un array constante de caracteres";

int var;char c;

var = pgm_read_word(&a[1]); // Con esto var valdrá 56c = pgm_read_byte(&vocals[3]); // Con esto c valdrá 'o'c = pgm_read_byte(&text[11]); // Con esto c valdrá 'a'

El manual de AVR GCC nos presenta una forma que puede resultar más fácil de asimilar elacceso a los elementos de estos arrays: dice que primero asumamos acceder al elemento como siperteneciera 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 expresión. Es muyimportante recordar esto puesto que las expresiones previas son también válidas para el

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES-----------------------------------------------------------------------------------------------------------------------------

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

compilador y no generará errores. Solo nos daremos con la sorpresa de ver nuestro programafuncionando desastrosamente. Me pasa con frecuencia porque en mis códigos tengo lacostumbre de ubicar primero los arrays en la RAM para luego de obtener buenos resultadosmudarla a la memoria FLASH. Si eres nuevo te recomiendo seguir la misma práctica. Trabajarcon datos en FLASH desde el inicio requiere de mucha experiencia. Hay otras macros y tipos dedatos que debemos saber usar, y si no estamos seguros de lo que hacemos, repito, el compiladorno nos ayudará.

Los parámetros 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 medianteellas podemos acceder a los arrays cuyos elementos cubren un espacio de 216 = 65536 bytes dela memoria FLASH. En la gran mayoría de los casos es mucho más de lo que se necesita,considerando que solo hay dos megaAVR que superan esa memoria, los ATmega128 y losATmega256. Pero si se presentara la descomunal situación donde tengamos que trabajar conarrays de más de 64 KB, la librería pgmspace.h nos provee de otras macros ad hoc. Retomamoseste aspecto al final de la página.

18.1 Variables PROGMEM locales

Las variables PROGMEM tienen un espacio asignado en la memoria que no cambia durante laejecución del programa. Parece una perogrullada pero los lectores perspicaces saben que esecomportamiento es propio de dos tipos de variables del C: las variables globales y las variablesstatic locales. Todos los otros tipos de variables tienen posiciones dinámicas.

Más que una coincidencia, lo dicho arriba es una condición necesaria para todas las variablesalmacenadas en la FLASH para AVR IAR C y AVR GCC. Es decir, en estos compiladores lasvariables PROGMEM deben o ser globales o static locales. Todos los ejemplos mostradosarriba funcionan bien asumiendo que están declaradas a nivel global. Si los colocamos dentro deuna función habrá problemas.

Por ejemplo, el siguiente extracto de función no dará errores en AVR GCC pero el programafuncionará defectuosamente, a pesar de que los arrays están declarados e inicializados conformea 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 están declarados como si fueran locales ordinarias. Si los hubiéramosdeclarado globalmente estaría bien. Pero como son locales es necesario que sean además de tipostatic. Como sabemos, estas variables en C se forman añadiéndoles la palabra reservada static asu declaración habitual. Con esto aclarado, el código anterior trabajará perfectamente si loescribimos de esta forma.

/************************************************************* Toca las notas del ringtone apuntado por pRingtone.**********************************************************/

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES-----------------------------------------------------------------------------------------------------------------------------

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

void Tune(PGM_P pRingtone){ // C C# D D# E F F#static PROGMEM const unsigned int NoteFreqs[] = {262,277,294,311,330,349,370};static PROGMEM const unsigned char Octaves[]= {6,7,5};static PROGMEM const unsigned int Bpms[]= {0,812,406,270,203,162,135};static PROGMEM const unsigned char Durations[] = {4,8,1,2};

/* ... */}

Ese trozo de código pertenece al programa de la práctica reproductor de ringtones PICAXE. Sideseas comprobar lo expuesto puedes descargarlo y recompilarlo haciendo las modificacionesexplicadas.

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 espermitir 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. Sudefinición 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 más transparentes posible trato de evitarel uso excesivo de los #defines que conducen a términos innecesarios. El hecho de estarestudiando PGM_P sugiere que se trata de una excepción. Notemos en primer lugar que elarchivo avr_compiler.h que se usa en esta web define PROGMEM como __flash con lo cual lasdos expresiones de arriba serían idénticas asumiendo que en AVR GCC const PROGMEM charequivale a const char PROGMEM y también a PROGMEM const char, siendo esta últimapresentación la forma en que hemos venido trabajando. Debido a ello en muchas ocasionespodremos prescindir de PGM_P, pero surgirán algunos casos en que AVR IAR C muestre sudisconformidad por ese reacomodo de términos. PGM_P no solo termina de arreglar estosdesajustes sino que facilita notablemente la escritura del código ante la aparición deconstrucciones más complejas como las que veremos después.

Si PGM_P define un tipo puntero que apunta a variables char (o de un byte en general), alguienpodría preguntar cuáles son los punteros para las variables de tipo int, short, float, etc. No haydefiniciones especiales para esos casos. Podemos crearlas por cuenta propia si deseamos peroserá raramente necesario porque a fin de cuentas el puntero seguirá siendo de 16 bits. Solo sueleinteresar la dirección de un dato. Para leer ese dato con el formato deseado habrá que usar lamacro 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 AVRGCC Notes es un array de enteros de 16 bits y Octaves un array de enteros de 8 bits, ambosresidentes en la FLASH.

#include "avr_compiler.h"

PROGMEM const unsigned int Notes[] = {262, 277, 294, 311, 494};PROGMEM const unsigned char Octaves[] = {6, 7, 5};

int main(void)

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES-----------------------------------------------------------------------------------------------------------------------------

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

{PGM_P p; // Declarar el puntero punsigned char c;unsigned int n;

p = (PGM_P)Octaves; // Apuntar al array Octavesc = pgm_read_byte(p + 1); // Obtener el elemento Octaves[1]

p = (PGM_P)Notes; // Apuntar al array Notesn = pgm_read_word((PROGMEM int*)p + 3); // Obtener Notes[3]

while(1);}

Como los arrays pueden ser entendidos como punteros también, en principio se podrían hacerlas asignaciones a p directamente como p = Notes, pero para evitar protestas del compilador sedeben usar conversiones de tipo, poniendo al lado izquierdo y entre paréntesis el tipo de lavariable 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 porquees un array de bytes y para Notes usamos pgm_read_word porque es un array de enteros de 2bytes.

A pgm_read_byte se le envía el puntero p más el índice del elemento accedido. Recordemos queestas macros reciben direcciones y como los punteros contienen direcciones, no es necesariosacar direcciones mediante el operador &. Este caso fue sencillo porque el tipo de Octave seacoplaba fácilmente a PGM_P.

pgm_read_word también espera recibir una dirección pero no se le puede enviar a pdirectamente como en el caso previo. Es preciso hacer una conversión de tipo para que el valorde p sea entendido como una dirección de variables de 2 bytes. Por eso colocamos al ladoizquierdo de p la expresión (PROGMEM int *) que se adapta al tipo del array Notes. Tambiénse pudo haber puesto (PROGMEM const unsigned int *) para una mejor claridad en lacorrespondencia. 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 bytesque son accedidas mediante la macro pgm_read_byte. De hecho es así en la gran mayoría de loscasos y todo queda bien. La legibilidad se pierde en programas como el ejemplo previo donde elmismo puntero se usa también para acceder a un array de enteros de 2 bytes. Si de todos modosvamos a estar haciendo conversiones de tipos lo más recomendable sería 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 AVRIAR 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 alpuntero PGM_P, así que lo asimilaremos de inmediato. El programa anterior por ejemplo sepuede reescribir de la siguiente forma. (El código quedó con mejor acabado y con una lindasimetría.)

#include "avr_compiler.h"

PROGMEM const unsigned int Notes[] = {262, 277, 294, 311, 494};PROGMEM const unsigned char Octaves[] = {6, 7, 5};

int main(void){

PGM_VOID_P p; // Declarar el puntero p

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES-----------------------------------------------------------------------------------------------------------------------------

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

unsigned char c;unsigned int n;

p = (PGM_VOID_P)Octaves; // Apuntar al array Octavesc = pgm_read_byte((PROGMEM char*)p + 1); // Obtener el elemento Octaves[1]

p = (PGM_VOID_P)Notes; // Apuntar al array Notesn = pgm_read_word((PROGMEM int*)p + 3); // Obtener Notes[3]

while(1);}

Los punteros PGM_P y PGM_VOID_P también pueden actuar sobre variables de tipocomplejo. Estamos hablando por ejemplo de estructuras definidas por el usuario. Por el mismohecho de ser variables complejas poner aquí un programa de demostración abarcaría demasiadoespacio. Prefiero remitirme a la librería para USB que distribuye Atmel. Me parece un perfectoejemplo. Puedes encontrarla en varias notas de aplicación como AVR270, AVR271, AVR272 yAVR273, por citar algunas. En el archivo usb_standar_request.c se declara y usa el punteropbuffer de tipo PGM_VOID_P para acceder a los descriptores de USB que por su tamañoresiden en la FLASH.

Esa librería USB se vale de un archivo llamado compiler.h para guardar la compatibilidad decódigos entre los compiladores AVR IAR C y AVR GCC para los que está escrita. Contienevarias imprecisiones que, imagino, se deben a los defectos que AVR GCC presentabaantiguamente, cuando se escribió la librería. 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 quelas variables que se le envían. Si las variables son residentes en la FLASH, lo cual deja suponerque son arrays o estructuras complejas, el método a usar son los punteros, no solo por el tamañode esas variables sino por la capacidad de adaptación de los punteros que estudiamos en lasección anterior.

Allí se describió la operación de los punteros PGM_P y PGM_VOID_P y se explicó loimprescindibles que serían. No es recomendable que los argumentos de las funciones seanpunteros con tipos diferentes de PGM_P o PGM_VOID_P. Solo ellos garantizan que nuestrosprogramas funcionarán bien en los dos compiladores AVR IAR C y AVR GCC. Dicho eso,podemos pasar al ejemplo.

En el siguiente programa la función print imprime un mensaje por el puerto serie, similar a putsde la librería stdio.h del compilador. La función puts solo trabaja con mensajes en la RAM adiferencia de print que recibe arrays residentes en la FLASH. Los dos compiladores que usamostambién ofrecen funciones de FLASH y a eso pretendemos llegar: a su uso.

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

PROGMEM const char rt01[] = "\r Deck the halls";PROGMEM const char rt02[] = "\r Jingle bells";PROGMEM const char rt03[] = "\r We wish you a merry christmas";PROGMEM const char rt04[] = "\r Silent night";

/************************************************************** Envía por el USART el texto pasado en p*******************************************************/

void print(PGM_P p){

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES-----------------------------------------------------------------------------------------------------------------------------

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

char c;while( (c = pgm_read_byte(p++)) != 0x00 )

putchar(c);}

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

int main(void){

usart_init(); // Inicializar USARTprint(rt01); // Imprimir mensaje de rt01print(rt02); // ...print(rt03); // ...print(rt04); // ...

while (1);}

Creo que el código está bastante claro. Como los arrays son de texto (de caracteres de 1 byte), seoptó por el puntero PGM_P y por la macro pgm_read_byte para la que no fue necesaria unaconversión de tipo. La conversión de tipo para p es opcional, por ejemplo, también se pudoescribir print((PGM_P)rt01).

Y ahora la pregunta que nos trae aquí: ¿Se puede enviar a una función una variable en flashdirectamente? Es decir, qué pasa si en vez de declarar los arrays por separado, los escribimosdirectamente en el argumento de la siguiente forma.

print("\r Deck the halls"); // Imprimir este mensajeprint("\r Jingle bells"); // ...print("\r We wish you a merry christmas"); // ...print("\r Silent night"); // ...

El compilador AVR GCC todavía acepta las sentencias y construye el programa limpiamente,sin presentar errores, ni siquiera advertencias. Pero el resultado es un programa mostrandomamarrachos en vez de los villancicos esperados. El compilador AVR IAR C, por su parte,simplemente no admite el código fuente. ¿Qué pasó?

Como variables locales ordinarias que son, los compiladores tratan de implementar las cadenaspasadas en la memoria RAM. AVR IAR C nota la incompatibilidad de tipos y rechaza el códigode plano, en tanto que AVR GCC sí cumple el cometido pasando por alto la divergencia detipos porque, recordemos, este compilador hace la diferencia en el momento de acceder a lasvariables usando sus macros como pgm_read_byte. Esa macro recibe en el programa unadirección RAM (también de 16 bits) y la usa para leer de la memoria FLASH como si lascadenas de texto estuvieran allí. Lee "cualquier cosa" menos las cadenas.

Alguien más avezado podría decir que eso se puede arreglar con conversiones de tipos porejemplo reescribiendo las sentencias así

print((PGM_P)("\r Deck the halls")); // Imprimir este mensajeprint((PGM_P)("\r Jingle bells")); // ...print((PGM_P)("\r We wish you a merry christmas")); // ...print((PGM_P)("\r Silent night")); // ...

El código vuelve a compilarse limpiamente. Hasta AVR IAR C es engañado. Pero cuandovemos el programa en acción descubrimos que solo nos hemos engañado a nosotros mismos. Eneste programa los garabatos que se visualizan en el terminal serial nos quitaron la venda de losojos rápidamente, felizmente. En otras circunstancias detectar el error hubiera costado más. Las

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES-----------------------------------------------------------------------------------------------------------------------------

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

cadenas siguen siendo colocadas en la RAM. Recordemos que para que las variables residan enla FLASH deben o ser globales o static locales. Lo primero es obviamente un imposible; y losegundo, que sean static, solo es posible en el compilador AVR GCC gracias a una macrollamada PSTR que inicializa el array como static y toma su dirección automáticamente. Lasiguiente construcción entonces funcionará como se desea pero solo en este compilador.

print(PSTR("\r Deck the halls")); // Imprimir este mensajeprint(PSTR("\r Jingle bells")); // ...print(PSTR("\r We wish you a merry christmas")); // ...print(PSTR("\r Silent night")); // ...

Solo por curiosidad, la macro PSTR tiene la siguiente definición. PSTR es ampliamente usadacuando se trabaja con las funciones _P del compilador AVR GCC. Así que hablaremos más deella 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 programación.

Para crear un array de cadenas en FLASH primero se declaran e inicializan las cadenas de laforma ya conocida y luego se construye el array con los nombres de las cadenas. Esta regla esúnica, inflexible, limitante e igualmente válida para los dos compiladores que usamos, AVRIAR C y AVR GCC. Con un ejemplo lo vamos a entender mejor.

El objeto del siguiente programa es idéntico al ejemplo de la sección anterior: el programa debemostrar los mismos mensajes almacenados en la FLASH solo que esta vez se les desea accedermediante un índice, por eso los mensajes están agrupados en un array.

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

PROGMEM const char ringt01[] = "\r Deck the halls";PROGMEM const char ringt02[] = "\r Jingle bells";PROGMEM const char ringt03[] = "\r We wish you a merry christmas";PROGMEM const char ringt04[] = "\r Silent night";

PGM_P ringtones[] ={

ringt01,ringt02,ringt03,ringt04

};

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

int main(void){

usart_init(); // Inicializar USART

puts_P(ringtones[0]); // Imprimir Deck the hallsputs_P(ringtones[1]); // Imprimir Jingle bellsputs_P(ringtones[2]); // Imprimir We wish you a merry christmasputs_P(ringtones[3]); // Imprimir Silent night

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES-----------------------------------------------------------------------------------------------------------------------------

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

while (1);}

La función puts_P es proveída por los compiladores. Es similar a la función puts pero lascadenas que recibe deben ubicarse en la FLASH. En otras palabras, puts_P es similar a lafunción print que creamos en el ejemplo previo.

Se nota que el array ringtones ha sido declarado para ubicarse en la RAM, por eso accedemos asus elementos de forma regular y no empleando macros. Se hizo así porque cada elemento es unpuntero de 2 bytes y como solo son 4 punteros, no ocupan mucho espacio. Si hubiera muchosmás elementos en el array ringtones, la situación cambiaría y sería mejor que también residieraen la FLASH. Eso lo veremos al final.

Elaborar el array y el contenido de sus elementos por separado es incómodo por el hecho detener que poner nombres a cada elemento, nombres que no son necesarios en otra parte delprograma, pero no hay otro camino. Quisiéramos que fuera posible implementar el array porejemplo como se muestra abajo donde cada elemento se inicializa directamente, pero eso solo esfactible 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á loselementos en la RAM. En AVR GCC ni siquiera la macro PSTR, que sirve para inicializar enlínea datos residentes en FLASH, podrá ayudarnos esta vez. Y, lo olvidaba, no intentes forzar eldestino del array o de sus elementos utilizando conversiones de tipos. Solo evadirás los erroresy advertencias, pero los datos seguirán yendo a la RAM y el programa funcionaráincorrectamente. (Haz clic aquí si quieres ver la versión 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 presentación coincide con la forma en que hemos estado trabajando antes: primeroPROGMEM, luego const y al final el tipo de dato. En AVR IAR C una permutación a vecesproducirá incompatibilidades. Por tanto, el uso de PGM_P más que una cuestión desimplificación es una necesidad que facilitará la compatibilidad de códigos.

Como dijimos antes, cada elemento del array ringtones es un puntero de 2 bytes y en conjuntono ocupan mucha RAM en este programa. Ahora bien si nuestro código requiriera incluso eseespacio para otros datos o si el array es bastante más grande, entonces el mismo array ringtonestambién debería almacenarse en la memoria FLASH. Solo hay un arreglo para esto y, como yadiscutimos demasiado, vamos directamente a poner la forma que debe tener el programa en esecaso.

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

PROGMEM const char ringt01[] = "\r Deck the halls";PROGMEM const char ringt02[] = "\r Jingle bells";PROGMEM const char ringt03[] = "\r We wish you a merry christmas";PROGMEM const char ringt04[] = "\r Silent night";

PROGMEM PGM_P const ringtones[] ={

ringt01,ringt02,ringt03,

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES-----------------------------------------------------------------------------------------------------------------------------

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

ringt04};

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

int main(void){

usart_init(); // Inicializar USART

puts_P((PGM_P)pgm_read_word(&ringtones[0]));//Imprimir 'Deck the halls'puts_P((PGM_P)pgm_read_word(&ringtones[1])); // Imprimir 'Jingle bells'puts_P((PGM_P)pgm_read_word(&ringtones[2]));//Imprimir 'We wish you a ...puts_P((PGM_P)pgm_read_word(&ringtones[3])); // Imprimir 'Silent night'

while (1);}

La conversión de tipo con (PGM_P) no es necesaria para AVR IAR C y para AVR GCC sirvepara evitar warnings aunque el programa funciona igual.

Cambiando de tema, en AVR GCC el programa se compila en 528 bytes de memoria FLASH y20 bytes de RAM. En CodeVisionAVR había tomado 490 bytes de FLASH y solo 8 bytes deRAM. 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 Chubiera puesto los datos en la RAM, pero no. Es el estilo de este compilador tomar un poco másde RAM para ahorrar más FLASH. En los tres casos la compilación se realizó con laoptimización a máximo nivel.

18.5 Arrays más que grandes

Los parámetros 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 medianteellas podemos acceder a los arrays cuyos elementos cubren un espacio de 216 = 65536 bytes dela memoria FLASH. En la gran mayoría de los casos es mucho más de lo que se necesita,considerando que solo hay dos megaAVR que superan esa memoria, los ATmega128 y losATmega256. Pero si se presentara la descomunal situación donde tengamos que trabajar conarrays de más de 64 KB, la librería 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. Ahorallevan el sufijo _far. Estas macros reciben como argumento direcciones de 32 bits con lo queteóricamente tienen un alcance de hasta 4 GB de datos en FLASH. Como en la práctica lospunteros X, Y y Z de los AVR de 8 bits solo llegan a ser de 24 bits, su alcance es en realidad de16 MB. El uso de estas macros es completamente igual al de sus pares de 16 bits, por eso se vencomo redundantes los siguientes ejemplos. Las variables sobre las que actúan también deben serdeclaradas con PROGMEM.

PROGMEM const int a[5] = { 20, 56, 87, -58, 5000 }; // Array constantePROGMEM const char vocals[5] = { 'a', 'e', 'i', 'o', 'u' }; // Array constantePROGMEM const char text[] = "Este es un array constante de caracteres";

int var;char c;

UNIVERSIDAD NACIONAL DEL CALLAO FIEE - 2014V MICROCONTROLADORES-----------------------------------------------------------------------------------------------------------------------------

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

var = pgm_read_word_far(&a[1]); // Con esto var valdrá 56c = pgm_read_byte_far(&vocals[3]); // Con esto c valdrá 'o'c = pgm_read_byte_far(&text[11]); // Con esto c valdrá 'a'

Podemos entender que los únicos AVR que aceptan estas macros son los que tienen más de 64KB de memoria FLASH. Con esos AVR, es posible usar los dos tipos de macros, las de 16 bitsy las de 32 bits, sin embargo no siempre serán igual de eficientes. Si el código de arriba, porejemplo, estuviera escrito para un ATmega1284P, el acceso se dilataría ligeramente al tenerseque trabajar con 32 bits. Puede ser un detalle insignificante pero a veces servirá para optimizarprocesos.

Para complementar el tema, diremos que si existen macros con _far (lejos, en inglés), en lalibrería pgmspace.h también hay macros con el apéndice _near (cerca). Estas nuevas macros sonpgm_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 más que dealias 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 demás. Es bueno saberlopara no quedar sorprendidos por los códigos de quienes prefieren usar la una u otra forma.