PROGRAMACION EN C_AVR_2014.pdf

  • Upload
    mipapi

  • View
    47

  • Download
    4

Embed Size (px)

Citation preview

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

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

    UNIVERSIDAD NACIONAL DEL CALLAOFACULTAD DE INGENIERA ELCTRICA Y ELECTRNICAESCUELA PROFESIONAL ACADMICO DE INGENIERA

    ELECTRNICA

    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

    INTRODUCCINUn curso de microcontroladores como ste implica abarcar tres reas:

    Conocer el microcontrolador. Un microcontrolador es un circuito integrado genricocuyas partes debemos adaptar para que funcionen segn los requerimientos de nuestrodiseo. Obviamente no podramos programar lo que no conocemos.

    Conocer los perifricos externos. Un micro CONTROLADOR no sera muy til si notiene qu controlar. Muchos dispositivos a controlar o mediante los cuales se va acontrolar son comunes de la electrnica analgica, como transistores, rels, diodosLED, registros de desplazamiento e incluso los motores, y se da por hecho que el lectorya conoce lo suficiente de ellos. Tambin estn los perifricos que difcilmente 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 perifricos externos el que se cubre en un curso demicrocontrolador como ste.

    Conocer un lenguaje de programacin. Conocer un lenguaje de programacin 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 ms 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 bsicas).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. Adems, los procesadores no son como las personas: si en uncdigo de 100 lneas te olvidaste de una sola coma, los compiladores no te lo pasarn por alto.ESTRUCTURA DE UN PROGRAMA EN CTomaremos en cuenta este sencillsimo 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

    }//****************************************************************************// Funcin 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 ejecutacclicamente.Los elementos ms notables de un programa en C son; las sentencias, lasfunciones, las directivas, los comentarios y los bloques. A continuacin, una brevedescripcin de ellos.1.-LOS COMENTARIOSLos comentarios tienen el mismo propsito que en ensamblador: documentar yadornar el cdigo. Es todo es texto que sigue a las barritas // y todo lo que est entrelos signos /* y */. Se identifican fcilmente porque suelen aparecer en color verde.Ejemplos.// ste es un comentario simple/*sta es una forma de comentar varias lneas a la vez.Sirve mucho para enmascarar bloques de cdigo.

    */2.- LAS SENTENCIASUn programa en C, en lugar de instrucciones, se ejecuta por sentencias.Una sentencia es algo as como una mega instruccin, que hace lo que varias instrucciones delensamblador.Salvo casos particulares, donde su uso es opcional, una sentencia debe finalizar con un puntoy coma (;).As que tambin podemos entender que los; sirven para separar las sentencias. Alguna vez leque el compilador C lee el cdigo como si lo absorbiera con una caita, lnea por lnea, una acontinuacin 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 funcin main del programa de arriba bien puede escribirse del siguiente modo.//****************************************************************************// Funcin principal//****************************************************************************int main(void){

    DDRB = 0x01;for( ;; )

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

    }}Sorprendido? Podrs deducir que los espacios y las tabulaciones solo sirven paradarle un aspecto ordenado al cdigo. Es una buena prctica de programacinaprender a acomodarlas.Las sentencias se pueden clasificar en;

    sentencias de asignacin, sentencias selectivas, sentencias iterativas, sentenciasde llamadas de funcin, etc.

    Las describiremos ms adelante.3.- LOS BLOQUESUn 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 dndeempieza y termina cada bloque. Podrs ver cmo las tabulaciones ayudan a distinguir unosbloques de otros. Afortunadamente, los editores de los buenos compiladores C pueden resaltarcules son las llaves de inicio y de cierre de cada bloque. Te ser fcil acostumbrarte a usarlas.4.-LAS DIRECTIVASSon 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 smismas no son cdigo ejecutable. Suelen ser indicaciones sobre cmo se compilar el cdigo.Entre las pocas directivas del C estndar que tambin son soportadas por los compiladores Cpara AVR estn;#include (para incluir archivos, parecido al Assembler),#define (mejor que el #define del ensamblador)y las #if, #elif, #endif y similares. Fuera de ellas, cada compilador maneja sus propias directivasy sern 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 mejorestructuracin, un programa en C se puede componer de funciones. Por supuesto que lasfunciones son muchsimo ms potentes y, por cierto, algo ms complejas de aprender. Por eso nisiquiera el gran espacio que se les dedica ms 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 lafuncin principal, llamada main.Donde quiera que se encuentre, la funcin main siempre ser la primera en ser ejecutada. Dehecho, all empieza y no debera salir de ella.

    6.- Variables y Tipos de DatosEn 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 nmeros de 8, 16 32 bits (a veces msgrandes), con signo o sin l, nmeros enteros o decimales. Esos son los tipos de datos bsicos.Las variables de los compiladores pueden incluso almacenar matrices de datos del mismo tipo(llamadas arrays) o de tipos diferentes (llamadas estructuras). Estos son los tipos de datoscomplejos.Los siguientes son los principales tipos de datos bsicos 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 Tamao 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 - 1float 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 tambin manejantipos de un bit como bool (o boolean) o bit, pero con pequeas divergencias que pueden afectarla portabilidad de los cdigos adems 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 ms 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 ms grandes yde mayor precisin.Los especificadores signed (con signo) mostrados entre parntesis son opcionales. Es decir, dalo mismo poner int que signed int, por ejemplo. Es una redundancia que se suele usar parareforzar su condicin o para que se vea ms ilustrativo.El tipo char est pensado para almacenar caracteres ASCII como las letras. Puesto que estosdatos son a fin de cuentas nmeros tambin, es comn usar este tipo para almacenar nmeros de8 bits. Es decir es equivalente a signed char o unsigned char, dependiendo de la configuracinestablecida por el entorno compilador. Y como es preferible dejar de lado estas cuestiones, sivamos a trabajar con nmeros lo mejor es poner el especificador signed o unsigned en el cdigo.Quiz te preguntes cul es la diferencia entre los tipos de datos int y short si aparentementetienen el mismo tamao 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 debera ser de 16 bits, en tanto que int fueconcebido para adaptarse al bus de datos del procesador. Esto todava se cumple en laprogramacin 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 diseo un tipo int debera 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 entradicin y en regla de facto para los microcontroladores. Actualmente solo en CCS C el tipo intes de 8 bits. Es irnico para ser el compilador que menos respeta los tipos de datos del ANSI C.A pesar de todo, se nota que todava pueden aparecer ciertas imprecisiones en los tipos de datosque pueden perturbar la portabilidad de los programas entre los diferentes compiladores. Es poresto que el lenguaje C/C++ provee la librera stdint.h para definir tipos enteros que sern de untamao especfico independientemente de los procesadores y de la plataforma software en quese trabaje.

    Tabla de variables y tipos de datos del lenguaje CTipo de dato Tamao 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 fcil 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 Esta inclusin 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 cdigos 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 librera DLIB. Como

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

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

    las prcticas de curso trabajan sobre la librera CLIB, he evitado recurrir a los tipos extendidosde stdint.h.Finalmente, existen adems 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 lascaractersticas 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.-Declaracin de variablesEsta 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 ms simple de hacerlo es la siguiente:

    data_type myvar;Donde data_type es un tipo de dato bsico o complejo, del compilador o definido por el 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 tambin es una variable int con signounsigned int k; // k es una variable int sin signo

    Tambin es posible declarar varias variables del mismo tipo, separndolas 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 datosA la declaracin de una variable se le puede aadir un especificador de tipo como const, static,volatile, extern, register, etc. Dichos especificadores tienen diversas funciones y, salvo const, sesuelen usar en programas ms elaborados. Como no queremos enredarnos tan pronto, lodejaremos para otro momento.Una variable const debe ser inicializada en su declaracin. Despus 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; // Vlidob = 150; // Vlidoa = 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 ms que las variables constantes sean de solo lectura, ocuparn 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 clsicasdirectivas #define (como se hace en el ensamblador).

    #define a 100 // Definir constante a

    9.-SENTENCIAS SELECTIVAS

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

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

    Diagrama de flujo de la sentencia if.La forma codificada sera as:

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

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

    sentenciaB;sentenciaC;

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

    Despus de ejecutar sentenciaA el programa evala expresin. 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 ELSELa sentencia if brinda una rama que se ejecuta cuando una condicin lgica es verdadera.Cuando el programa requiera dos ramas, una que se ejecute si cierta expression es cierta y 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 cdigo 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 fcil, 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 ESCALONADAEs la versin ampliada de la sentencia if else.En el siguiente boceto se comprueban tres condiciones lgicas, aunque podra haber ms. 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 evalan de arriba abajo. Cuando alguna de ellas sea verdadera, se ejecutarsu bloque correspondiente y los dems bloques sern salteados. El bloque final (de else) seejecuta si ninguna de las expresiones es verdadera. Adems, si dicho bloque est vaco, puedeser omitido junto con su else.

    9.4 LA SENTENCIA SWITCHLa sentencia switch brinda una forma ms elegante de bifurcacin mltiple. Podemosconsiderarla como una forma ms estructurada de la sentencia if else if escalonada, aunquetiene algunas restricciones en las condiciones lgicas a evaluar, las cuales son comparaciones devalores enteros.Para elaborar el cdigo en C se usan las palabras reservadas switch, case, break y default.El siguiente esquema presenta tres cases pero podra haber ms, as como cada bloque tambinpodra tener ms sentencias.

    switch ( expression ){ case constante1: // Si expression = constante1, ejecutar este bloque

    sentencia1;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 cdigo 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

    Expresin puede ser una variable compatible con entero. No es una expresin que conduce auna condicin lgica 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 fcilmente. Los bloques incluyen la sentencia break. Qu es eso?La sentencia break hace que el programa salga del bloque de switch y ejecute la sentencia quesigue (en el boceto, sentenciaX). Atento!: de no poner break, tambin se ejecutar el bloque delsiguiente case, sin importar si su constante coincida con expression o no.No sera necesario poner el default si su bloque estuviera vaco.10. SENTENCIAS ITERATIVAS

    Las sentencias de control iterativas sirven para que el programa ejecute una sentencia o ungrupo de ellas un nmero determinado o indeterminado de veces. As es, esta seccin 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 versin ms 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 ingls) unaexpresin 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 evala 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 WHILEComo dije antes, es una variacin de la sentencia while simple. La principal diferencia es que lacondicin lgica (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 forLas 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 algn bucle de ese tipo.)sta es la sintaxis general de la sentencia for en C:

    for ( expression_1 ; expression_2 ; expression_3 ){

    sentencia1;sentencia2;// ...

    }; // Este ; es opcional

    Ahora veamos por partes cmo funciona:

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

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

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

    Por la forma y orden en que se ejecutan estas expresiones, el bucle for es equivalente a lasiguiente construccin, 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 ms rara an; as que, mejor, veamos estos ejemplos, que sonsus presentaciones ms clsicas. (i es una variable y a y b son constantes o variables):

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

    sentencias;}

    Se lee: para (for) i igual a 0 hasta que sea menor que 10 ejecutar sentencias. La sentencia i++indica que i se incrementa tras cada ciclo. As, el bloque de for se ejecutar 10 veces, desde 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 = 0 ; i-- ){

    sentencias;}

    Se pueden hacer muchas ms construcciones, todas coincidentes con la primera plantilla, perotambin son menos frecuentes.11.- SENTENCIAS CON BLOQUES SIMPLESCuando 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 b)

    a = 0;if(a == b)

    a++;else

    b--;while( a >= b)

    a = a + b;for(i=0; i

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

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

    Tabla de Operadores aritmticosOperador Accin++ Incrementar en uno-- Decrementar en uno

    Ejemplos:

    int a, b, c; // Declarar variables a, b y ca = 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 ms b/c ms bb. Resultado en bc = a % b; // Residuo de dividir ab 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 matemticas, estasexpresiones no son ecuaciones; significan las operaciones que indican sus comentarios.Por lo visto, los operadores ++ y -- funcionan igual si estn antes o despus de una variable enuna expresin 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 cdigo ms compacto, es decir, escribir dos sentencias en una.

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

    Si ++ o -- estn despus del operando, primero se evala la expresin y luego se suma oresta 1 al operando.

    int a, b; // Declarar variables enteras a y ba = b++; // Lo mismo que a = b; y luego b = b + 1;a = ++b; // Lo mismo que b = b + 1; y luego a = b;if (a++ < 10) // Primero comprueba si a < 10 y luego{ // incrementa a en 1

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

    // algn cdigo}

    12.2 OPERADORES DE BITSSe aplican a operaciones lgicas con variables a nivel binario. Aqu tenemos las clsicasoperaciones AND, OR inclusiva, OR exclusiva y la NEGACIN. Adicionalmente, he incluidoen esta categora 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 anlogos a los de las instrucciones deensamblador los operadores lgicos del C pueden operar sobre variables de distintos tamaos,ya sean de 1, 8, 16 32 bits.

    Tabla de operadores de bitsOperador Accin& AND a nivel de bits| OR inclusiva a nivel de bits^ OR exclusiva a nivel de bits~ Complemento a uno a nivel de bits> Desplazamiento a la derecha

    Ejemplos:char m; // variable de 8 bitsint n; // variable de 16 bitsm = 0x48; // m ser 0x48m = m & 0x0F; // Despus de esto m ser 0x08m = m | 0x24; // Despus de esto m ser 0x2Fm = m & 0b11110000; // Despus 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 mm = 0b00011000; // Cargar m con 0b00011000m = m >> 2; // Desplazar m 2 posiciones a la derecha

    // Ahora m ser 0b00000110n = 0xFF1F;n = n y

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

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

    Se emplean para construir las condiciones lgicas 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 Accin== Igual!= No igual> Mayor que< Menor que>= Mayor o igual que

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

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

    op puede ser cualquiera de los operadores aritmticos o de bit estudiados arriba. O sea, op puedeser +, - , *, /, %, &, | , ^, ~, >. Nota: no debe haber ningn espacio entre el operador y elsigno igual.Ejemplos:

    int a; // Declarar aa += 50; // Es lo mismo que a = a + 50;a += 20; // Tambin significa sumarle 20 a aa *= 2; // Es lo mismo que a = a * 2;a &= 0xF0; // Es lo mismo que a = a & 0xF0;a

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

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

    Donde: function_name es el nombre de la funcin. Puede ser un identificador cualquiera. data_type1 es un tipo de dato que identifica el parmetro de salida. Si no lo hubiera, se

    debe poner la palabra reservada void (vaco, en ingls). arg1 y arg2 (y puede haber ms) son las variables de tipos data_type1, data_type2...,

    respectivamente, que recibirn los datos que se le pasen a la funcin. Si no hay ningnparmetro de entrada, se pueden dejar los parntesis vacos o escribir un void entreellos.

    13.1 FUNCIONES SIN PARMETROSPara una funcin que no recibe ni devuelve ningn valor, la plantilla de arriba se reduce alsiguiente esquema:

    void function_name ( void ){

    // Cuerpo de la funcin}

    Y se llama escribiendo su nombre seguido de parntesis vacos, as:function_name();

    La funcin principal main es otro ejemplo de funcin sin parmetros. Dondequiera que seubique, siempre debera ser la primera en ejecutarse; de hecho, no debera terminar.

    void main (void){

    // Cuerpo de la funcin}

    13.2 FUNCIONES CON PARMETROS (POR VALOR)Por el momento, solo estudiaremos las funciones que pueden tener varios parmetros de entradapero solo uno de salida.Si la funcin no tiene parmetros de entrada o de salida, debe escribirse un void en su lugar. Elvalor devuelto por una funcin se indica con la palabra reservada return.Segn el comportamiento de los parmetros de entrada de la funcin, estos se dividen enparmetros por valor y parmetros por referencia. Lo expuesto en este apartado corresponde alprimer grupo porque es el caso ms ampliamente usado. Con esto en mente podemos seguir.Para llamar a una funcin con parmetros es importante respetar el orden y el tipo de losparmetros que ella recibe. El primer valor pasado corresponde al primer parmetro de entrada;el segundo valor, al segundo parmetro; y as sucesivamente si hubiera ms.Cuando una variable es entregada a una funcin, en realidad se le entrega una copia suya. 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' debera ser el menor entre 'a', 'b' y 'c'while (1); // Bucle infinito

    }

    En el programa mostrado la funcin minor recibe tres parmetros de tipo int y devuelve uno,tambin de tipo int, que ser el menor de los nmeros recibidos.El mecanismo funciona as: siempre respetando el orden, al llamar a minor el valor de a secopiar a la variable arg1; el valor de b, a arg2 y el valor de c, a arg3. Despus de ejecutarse elcdigo de la funcin 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 comprobacin de tipos de datos como Pascal, siempredeberamos revisar que los datos pasados sean compatibles con los que la funcin espera, ascomo los datos recibidos, con los que la funcin devuelve. Por ejemplo, estara mal llamar a lafuncin minor del siguiente modo:d = minor(-15, 100, 5.124); // Llamar a minor

    Aqu los dos primeros parmetros estn bien, pero el tercero es un nmero decimal (de 32 bits),no compatible con el tercer parmetro que la funcin espera (entero de 16 bits). En estos casosel compilador nos mostrar mensajes de error, o cuando menos de advertencia.13.3 PARMETROS POR REFERENCIALa funcin que recibe un parmetro por referencia puede cambiar el valor de la variable pasada.La forma clsica de estos parmetros se puede identificar por el uso del smbolo &, tal como seve en el siguiente boceto de funcin.

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

    // Cuerpo de la funcin.// arg1, arg2 y arg3 son parmetros por referencia.// Cualquier cambio hecho a ellos desde aqu afectar a las

    variables// que fueron entregadas a esta funcin al ser llamada.

    }

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

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

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

    13.4 PROTOTIPOS DE FUNCIONESEl prototipo de una funcin le informa al compilador las caractersticas que tiene, como su tipode retorno, el nmero de parmetros que espera recibir, el tipo y orden de dichos parmetros.Por eso se deben declarar al inicio del programa.El prototipo de una funcin es muy parecido a su encabezado, se pueden diferenciar tan solo 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 permitirn que dichas funciones sean accedidas desdecualquier parte del programa. Adems, sin importar dnde se ubique la funcin main, ellasiempre ser la primera en ejecutarse. Por eso su prototipo de funcin es opcional.

    #include void func1(char m, long p); // Prototipo de funcin "func1"char func2(int a); // Prototipo de funcin "func2"void main(void); // Prototipo de funcin "main". Es

    opcionalvoid main(void){

    // Cuerpo de la funcin// Desde aqu se puede acceder a func1 y func2

    }void func1(char m, long p){

    // Cuerpo de la funcin// Desde aqu se puede acceder a func2 y main

    }char func2(int a){

    // Cuerpo de la funcin// Desde aqu se puede acceder a func1 y main

    }

    La llamada a main, por supuesto, no tiene sentido; solo lo pongo para ilustrar.Si las funciones no tienen prototipos, el acceso a ellas ser restringido. El compilador solo verlas funciones que estn implementadas encima de la funcin llamadora o, de lo contrario,mostrar errores de funcin no definida. El siguiente boceto ilustra este hecho. (Atiende a loscomentarios.)

    #include void main(void){

    // Cuerpo de la funcin// Desde aqu no se puede acceder a func1 ni func2 porque estn abajo

    }void func1(char m, long p){

    // Cuerpo de la funcin// Desde aqu se puede acceder a main pero no a func2

    }char func2(int a){

    // Cuerpo de la funcin

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

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

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

    Para terminar, dado que los nombres de las variables en los parmetros de entrada sonopcionales, los prototipos de func1 y func2 tambin se pueden escribir asi

    void func1(char, long);char func2(int );

    14. VARIABLES LOCALES Y VARIABLES GLOBALESLos lenguajes de alto nivel como el C fueron diseados para desarrollar los programas msgrandes y complejos que se puedan imaginar, programas donde puede haber cientos devariables, entre otras cosas. Imaginas lo que significara 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 seraccesible. Hay diversos tipos de mbito, pero empezaremos por familiarizarnos con los dos msusados, que corresponden a las variables globales y variables locales.

    Las variables declaradas fuera de todas las funciones y antes de sus implementacionestienen carcter global y podrn ser accedidas desde todas las funciones.

    Las variables declaradas dentro de una funcin, incluyendo las variables delencabezado, tienen mbito local. Ellas solo podrn ser accedidas desde el cuerpo dedicha funcin.

    De este modo, puede haber dos o ms variables con el mismo nombre, siempre y cuando estnen diferentes funciones. Cada variable pertenece a su funcin y no tiene nada que ver con lasvariables de otra funcin, por ms que tengan el mismo nombre.En la mayora de los compiladores C para microcontroladores las variables locales debendeclararse al principio de la funcin.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 funcinint speed; // Variable globalconst long limit = 100; // Variable global constantevoid 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 speedvari = 0; // Esto dar ERROR porque vari solo pertenece

    // a la funcin foo. No compilar.}void main(void){

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

    de las funciones inter o foo */

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

    ------------------------------------------------------------------------------------------------------------------------------M.S.c Ing. 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 funcin y se destruyen al salir deella. Qu significa eso? Lo explico en el siguiente apartado.Si dentro de una funcin hay una variable local con el mismo nombre que una variable global, laprecedencia en dicha funcin la tiene la variable local. Si te confunde, no uses variablesglobales y locales con el mismo nombre.

    14.1 Variables staticAntes 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 ms comn.Cuando se llama a una funcin sus variables locales se crearn en ese momento y cuando sesalga de la funcin se destruirn. Se entiende por destruir al hecho de que la locacin dememoria que tena una variable ser luego utilizada por el compilador para otra variable local(as se economiza la memoria). Como consecuencia, el valor de las variables locales no ser elmismo entre llamadas de funcin.Por ejemplo, revisa la siguiente funcin, 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 despus de llamar a esta funcin 10 veces,el valor de a se habr incrementado en 10?... Pues, no necesariamente. Cada vez que se llame aincrem se crea a, luego se incrementa y, al terminar de ejecutarse la funcin, se destruye.Para que una variable tenga una locacin de memoria independiente y su valor no cambie entrellamadas de funcin tenemos dos caminos: o la declaramos como global, o la declaramos comolocal esttica. Los buenos programadores siempre eligen el segundo.Una variable se hace esttica anteponiendo a su declaracin el especificador static. Por defectolas variables estticas se auto inicializan a 0, pero se le puede dar otro valor en la mismadeclaracin (dicha inicializacin solo se ejecuta la primera vez que se llama a la funcin), as:

    static int var1; // Variable static (inicializada a 0 por defecto)static int var2 = 50; // Variable static inicializada a 50

    Ejemplos.void increm(){

    static int a = 5; //Variable local esttica inicializada a 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 cdigo llama 10 veces a incremfor(i=0; i

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

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

    nada, perdera 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 muchsimo ms 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 bsicos, incluyendo los mismosarrays. Aun as, te sugiero que tengas un par de aspirinas al lado.15.1 Los arrays o matricesUn 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 quehacamos con las tablas (de bsqueda) del ensamblador y muchsimo ms.Un array completo tiene un nombre y para acceder a cada uno de sus elementos se utilizanndices 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 Declaracin de arraysPara 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 lneas 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 quisiramos asignar a cada uno de los elementos de letters los caracteresdesde la a hasta la j, lo podramos 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, nmeros en cdigos ASCII y se les puede comparar.)

    char c;for ( c = 'a'; c

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

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

    Estructura de una cadena de texto.16 LOS PUNTEROSLos punteros suelen ser el tema que ms cuesta entender en programacin. 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 fsicas de otras variables. Si tenemos la direccin de una variable, tenemos acceso aesa variable de manera indirecta y podemos hacer con ellas todo lo que queramos.16.1 Declaracin de punterosLos punteros pueden apuntar a todo tipo de variables, pero no a todas al mismo tiempo. Ladeclaracin de un puntero es un tanto peculiar. En realidad, se parece a la declaracin 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 fcil que es familiarizarse con la declaracin 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 variablesDecimos que una variable puntero apunta a una variable x si contiene la direccin de dichavariable. Para ello se utiliza el operador &, el cual extrae la direccin de la variable a la queacompaa. Un puntero siempre debera apuntar a una variable cuyo tipo coincida con el tipo delpuntero.En los siguientes ejemplos vemos cmo apuntar a variables de tipo bsico, como int, char ofloat. Ms adelante veremos cmo apuntar a arrays.

    void main (void){

    int height, width;char a, b, c;float max;int * ip; // ip es un puntero a variable tipo int

  • 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 floatip = &height; // Con esto ip tendr la direccin de heightip = &width; // Ahora ip apunta a widthcp = &a; // cp apunta a acp = &c; // Ahora cp apunta a ccp = &a; // Ahora cp apunta a a otra vezfp = &max; // fp apunta a maxfp = &height; // Error! height no es una variable float//...

    }16.3 Asignaciones indirectas mediante punterosUna 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 intp = &height; // p apunta a height*p = 10; // Esto es como height = 10p = &width; // p apunta a width*p = 50; // Esto es como width = 50height = *p; // Esto es como height = widthq = &height; // q apunta a heightn = (*p + *q)/2; // Esto es como n = (height + width)/2//...

    }

    La expresin *p se debera leer: la variable apuntada por p. Eso tambin 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 ARRAYSCmo 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 ladireccin de su primer elemento, a menos que se especifique otro elemento.

    Luego, bastara con modificar el valor del puntero para que apunte a los otros elementos delarray. Todo lo indicado se refleja en el siguiente cdigo: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 tambin se lesllama punteros estticos. En la prctica 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 dems, las operaciones de asignacin son similarespara punteros y arrays, tal como se puede apreciar en el siguiente cdigo. (Por si las moscas,str1 es el array y str2, el puntero.)void main(void)

    {char str1[] = { 'A', 'r', 'r', 'a', 'y' };char * str2 = { 'P', 'o', 'i', 'n', 't', 'e', 'r' };char a;a = str1[0]; // 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 estticostr2 += 2; // Correcto. Ahora str2 apunta a 'i'str1++; // Error otra vez! Str1 es estticostr2++; // Correcto. Ahora str2 apunta a 'n'a = *str2; // Esto da a = 'n'// ...

    }

    16.5 Paso de punteros y arrays a funcionesRecuerdas el paso de variables por valor y por referencia? Pues aqu vamos de nuevo.Bien, recordemos: una variable pasada por valor a una funcin, en realidad le entrega una copiasuya; por lo que la variable original no tiene por qu ser afectada por el cdigo de la funcin.Ahora bien, pasar una variable por referencia significa que se pasa la direccin de dichavariable. Como consecuencia, la funcin tendr acceso a la variable original y podr modificarsu contenido. Esto podra resultar riesgoso, pero, bien usada, la tcnica 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 conparmetros por referencia. Hay dos casos de particular inters: uno, cuando deseamos en serioque la variable pasada a la funcin 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 tambin son punteros al fin.La sintaxis de los punteros en el encabezado de la funcin no es nada nuevo, teniendo en cuentaque tambin tienen la forma de declaraciones de variables.En el siguiente ejemplo la funcin interchange intercambia los valores de las dos variablesrecibidas. En seguida explicar por qu vara un poco la forma en que se llama a la funcin.void interchange( int * p1, int * p2 )

    {int tmp = *p1; //Guardar valor inicial de variable apuntada por p1.*p1 = *p2; // Pasar valor de variable apuntada por p2 a...

    // variable apuntada por p1.*p2 = tmp; // Variable apuntada por p2 valdr tmp.

    }void main (void){

    int i, j;/* Hacer algunas asignaciones */i = 10;j = 15;/* Llamar a funcin interchange pasando las direcciones de i y j */interchange( &i, &j );// En este punto i vale 15 y j vale 10// ...

    }

    Al llamar a interchange le entregamos &i y &j, es decir, las direcciones de i y j. Por otro lado, lafuncin interchange recibir dichos valores en p1 y p2, respectivamente. De ese modo, p1 y p2estarn apuntando a i y j, y podremos modificar sus valores.Ten presente que se mantiene la forma de asignacin puntero = &variable (puntero igual adireccin de variable).Ahora veamos ejemplos donde la forma de asignacin 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 funcin prom, la cual devuelve el valorpromedio de los elementos del array recibido. Como para ese clculo se necesita conocer lacantidad de elementos que tiene el array, prom recibe dicho valor en el parmetro size.float prom ( int * p, int size )

    {int i; float tmp = 0;for ( i=0; i

  • 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 debera valer (51 + 14 + 36 + 78 )/8 = 44.75avrg = prom (array2, 6);

    // Ahora avrg debera valer (-85 + 4 + 66 + 47 - 7 + 85 )/6 = 18.3333while( 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 enmaysculas, 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 carcter apuntado sea diferente de 0x00{if( ( *p >= 'a' ) && ( *p

  • 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 carcter 0x00.Segn la tabla de caracteres ASCII, las letras maysculas estn ubicadas 32 posiciones pordebajo de las minsculas. Por eso basta con sumarle o restarle ese valor a un carcter ASCIIpara pasarlo a mayscula o minscula.En ambas funciones el puntero p navega por los elementos del array apuntado hasta queencuentra el final, indicado por un carcter nulo (0x00).17. ARRAYS CONSTANTESNo es que me haya atrasado con el tema, es solo que los arrays constantes son uno de los temascuyo tratamiento vara mucho entre los distintos compiladores. Veamos en qu.Un array constante es uno cuyos elementos solo podrn ser ledos pero no escritos; tan simplecomo eso.En principio, para que un array sea constante a su clsica declaracin con inicializacin de unarray se le debe anteponer el calificador const. No es posible declarar un array constante vaco yllenar sus elementos despus pues eso equivaldra a modificar sus elementos. Enseguidatenemos ejemplos de declaracin 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 sern de solo lectura, y sus elementos podrn serledos, 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 compilacinvocals[0] = 'a';//Error! no se puede escribir por mas que sea el mismo dato

    Ahora bien, que los datos no cambien durante la ejecucin del programa no necesariamentesignifica los arrays constantes estn 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 dnde 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 accesoPor otro lado, los compiladores de AVR ofrecen mtodos 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 podran 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 pgina se almacenen en la FLASH debemos declararlas e inicializarlasde esta formaPROGMEM 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 aadirles 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 despus. Para acceder a los elementos de estosarrays hay que emplear una forma un tanto extica. Se deben usar algunas macros propias delcompilador, todas provedas por la librera pgmspace.h. Las principales son estas cuatro

    pgm_read_byte. Lee de un array residente en FLASH un entero compuesto por un bytede dato. En el lenguaje C los nicos datos de este tamao 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 seran arrays de tipo (signed) int, unsigned int, (signed) short y unsigned short.

    pgm_read_dword. Lee de un array residente en FLASH un entero compuesto por 4bytes. En el C seran arrays de tipo (signed) long y unsigned long.

    pgm_read_float. Lee de un array residente en FLASH un nmero de punto flotantecompuesto por 4 bytes. En el lenguaje C seran arrays de tipo float y double operandoen modo de 32 bits.

    Estas macros reciben como argumento la direccin de un elemento del array en FLASH. Comola direccin 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 ms fcil 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 expresin. Es muyimportante recordar esto puesto que las expresiones previas son tambin vlidas 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 cdigos 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 prctica. 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 parmetros que reciben como argumento las macros pgm_read_byte, pgm_read_word,pgm_read_dword y pgm_read_float son direcciones de 16 bits. Esto quiere decir que medianteellas podemos acceder a los arrays cuyos elementos cubren un espacio de 216 = 65536 bytes dela memoria FLASH. En la gran mayora de los casos es mucho ms de lo que se necesita,considerando que solo hay dos megaAVR que superan esa memoria, los ATmega128 y losATmega256. Pero si se presentara la descomunal situacin donde tengamos que trabajar conarrays de ms de 64 KB, la librera pgmspace.h nos provee de otras macros ad hoc. Retomamoseste aspecto al final de la pgina.

    18.1 Variables PROGMEM localesLas variables PROGMEM tienen un espacio asignado en la memoria que no cambia durante laejecucin 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 dinmicas.Ms que una coincidencia, lo dicho arriba es una condicin 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 estn declaradas a nivel global. Si los colocamos dentro deuna funcin habr problemas.Por ejemplo, el siguiente extracto de funcin no dar errores en AVR GCC pero el programafuncionar defectuosamente, a pesar de que los arrays estn 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 estn declarados como si fueran locales ordinarias. Si los hubiramosdeclarado globalmente estara bien. Pero como son locales es necesario que sean adems de tipostatic. Como sabemos, estas variables en C se forman aadindoles la palabra reservada static asu declaracin habitual. Con esto aclarado, el cdigo 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 cdigo pertenece al programa de la prctica reproductor de ringtones PICAXE. Sideseas comprobar lo expuesto puedes descargarlo y recompilarlo haciendo las modificacionesexplicadas.

    18.2 Los punteros PGM_P y PGM_VOID_PJusto 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. Sudefinicin en el archivo pgmspace.h de AVR GCC es

    #define PGM_P const PROGMEM char *pero el archivo pgmspace.h de AVR IAR C lo define como

    #define PGM_P const char __flash *Con el fin de que los programas de cursomicros sean lo ms transparentes posible trato de evitarel uso excesivo de los #defines que conducen a trminos innecesarios. El hecho de estarestudiando PGM_P sugiere que se trata de una excepcin. 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 seran idnticas asumiendo que en AVR GCC const PROGMEM charequivale a const char PROGMEM y tambin a PROGMEM const char, siendo esta ltimapresentacin la forma en que hemos venido trabajando. Debido a ello en muchas ocasionespodremos prescindir de PGM_P, pero surgirn algunos casos en que AVR IAR C muestre sudisconformidad por ese reacomodo de trminos. PGM_P no solo termina de arreglar estosdesajustes sino que facilita notablemente la escritura del cdigo ante la aparicin deconstrucciones ms complejas como las que veremos despus.Si PGM_P define un tipo puntero que apunta a variables char (o de un byte en general), alguienpodra preguntar cules 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 direccin 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 tambin, en principio se podran 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 parntesis el tipo de lavariable que recibe el valor, en este caso PGM_P porque p es de ese tipo. De ese modo p podrapuntar 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 enva el puntero p ms 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 fcilmente a PGM_P.pgm_read_word tambin espera recibir una direccin pero no se le puede enviar a pdirectamente como en el caso previo. Es preciso hacer una conversin de tipo para que el valorde p sea entendido como una direccin de variables de 2 bytes. Por eso colocamos al ladoizquierdo de p la expresin (PROGMEM int *) que se adapta al tipo del array Notes. Tambinse 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 mayora de loscasos y todo queda bien. La legibilidad se pierde en programas como el ejemplo previo donde elmismo puntero se usa tambin para acceder a un array de enteros de 2 bytes. Si de todos modosvamos a estar haciendo conversiones de tipos lo ms recomendable sera usar un puntero"neutro" lo cual deja por sentado que trabajar sobre variables de distintos tipos.Ese tipo de puntero existe y se llama PGM_VOID_P. Es aceptado as en los compiladores 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 cdigo qued con mejor acabado y con una lindasimetra.)#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 tambin 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 demostracin abarcara demasiadoespacio. Prefiero remitirme a la librera para USB que distribuye Atmel. Me parece un perfectoejemplo. Puedes encontrarla en varias notas de aplicacin 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 tamaoresiden en la FLASH.Esa librera USB se vale de un archivo llamado compiler.h para guardar la compatibilidad decdigos 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 librera. Igual vale la pena revisarla.18.3 Variables PROGMEM como argumentos de funcionesEn primer lugar recordemos que los argumentos de las funciones deben ser del mismo tipo quelas variables que se le envan. Si las variables son residentes en la FLASH, lo cual deja suponerque son arrays o estructuras complejas, el mtodo a usar son los punteros, no solo por el tamaode esas variables sino por la capacidad de adaptacin de los punteros que estudiamos en laseccin anterior.All se describi la operacin de los punteros PGM_P y PGM_VOID_P y se explic loimprescindibles que seran. 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 funcionarn bien en los dos compiladores AVR IAR C y AVR GCC. Dicho eso,podemos pasar al ejemplo.En el siguiente programa la funcin print imprime un mensaje por el puerto serie, similar a putsde la librera stdio.h del compilador. La funcin puts solo trabaja con mensajes en la RAM adiferencia de print que recibe arrays residentes en la FLASH. Los dos compiladores que usamostambin 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";/************************************************************** Enva por el USART el texto pasado en p*******************************************************/

    void print(PGM_P p){

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

    ------------------------------------------------------------------------------------------------------------------------------M.S.c Ing. 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 cdigo 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 unaconversin de tipo. La conversin de tipo para p es opcional, por ejemplo, tambin se pudoescribir print((PGM_P)rt01).Y ahora la pregunta que nos trae aqu: Se puede enviar a una funcin 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 todava 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 cdigo 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 cdigode 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 unadireccin RAM (tambin 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 ms avezado podra 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 cdigo vuelve a compilarse limpiamente. Hasta AVR IAR C es engaado. Pero cuandovemos el programa en accin descubrimos que solo nos hemos engaado a nosotros mismos. Eneste programa los garabatos que se visualizan en el terminal serial nos quitaron la venda de losojos rpidamente, felizmente. En otras circunstancias detectar el error hubiera costado ms. 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 direccin automticamente. Lasiguiente construccin 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 definicin. PSTR es ampliamente usadacuando se trabaja con las funciones _P del compilador AVR GCC. As que hablaremos ms deella en adelante.#define PSTR(s) (__extension__({static const char __c[] PROGMEM = (s);&__c[0];}))

    18.4Arrays de cadenas PROGMEMEste es un tema recurrente en programacin.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 esnica, inflexible, limitante e igualmente vlida 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 idntico al ejemplo de la seccin 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 estn 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 USARTputs_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 funcin puts_P es proveda por los compiladores. Es similar a la funcin puts pero lascadenas que recibe deben ubicarse en la FLASH. En otras palabras, puts_P es similar a lafuncin prin