Lenguaje C Para Microcontroladores

37
Lenguaje C para Microcontroladores http://www.cursomicros.com/avr/lenguaje-c/lenguajec.html Contenido Introducción o ¿Por qué C y no Basic? o ¿Qué compilador usar? o ¡Ánimo! No es tan difícil Estructura de un programa o Los comentarios o Las sentencias o Los bloques o Las directivas o Las funciones Variables y Tipos de Datos o Declaración de variables o Especificadores de tipo de datos Sentencias selectivas o La sentencia if o La sentencia if - else o La sentencia if - else - if escalonada o La sentencia switch Sentencias iterativas o La sentencia while o La sentencia do - while o La sentencia for Bloques simples Los operadores o Operadores aritméticos o Operadores de bits o Operadores relacionales o Operadores lógicos o Composición de operadores o Precedencia de operadores Las funciones a Fondo o Funciones sin parámetros o Parámetros por valor o Parámetros por referencia o Prototipos de funciones o Variables locales y globales o Variables static o Variables volatile Arrays y Punteros o Los arrays o matrices o Declaración de arrays o Inicialización de arrays o Cadenas de texto o Los punteros o Declaración de punteros o Apuntando a variables o Asignaciones con punteros o Punteros y arrays

Transcript of Lenguaje C Para Microcontroladores

Page 1: Lenguaje C Para Microcontroladores

Lenguaje C para Microcontroladores http://www.cursomicros.com/avr/lenguaje-c/lenguajec.html

Contenido

Introducción

o ¿Por qué C y no Basic?

o ¿Qué compilador usar?

o ¡Ánimo! No es tan difícil

Estructura de un programa

o Los comentarios

o Las sentencias

o Los bloques

o Las directivas

o Las funciones

Variables y Tipos de Datos

o Declaración de variables

o Especificadores de tipo de datos

Sentencias selectivas

o La sentencia if

o La sentencia if - else

o La sentencia if - else - if escalonada

o La sentencia switch

Sentencias iterativas

o La sentencia while

o La sentencia do - while

o La sentencia for

Bloques simples

Los operadores

o Operadores aritméticos

o Operadores de bits

o Operadores relacionales

o Operadores lógicos

o Composición de operadores

o Precedencia de operadores

Las funciones a Fondo

o Funciones sin parámetros

o Parámetros por valor

o Parámetros por referencia

o Prototipos de funciones

o Variables locales y globales

o Variables static

o Variables volatile

Arrays y Punteros

o Los arrays o matrices

o Declaración de arrays

o Inicialización de arrays

o Cadenas de texto

o Los punteros

o Declaración de punteros

o Apuntando a variables

o Asignaciones con punteros

o Punteros y arrays

Page 2: Lenguaje C Para Microcontroladores

o Paso de punteros y arrays a funciones

o Arrays constantes

INTRODUCCIÓN

Los lenguajes de alto nivel son mucho más potentes que el ensamblador aunque su aprendiza-

je demanda un mayor esfuerzo. Para empezar a programar en ensamblador nos puede bastar

con aprender unas 50 palabras (las instrucciones básicas). En cambio dominar un lenguaje de

alto nivel como el C es como aprender a hablar en un nuevo idioma. No basta con memorizar

palabras nuevas, sino que debemos aprender a manejar una nueva estructura gramatical.

Además, los procesadores no son como las personas: si en un código de 100 líneas te olvidaste

de una sola coma, los compiladores no te lo pasarán por alto.

Por qué C y no Basic

Ciertamente, el Basic es el lenguaje más fácil de aprender (no es exactamente la razón de su

nombre). Y aunque los programadores en C de computadoras miren con desdén a los que usan

el Basic, en el mundo de los microcontroladores los compiladores Basic no tienen motivo para

sentirse menos. De hecho, algunos pueden ser casi tan eficientes como los mejores compilado-

res C.

Las características (muchas veces complejas) del C fueron ideadas para el trabajo con sofisti-

cados proyectos, propios de las computadoras. Muchas de esas características ya no resultan

tan ventajosas en el limitado hardware de los microcontroladors y se convierten en prescindi-

bles.

Además, la simplicidad de los compiladores Basic para microcontroladores también permite

que varios de ellos, como MBasic o PIC Basic Pro (por citar algunos) mantengan una compati-

bilidad entre sus códigos que no se encuentra entre los compiladores C.

Ésas podrían ser razones más que convincentes para empezar por el Basic y, de hecho, es la

opción que muchos han elegido. ¿Por qué nosotros no?

Porque es verdad comprobable que los mejores programadores trabajan en C (no siempre ex-

clusivamente, pero lo manejan). Por consiguiente, los proyectos más fantásticos y alucinantes

que se pueden encontrar están en C. Es más, la mayoría de, por no decir todos, los programa-

dores de Basic tarde o temprano se ven obligados a aprender el C. No sé tú, pero yo opino que

esa razón pesa más.

Además, dada la robustez y la aceptación del lenguaje C, se lo ha tomado como referencia

para lenguajes de otros propósitos como Java, JavaScript, php o de Matlab, entre otros. Así

que, el C podrá servirte para trabajar en otros campos. El programador de C podría, inclusive,

aprender luego el Basic sin el menor esfuerzo; lo contrario no es cierto.

¿Qué compilador C utilizar?

No quiero burlarme de nadie, pero una vez leí en Internet el comentario de un novato: “Quiero

programar microcontroladores en C. Ya descargué el Visual C++. ¿Qué más necesito?” :).

Aparte del lenguaje, nada tiene que ver un compilador para computadoras con los compilado-

res para µCs. Poco tiene que ver un compilador para PICs que otro para otros µCs. Inclusive,

poco tiene que ver un compilador de PICs de una compañía con otro de otra compañía.

Veamos grosso modo algunos aspectos de los compiladores de PICs más conocidos.

Hi-tech C. Es uno de los compiladores producidos por la empresa htsoft. Es quizá el más efi-

ciente y el que mejor soporta el lenguaje C estándar. Su entorno IDE también incluye el mejor

depurador ICD. Como contraparte, su apego al hardware del µC le resta algo de portabilidad.

Page 3: Lenguaje C Para Microcontroladores

Tampoco luce librerías incorporadas como otros productos. Pero su principal desventaja es su

elevado precio. Y, por si fuera poco, el compilador para la familia de partes PIC18 se vende por

separado.

AVR IAR C. Los compiladores C de la compañía iar systems tienen básicamente las mismas

características mencionadas de los compiladores de htsoft, incluyendo sus propios depurado-

res. Así mismo, las versiones para los PIC16 y PIC18 se distribuyen por separado. Actualmen-

te, no sé por qué, ya no está disponible la primera versión.

CCS C. La empresa ccsinfo decidió dotar a sus compiladores C una capa extra que aísla al pro-

gramador de los recursos intrínsecos del µC. Esto puede afectar la portabilidad de sus códigos

a otros compiladores, pero resulta inmejorable, si solo se trabaja en el lenguaje de CCS C,

para transportar los códigos de un PIC a otro (de cualquier familia) con un esfuerzo sin compa-

ración. Además, incluye en un solo paquete los compiladores para los PICs de las familias Ba-

seline, Midrange (PIC16 básicamente) y High performance (PIC18).

Al igual que los softwares anteriores, sus librerías estándar, como stdlib.h, stdio.h, string.h

y math.h, son muy completas y potentes; pero CCS C supera a sus rivales al incorporar libre-

rías para controlar todos los módulos internos del PIC y también muchísimos dispositivos ex-

ternos.

Mikro C. La compañía Mikroelektronika vende compiladores para PICs en los lenguajes C

(MikroC), Basic (MikroBasic) y Pascal (MikroPascal).

Yo diría que el estilo de Mikro C se parece al de Hi-tech C y sus facilidades tratan de acercarse

a las de CCS C: aunque en muchos casos aún es necesario acceder a los registros internos del

PIC, cuenta con librerías para controlar sus módulos internos. También tiene una apreciable

cantidad de librerías para interfacear dispositivos externos. Lo malo es que todas ellas están

precompiladas y no se podrían modificar, en caso de ser necesario.

Mikroelektronika y CCS también comercializan sus propias tarjetas de entrenamiento para el

aprendizaje de sus productos. Para más información puedes visitar sus sitios web.

