|
|
|||||||||||||||||||||||||||||
Introducción. Esta es la continuación de este artículo que
me propuse desarrollar para describir los pasos necesarios por cualquier interesado
a seguir para acercarse a la programación de los populares
microcontroladores PIC, sin necesidad (por que no la hay) de ser
universitario, sin menospreciar a los colegas universitarios, nada más
lejos de mi propósito para este colectivo. Veamos como proceder para realizar las prácticas que nos sirvan para
tomar la habilidad de plantear los criterios que son necesarios en cada situación. Esto se
consigue con la continua practica y más practica, no basta con leer todos los artículos
que estén a nuestro alcance, aseguro que se adquiere gran experiencia con
las prácticas continuadas y esto se refuerza con la comprensión de la
teoría de los libros o de este tipo de artículos. Por
ese motivo insisto en que se hagan muchas prácticas y se trate de comprender el motivo
de cada instrucción.
Si se quiere hacer que un LED se encienda y apague
automáticamente, es necesario un juego de instrucciones diferente.
En primer lugar necesitamos instrucciones para encender el
LED, luego una rutina para mantenerlo encendido por un periodo de tiempo (lo
que se llama un retardo), seguido entonces con otras instrucciones para
apagarlo y una última rutina de retardo o espera hasta recibir una señal si este
es el caso. El programa debe entonces enlazar atrás (realimentarse) donde se
activa el LED, el resultado es un LED que destella. Se puede crear un organigrama para esta
secuencia usando los símbolos mostrados anteriormente, lo cual ayudará
mostrando los pasos implicados en el programa. Del organigrama, podemos deducir la
disposición para el programa: Esto introduce tres nuevos términos: Rutina, Subrutina y Bucle o lazo.
Los programas se escriben con una o más subrutinas seguidas de una rutina principal, se
llaman subrutinas de la rutina principal y la rutina principal se diseña como un bucle o lazo.
Dado que el micro ejecuta instrucciones a razón de un millón por segundo, un retardo de
1/2 segundo requerirá una rutina de 500.000 instrucciones, ver más abajo para más detalle.
Naturalmente una rutina de 500.000 instrucciones debe ser, alguna forma
de operar en lazos. En segundo lugar si hacemos el tiempo de ENcendido igual al tiempo
de APagado, podremos llamar a la misma rutina de retardo. Una rutina de retardo también
se llama rutina de "no hacer nada" y se crea por la carga de un archivo con un valor y se
decrementa hasta cero o incrementa hasta 255. Una instrucción en el juego de instrucciones,
detecta la condición de cero de un archivo y hace que el programa salga de la rutina. Un archivo sólo producirá 255 lazos (256 lazos si el archivo
comienza desde 00, como el archivo es decrementado antes de empezar, esto
está probado). Cada instrucción toma un ciclo de máquina o 1 microsegundo, excepto
cuando el contador de programa PC es alterado por un GOTO, CALL, RETLW, RETURN y
DECFSZ, (cuando el archivo es cero) estas instrucciones toman 2 ciclos de máquina. Un simple retardo es como sigue: El lazo consiste en las instrucciones DECFSZ 0C,1 y GOTO RetA.
Cada lazo tomará 3 ciclos de máquina, hay dos ciclos de máquina para
entrar en el lazo, 255 x 3 ciclos de máquina y 4 ciclos de máquina para
salir de la rutina, hacen un total de 771 ciclo de máquina o 0.00771
segundos. Esto es un retardo muy corto y para aumentar el tiempo tenemos que
controlar esta rutina aproximadamente 10.000 veces. Esto se hace agregando
otro archivo (llamado 'cuenta') que llamará esta rutina y decrementamos
sobre cada llamada. El modo más simple de llamar esta rutina y decrementar el
archivo de 'cuenta' es combinar los dos, a esto se llama un 'lazo anidado' o
'rutina anidada'. Cuando el archivo 0C es decrementado a cero, el programa
llega al lazo interior y el archivo 0D decrementa en 1, entonces esto lleva
al lazo exterior donde el archivo 0C se decrementa a cero otra vez y esto
se repite 256 veces, el número total de ciclos de máquina es 3
+256(768+3) + 1 + 2= 197.382 ciclos, esto es, aproximadamente 1/5 de segundo,
si se necesita un retado de 1/2 segundo, debe llamar la rutina 3 veces. Una
rutina anidada de 3 archivos es como sigue:
Utilizaremos el registro 0Eh, como tercer registro en la rutina anidada.
El último paso es para crear el Sistema y la rutina principal. El programa anterior parece realmente simple, y lo es, pero esto
presenta un número de hechos; la rutina de retardo (Ret0) es llamada por una
subrutina y ésta es llamada dos veces por la rutina principal (Inicio). Refiérase
a la rutina "retardo simple" del apartado anterior para entender cómo se
decrementa a cero. La instrucción <CALL Ret0> en la rutina Inicio
envía al micro a la subrutina Ret0 y la instrucción RETURN del final de la
subrutina envía al micro de vuelta a la rutina principal Inicio. Como el micro nunca se para, el programa se hace infinito ya
que el Contador de Programa es incrementado constantemente, éste avanza
bajando por el programa hasta que se ejecute una instrucción que cambie el
valor del Contador de Programa causando un salto a alguna otra parte del
programa. Puede ser una subrutina o un salto atrás al programa
principal. Las instrucciones de salto son CALL, RETRUN, RETLW y GOTO así como
instrucciones DECFSZ, este es el modo en que un programa de micro mantiene la
condición de lazo. Por ahora hemos aprendido que el puerto in/out de los '508 y F84 es
el archivo 06, esto sorprende a cualquiera, un hecho importante oculto en uno
de los archivos. No sólo está 'oculto' el puerto, pero el registro que
controla las líneas del puerto empieza a entenderse. El registro TRIS manda cada una de las 6 líneas del archivo
06 individualmente y pueden ser entrada o salida con sólo hacer el bit
correspondiente del TRIS un "0" para salida o un "1" para
entrada. Cada línea se llama "línea entrada/salida de propósito
general" y lo único que hay que recordar es que GP3 (en el '508 = 1), sólo
puede ser una línea de entrada, esto produce una línea de entrada y 5 líneas de
salida, no así en los F84.
Pero la flexibilidad del chip y la magia de programar nos permite
cambiar cualquiera de las líneas de salida a una línea de entrada durante
el proceso del programa. Por ejemplo: Si TRISA es igual a 11110 todos sus pines serán
entradas salvo GP0 que esta como salida. Así de fácil. Añadiendo un pulsador sobre cualquier línea de entrada, se pueden
realizar funciones diferentes, según las instrucciones del programa. Al
principio de un programa esto puede incrementar la cuenta sobre un contador,
hacer sonar un pitido en medio de un programa, reponer (resetear) el programa
o comenzar el final de la rutina.
- El punto importante a recordar con un puerto es: la necesidad de
usar dos registros (archivos) para producir la condición de entrada o
salida.
- El registro TRIS determina la condición de entrada o salida y el archivo 06
determina el nivel alto o bajo sobre una línea de salida.
Si la línea es una entrada, mediante programa no se hace la línea alta o baja,
ésta se hace alta o baja mediante una tensión exterior y ésta es leída por la
instrucción BTFSS 06,3 y las ramificaciones (CALL's, GOTO's) del programa
según la condición en la línea. Si se quieren leer todas las líneas inmediatamente, debe usar la
instrucción MOVF 06,0 esto copiará todas las líneas al registro W, pero
recuerde que en el ' 508 los bits 6 y 7 no son funcionales. Una línea de salida
"leerá" como 1 si éste es Alto, etc. La siguiente etapa en el programa implica el empleo de una
tabla, una tabla permite la información preprogramada (datos llamados,
constantes, números o literales -nombres diferentes para lo mismo) para ser
colocados en un programa, no hay nada de complejo sobre la realización de
una tabla. En algunos micros los datos pueden ser recogidos de una
posición particular (como la RAM, la memoria de sólo lectura o de cualquier
parte del programa) y colocada en un archivo con una sola instrucción. Sin embargo el '508A, requiere tres instrucciones para hacer esto, necesitamos
en primer lugar la estructuración de una tabla al principio de la memoria e
insertar la instrucción: Tabla 1 ADDWF 02,1 Dentro de una rutina, es necesario una instrucción para cargar
(MOVer) el registro W con el valor del "salto", ese es el valor
para localizar el byte de datos en la tabla y es por lo general, el valor
copiado de un archivo al registro W, entonces la instrucción: CALL Tabla1.
En Tabla1, con la instrucción ADDWF 02,1 se carga el valor del salto en el
contador de programa PC para que el programa salte hacia abajo, en la tabla. Cada byte de datos se debe combinar con la instrucción RETLW
para que cuando el micro salte dentro de la tabla y recoja el byte de datos,
esto devolverá a la rutina de donde proviene el puntero con el byte de
datos en el registro W. La disposición típica para estas instrucciones es
la siguiente: Mover el archivo 0C al registro W (en la rutina Principal)
permite al valor en 0C sea cambiado y se puedan acceder los diferentes bytes
en la tabla. Si 0C es cero, se accederá al primer byte de la tabla, si 0C
es 1, se accederá al segundo byte, etc. Las instrucciones que contienen datos (valores literales) son
clasificadas como de DIRECCIÓN INMEDIATA. Por ej.
CALL K La dirección directa simplemente significa operar sobre el
archivo contenido en la instrucción (como SWAPF, INCF, DECF, ect.) para la
instrucción INCF 0C,1 la dirección es 0C. La dirección relativa implica el cambio del contenido del
contador de programa. La utilización de una tabla implica la dirección
relativa, ver las rutinas de ejemplo. El último truco en nuestro artículo es la habilidad de
dirección indirecta. Esto nos permite tomar cualquier número de bytes de
datos de una tabla y cargarlos en archivos consecutivos con el menor número
de instrucciones. Se usan dos archivos para la función de dirección indirecta,
estos son el archivo 00h, (INDF -archivo indirecto) y el archivo 004h (FSR -
archivo registro seleccionado). El archivo INDF es como "un agujero en
la pared". Se mira el archivo INDF por el archivo indicado por el FSR.
El archivo FSR es como "una extensión del brazo", con él se
puede alcanzar todos los archivos (00h a 1Fh). Supongamos que queremos cargar 08 en el archivo 1A, Usaremos
el modo de Dirección Indirecto: El FSR se cargara con la dirección del primer archivo
(moviendo 1A en FSR). Move 08 en INDF (Mueve 08 en el INDF). 08 es colocado en el archivo visto por FSR, así 08 se carga
en el archivo 1A. Si leemos INDF, leemos el contenido del archivo que mira FSR, en
otras palabras leemos el valor en el archivo 1A y el valor es 08.
Esto es la Dirección Indirecta. Al FSR también se le conoce por el archivo
"indicador". La ventaja de esta técnica de programar es poder incrementar
FSR y así mirar dentro de un juego de archivos y leerlos o escribirlos, con
el menor número de instrucciones. También podemos comprobar un bit
particular en cualquier archivo o ver si esta EN o claro (clear). Para hacer
esto, poner EN o Claro un bit en INDF y mirar el mismo bit por FSR, será
Claro o EN (CLEAR o SET). Ejemplo: Limpiar 6 archivos de 1Ah a 1Fh utilizando la
Dirección Indirecta. Vea el artículo "cosas que se pueden querer hacer"
para ver cómo transferir bytes de una tabla y colocados en un juego de
archivos. Estos son más o menos algunos de los trucos que debemos conocer
sobre programación, esto es solamente una muestra del uso de los trucos
para hacer algo que nosotros queramos. Todos los tipos de efectos complejos y resultados se pueden
producir simplemente uniendo dispositivos al micro y escribiendo el
programa. Podríamos seguir aconsejando en más de 100 páginas, pero lo
mejor es hacer los proyectos ya escritos para ver como están hechas algunas
cosas. Se puede empezar por los proyectos que se describen en los artículos
que se presentan en estas páginas, los cuales son muy simples y siguen este
temario u otros proyectos que se pueden encontrar el la red. Todo lo que Ud. hace en electrónica, debería documentarlo,
así Ud. puede volver en una etapa posterior y continuar tan rápidamente
como le sea posible. Esto es aún más importante con la programación tanto
de la idea que le asalta en la producción ya que no es tan evidente en el
código en sí mismo. El lector puede volver y preguntarse ¿porqué cargué este
archivo con 3E? o ¿porqué puse el bit7 del archivo a 1F? A no ser que
agregue apuntes después de cada instrucción, puede pensar que su memoria
le evita. Los comentarios también ayudarán a alguien que más tarde tiene
que asumir el proyecto o talvez solucionar el problema. La mayor parte de los programas se escriben con una rutina
principal y un número variable de subrutinas, este es el modo estándar de
presentar un programa y esto es lo que hacer fácil su diagnóstico, como
las subrutinas se diseñan para hacer una sola tarea eso es lo que le
simplifica las operaciones de un programa. El único punto a recordar es que el stack es de 2 niveles
(tipo LIFO = primero en entrar, primero en salir),
esto significa que sólo debería tener una rutina principal con
instrucciones de llamada (CALL's). Puede tener una subrutina con una llamada
remota pero este es el máximo número de CALL's que hagan una llamada en el
'508A. No se equivoque, la rutina principal puede tener tantas
instrucciones de llamada como se necesite ya que cada CALL irá a una
subrutina y al final de la subrutina tiene un RETURN hará que stack vuelva
a cero. Un CALL y un RETURN usan el stack una vez y así dejan el
nivel del stack vacío para la siguiente llamada CALL y RETURN. La pila es una zona de memoria que se encuentra separada de la
memoria de programa y de la de datos. Tiene una estructura de LIFO (Last In
First Out) o sea que, el último valor que se guarda es el primero en salir. Se
dispone de 8 niveles de profundidad con una longitud de 13 bits.
Los 12CXXX, disponen de 2 niveles de profundidad, cada uno de ellos con una
longitud de 13 bits. Su funcionamiento es el de un buffer, de tal forma que
el valor si se supera el segundo nivel se pierde el primero y se produce
desbordamiento. No se dispone de indentificador o bandera que indique un desbordamiento de la Pila. Dos formas de cargar la Pila son posibles, una es a través de
la instrucción CALL (llamada a subrutina) o por una interrupción, ambas
hacen que el valor del PC se cargue en la Pila como último valor (o valor
superior), para recuperar el contenido de la Pila en el PC, hay que ejecutar
una de estas instrucciones: RETURN, RETLW o RETFIE (retorno de una subrutina
o por interrupción). Hay aproximadamente 33 palabras abreviadas para recordar
(llamadas mnemónicos) más un valor "0" si guarda el resultado en
el registro W o un "1" si el resultado se almacena en el archivo f
involucrado en la instrucción. Todas las instrucciones de los PIC, se
presentan en el artículo juego de
instrucciones. Se proporciona una definición completa de cada instrucción
en el artículo después del juego de instrucciones que le ayudaran a
entender lo que hace cada instrucción. Pero el verdadero medio para entender una instrucción es
verla en un programa como en los proyectos de los artículos de este sitio.
Una forma a modo de sugerencia en un programa proporcionado por la lista de
rutinas como "Plantilla de Programar". Presenta la
disposición de un programa con "Comparaciones" de 0Ch a 1Fh, cada vez
que se usa un archivo para contar o almacenar información temporal,
simplemente se da un nombre al archivo (como tono1, reloj, cajAzul,
etc) y escribe el nombre en la sección "Etiqueta". Lo primero que el compilador quiere ver es "ORG"
para el origen y un valor como 000, así el programa es ligado al principio
en la dirección 000, después de ORG colocamos la rutina de sistema, esto
en primer lugar establece el registro TRIS y define si las líneas (los
bits) del archivo 06 deben ser entradas o salidas, la rutina del
sistema pone 1 (alto) o 0 (bajo) sobre las líneas requeridas del archivo
06h. Después, cualquier tabla a usar en el programa será
colocada al principio de la memoria, de modo que no rebase la localización
de dirección 0FFh (256 bytes bajo la página), las subrutinas son lo
siguiente y deben incluir una declaración de RETURN al final de cada
rutina. Finalmente la rutina principal es escrita después de todas
las subrutinas, esto podría parecer ser un modo insólito de disponer un
programa pero las rutinas principales consisten en CALL's a las subrutinas y
esto simplemente une todas juntas en un lazo grande, una declaración de
"Final" indica al compilador que deje de compilar, sin embargo
esto se ha de convertir a código máquina para el micro. Cuando ya estemos listos para comenzar a escribir un programa,
debería escribirlo en un cuaderno de notas con etiquetas en una primera
columna imaginaria, los mnemónicos (instrucciones) en la segunda columna y
los comentarios en la tercera columna, como venimos mostrando hasta el
momento en los ejemplos y finalmente lo guardamos con la extensión .asm. Copie todo, en el editor del MPLAB (refiérase al acertado manual
del MPLAB de Luis Rueda) para transferir el opcode a .hex que es el
lenguaje (máquina) que entiende el chip. Si lo que pretende hacer es una
práctica de las que se describen en este curso, debe copiar y pegar en
dicho editor el archivo .asm y proceder para convertirlo en .hex, luego
copie y pegue dicho código hex resultante en el quemador que utilice
(el IcProg
es de uso libre y resulta muy apropiado) y siga las especificaciones del
mismo o del artículo existente en estas páginas llamado manual del TE20se. Este microcontrolador a pesar de su reducido tamaño, tiene
ciertos trucos o pequeños detalles que deben tenerse presentes en su
programación para poder optimizarlo, el olvido de alguna de estas características
puede llevar el fracaso con el programa. No debemos olvidar que la familia
de los 12C5XX es bastante extensa y guardan sensibles diferencias que en
muchos casos impiden que los programas sean compatibles entre estos chips. El alto rendimiento de la familia PIC12C5XX puede ser
atribuido a un número de rasgos arquitectónicos Comúnmente encontrado en
microprocesadores RISC. Comenzar con el PIC12C5XX, usa una arquitectura de
Harvard en el que el programa y datos se acceden sobre buses separados. La familia de gama media de los PIC, disponen de una palabra
de configuración de 14 bits que se escribe durante el proceso de grabación
del dispositivo (y que debe hacerse de acuerdo con el sistema en el que se
va a insertar), dichos bits ocupan la posición reservada de memoria de
programa 2007h. En la siguiente tabla se muestra la estructura de la palabra
de configuración:
Además dispone de cuatro posiciones de memoria reservadas para las palabras de identificación ID en las direcciones 2000h:2003h. Dichas palabras se escriben durante el proceso de grabación, se utilizan por el programador para indicar el código del dispositivo, el número de serie, la versión del programa, etc. y sólo emplean los 4 bits de menor peso. En el caso de los PIC12C5XX, cada instrucción es una palabra de 12 bit dividida en un OPCODE, que especifica el tipo de instrucción y uno o más operandos que más tarde especifican la operación de la instrucción. El sumario de instrucciones pone en la tabla 9-2 grupos de instrucciones por byte orientado y literal y operaciones de control. La tabla 9-1 muestra descripciones del opcode. En el registro CONFIG la dirección FFFh de 12 bits, los 7 bits más altos (bit11 a bit5) no son direccionables, estas direcciones no se pueden direccionar directamente, sólo se pueden acceder por el soft de grabación del programador utilizado. LAS INTERRUPCIONES.Hasta ahora, hemos hablado de retardos tratados mediante un 'bucle' que es lo que se llama una temporización por software. El micro se queda ejecutando un 'bucle' o lo que es lo mismo repeticiones cíclicas durante una determinada cantidad de veces, hasta completar una cantidad de ciclos de programa. La temporización se realiza calculando la cantidad de ciclos por el tiempo de ejecución de cada ciclo. El tiempo de ejecución de un ciclo es ¼ de la velocidad de reloj. Por ejemplo: en un proyecto estamos utilizando un cristal de 4 Mhz. La velocidad de ejecución interna del microcontrolador es 1Mhz, es decir, 1.000.000 de ciclos por segundo, con lo cual cada instrucción se ejecuta en 1 microsegundo (uS). Este tipo de temporización, se realiza ejecutando el microcontrolador una serie de líneas de código, por lo que no puede atender otras tareas mientras se realiza la temporización. Las interrupciones son una característica, quizás la más importante que constituyen los microcontroladores, es la capacidad de sincronizar la ejecución de programas con acontecimientos externos, esta es la entrada que precede al documento sobre interrupciones que describe su comportamiento.Una de las interrupciones la genera el temporizador interno del microcontrolador (TMR0): El microcontrolador PIC 16F84 incluye un modulo temporizador (llamado TMR0) el cual trabaja de manera autónoma y como se ha dicho, genera una 'interrupción' cuando se completa una temporización. El usuario puede configurar con bastante facilidad el temporizador para realizar sus temporizaciones. Como el TMR0, ejecuta la temporización de manera independiente a la ejecución del resto de código, el microcontrolador puede así realizar otras tareas mientras la temporización se realiza. REGISTROS.Los microcontroladores PIC, tienen un juego reducido de instrucciones, unas 35 instrucciones como se describe en el juego reducido de instrucciones de Microchip. Ver artículo descripción de instrucciones y juego de instrucciones. Hasta aquí hemos visto los puntos más relevantes que corresponden a los microcontroladores PIC. Volver al índice de artículos PIC. Creado el:
29-04-2005 |