Prosiguiendo con nuestro estudio sobre las herramientas de programación para la gama media hemos buscado distintas herramientas gratuitas que ofrezcan soporte para el lenguaje C. Microchip se limita a ofrecer tan solo los MPLAB-C17 y MPLAB-C18, limitadas a la gama alta (PIC17CXX y PIC18CXX, respectivamente).
El entorno MPLAB, es del tipo container, y carece de lenguajes en su código, dejando la tarea de compilación para otros programas, requiere que estos creen, a su fin, determinados ficheros para atender sus propias necesidades, como la de mostrar errores del programa.
Veamos un sencillo programa:
|
char aa, bb, cc, dd; // Creamos cuatro variables de tipo char (caracteres, tamaño 1 byte, lo usual) que al estar declaradas // fuera del main() son globales main() // Proceso principal { char kk; // Nuevo char, utilizable solo en main, variable local
aa = 3; // Damos un par de valores, y calculamos una multiplicación bb = 5; cc = aa*bb;
kk = cc+1; dd = myfunc( kk ); // Llamada a la función myfunc, con el parámetro kk. // Su valor de vuelta es asignado a la variable dd }
myfunc(char t ) { char c;
c = t+4; // Calculamos la función deseada return( c ); // Devolvemos el valor c.
}
|
No es nuestro deseo tampoco hacer un estudio profundo del lenguaje c, del que es abundante la bibliografía y con el que pensamos que se hallará familiarizado cualquier lector que se aventure en estas páginas, pero sí hablaremos de sus ventajas, sus desventajas y hasta dónde llega su desarrollo como lenguaje (qué encontraremos en sus librerías).
Por lo pronto, se tiene aquí la comparación directa con el resultado de la compilación. Es fácil apreciar lo simple, escueto, legible y fácil de modificar que resulta el código en c.
|
;Small C PIC16C84; ; Coder (1.0 2/10/95) ; Version 0.002
; Front End (PIC Ver 1.0 2/19/95)
include '16c84.h'
; **************code segment cseg*******************
org _CSEG
; Begin Function
main_ sub _stackptr, #1 mov _primary, #3 mov aa_ , _primary mov _primary, #5 mov bb_ , _primary mov _primary, aa_ call _push_ mov _primary, bb_ call _pop_ call _mul_ mov cc_ , _primary mov _primary, #0 add _primary, _stackptr call _push_ mov _primary, cc_ call _push_ mov _primary, #1 call _pop_ add _primary, _secondary call _pop_ call _putstk_ mov _primary, #0 add _primary, _stackptr call _indr_ call _push_ ;;(# args passed) mov W, #1 call myfunc_ add _stackptr, #1 mov dd_ , _primary _1_ add _stackptr, #1 ret
; Begin Function
myfunc_ sub _stackptr, #1 mov _primary, #0 add _primary, _stackptr call _push_ mov _primary, #2 add _primary, _stackptr call _indr_ call _push_ mov _primary, #4 call _pop_ add _primary, _secondary call _pop_ call _putstk_ mov _primary, #0 add _primary, _stackptr call _indr_ jmp _2_ _2_ add _stackptr, #1 ret
;*******need mult/div standard library******** include 'mlibpic.h' ; **************data segment dseg*******************
org _DSEG
aa_ ds 1 bb_ ds 1 cc_ ds 1 dd_ ds 1
;0 error(s) in compilation ; literal pool:0 ; global pool:112 ; Macro pool:51 end
|
Pocos comentarios quedan al respecto. De hecho es prácticamente indescifrable el resultado. Sin embargo, como siempre, una buena programación directa en ensamblador reducirá código, y será de ejecución más rápida. Muchas veces, incluso, resultará imprescindible para determinados módulos. Sin embargo, también necesitará un mayor tiempo de estudio, desarrollo e implementación que en el caso del c.
Pues, por ejemplo, como apreciaremos del ejemplo, los punteros:
|
char val;
main() { char aa,bb,cc,dd;
load( &aa, &bb, &cc, &dd); // El carácter & referencia a la dirección de la variable val = aa+bb+cc+dd;
}
load(char *a, char *b, char *c, char *d ) { // Y aquí a, b, c y d son direcciones de memoria, con lo que, *a = 1; // para aludir a sus valores, las precedemos de *. *b = 2; *c = 3; *d = 4; }
|
Otro ejemplo que muestra las ventajas de un bucle while, o de los operadores lógicos, como igual, distinto, >=, etc.
|
#define TRUE 1 #define FALSE 0
char ad, d1, d2, d3;
main() { char cnt;
while(TRUE) { ad = GetAD(); cnt = 0; while (ad>=100) { ad =a d-100; cnt++; } // while d1 = cnt; cnt = 0; while (ad>=10) { ad = ad-10; cnt++; } // while d2 = cnt; cnt = 0; while (ad>0) { ad = ad-1; cnt++; } // while d3 = cnt; } // while principal }
char GetAD(void) { char v;
v = 0xff; return(v);
}
|
Ejemplo del uso de switch, para escoger entre distintos valores de una variable.
|
char z;
main() { char i;
for (i=0; i<5; i++) { switch(i) { case 0: z = f1(); break; case 1: z = f2(); break; case 2: z = f3(); break; case 3: z = f4(); break; case 4: z = f5(); break; } } }
f1() { return(1); } f2() { return(2); } f3() { return(3); } f4() { return(4); } f5() { return(5); } |
Ejemplo de la sentencia condicional if:
|
char z;
main() { char i;
i = 0; while(i<5) { if (i==0) z = f1(); if (i==1) z = f2(); if (i==2) z = f3(); if (i==3) z = f4(); if (i==4) z = f5(); i++; } }
f1() { return(1); } f2() { return(2); } f3() { return(3); } f4() { return(4); } f5() { return(5); }
|
Ejemplo del bucle for, y de las cadenas de caracteres (la sentencia GetChar será explicada en el siguiente apartado):
|
/* ejemplo de inclusión en el compilador */
char a, b, c, d;
main() {
a = "hello"; b = "goodbye"; for (d=0; d<7; d++) c = GetChar(b,d); /* Indice a travéz del string */ for (d=0; d<5; d++) /* ditto */ c = GetChar(a,d); }
#include "getchar.c" // inclusión explicita del codigo correspondiente a getchar()
|
En este caso, hemos vuelto a comentarlo para la más sencilla comprensión:
|
/* test i/o port */
#include "io.c"
char a, b;
main() {
SetP_A(0x03); // Se establece como puerto A la dirección 0x03 SetP_B(0x0f); // Se establece como puerto B la dirección 0x0F a = RdPortA(); // El valor del puerto A se guarda en la variable a b = RdPortB(); // El valor del puerto B se guarda en la variable b WrPortA(0x1); // Escribimos en el puerto A el valor 0x01 WrPortB(0x55); // Escribimos en el puerto B el valor 0x55
}
|
En realidad es posible asignar como puerto A o B cualquier dirección física de memoria de datos, aunque esta no sea propiamente un puerto. Esto puede ser útil para manejar directamente registros como STATUS o INTCON.
Pero dejamos para el último apartado lo no descrito en los ejemplos, es decir, aquellas funciones incluidas en las librerías.
Esta librería sólo contiene la función GetChar (cadena, índice), que, como pudimos ver en el ejemplo, toma de una cadena de caracteres (definida, por ejemplo, como a = ”cadena”) el carácter situado en la posición índice. Por ejemplo, b = GetChar (a,2) nos daría b = “d”, ya que se empieza a contar desde 0.
Esta librería se encarga de la gestión de puertos, y contiene varias funciones:
SetP_A(valor) asigna como dirección del puerto A valor.
SetP_B(valor) asigna como dirección del puerto B valor.
RdPortA() y RdPortB() devuelven el valor leído en ambos puertos (siempre habrá que definir sus direcciones antes, o tendremos resultados imprevistos).
WrPortA(valor) y WrPortB(valor), respectivamente, enviarán valor a las salidas de los puertos A y B. Como en el caso de las anteriores, deben estar previamente definidas sus direcciones para no sufrir imprevistos.
Contiene la función ee_read (addr), que leerá un valor de la memoria EEPROM interna del microprocesador, sito en la dirección addr.
Contiene la función ee_write (addr), que escribirá un valor de la memoria EEPROM interna del microprocesador, sito en la dirección addr.
Estas dos últimas funciones no han sido detalladas en el apartado de ensamblador por no aparecer en la bibliografía asociada. Consultando el Databook en el apartado correspondiente al PIC16C84 se nos mostrará la manera apropiada de hacerlo, a través de los registros EECON1 (dirección 0x08 en la página 0), EECON2 (dirección 0x09 en la página 0), EEDATA (dirección 0x08 en la página 1, 0x88 como dirección absoluta) y EEADR (dirección 0x09 en la página 1, 0x89 como dirección absoluta).
Este C asume la directiva #asm, a continuación de la cuál seguirá un trozo de código en ensamblador. Para regresar a la programación en C deberemos incluir, tras la secuencia de código, #endasm. Puede ver ejemplos editando cualquiera de las librerías, sitas en el directorio Pic_lib. Podrá, a su vez, hacer referencia a los argumentos de la rutina en la que esté el código mediante #0, #1 ... #n, siendo el número la posición del argumento en la llamada a la rutina, comenzando por 0.
Las definiciones de constantes en ensamblador están en Pic_rt\16c84. _portA y _portB, por ejemplo, referencian a esos puertos, así como eedata, eeaddr, eecon1 y eecon2. También contiene macros como _push_, _pop_, _swap_, _swaps_, o _indr_, que son usados internamente por el compilador. Puesto que manejan los cuatro registros reservados para el C (estos son _primary, _secondary, _temp y _stackptr) no conviene que los use si no los comprende muy bien, ya que podría alterar el buen funcionamiento de su programa. Las direcciones de esto cuatro registros son 0x2f, 0x2e, 0x2c y 0x2b. Procure no usarlos en su código en ensamblador.