MPLAB C18. Excelente compilador desarrollado por los ingenieros de Microchip. No es gratuito

como el MPLAB, pero creo que es el que ofrece la versión demo más generosa: es 100 % fun-

cional por 60 días. Lamentablemente, como sugiere su nombre, solo trabaja con las partes

PIC18. Quizá lo probemos en otro momento.

Otros. Aún hay otros compiladores C (como Bytecraft, BoostC y FedC) que algo menos reco-

nocidos como los anteriores, lo que no significa que sean malos.

También he visto algunos de código abierto, pero no son buenos: la gente del GNU trabaja

más con el AVR GCC, un “Señor Compilador”. Es uno de los pocos casos donde el software li-

bre supera a los comerciales. Como se puede entrever, está orientado a los microcontroladores

AVR, de Atmel. Es, además, el compilador más difícil de todos; por eso lo estudiaremos en el

Módulo 4.

En cuanto a cuál compilador usar: la idea de este curso no es aprender a programar con un

compilador en particular, y tampoco pretendo promocionar alguno. Después de todo, una vic-

toria depende más de la habilidad guerrero que de su espada. He visto “super programas” he-

chos con el compilador más modesto.

En este Módulo 2 uso BoostC porque es muy fácil, porque nos permitirá ver más de cerca có-

mo funcionan las cosas dentro del PIC y, sobre todo, porque el salto de él a otros compiladores

será mucho más fácil que hacerlo al revés. En el Módulo 3 migraremos al CCS C (que además

del lenguaje C usa su propio “argot”) y en el Módulo 4 trabajaremos especialmente con AVR

GCC.

Page 4: Lenguaje C Para Microcontroladores

¡Ánimo! No es tan difícil

Pienso que, comparado con el Basic para microcontroladores, el C es infinitamente más difícil

de aprender. Quienes lo usan, en gran parte, son personas que han tenido experiencia pro-

gramando computadoras, personas que han estudiado más de un libro para dominarlo. Es,

literalmente, como aprender un nuevo idioma, y eso no es algo que se hace de la noche a la

mañana. ¿Eso no suena muy alentador?

Para simplificar las cosas, en este capítulo no voy a exponer todas las reglas del lenguaje C,

aunque sí la mayoría; digamos el 95 % de lo necesario. El resto: o es solo aplicable a los PCs,

o son temas raros o que difieren demasiado entre de compilador a otro y conviene más revi-

sarlos en sus respectivos manuales.

También, y para ahorrar los ejemplos prácticos, asumo que no eres un novato cualquiera,

asumo que conoces algo de programación (aunque sea en ensamblador), que sabes cómo usar

las subrutinas, que sabes cómo emplear los bucles, que sabes lo que significa redirigir el flujo

de un programa, que sabes para qué sirven las variables, etc. Si no, estarás algo perdido.

Finalmente, no es necesario que te aprendas de golpe todo el capítulo; bastará con que lo leas

fluidamente una primera vez y regresar luego a consultar algunos puntos de duda. La parte

más complicada es Arrays y Punteros, sobre todo los punteros. Así que, ten paciencia con

ellos.

ESTRUCTURA DE UN PROGRAMA EN C

Tomaremos en cuenta este sencillísimo ejemplo, escrito para el compilador Hitech PICC.

#include <pic.h> // Incluir este archivo /* La siguiente directiva establece la Palabra de Configuración */ __CONFIG ( PWRTEN & WDTDIS & XT & UNPROTECT ); void pausa(void) { // Llave de apertura del bloque de pausa unsigned int c; // Declarar variable c (de 16 bits) for(c=0; c<60000; c++) { // Llave de apertura del bloque de for /* este bloque está vacío, solo cuenta c desde 0 hasta 59999 */ } // Llave de cierre del bloque de for } // Llave de cierre del bloque de pausa void main(void) { // Llave de apertura del bloque de main TRISB0 = 0; // Configurar pin RB0 como salida while(1) // Bucle infinito { // Llave de apertura del bloque de while RB0 = 1; // Setear bit RB0 pausa(); // Llamar función pausa RB0 = 0; // Limpiar bit RB0 pausa(); // Llamar función pausa } // Llave de cierre del bloque de while } // Llave de cierre del bloque de main

No hay que ser muy perspicaz para descubrir lo que hace este programa: configura el pin RB0

como salida y luego lo setea y lo limpia tras pausas. Es como hacer parpadear un LED conec-

tado al pin RB0. Parpadea porque el bloque de while se ejecuta cíclicamente.

Los elementos más notables de un programa en C son las sentencias, las funciones, las direc-

tivas, los comentarios y los bloques. A continuación, una breve descripción de ellos.

Page 5: Lenguaje C Para Microcontroladores

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á entre los 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. */

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 del ensamblador.

Salvo casos particulares, donde su uso es opcional, una sentencia debe finalizar con un punto

y 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 a continuación de otra (evadiendo los comentarios por supuesto). Por ejemplo, la

función main del programa de arriba bien pudo escribirse del siguiente modo.

void main(void) { TRISB0=0; while(1) { RB0=1; pausa(); RB0=0; pausa(); } }

¿Sorprendido? Podrás deducir que los espacios y las tabulaciones solo sirven para darle un

aspecto ordenado al código. Es una buena práctica de programación aprender a acomodarlas.

Las sentencias se pueden clasificar en sentencias de asignación, sentencias selectivas, senten-

cias iterativas, de llamadas de función, etc. Las describiremos más adelante.

Los bloques

Un bloque establece y delimita el cuerpo de las funciones y algunas sentencias mediante llaves

({}).

Como ves en el ejemplo de arriba, las funciones main y pausa tienen sus bloques, así como los

bucles while y for. Creo que exageré con los comentarios, pero sirven para mostrarnos dónde

empieza y termina cada bloque. Podrás ver cómo las tabulaciones ayudan a distinguir unos

bloques de otros. Afortunadamente, los editores de los buenos compiladores C pueden resaltar

cuáles son las llaves de inicio y de cierre de cada bloque. Te será fácil acostumbrarte a usarlas.

Las directivas

Son conocidas en el lenguaje C como directivas de preprocesador, de preprocesador porque

son evaluadas antes de compilar el programa. Como pasaba en el ensamblador, las directivas

por sí mismas no son 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 C

para PICs 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 compi-

lador maneja sus propias directivas y serán tratadas por separado.

Las funciones

Si un programa en ensamblador se puede dividir en varias subrutinas para su mejor estructu-

ración, un programa en C se puede componer de funciones. Por supuesto que las funciones

son muchísimo más potentes y, por cierto, algo más complejas de aprender. Por eso ni siquie-

Page 6: Lenguaje C Para Microcontroladores

ra el gran espacio que se les dedica más adelante es suficiente para abarcarlas. Pero, no te

preocupes, aprenderemos de a poco.

En un programa en C puede haber las funciones que sean posibles, pero la nunca debe faltar la

función principal, llamada main. Donde quiera que se encuentre, la función main siempre será

la primera en ser ejecutada. De hecho, allí empieza y no debería salir de ella.

VARIABLES Y TIPOS DE DATOS

En ensamblador todas nuestras variables de programa eran registros de la RAM crudos, es

decir, datos de 8 bits sin formato. En los lenguajes de alto nivel estos registros son tratados de

acuerdo con formatos que les permiten representar números de 8, 16 ó 32 bits (a veces más

grandes), con signo o sin él, números enteros o decimales. Esos son los tipos de datos bási-

cos.

Las variables de los compiladores pueden incluso almacenar matrices de datos del mismo tipo

(llamadas arrays) o de tipos diferentes (llamadas estructuras). Estos son los tipos de datos

complejos.

Los siguientes son los principales tipos de datos básicos del lenguaje C:

Tabla de variables y tipos de datos del lenguaje C

Tipo de dato Tamaño Rango

char 8 0 a 255 ó -128 a 127

signed char 8 -128 a 127

unsigned char 8 0 a 255

(signed) int 16 -32,768 a 32,767

unsigned int 16 0 a 65,536

(signed) long 32 -2,147,483,648 a 2,147,483,647

unsigned long 32 0 a 4,294,967,295

float 32 +/- 1.18E–38 a +/- 3.40E+38

Por desgracia, excepto signed char y unsigned char, los otros tipos establecen variables

de tamaños y/o rangos que suelen variar de un compilador C a otro. Otros compiladores tam-

bién manejan los tipos short, double, bool (o boolean), bit, etc. Esas divergencias pueden

afectar la portabilidad de los códigos, además de confundir a los programadores. Los valores

de esta tabla son los utilizados por la mayoría de los compiladores C.

Los especificadores signed (con signo) mostrados entre paréntesis son opcionales. Es de-

cir, da lo 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.

Declaración de variables

Esta parte es comparable, aunque lejanamente a cuando identificábamos nuestras variables

del ensamblador con las directivas equ o cblock – endc. No se puede usar una variable si

antes no se ha declarado. La forma general 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 usua-

rio y myvar es un identificador cualquiera, siempre que no sea palabra reservada.

Ejemplos.

unsigned char d; // Variable para enteros de 8 bits sin signo char b; // Variable de 8 bits (para almacenar

Page 7: Lenguaje C Para Microcontroladores

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

También es posible declarar varias variables del mismo tipo, separándolas con comas. Así nos

ahorramos algo de tipeo. Por ejemplo:

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

Especificadores de tipo de datos

A la declaración de una variable se le puede añadir un especificador de tipo como const, sta-

tic, volatile, extern, register, etc. Dichos especificadores tienen diversas funciones

y, salvo const, se suelen usar en programas más elaborados. Como no queremos enredarnos

tan pronto, lo dejaremos para otro momento.

Una variable const debe ser inicializada en su declaración. Después de eso el compilador solo

permitirá su lectura más no su escritura. Ejemplos:

const int a = 100; // Declarar constante a int b; // Declarar variable b //... b = a; // Válido b = 150; // Válido a = 60; // Error! a es constante a = b; // Error! a es constante

Por más que las variables constantes sean de solo lectura, ocuparán posiciones en la RAM del

µC. Por eso muchas veces es preferible definir las constantes del programa con las clásicas

directivas #define (como lo hacíamos en el ensamblador).

#define a 100 // Definir constante a

SENTENCIAS SELECTIVAS

Llamadas también sentencias de bifurcación, sirven para redirigir el flujo de un programa se-

gún la 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. Ade-

más de ellas están las sentencias if–else escalonadas y switch–case.

La sentencia if

La sentencia if (si condicional, en inglés) hace que un

programa ejecute una sentencia o un grupo de ellas si

una expresión es cierta. Esta lógica se describe en el si-

guiente esquema.

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 bloque sentenciaX;

Diagrama de flujo de la sentencia if.

Page 8: Lenguaje C Para Microcontroladores

Después de ejecutar sentenciaA el programa evalúa expression. Si resulta ser verdadera,

se ejecutan todas las sentencias de su bloque y luego se ejecutará la sentenciaX.

En cambio, si expression es falsa, el programa se salteará el bloque de if y ejecutará sen-

tenciaX.

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 expresión es cierta y otra

si es falsa, entonces se debe utilizar la sentencia 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

la sentencia if – else, pero nunca los dos a la vez.

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. Del

mismo 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; } else if ( expression_2 ) // En caso contrario y si expression_2 es { // verdadera, ejecutar este bloque sentencia3;

Page 9: Lenguaje C Para Microcontroladores

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) se ejecuta si ninguna de las expresiones es verdadera. Además, si dicho bloque está

vacío, puede ser omitido junto con su else.

La sentencia switch

La sentencia switch brinda una forma más elegante de bifurcación múltiple. Podemos consi-

derarla como una forma más estructurada de la sentencia if – else – if escalonada, aun-

que tiene algunas restricciones en las condiciones lógicas a evaluar, las cuales son compara-

ciones de valores 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én podría tener más sentencias.

switch ( expression ) { case constante1: // Si expression = constante1, ejecutar este bloque sentencia1; sentencia2; break; case constante2: // Si expression = constante2, ejecutar este bloque sentencia3; sentencia4; break; case constante3: // Si expression = constante3, ejecutar este bloque sentencia5; sentencia6; break; default: // Si expression no fue igual a ninguna de las // constantes anteriores, ejecutar este bloque sentencia7; sentencia8; break; } sentenciaX; // todo...

donde constante1, constante2 y constante3 deben ser constantes enteras, por ejem-

plo, 2, 0x45, ‘a’, etc. (‘a’ tiene código ascii 165, que es, a fin de cuentas, un entero.)

expresion puede ser una variable compatible con entero. No es una expresión que conduce a

una condición lógica como en los casos anteriores.

El programa solo ejecutará uno de los bloques dependiendo de qué constante coincida con ex-

pression. Usualmente los bloques van limitados por llaves, pero en este caso son opcionales,

Page 10: Lenguaje C Para Microcontroladores

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

que sigue (en el boceto, sentenciaX). ¡Atento!: de no poner break, también se ejecutará el

bloque del siguiente case, sin importar si su constante coincida con expression o no.

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

SENTENCIAS ITERATIVAS

Las sentencias de control iterativas sirven para que el programa ejecute una sentencia o un

grupo de ellas un número determinado o indeterminado de veces. Así es, esta sección no habla

de otra cosa que de los bucles en C.

El lenguaje C soporta tres tipos de bucles, las cuales se construyen con las sentencias while,

do – while y for. El segundo es una variante del primero y el tercero es una versión más

compacta e intuitiva del bucle while.

La sentencia while

El cuerpo o bloque de este bucle se ejecutará una y otra vez mientras (while, en inglés) una

expresió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

sea verdadera, ejecutar el siguiente bloque.

sentenciaA; while ( expression ) // Mientras expression sea verdadera, ejecutar el // siguiente bloque { sentenciaB; sentenciaC; // ... }; // Este ; es opcional sentenciaX; // ...

Nota que en este caso primero se evalúa expression. Por lo tanto, si desde el principio ex-

pression es falsa, el bloque de while no se ejecutará nunca. Por otro lado, si expression

no deja de ser verdadera, el programa se quedará dando vueltas “para siempre”.

Page 11: Lenguaje C Para Microcontroladores

La sentencia do - while

Como dije antes, es una variación de la sentencia while simple. La principal diferencia es que

la condición lógica (expression) de este bucle se presenta al final. Como se ve en la siguien-

te figura, esto implica que el cuerpo o bloque de este bucle se ejecutará al menos una vez.

Diagrama de flujo de las sentencia do – while.

La sintaxis para la sentencia do – while es la siguiente y se lee: Ejecutar (do) el siguiente

bloque, mientras (while) expression sea verdadera.

sentenciaA; do { sentenciaB; sentenciaC; // ... } while ( expression ); // Este ; es mandatorio sentenciaX; // ...

La sentencia for

Las dos sentencias anteriores, while y do – while, se suelen emplear cuando no se sabe

de antemano la cantidad de veces que se va a ejecutar el bucle. En los casos donde el bucle

involucra alguna forma de conteo finito es preferible emplear la sentencia for. (Inversamente,

al ver un for en un programa, debemos suponer que estamos frente a algún bucle de ese ti-

po.)

É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:

expression_1 suele ser una sentencia de inicialización.

expression_2 se evualú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.

Page 12: Lenguaje C Para Microcontroladores

Por la forma y orden en que se ejecutan estas expresiones, el bucle for es equivalente a la

siguiente construcción, utilizando la sentencia while. Primero se ejecuta expression_1 y

luego se ejecuta el bloque indicado tantas veces mientras expression_2 sea verdadera.

expression_1; while ( expression_2 ) { sentencia1; sentencia2; // ... expression_3; }

No obstante, de esa forma se ve más rara aún; así que, mejor, veamos estos ejemplos, que

son sus 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 que i valga 0 hasta que valga 9.

En este otro ejemplo las sentencias se ejecutan desde que i valga 10 hasta que valga 20.

Es decir, el bucle dará 11 vueltas en total.

for ( i = 10 ; i <= 20 ; i++ ) { sentencias; }

El siguiente bucle for empieza con i inicializado a 100 y su bloque se ejecutará mientras i

sea mayor o igual a 0. Por supuesto, en este caso i se decrementa tras cada ciclo.

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

Se pueden hacer muchas más construcciones, todas coincidentes con la primera plantilla, pero

también son menos frecuentes.

SENTENCIAS CON BLOQUES SIMPLES

Cuando las sentencias selectivas (como if) o de bucles (como while o for) tienen cuerpos o

bloques que constan de solo una sentencia, se pueden omitir las llaves. Aun así, es aconseja-

ble seguir manteniendo las tabulaciones para evitarnos confusiones.

Por ejemplo, las siguientes sentencias:

if(a > b) { a = 0; } if(a == b) { a++; } else {

Page 13: Lenguaje C Para Microcontroladores

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

bien se pueden escribir de la siguiente forma:

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

LOS OPERADORES

Sirven para realizar operaciones aritméticas, lógicas, comparativas, etc. Según esa función se

clasifican en los siguientes grupos.

Operadores aritméticos

Además de los típicos operadores de suma, resta, multiplicación y división, están los operado-

res de módulo, incremento y decremento.

Tabla de Operadores aritméticos

Operador Acción

+ Suma

- Resta

* Multiplicación

/ División

% Módulo. Retorna el residuo de una división en-

tera. Solo se debe usar con números enteros.

++ Incrementar en uno

-- Decrementar en uno

Ejemplos:

int a, b, c; // Declarar variables a, b y c a = b + c; // Sumar a y b. Almacenar resultado en c b = b * c; // Multiplicar b por c. Resultado en b b = a / c; // Dividir a entre c. Colocar resultado en b a = a + c – b; // Sumar a y c y restarle b. Resultado en a c = (a + b) / c; // Dividir a+b entre c. Resultado en c b = a + b / c + b * b; // Sumar a más b/c más b×b. Resultado en b

Page 14: Lenguaje C Para Microcontroladores

c = a % b; // Residuo de dividir a÷b a c a++; // Incrementar a en 1 b--; // Decrementar b en 1 ++c; // Incrementar c en 1 --b; // Decrementar b en 1

¿Te recordaron a tus clases de álgebra del colegio? A diferencia de esas matemáticas, estas

expresiones 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

en una expresión simple. Sin embargo, hay una forma (tal vez innecesaria y confusa para un

novato, pero muy atractiva para los que ya estamos acostumbrados a su uso) que permite

escribir 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 se

evalúa la expresión.

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

resta 1 al operando.

int a, b; // Declarar variables enteras a y b a = b++; // Lo mismo que a = b; y luego b = b + 1; a = ++b; // Lo mismo que b = b + 1; y luego a = b; if (a++ < 10) // Primero comprueba si a < 10 y luego { // incrementa a en 1 // algún código } if (++a < 10) // Primero incrementa a en 1 y luego { // comprueba si a < 10 // algún código }

Operadores de bits

Se aplican a operaciones lógicas con variables a nivel binario. Aquí tenemos las clásicas opera-

ciones AND, OR inclusiva, OR exclusiva y la NEGACIÓN. Adicionalmente, he incluido en

esta categoría las operaciones de desplazamiento a la derecha y la izquierda.

Si bien son operaciones que producen resultados análogos a los de las instrucciones de en-

samblador iorlw y iorwf para la OR inclusiva, xorlw y xorwf para la OR exclusiva,

andlw y andwf para la AND y comf para la negación; 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 bits

Operador 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 bits

Page 15: Lenguaje C Para Microcontroladores

int n; // variable de 16 bits m = 0x48; // m será 0x48 m = m & 0x0F; // Después de esto m será 0x08 m = m | 0x24; // Después de esto m será 0x2F m = m & 0b11110000; // Después de esto m será 0x20 n = 0xFF00; // n será 0xFF00 n = ~n; // n será 0x00FF m = m | 0b10000001; // Setear bits 0 y 7 de variable m m = m & 0xF0; // Limpiar nibble bajo de variable m m = m ^ 0b00110000; // Invertir bits 4 y 5 de variable m m = 0b00011000; // Cargar m con 0b00011000 m = m >> 2; // Desplazar m 2 posiciones a la derecha // Ahora m será 0b00000110 n = 0xFF1F; n = n << 12; // Desplazar n 12 posiciones a la izquierda // Ahora n será 0xF000; m = m << 8; // Después de esto m será 0x00

Fíjate en la semejanza entre las operaciones de desplazamiento con >> y << y las operaciones

del rotación del ensamblador. La diferencia es que cuando una variable se desplaza hacia un

lado, los bits que salen por allí se pierden y los bits que entran por el otro lado son siempre

ceros. Es por esto que en la última sentencia, m = m << 8, el resultado es 0x00. Por cierto,

en el lenguaje C no existen operadores de rotación. Hay formas alternativas de realizarlas.

Desplazamientos producidos por los operadores << y >>.

Operadores relacionales

Se emplean para construir las condiciones lógicas de las sentencias de control selectivas e ite-

rativas, como ya hemos podido apreciar en las secciones anteriores. La siguiente tabla muestra

los operadores relacionales disponibles.

Operadores lógicos

Generalmente se utilizan para enlazar dos o más condiciones lógicas simples. Por suerte, estos

operadores solo son tres y serán explicados en las prácticas del curso.

Tabla de Operadores relacionales

Operador Acción

== Igual

!= No igual

> Mayor que

< Menor que

>= Mayor o igual que

<= Menor o igual que

Tabla de Operadores lógicos

Operador Acción

&& AND lógica

|| OR lógica

! Negación lógica

Page 16: Lenguaje C Para Microcontroladores

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 }

Composición de operadores

Se utiliza en las operaciones de asignación y nos permite escribir código más abreviado. La

forma 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;

op puede ser cualquiera de los operadores aritméticos o de bit estudiados arriba. O sea, op

puede ser +, - , *, /, %, &, |, ^, ~,<< ó >>. Ver Tabla siguiente.

Nota: no debe haber ningún espacio entre el operador y el signo igual.

Operador Descripción

+= Asignación de suma

-= Asignación de resta

*= Asignación de multiplicación

/= Asignación de división

%= Asignación de resto de división

<<= Asignación de desplazamiento a la izquierda

>>= Asignación de desplazamiento a la derecha

&= Asignación de AND de bits

|= Asignación de OR de bits

^^= Asignación de OR exclusivo de bits

~= Asignación de negación de bits

Ejemplos:

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

Precedencia de operadores

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

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

Page 17: Lenguaje C Para Microcontroladores

A diferencia del lenguaje Basic, donde la expresión se evalúa de izquierda a derecha, en esta

sentencia no queda claro en qué orden se ejecutarán las operaciones indicadas. Hay ciertas

reglas que establecen dichas prioridades; por ejemplo, las multiplicaciones y divisiones siem-

pre se ejecutan antes que las sumas y restas. Pero es más práctico emplear los paréntesis, los

cuales ordenan que primero se ejecuten las operaciones de los paréntesis más internos. Eso es

como en el álgebra elemental de la escuela, así que no profundizaré.

Por ejemplo, las tres siguientes sentencias son diferentes.

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

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

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

LAS FUNCIONES

Una función es un bloque de sentencias identificado por un nombre y puede recibir y devolver

datos. En bajo nivel, en general, las funciones operan como las subrutinas de assembler, es

decir, al ser llamadas, se guarda en la Pila el valor actual del PC (Program Counter), des-

pués se ejecuta 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 a

todas 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 }

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ún pará-

metro de entrada, se pueden dejar los paréntesis vacíos o escribir un void entre ellos.

Funciones sin parámetros

Para una función que no recibe ni devuelve ningún valor, la plantilla de arriba se reduce al si-

guiente esquema:

void function_name ( void ) { // Cuerpo de la función }

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

Page 18: Lenguaje C Para Microcontroladores

function_name();

La función principal main es otro ejemplo de función sin parámetros. Dondequiera que se ubi-

que, siempre debería ser la primera en ejecutarse; de hecho, no debería terminar.

void main (void) { // Cuerpo de la función }

Funciones con parámetros (por valor)

Por el momento, solo estudiaremos las funciones que pueden tener varios parámetros de en-

trada pero solo uno de salida.

Si la función no tiene parámetros de entrada o de salida, debe escribirse un void en su lugar.

El valor 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 en pa-

rámetros por valor y parámetros por referencia. Lo expuesto en este apartado corresponde al

primer 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 los pa-

rá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. De

este modo, el valor de la variable original no será alterado. Mejor, plasmemos todo esto en el

siguiente ejemplo.

int minor ( int arg1, int arg2, int arg3 ) { int min; // Declarar variable min min = arg1; // Asumir que el menor es arg1 if ( arg2 < min ) // Si arg2 es menor que min min = arg2; // Cambiar a arg2 if ( arg3 < min ) // Si arg3 es menor que min min = arg3; // Cambiar a arg3 return min; // Retornar valor de min } void main (void) { int a, b, c, d; // Declarar variables a, b, c y d /* Aquí asignamos algunos valores iniciales a 'a', 'b' y 'c' */ /* ... */ d = minor(a,b,c); // Llamar a minor // En este punto 'd' 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 se

copiará a la variable arg1; el valor de b, a arg2 y el valor de c, a arg3. Después de ejecu-

tarse el código de la función, el valor de retorno (min en este caso) será copiado a una varia-

ble temporal y de allí pasará a d.

Page 19: Lenguaje C Para Microcontroladores

Aunque el C no es tan implacable con la comprobación de tipos de datos como Pascal, siempre

deberí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 la

funció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 24 ó

32 bits), no compatible con el tercer parámetro que la función espera (entero de 8 ó 16 bits).

En estos casos el compilador nos mostrará mensajes de error, o cuando menos de advertencia.

Parámetros por referencia

La función que recibe un parámetro por referencia puede cambiar el valor de la variable pasa-

da. La forma clásica de estos parámetros se puede identificar por el uso del símbolo &, tal co-

mo se ve 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 esta

forma. Otra forma de pasar un parámetro por referencia es mediante los punteros, pero eso lo

dejamos para el final porque no es nada nada fácil para un novato.

Prototipos de funciones

El prototipo de una función le informa al compilador las características que tiene, como su tipo

de 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

por terminar en un punto y coma (;). Los nombres de las variables de entrada son opcionales.

Por ejemplo, en el siguiente boceto de programa los prototipos de las funciones main, func1 y

func2 declaradas al inicio del archivo permitirán que dichas funciones sean accedidas desde

cualquier parte del programa. Además, sin importar dónde se ubique la función main, ella

siempre será la primera en ejecutarse. Por eso su prototipo de función es opcional.

#include <pic.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)

Page 20: Lenguaje C Para Microcontroladores

{ // 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, mos-

trará errores de “función no definida”. El siguiente boceto ilustra este hecho. (Atiende a

los comentarios.)

#include <pic.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 // Desde aquí se puede acceder a func1 y main }

Para terminar, dado que los nombres de las variables en los parámetros de entrada son opcio-

nales, los prototipos de func1 y func2 también se pueden escribir así

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

Variables locales y variables globales

Los lenguajes de alto nivel como el C fueron diseñados para desarrollar los programas más

grandes y complejos que se puedan imaginar, programas donde puede haber cientos de varia-

bles, entre otras cosas. ¿Imaginas lo que significaría buscar nombres para cada variable si to-

dos tuvieran que ser diferentes? Pues bien, para simplificar las cosas, el C permite tener varias

variables con el mismo nombre.

Así es. Esto es posible gracias a que cada variable tiene un ámbito, un área desde donde será

accesible. Hay diversos tipos de ámbito, pero empezaremos por familiarizarnos con los dos

más usados, que corresponden a las variables globales y variables locales.

Las variables declaradas fuera de todas las funciones y antes de sus implementaciones tie-

nen carácter global y podrán ser accedidas desde todas las funciones.

Las variables declaradas dentro de una función, incluyendo las variables del encabezado,

tienen ámbito local. Ellas solo podrán ser accedidas desde el cuerpo de dicha función.

De este modo, puede haber dos o más variables con el mismo nombre, siempre y cuando es-

tén en diferentes funciones. Cada variable pertenece a su función y no tiene nada que ver con

las variables de otra función, por más que tengan el mismo nombre.

En la mayoría de los compiladores C para PICs las variables locales deben declararse al

principio de la función.

Page 21: Lenguaje C Para Microcontroladores

Por ejemplo, en el siguiente boceto de programa hay dos variables globales (speed y limit)

y cuatro variables locales, tres de las cuales se llaman count. Atiende a los comentarios.

char foo(long ); // Prototipo de función int speed; // Variable global const long limit = 100; // Variable global constante void inter(void) { int count; // Variable local /* Este count no tiene nada que ver con el count de las funciones main o foo */ speed++; // Acceso a variable global speed vari = 0; // Esto dará ERROR porque vari solo pertenece // a la 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 */ count = 0; // Acceso a count local speed = 0; // Acceso a variable global speed } char foo(long count) // Variable local count { int vari; // Variable local vari }

Algo muy importante: a diferencia de las variables globales, las variables locales tienen al-

macenamiento temporal, es decir, se crean al ejecutarse la función y se destruyen al salir

de ella. ¿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,

la precedencia en dicha función la tiene la variable local. Si te confunde, no uses variables glo-

bales y locales con el mismo nombre.

Variables static

Antes de nada debemos aclarar que una variable static local tiene diferente significado que

una variable static global. Ahora vamos a enfocarnos al primer caso por ser el más común.

Cuando se llama a una función sus variables locales se crearán en ese momento y cuando se

salga de la función se destruirán. Se entiende por destruir al hecho de que la locación de me-

moria 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á el

mismo 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 a a++; // Incrementar a }

Cualquiera que haya sido su valor inicial, ¿crees que después de llamar a esta función 10 ve-

ces, el valor de a se habrá incrementado en 10?... Pues, no necesariamente. Cada vez que se

Page 22: Lenguaje C Para Microcontroladores

llame a increm se crea a, luego se incrementa y, al terminar de ejecutarse la función, se des-

truye.

Para que una variable tenga una locación de memoria independiente y su valor no cambie en-

tre llamadas de función tenemos dos caminos: o la declaramos como global, o la declaramos

como local estática. Los buenos programadores siempre eligen el segundo.

Una variable se hace estática anteponiendo a su declaración el especificador static. Por defec-

to las variables estáticas se autoinicializan a 0, pero se le puede dar otro valor en la misma

declaració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 5 a++; // Incrementar a } void main() { int i; // Declarar variable i // El siguiente código llama 10 veces a increm for(i=0; i<10; i++) increm(); // Ahora la variable a sí debería valer 15 while(1); // Bucle infinito }

Variables volatile

A diferencia de los ensambladores, los compiladores tienen cierta “inteligencia”. Es decir, pien-

san un poco antes de traducir el código fuente en código ejecutable. Por ejemplo, veamos el

siguiente 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 que significa 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ás difí-

ciles de localizar, cuya optimización puede ser contraproducente. El caso más notable que des-

tacan los manuales de los compiladores C para microcontroladores es el de las variables globa-

les 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 declarar-

la como volatile, anteponiéndole dicho calificador a su declaración habitual.

Por ejemplo, en el siguiente boceto de programa la variable count debe ser accedida desde la

función interrupt como desde la función main; por eso se le declara como volatile. Nota:

el esquema de las funciones de interrupción suele variar de un compilador a otro. Éste es solo

un ejemplo.

Page 23: Lenguaje C Para Microcontroladores

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 }

ARRAYS Y PUNTEROS

Probablemente éste sea el tema que a todos nos ha dado más de un dolor de cabeza y que

más hemos releído para captarlo a cabalidad. Hablo más bien de los punteros. Si ellos el C no

sería nada, perdería la potencia por la que las mejores empresas lo eligen para crear sus soft-

wares de computadoras.

Pero bueno, regresando a lo nuestro, estos temas se pueden complicar muchísimo más de lo

que veremos aquí. Solo veremos los arrays unidimensionales y los punteros (que en principio

pueden apuntar a todo tipo de cosas) los abocaremos a los datos básicos, incluyendo los mis-

mos arrays. Aun así, te sugiero que tengas un par de aspirinas al lado.

Los arrays o matrices

Un array es una mega variable compuesto de un conjunto de variables simples del mismo

tipo y ubicadas en posiciones contiguas de la memoria. Con los arrays podemos hacer todos

lo que hací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 el siguiente esquema se ve que el primer elemento de un array tiene índice 0 y el último,

N-1, siendo N la cantidad de elementos del array.

Estructura de un array unidimensional de N elementos.

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 y Nu-

mElementos 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 char long HexTable[16]; // HexTable es un array de 16 elementos de tipo long int address[100]; // address es un array de 100 elementos de tipo int

Page 24: Lenguaje C Para Microcontroladores

Para el array letters el primer elemento es letters[0] y el último, letters[9]. Así,

tenemos 10 elementos en total. Si quisiéramos asignar a cada uno de los elementos de let-

ters los caracteres desde la ‘a’ hasta la ‘j’, lo podríamos hacer individualmente así:

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

Pero así no tiene gracia utilizar arrays, ¿verdad? En este caso lo mejor es utilizar un bucle,

así: (Nota: los caracteres son, al fin y al cabo, números en códigos ascii y se les puede compa-

rar.)

char c; for ( c = 'a'; c <= 'j'; c++ ) letters[i] = c;

Inicialización de arrays

Los elementos de un array se pueden inicializar junto con su declaración. Para ello se le asig-

na una lista ordenada de valores encerrados por llaves y separados por comas. Por supuesto,

los valores deben ser compatibles con el tipo de dato del array. Este tipo de inicialización solo

está permitido en la declaración del array.

Ejemplos:

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

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á precalculado y puesto por el compilador. Ésta es

una forma bastante usada en los arrays de texto, donde puede resultar muy incómodo estar

contando las letras de una cadena. Por ejemplo:

int a[] = { 70, 1, 51 }; // Un array de 3 elementos char vocals[] = { 'a', 'e', 'i', 'o', 'u' }; // Un array de 5 elementos char msg[] = "Este es un array de caracteres"; // Un array of 31 elementos

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

Cadenas de texto terminadas en nulo

Son arrays de tipo de dato char. Hay dos características que distinguen a estas cadenas de

los demá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 elementos char msg[] = "Hello"; // Un array de 6 elementos

Page 25: Lenguaje C Para Microcontroladores

El array Greet tiene espacio para 10 elementos, de los cuales solo los 5 primeros han sido

llenados con las letras de Hello, el resto se rellena con ceros.

El array msg tiene 6 elementos porque además de las 5 letras de “Hello” se le ha añadido

un Null (0x00) al final (claro que no se nota). Es decir, la inicialización de msg es equivalen-

te a:

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

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

Estructura de una cadena de texto.

Los punteros

Los punteros suelen ser el tema que más cuesta entender en programación. Pero si ya llegaste

aquí, es el momento menos indicado para detenerte.

Los punteros son un tipo de variables muy especial. Son variables que almacenan las direccio-

nes físicas de otras variables. Si tenemos la dirección de una variable, tenemos acceso a esa

variable de manera indirecta y podemos hacer con ellas todo lo que queramos ;).

Declaración de punteros

Los punteros pueden apuntar a todo tipo de variables, pero no a todas al mismo tiempo. La

declaración de un puntero es un tanto peculiar. En realidad, se parece a la declaración de una

variable ordinaria solo que se pone un asterisco de por medio. En este punto debes recordar

las declaraciones de todo tipo de variables que hemos visto, incluyendo las influenciadas por

los calificadores const, static, etc. Todas excepto los arrays; ¿por qué?

La forma general de declarar un puntero es la siguiente:

data_type * PointerName;

Los siguientes ejemplos muestran lo fácil que es familiarizarse con la declaración de los punte-

ros:

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

Apuntando a variables

Decimos que una variable puntero “apunta” a una variable x si contiene la dirección de dicha

variable. Para ello se utiliza el operador &, el cual extrae la dirección de la variable a la que

acompaña. Un puntero siempre debería apuntar a una variable cuyo tipo coincida con el tipo

del puntero.

En los siguientes ejemplos vemos cómo apuntar a variables de tipo básico, como int, char o

float. Más adelante veremos cómo apuntar a arrays.

Page 26: Lenguaje C Para Microcontroladores

void main (void) { int height, width; char a, b, c; float max; int * ip; // ip es un puntero a variable tipo int char * cp; // cp es un puntero a variable tipo char float * fp; // Puntero a variable tipo float ip = &height; // Con esto ip tendrá la dirección de height ip = &width; // Ahora ip apunta a width cp = &a; // cp apunta a a cp = &c; // Ahora cp apunta a c cp = &a; // Ahora cp apunta a a otra vez fp = &max; // fp apunta a max fp = &height; // Error! height no es una variable float //... }

Asignaciones indirectas mediante punteros

Una vez que un puntero apunte a una variable cualquiera, se puede acceder a dicha variable

utilizando el nombre del puntero precedido por un asterisco, de esta forma:

void main (void) { int height, width, n; // Variables ordinarias int * p, * q; // p y q son punteros a variables de tipo int p = &height; // p apunta a height *p = 10; // Esto es como height = 10 p = &width; // p apunta a width *p = 50; // Esto es como width = 50 height = *p; // Esto es como height = width q = &height; // q apunta a height n = (*p + *q)/2; // Esto es como n = (height + width)/2 //... }

La expresión *p se debería leer: “la variable apuntada por p”. Eso también ayuda mu-

cho a comprender a los punteros.

¿Y para esto se inventaron los punteros? Yo me preguntaba lo mismo en mis inicios. El tema

de los punteros se puede complicar casi “hasta el infinito”, por eso quiero ir con cuida-

do y poco a poco para que nadie se pierda.

Punteros y arrays

¿Cómo se declara un puntero a un array? Un puntero a un array es simplemente un puntero

al tipo de dato del array. Cuando se asigna un puntero a un array, en realidad el puntero

toma la direcció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 del

array. Todo lo indicado se refleja en el siguiente código:

void main (void) {

Page 27: Lenguaje C Para Microcontroladores

int * p; // Declara p como puntero a int int n; // Alguna variable int mat[3] = { 78, 98, 26 }; // Array de variables int p = &mat; // p apunta a mat (a su primer elemento) n = *p; // Esto da n = 78 p++; // Incrementar p para apuntar a siguiente elemento n = *p; // Esto da n = 98 p++; // Incrementar p para apuntar a siguiente elemento n = *p; // Esto da n = 26 *p = 10; // Con esto mat[3] valdrá 10 p--; // Decrementar p para apuntar a elemento anterior *p = 100; // Con esto mat[2] valdrá 100 p = mat; // p apunta a mat. Es lo mismo que p = &mat p = NULL; // Desasignar p. Lo mismo que p = 0x0000 // ... }

En el fondo los arrays y los punteros trabajan de la misma forma, por lo menos cuando refe-

rencian a variables almacenadas en la RAM del microcontrolador. La única diferencia es que los

arrays no pueden direccionar a datos diferentes de su contenido; por eso también se les lla-

ma punteros estáticos. En la práctica esto significa que un array es siempre compatible

con un puntero, pero un puntero no siempre es compatible con un array.

Por ejemplo, a un array no se le puede asignar otro array ni se le pueden sumar o restar

valores para que apunten a otros elementos. Por lo demás, las operaciones de asignación son

similares para 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ático str2 += 2; // Correcto. Ahora str2 apunta a 'i' str1++; // Error otra vez! Str1 es estático str2++; // Correcto. Ahora str2 apunta a 'n' a = *str2; // Esto da a = 'n' // ... }

Paso de punteros y arrays a funciones

¿Recuerdas el paso de variables por valor y por referencia? Pues aquí vamos de nuevo.

Page 28: Lenguaje C Para Microcontroladores

Bien, recordemos: una variable pasada por valor a una función, en realidad le entrega una

copia suya; por lo que la variable original no tiene por qué ser afectada por el código de la fun-

ción. Ahora bien, pasar una variable por referencia significa que se pasa la dirección de dicha

variable. Como consecuencia, la función tendrá acceso a la variable original y podrá modificar

su contenido. Esto podría resultar riesgoso, pero, bien usada, la técnica es una potente arma.

Ya que los punteros operan con direcciones de variables, son el medio ideal para trabajar con

parámetros por referencia. Hay dos casos de particular interés: uno, cuando deseamos en se-

rio que la variable pasada a la función cambie a su regreso; y dos, cuando la variable pasada

es demasiado grande (un array) como para trabajar con copias. De hecho, los arrays siem-

pre se pasan 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 cuenta que también tienen la for-

ma de declaraciones de variables.

En el siguiente ejemplo la función interchage intercambia los valores de las dos variables

recibidas. 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 la // 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, la función interchange recibirá dichos valores en p1 y p2, respectivamente. De ese

modo, p1 y p2 estará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 a dirección de variable).

Ahora veamos ejemplos donde la forma de asignación cambia a “puntero = puntero”. Esto

incluye a los arrays porque, recordemos, un puntero siempre puede ser tratado como un

array, aunque lo contrario no siempre es posible.

En el siguiente programa array1 y array2 se pasan a la función prom, la cual devuelve el

valor promedio de los elementos del array recibido. Como para ese cálculo se necesita cono-

cer la cantidad de elementos que tiene el array, prom recibe dicho valor en el parámetro si-

ze.

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.

Page 29: Lenguaje C Para Microcontroladores

tmp += p[i]; // Sumar elemento p[i] a tmp. return ( tmp/size ); // Retornar valor promediado. } void main (void) { int array1[4] = { 51, 14, 36, 78 }; // Un array de 4 elementos int array2[] = { -85, 4, 66, 47, -7, 85 }; // Un array de 6 elementos float avrg; // Una variable tipo float, para decimales avrg = prom (array1, 8); // Ahora avrg 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 en mayúscu-

las, y lon calcula la longitud del texto almacenado en el array recibido. Ambas funciones re-

ciben 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 = 9 L = lon(song2); // Debería dar L = 15 L = 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);

Page 30: Lenguaje C Para Microcontroladores

// 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 }

En el programa se crean tres arrays de texto de 20 elementos (song1, song2 y song3), pe-

ro el texto 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

por debajo de las minúsculas. Por eso basta con sumarle o restarle ese valor a un carácter

ascci para pasarlo a mayúscula o minúscula.

En ambas funciones el puntero p navega por los elementos del array apuntado hasta que en-

cuentra el final, indicado por un carácter nulo (0x00).

Arrays constantes

No es que me haya atrasado con el tema, es solo que los arrays constantes son uno de

los temas cuyo 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

simples como eso.

En principio, para que un array sea constante a su clásica declaración con inicialización de un

array se le debe anteponer el calificador const. Por ejemplo:

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

De este modo, los arrays a, vocals y text serán de solo lectura, y sus elementos podrán

ser leídos, mas no escritos. Es como si estuviéramos frente a una tabla hecha en ensamblador

(de PICs) a base de instrucciones retlw. De hecho, los compiladores Mikro C, CCS C, Hi-

tech C e AVR IAR C, construirán internamente tablas semejantes para representar estos

arrays.

Si estos arrays constantes van a ser leídos directamente y mientras se utilice la notación de

los corchetes para especificar a cada elemento, todo estará ok.

Por otro lado, cada compilador trabaja diferente el paso de arrays constantes a funciones y

su compatibilidad con los punteros. Por ejemplo, Hi-tech C soporta magistralmente estos te-

mas. Por otro lado, CCS C no acepta nada, pero ofrece excelentes métodos alternativos para

realizar esas tareas. De modo que será necesario revisar el manual de cada compilador en par-

ticular.

En otros compiladores, como MPLAB C18 o BoostC, debe además añadirse el calificador rom

(no perteneciente al C estándar). BoostC solo soporta arrays constantes con datos de 8

bits (de tipo char y compatibles) y su peculiar forma de declarlas es la siguiente.

rom char * a = "cadena de texto constante en BoostC"; rom char * mat = { 20, 56, 87, -58, 50 }; // Array constante rom unsigned char * dec = { 10, 20, 30, 40, 50 }; // Array constante

Page 31: Lenguaje C Para Microcontroladores

Por lo demás, el acceso a los elementos de estos arrays tiene que seguir siendo mediante

índices y corchetes.

En el compilador MPLAB C18, la palabra rom va junto al const. Por supuesto que en los PIC18

los arrays en ROM se implementan con más eficiencia sin recurrir a los limitados retlw.

rom const char * a = "cadena de texto constante en MPLAB C18"; rom const char a[] = "otra cadena de texto constante en MPLAB C18"; rom const int mat[5] = { 20, 56, 87, -58, 5000 }; // Array constante rom const long pots[] = {10, 100, 1000, 10000, 100000}; // Array constante

http://www.cursomicros.com/avr/practicas-del-curso.html

http://www.cursomicros.com/pic/vidtuts/proteus/tutorial_de_proteus.html

http://www.todopic.com.ar/foros/index.php?action=printpage;topic=4533.0

CODIGO 1

///////////////////////////////////////////////////////////////////////// //// PARPADEO //// //// //// //// Ejemplo de parpadeo de todos los leds colocados en el PORTB //// //// //// //// Realizado por Manuel Jiménez (nocturno) para el foro TODOPIC //// //// //// //// 4/8/2004 //// //// //// ///////////////////////////////////////////////////////////////////////// #include <16F84.h> #fuses HS,NOWDT,NOPROTECT #use delay(clock=4000000) #byte port_b=6 void main() { int contador; set_tris_b(0); // declaramos el puerto B como salidas port_b=0; while (true) { // bucle infinito contador=200; // retraso, 200 ciclos de 1000 microsegundos, // o sea, 2 milisegundos while(--contador!=0) delay_us(1000); port_b=0xff; // activa todos los pins del puerto B contador=200; while(--contador!=0) // retraso, 200 ciclos de 1000 microsegundos, // o sea, 2 milisegundos delay_us(1000); port_b=0x00; // apaga todos los pins del puerto B }; }

CODIGO 2

Empezamos algo bueno!!

Otro ejemplillo con leds, esta vez las lucecitas del coche fantástico en el puerto b

Page 32: Lenguaje C Para Microcontroladores

///////////////////////////////// PREPROCESADO ///////////////////////////////// #include <16F84.h> #use delay(clock=4000000) #fuses XT,NOWDT,NOPUT,NOPROTECT #use fast_io(B) #byte PORTB = 0x06 /////////////////////////////////// PRINCIPAL ////////////////////////////////// void main() { set_tris_b(0b00000000); // Puerto B al completo configurado como salida PORTB = 1; // Encendemos el primer led for(;;) // Bucle infinito { while(PORTB != 128) // Avanzamos en una dirección { PORTB = PORTB * 2; // Apagamos el led que esté encendido y // encendemos el siguiente delay_ms(200); // Retardo de 200 ms } while(PORTB != 1) // Avanzamos en la otra dirección { PORTB = PORTB / 2; // Apagamos el led encendido y encendemos el anterior delay_ms(200); } } }

CODIGO 3

Lo del parpadeo un poco más corto:

#include <16F84.h> #fuses XT,NOWDT,NOPROTECT #use delay(clock=4000000) #use fast_io(B) void main() { set_tris_b(0x00); while (1) { output_b(0xff); delay_ms(200); output_b(0x00); delay_ms(200); } }

NOTA. El programa se puede hacer también así:

- Sin el fast_io(B) y sin el set_tris_b(0x00) y todo lo demás como está.

- O se deja el fast_io(B) y se añade en el main el set_tris_b(0x00)

Personalmente soy partidario de la segunda opción. En todos los programas se definan entradas y salidas

con los correspondientes set_tris, así vamos a lo seguro. Además usando el fast_io se consiguen al com-

pilar, menos instrucciones de programación en asembler ya que cada vez que en el programa se encuen-

tre con un input o un output no se reprograma el puerto, solo se programa una vez con el primer input u

output, cosa que no ocurre sin el fast.

Un saludo

Page 33: Lenguaje C Para Microcontroladores

CODIGO 4

///////////////////////////////////////////////////////////////////////// //// PARPADEO v.2 //// //// //// //// Ejemplo de parpadeo de todos los leds colocados en el PORTB //// //// //// //// Realizado por Manuel Jiménez (nocturno) para el foro TODOPIC //// //// //// //// 4/8/2004 //// //// //// ///////////////////////////////////////////////////////////////////////// #include <16F84.h> #fuses XT,NOWDT,NOPROTECT #use delay(clock=4000000) #use fast_io(B) void main() { int i; while (true) { // bucle infinito // en esta primera parte va encendiendo la // mitad alta del puerto B y apagando la mitad baja for (i=1;i<=24;++i) { // va cambiando el retraso, de 1 a 24 ms output_b(0xF0); // encendemos la mitad del puerto B delay_ms (i); // hacemos una parte de la espera output_b(0x0F); // encendemos la otra mitad del puerto B delay_ms ((25-i)); // realizamos la espera complementaria }; // en esta segunda parte va encendiendo la // mitad baja del puerto B y apagando la mitad alta for (i=1;i<=24;++i) { // va cambiando el retraso, de 1 a 24 ms output_b(0x0F); // encendemos la mitad del puerto B delay_ms (i); // hacemos una parte de la espera output_b(0xF0); // encendemos la otra mitad del puerto B delay_ms ((25-i)); // realizamos la espera complementaria }; }; }

CODIGO 5

MASCARA

Que diferencia hay entre:

valor == (valor && 0x80) y valor & = 0x80

La primera no me funciona, la segunda si.

Título: Microcursillo en C

La segunda lo que hace es asignar a valor la operación AND, a nivel de bits, de su contenido con el literal

0x80. La primera no vale para asignar al ser un doble igual (==). Al ser una comparación, supongo que

lo tendrás como condición de control en una estructura de control (while, for, if...). Tal y como está escri-

ta, no puede desempeñar ninguna otra función.

Saludos

Título: Microcursillo en C

Gracias Modulay por responder, lo que quiero hacer es una mascara para saber el estado del bit mas

significativo de un puerto... leo el puerto y lo guardo en valor, luego con un "and lógico" pongo todo a

cero menos el ultimo bit para saber si esta a 1 o a cero.

Page 34: Lenguaje C Para Microcontroladores

Título: Microcursillo en C

Pues el segundo método es el que te va al pelo. El operador && trabaja a nivel lógico, pero no a nivel de

bits. Un uso correcto seria...

BOOLEAN condicion;

int numero;

if (condicion1 && numero == 15)

{

bla bla bla...

}

Aunque si quieres saber el estado de un bit de cualquiera de los puertos basta con hacer:

if ( input(PIN_B7) )

{

// Esto se ejecuta si RB7 está a 1

}

Espero que te ayude

Saludos

Título: Microcursillo en C

Bueno, lo importante es dar ideas y que cada uno aporte dentro de sus posibilidades. Qué mas da quien

esté al frente mientras sigamos aprendiendo unos de otros. Aun así...gracias!!!!

Un saludote

CODIGO 5

Hola a todos, estoy empezando a programar en C y la verdad no parece tan difícil como el asm. Aquí os

dejo otro programita de led's usando funciones.

#include <16F84.h>

#use delay(clock=4000000)

#fuses XT,NOWDT,NOPROTECT

#use standard_io(B)

//////////////////////// FUNCIONES //////////////////////

void Parpadea_0(int Repeticiones)

{

int i;

for(i=1;i<=Repeticiones;++i)

{

output_high(pin_B0);

delay_ms(500);

output_low(pin_B0);

delay_ms(500);

}

}

void Parpadea_1(int Repeticiones)

{

int i;

for(i=1;i<=Repeticiones;++i)

{

output_high(pin_B1);

delay_ms(500);

output_low(pin_B1);

Page 35: Lenguaje C Para Microcontroladores

delay_ms(500);

}

}

void Parpadea_2(int Repeticiones)

{

int i;

for(i=1;i<=Repeticiones;++i)

{

output_high(pin_B2);

delay_ms(500);

output_low(pin_B2);

delay_ms(500);

}

}

void Parpadea_3(int Repeticiones)

{

int i;

for(i=1;i<=Repeticiones;++i)

{

output_b(0x07);

delay_ms(500);

output_b(0x00);

delay_ms(500);

}

}

///////////////////////////// PROGRAMA PRINCIPAL /////////////////////////

void main()

{

set_tris_b(0x00);

output_b(0x00);

Parpadea_0(5);

Parpadea_1(5);

Parpadea_2(5);

Parpadea_3(5);

while(true);

}

CODIGO 6

Título: Microcursillo en C

Hola, que tal a todos. Algunos que me conocen en el foro, saben que programo en ASM, peor después de

ver todo lo que se podía hacer con la serie 18f, y después de ver todo lo que se necesitaba en ASM para

hacer funcionar esos pics, tome la decisión de comenzar a tocar el lenguaje C para pics, el compilador

con el cual estoy comenzando mis primeros pasos es el CCS 3.249. Y como no podía ser de otra manera

había que comenzar con algo fácil, simple y ameno. El parpadeo de un led, al cual le agregue una condi-

ción con el PIN RA0 del PORTA, y bueno acá esta el resultado de eso, claro con la ayuda de el_guitre que

me asesoro "on-line". Ahora me tendrá a MI dándoles lata por que ahora tendré que pensar en C y dejar

el ASM por un momento

Page 36: Lenguaje C Para Microcontroladores

Código

GeSHi (c):

1. #include <16f877A.h> 2. #fuses XT,NOWDT,PUT,NODEBUG,NOPROTECT,NOBROWNOUT,NOLVP,NOCPD,NOWRT 3. #use delay(clock=4000000) 4. #use fast_io(b) 5. #use fast_io(a) 6. #zero_ram //pone a "0" todos los registros internos que son usados

como 7. //variables 8. 9. 10. void main() 11. { 12. 13. set_tris_b(0x00); // configuro el puerto B como salida 14. set_tris_a(0b00001); // configura el RA0=in, RA1-RA5=out 15. output_b(0x00); // pongo en cero las salidas del portb 16. 17. 18. while(true) 19. { 20. while(!input(PIN_A0)) //pregunto si presione RA0=0? 21. { //si, entonces enciendo RB1 22. output_high(PIN_B1); 23. } 24. output_low(PIN_B1); //no, apago RB1, y RB0 esta 25. delay_ms(1000); //encendido y apagado cada 1s 26. output_toggle(PIN_B0); 27. } 28. } 29.

P.D: Si la memoria no me falla, CCS tiene librerías para manejar LCD y teclado matricial, mi duda es si se

pueden usar el LCD y el teclado en el mismo portB, ya que en asm yo lo hago así, el lcd lo tengo conec-

tado en la parte alta del puerto y el detecto las pulsadas en el teclado por interrupción (cambio de -

estado en RB7-RB4) así me permite usar los dos dispositivos en un solo puerto. No se si la librería que

hizo aitopes puede hacer eso o tendré que hacer mis adaptaciones del asm de esas librerías al C: D

CODIGO 7

Título: Microcursillo en C – Funciones

Según tengo entendido, todas las funciones de C tienen un tipo de dato y regresa un valor cuando termi-

nan. En este caso main es la función principal, así que no importa que no regrese algún valor al terminar

de ejecutarse, por eso se le pone:

void main(void); el tipo de función es void y el tipo de dato que regresa es void, es decir, no impor-

tan.

Si no mal recuerdo se puede hacer algo así:

suma(dato_x)

{

dato_x=10;

return dato_x;

}

y en el programa se le pone

dato_a=suma();

Si ando medio perdido por favor corríjanme

Page 37: Lenguaje C Para Microcontroladores

Título: Microcursillo en C- Funciones

Hola migsantiago:

Yo creo que el comando "return" es exclusivo para las funciones. La forma de definir una función (la fun-

ción "Suma") sería la siguiente:

int Suma(int aux)

{

return (aux+10);

}

Esta función devuelve un valor de tipo "int", y tiene como parámetro de entrada otro "int". Por lo que los

tipos de datos deben de coincidir tanto en la cabecera como en la llamada a la función.

Esta función podría ser llamada de la siguiente forma

resultado=Suma(num);

Espero que sirva de ayuda...

Saludos.

Título: Microcursillo en C-Funciones

Hola pichi yo soy nuevo aquí pero creo que una función VOID es una función vacía. ¿Que quiere decir?

Quiere decir que no necesita ninguna variable de entrada. Ejemplo:

...

int a;

int x;

FUNCIÓN(VOID)

{

return a + x;

}

Otra forma:

FUNCIÓN(int x,int a)

{

return a + x;

}

La diferencia de estas dos funciones radica en la forma de llamarlas:

En la primera llamaríamos solo a la función y las variables X y A ya estarían definidas en otra parte del

programa. (Variables globales).

En la segunda las variables X y A solo están definidas dentro de esa función con lo que X y A son varia-

bles diferentes de cualquier otra variable X y A definidas fuera de esa función. (Variables locales).

Espero que te sirva de ayuda Pichi, aunque yo también soy un poco novato en C.

Saludos de Eleman.

Ejercicio 1. Prender y apagar todos los pines del pic 16f877a durante un segundo respectivamente.

Ejercicio 2. Configurar los puertos A y B para que 3 sean entradas y las restantes salidas.

Ejercicio 3. Verificar las funciones de CODIGO 7