Está en la página 1de 144

Indice

Introducción……………………………………..………………..………………..
¿Que es un Microcontrolador? …………………………………………………….
Partes que conforman a un microcontrolador………………………………………
Memoria y dispositivos de entrada y salida (I/O)………………………………….
Núcleo HC908 (CPU)..………………..………………..……………….. ………..
Assembly…………………………………………………………………………...
Modos de direccionamiento………………………………………………………..
Set de instrucciones del CPU HC08……………………………………………….
Formas de escribir un programa……………………………………………………
Sub-rutinas………………………………………………………………………….
Explicación de algunas instrucciones y sus diversos usos………………………….
Reset e Interrupciones………………………………………………………………
Causas de reset……………………………………………………………………...
Modulo SIM (Sistem Integration Module) ………………………………………...
MODULO COP (Computer operating properly) …………………………………..
Low Voltage Inhibit (LVI) ………………………………………………………...
Explorando la familia JK/JL………………..………………..………………..……
I/0 Ports…………………………………………………………………………….
Ejemplos de programas…………………………………………………………….
Modulo ADC (Analog to digital converter) ……………………………………….
IRQ (Interrupción externa) ………………………………………………………...
Keyboard Interrupt…………………………………………………………………
Timer……………………………………………………………………………….
Canales del Timer………………………………………………………………….
Modulo ADC de 10 bits …………………………………………………………..
Comunicaciones Serie……………………………………………………………..
Modulo de comunicación serie sincrónica SPI……………………………………
Modulo de comunicación serie asincrónica SCI…………………………………..
Introducción

El objetivo del presente texto es el de introducir al lector en el ámbito de los


microcontroladores, específicamente en los de NXP (HCS08). Hoy en día podemos
encontrarnos con microcontroladores en todas partes, quien más quien menos, en su
casa tiene una cantidad de microcontroladores distribuidos en diferentes aparatos, desde
mp3, alarmas, llaves, controles remotos, electrodomésticos, hasta en los autos (hoy en
día ya cuentan con redes sofisticadas de microcontroladores). La verstibilidad de los
microcontroadores hacen que podamos crear proyectos complejos de la nada, con
interfaces novedosas y solucionar problemas en el ámbito digital de manera muy
sencilla, rápida y lo que es mejor, a bajo costo.

¿Que es un Microcontrolador?

Para poder entrar de lleno en el tema y analizar como trabajar con ellos, primero
tenemos que saber que es un microcontrolador. Pensemos en un MCU (MicroControler
Unit) como un dispositivo, que interpretará una serie de instrucciones, que nosotros le
brindemos, en forma secuencial, al decir secuencialmente nos referimos a que las
instrucciones van a ser ejectudas en un cierto orden una tras la otra (de a una) y que
cada instrucción tardará un tiempo X dado por algún dispositivo generador de
tiempo/frecuencia. Obviamente que TODO microcontrolador sea de la empresa, marca
y arquitectura que fuere no seria nada ejecutando un programa en forma secuencial si no
tuviera conexión con el mundo exterior, por eso es que un MCU posee alguna forma de
ingresarle datos, que serán procesados o al menos almacenados y luego devueltos de
alguna forma al mundo exterior. La forma en que interactuar con el mundo exterior
varía de uno a otro, algunos toman variables digitales y las devuelven de la misma
forma, otros trabajan con variables analógicas sobre sus diferentes pines de conexión,
etc.
Como mencionamos en el párrafo anterior un MCU ejecutará una serie de
instrucciones que nosotros le proporcionemos. Debido a esto podemos decir que la
“estrella” en nuestro MCU es nuestro programa, el microcontrolador ejecutará nuestras
instrucciones al pie de la letra, así que el ingenio, valiéndonos de normas que
estudiaremos en este texto y en la forma que nosotros le digamos a nuestro dispositivo
que haga la cosas, nos dará el éxito de saber que nuestro programa, y en fin nuestro
MCU/ sistema hará lo que nosotros queramos que haga. Adelantándonos un poco a
cosas que veremos en detalle más adelante, es preciso tener en cuenta que nuestro
dispositivo solo puede resolver los problemas, sean sencillos o complejos utilizando
operaciones matemáticas simples como ser +,-,*,/, desplazamientos, aritméticos,
asignaciones de valores, operaciones lógicas, AND (&), OR(ºº), NOT(!), XOR (^) y
desplazamientos lógicos. El como el microcontrolador realiza estas operaciones y con
qué tipo de datos puede manejarse es lo que debemos estudiar en profundidad para
poder conocer la herramienta que vamos a manejar y que esta termine resolviendo el
problema en cuestión o la tarea que deba realizar o proceso a controlar. No solo
debemos tener en cuenta que el microcontrolador realiza esas operaciones antes
mencionadas y solo esas, sino que sobre los resultados de esas operaciones podrá
analizar ciertos parámetros que darán a nuestro programa la capacidad de “tomar
decisiones”. Es importantísimo a esta altura que entendamos que por más sencillo
complejo sea nuestro programa este va a solo contener estas operaciones, así que de
ahora en más para poder resolver un problema debemos pensar en estas operaciones o
combinaciones de ellas para poder resolverlo, ya que es lo único que tenemos para
hacerlo con esta herramienta, mientras mejor la conozcamos más provecho le sacaremos
y más eficiente será nuestro programa.
Para poder resolver cualquier problema que tengamos o realizar directamente un
programa, primero debemos analizar la situación, entender bien el problema punto por
punto, paso por paso es tener el 50% del problema resuelto, conocer la herramienta
(nuestro MCU en este caso) nos da el 30% restante, así que solo nos resta un 20% para
la realización en cuestión en algún código, pruebas y demás a fin de finalizar con éxito
nuestro programa.
Como mencionamos anteriormente vemos lo importante que es el conocer y
entender nuestro problema, esto involucra el saber que procesos pueden llevarnos al
éxito, que debemos realizar para poder lograrlo, cuales son las variables que posee
nuestro problema, cuales las condiciones iniciales, y cuál es el resultado esperado. El
análisis de cada una de estos puntos que mencionamos, es totalmente personal, pero el
ayudarse con diagramas, dibujos, cálculos, y sobre todo diagramas de flujo son algunas
de las herramientas que nos ayudarán a resolver de manera más rápida y eficiente
nuestro problema.
Partes que conforman a un microcontrolador

Un MCU esta conformado por varias partes que enunciaremos a continuación,


algunas dependen de la arquitectura (manera en como fueron realizadas), y otras varían
de modelo a modelo, por eso explicaremos cuales de ellas son las que no pueden
faltarnos para que exista el microcontrolador. Luego ampliaremos y las explicaremos
como fueron implementadas en los HC08.

Reloj: Para que el MCU ejecute secuencialmente nuestro programa, debe haber
alguna forma, en base al tiempo, que marque que hacer en cada instante. La circuiteria
asociada a esto es la de reloj, quien marcará los “pasos” de nuestro programa, como así
también la velocidad de los mismos. Nos referimos a la circuiteria debido a que existen
muchas formas de establecer el reloj de un MCU, cristales, osciladores externos,
circuitos RC, en algunos ni siquiera es necesario circuitería externa ya que incorporan
un oscilador interno, o pll ( lazos enganchados en fase) que con un cristal de muy baja
frecuencia, típicamente 32.768 khz, logran elevar a cualquier frecuencia interna,
llegando a la máxima que permite el MCU.

CPU: (Centra Procesor Unit) Es el corazón, muy a menudo llamado también el


núcleo (Core) del microcontrolador, es el que nos dicta la forma de trabajo, las
instrucciones que podemos usar, como también la forma, los registros, etc. En si cuando
hablamos de HCS08 nos referimos al núcleo (presente en todos los diversos MCU de
esta familia) luego uno a otros se diferenciaran por tener diversos módulos (timers,
conversores, etc.), más o menos memoria como también diferencia entre la cantidad de
pines etc. Hay que recalcar que el núcleo es lo que prevalece en cada miembro de esta
gran familia.

I/O Ports: Los puertos de entrada y salida de cada microcontrolador son los
encargados de darnos la conexión con el mundo exterior, ya sea para programarlos,
ingresarles datos etc. Estos pueden funcionar en forma digital, o contener funciones
asociadas para establecer comunicaciones series, conversores analógicos digitales, etc.

Memoria de programa: Como mencionamos un MCU se encarga de ejecutar


secuencialmente el programa que nosotros le brindamos, para eso tendrá en él una
memoria, que servirá para guardar el programa (tareas a realizar) y desde allí poder ir
ejecutándolo secuencialmente.

Memoria de datos: La capacidad de un microcontrolador sin una memoria para


almacenar datos sería muy reducida, ya que generalmente lo que haremos es recolectar
una serie de datos por los diferentes ports del mundo exterior, almacenarlos,
procesarlos, y devolverlos al mundo exterior de alguna manera.
HCS08

Una vez descriptos todos los aspectos que engloba, que conforman (por lo
menos básicamente). Ahondaremos en detalle sobre los puntos mencionados en el
capitulo anterior, ampliándolos a la arquitectura, formas que han sido implementados en
los HCS08.
Una breve reseña histórica que nos servirá para entender algunas
implementaciones. HCS08 es el derivado de una familia de microcontroladores de 8 bits
denominada HC08 que a su vez son derivados del microcontrolador HC05 diseñados,
ambos, por Motorola. Los HC05 fueron los microcontroladores para los cuales más se
ha programado en la historia. Cuando Motorola decide crear un nuevo modelo de MCU,
toma como objetivo hacer 100% compatible el código con sus sucesores, por eso
algunas cosas que veremos como fueron implementadas, fueron simplemente hechas
para que cualquier código creado para HC05 pueda ser incluido en cualquier HC08 y a
su vez en HCS08.
Después de 50 años siendo el sector encargado del diseño de semiconductores en
Motorola, en el año 2004, esté sector decide separarse y crear la empresa Freescale. A
fines de 2015 la empresa Freescale es conocida por NXP, y si bien es el actual
fabricante de estos microcontroladores suelen llamarse en el ámbito como “los
Motorola” cuando en realidad hoy en día es NXP quien los fabrica, siendo esté ahora el
proveedor de semiconductores de Motorola, entre otros ámbitos.

Memoria y dispositivos de entrada y salida (I/O)

Cuando pensamos en memoria, deberíamos pesar como una seguidilla de bits


que utilizaremos para un determinado propósito. La lectura o escritura de estos
diferentes bits será de acuerdo a la tecnología implementada para realizarlos, también
variara la forma de invocarlos o utilizarlos de acuerdo a los datos en sí ubicados en cada
uno de estos bits.
Como el trabajo a nivel de bits se torna un poco complejo, se ha agrupado a
estos bits de a ocho, formando lo que conocemos como byte (Figura 1). Así como una
cuadra esta formada por diversas casas, el byte esta conformado por una cantidad (8) de
bits. Así, uno a continuación de otro, es como debemos pensar en la memoria. Un byte
contiguo, o debajo de otro (Figura 2).

Bits: 7 6 5 4 3 2 1 0
Byte

Byte 0
Byte 1
Byte 2
Byte 3
…….. N Bytes
Byte N-3
Byte N-2
Byte N-1

El orden en que estos bytes ubicados uno debajo del otro es de la forma en la
cual nosotros podríamos invocarlos, ya que de nada serviría tener en nuestras casas un
archivo pero no saber donde esta cada cosa o no poder invocar al necesario. Podríamos
llamar al byte numero 0 y preguntar por su contenido, y así con cualquiera de estos N
bytes. El orden por el cual lo invocamos es lo que llamamos dirección del byte en
memoria. Al decir que preguntamos por el byte Nº 2, decimos que estamos
direccionando a ese byte.
De alguna forma tendremos que distinguir entre la dirección del byte y el
contendido. Estos dos formatos, viajaran dentro del MCU, a través de dos buses
denominados de Bus de direcciones y Bus de datos.
La cantidad de bits en el bus de direcciones nos da la noción de la cantidad de
bytes que puede llegar a direccionar, mientras que el bus de datos nos da la información
de la capacidad de procesamiento del MCU. En los microcontroladores que nos
competen (HC08) el bus de direcciones es de 16 bits, lo cual nos da un rango de 2 16 =
65536 posiciones de memoria, de ahora en más usaremos el símbolo $ o 0x para
expresar que el numero a continacion está en base 16 o hexadecimal por eso iremos de
la posición $0000 a $FFFF recordar que la cantidad de combinaciones son 2 16 = 65536
pero el valor máximo es 216 -1 = 65535. El bus de datos en cambio es de 8 bits, que es la
amplitud de los datos que podemos procesar, en este caso de 0 a 255 (256 posiciones) o
en hexadecimal de $00 a $FF.
$0000
$0001
$0002
Bus de direcciones $0003

$FFFD
$FFFE
$FFFF

Bus de datos

Obviamente para poder direccionar 65536 posiciones de memoria con 16 bits lo que
necesitaremos es un decodificador, obteniendo el siguiente esquema:
Como vemos en el diagrama el bus de direcciones es unidireccional, ya que el
CPU dice que memoria va a utilizar. Esto no sucede con el bus de datos, por el cual
fluirán los datos desde y hacia la memoria, de acuerdo a lo que imponga el CPU, en fin
nuestro programa.
Dentro de la memoria, ciertas direcciones estarán destinados a diversos
propósitos, dentro de los cuales tenemos: Memoria de datos, de programa, una serie de
registros que se utilizarán para configurar al MCU y a sus módulos internos.
Tendremos entonces una cantidad de memoria para nuestras variables que podrá
ser escrita y borrada durante el curso de la ejecución de nuestro programa llamada
genéricamente memoria RAM (Random Access Memory o memoria de acceso
aleatorio). Cabe aclarar que el nombre Random Access Memory no es identificativo del
uso de la misma como si pasa en el otro tipos de memoria, el nombre de memoria de
acceso aleatorio proviene de las clasificaciones de las memorias, ya que existen dos
grandes tipos de memorias: de acceso aleatorio y de acceso secuencial. En las memorias
de acceso secuencial para ubicarnos en una determinada posición de memoria debemos
si o si pasar por las memorias que se encuentran en el medio desde la que nos
encontramos hasta llegar a la que queremos, podemos hacer la analogía con los viejos
casetes, en donde para parar de un tema a otro hay que si o si (por más que lo hagamos
rápido) pasar por los temas en medio. En cambio una memoria de acceso aleatorio nos
permite ir a cualquier dirección sin tener que pasar por ninguna otra posición, así como
pasa con los CDs/DVD que podemos ir a cualquier tema sin importar en cual estemos.
También tendremos ubicada la memoria en la cual estará nuestro programa, el
cual deberá ejecutarse secuenciamente y solo será de lectura (cabe aclarar que si bien el
programa se ejecuta secuencialmente la memoria también es del tipo aleatorio) a este
otro tipo de memoria se lo llama genéricamente memoria ROM (Read Only Memory, o
memoria de solo lectura). La cantidad de memoria RAM y ROM en un MCU HCS08
depende de cada miembro de la familia.
Debido a el programador es el que sabe en que posición está que memoria y los
usos que le dará a cada una de ellas, se tendrá que ser cuidadoso y saber a lo largo de
nuestro programa a cual de ellas accedemos y con que propósito. A esta forma de
trabajo, a esta arquitectura, por la cual al MCU le es indistinto usar la memoria de
programa que la de datos se la llama, arquitectura Von Neumann. Que se diferencia de
la arquitectura Harvard, que posee dos buses diferentes para las memorias de datos y
programa (la arquitectura Harvard es usada en los PIC, microcontroladores de la
empresa Microchip). Cuando avancemos y veamos la forma en cómo ubicar el código
en memoria de programa y como ubicar las variables en RAM.

Arquitectura Von Neumann

Arquitectura Hardvard

Tipos de memoria Rom:


La memoria ROM es como bien dijimos anteriormente, memoria de solo lectura
donde alojaremos a nuestro código, si bien sea el tipo que sea siempre ese será nuestro
objetivo tenemos diferentes tipos de tecnologías con diferencias entre sí que veremos a
continuación:
Memoria por mascara: Este tipo de memoria ya vienen de fabrica con el valor
que nosotros queremos se le envía directamente al fabricante la máscara de cómo
queremos nosotros que salgan ya nuestros chips, es muchísimo más barato pero se
necesita trabajar con miles de piezas para poder llevarlo a la práctica, y si bien el costo
por unidad es mucho menor, es mucho el dinero necesario para poder llegar a utilizarlo.
Celda de un bit de memoria OTP, donde se coloca o no un diodo o transistor
BJT o FET en funcionamiento como diodo, de acuerdo a si queremos un ‘1’ o un ’0’ en
dicho bit.

Recordemos que una memoria son 8 bits uno al lado del otro para conformar el
byte así que nuestro esquema quedaría de la siguiente forma

Memoria OTP/PROM: La memoria OTP (One Time Program) o PROM


(Programable ROM) es la ROM básica y clásica de la que hablamos anteriormente es
una memoria que se la puede programar una vez sola. Es ideal para etapas de
producción en donde nuestro producto ya está totalmente testeado con un programa
100% confiable, ya que los costos de una memoria OTP son mucho menores que el de
las otras tecnologías que veremos a continuación.
Podemos ver en la figura el cambio acá en vez de colocar o no un diodo para
obtener el ‘1’ o ‘0’, lo que se realiza en la programación es quemar el fusible si es que
no queremos conexión ‘1’ o dejarlo en caso de querer conexión ‘0’, el fusible
obviamente debe soportar los valores nominales de corriente y para poder quemarlo se
debe usar valores de corriente altos (no lo suficientemente altos como para quemar los
otros componentes, pero si altos frente a la corriente nominal de uso), es evidente que
una vez quemado el fusible no puede volver para atrás. Con un generador de pulsos se
va direccionando bit a bit y enviando o no la corriente para poder quemar o no el bit
fusible correspondiente.

.
Memoria EPROM: La memoria EPROM (Eresable PROM) se diferencia de las
anteriores ya que puede ser borrada, y vuelta a grabar, la grabación de la misma se
realiza eléctricamente, con niveles altos de tensión, dependiendo el fabricante puede ser
2.5 veces la tensión nominal de uso, y se borra por medio de luz ultravioleta. La
memoria EPROM es fácil de identificar porque generalmente tienen una etiqueta en la
parte superior tapando la ventana que si se somete a luz ultravioleta borra la misa.

En este diagrama vemos un transistor de compuerta aislada que son


fotosensibles, por eso la luz ultravioleta produce el borrado del mismo. La
programación igual que antes se realiza por altos niveles de tensión (depende cada
fabricante) y demora entre 25 y 30 minutos (también depende de cada fabricante).
Memoria EEPROM: La memoria EEPROM o E2PROM (Electrical Eresable
Prom) tiene la particularidad que ya no necesita luz ultra violeta para ser borrada, esta
memoria puede ser borrada y escrita mediante electricidad, pero al igual que la memoria
EPROM utiliza tensiones por encima de las tensiones nominales para ser grabada y
borrada.
Memoria FLASH: La memoria Flash es el tipo de memoria que utilizamos en los
microcontroladores generalmente ya que es grabable y borrable eléctricamente, pero
con bajos niveles de tensión y corriente, por lo tanto pueden convivir en nuestro circuito
programador y circuito de uso perfectamente, lo que además brinda la posibilidad que el
integrado pueda reprogramarse a sí mismo a distancia o bajo ciertas circunstancias.

Núcleo HCS08 (CPU)

Como mencionamos en el capitulo anterior, el CPU es el núcleo del procesador


y es el que nos va a indicar de que forma vamos a trabajar, como manejar la memoria,
puertos, y como indicarle al microcontrolador que haga lo que queremos que haga.
El CPU del HCS08 cuenta con una serie de cinco registros, que son bytes con
diferentes específicos usos. Estos registros no se encuentran mapeados (ubicados) en
memoria, es decir que para acceder a ellos no puedo hacerlo mediante una posición de
memoria, sino que habrá instrucciones del microcontrolador que nos permitirán trabajar
con ellos, y el CPU por si solo ya sabe como acceder a ellos. Los registros son los
siguientes:

Acumulador [A]

Registro índice [H:X]

Stack Pointer (Puntero de pila) [SP]

Program Counter (Contador de programa) [PC]


Registro de código de condición [CCR]

Acumulador[A]: El acumulador es un registro de 8 bits, temporal que usaremos para ir


almacenando nuestros datos. Pensemos por un momento que necesitamos sumar dos
números A y B, y que tenemos dichos números en memoria, el MCU no puede sumarlos
directamente ambos números indicándole las diferentes posiciones de memoria en las
cuales se encuentra, para eso uno de estos dos operandos, deberá ubicarlo en el
Acumulador, y de esta manera si podrá sumar el contenido del acumulador con la
posición de memoria X donde se encuentre el 2º operando.
Todas las operaciones aritméticas y lógicas deberán contener al Acumulador
como uno de sus operandos. El MCU posee una unidad destinada a las operaciones
aritméticas y lógicas llamada ALU, en donde uno de los operandos es proporcionado
por el bus de datos, y el otro el acumulador, dejando el resultado en un registro
temporal que generalmente se vuelca en el acumulador.

Bus de datos

Registro Indice [H: X]: No es casual que este registro sea de 16 bits, al igual
que el bus de direcciones, esto es debido a que este registro tiene como finalidad
principal alojar en su contenido una valor correspondiente a una posición de memoria, y
con diversas instrucciones podremos extraer, y llevar al acumulador o a otros registros
el contenido de la posición de memoria APUNTADA por este registro. El nombre de
registro índice deriva justamente del concepto de puntero, y del concepto que tenemos
en nuestra mente del dedo índice que es el que nosotros usamos para señalar algo. El
hecho que este registro sea completamente de 16 bits pero igualmente este dividido, o
por lo menos se lo llame a la parte baja con el nombre de X y a la alta con el nombre de
H, es debido a que, el HC05 tenía solo registro índice de 8 bits, llamado X, vale decir
que solo podría apuntar a direcciones comprendidas entre $0000 y $00FF. Si bien el
registro fue diseñado para contender direcciones nada nos impide utilizarlo como un
segundo registro temporal donde guardemos datos.

Stack Poiner [SP]: Como su nombre lo indica el SP es un puntero que


generalmente estará apuntando al final de la memoria RAM (la pila). Debemos pensar a
la pila (Stack) como un lugar donde podemos almacenar temporalmente el contenido
(valor) de alguno de los registros. El nombre de pila deriva a su forma de trabajo LIFO
(last input – first output) es decir que si almacenamos una serie de valores en la pila,
deberemos sacar todos los datos guardados últimos hasta poder devolver el primer valor
almacenado. La forma de trabajo del stack es la siguiente:
Supongamos que el stack pointer se encuentra apuntando a la posición de
memoria $00FF y mediante una instrucción le decimos que almacene temporalmente el
contenido del acumulador, entonces el stack pointer guarda en la posición $00FF el
contenido del acumulador y automáticamente apunta a la posición anterior en memoria,
$00FE, si un nuevo dato quiere ser guardado en el stack se guardara en dicha posición y
el próximo valor al cual apuntará el stack será $00FD y así sucesivamente. El stack
crece en el sentido en que decrecen las posiciones de memoria, es por eso que el stack
pointer generalmente apunta al final de la RAM, ya que generalmente ubicaremos
nuestras variables al comienzo de la RAM.
Dado que el stack crece de abajo hacia arriba hay que ser cuidadoso ya que
podría pasar que en algún momento el almacenar muchas variables temporalmente en el
stack comience a solapar (a borrar) las posiciones definidas para guardar variables
(desborde de pila).
Vale hacer la asociación al concepto de variables locales definidas en una
función en C, que se utilizarán en tan solo una porción (rutina) de código, por lo general
estas variables son almacenadas en el stack. Y la analogía de las variables de comienzo
de RAM son con las variables globales.
Por default después de un reset el valor de SP es de $00FF, esto es debido a que
esa era la posición de fin de la memoria RAM en los HC05, si bien en algunos
miembros de la familia HC08 coincide también con el fin de la RAM, no es así para
todos, por eso lo conveniente es al principio del programa llevar al SP al final de la
RAM para no tener el problema de pisar nuestras variables.

Program counter [PC]: Cuando definidos al microcontrolador dijimos que lo


que haría, sería ejecutar secuencialmente una serie de órdenes o instrucciones. El PC es
el encargado de decirle al MCU cual es la siguiente instrucción a ejecutar. Vale decir
que el PC SIEMPRE APUNTARÁ A LA PROXIMA INSTRUCCIÓN A
EJECUTARSE. Es por eso que en algunos otros microcontroladores es llamado también
Puntero de instrucción (Instrucciton Pointer [IP]).
Luego de un reset el PC se cargará con el contenido de las posiciones de
memoria $FFFE y $FFFF a fin de encontrarse allí la dirección (parte alta y parte baja)
de la primera instrucción a ejecutarse.

Registro de código de condición [CCR]: El MCU como tal se encargará de


recibir cierta cantidad de variables de entrada, analizarlas y luego devolverlas al mundo
exterior. En el tema del análisis es donde utilizaremos este byte, que en realidad nos
deja de interesar como un byte completo sino que lo analizaremos a nivel de bit, ya que
cada uno de ellos tiene un significado especial. Al finalizar una operación aritmética o
lógica, tendremos que, de acuerdo a lo que nosotros estemos buscando, evaluar estos
bits que cambiaran al finalizar una instrucción aritmética o lógica.
Los significados de cada bit son los siguientes:

V: Este bit es el bit de overflow, se encarga de indicar la regla de los


signos en las sumas y restas, vale decir que analiza los operandos signados. Cabe
también acabar que el convenio utilizado por defecto en el MCU para números
negativos es complemento a 2. Sabemos que si sumamos dos números positivos,
el resultado va a tener que ser sí o si positivo y que al restar dos números
negativos si o si el resultado debe ser negativo. Al realizar cualquiera de las dos
operaciones anteriormente mencionadas si no sucede el resultado esperado, este
bit se pondrá en uno indicando que hubo un error en la operación con la regla de
los signos. De acuerdo a lo que nosotros estemos realizando será si tenemos o no
que tener en cuenta este bit.

Ej: sumamos dos números positivos:


01111111
00000001
10000000
Vemos en este ejemplo que los dos primeros números son dos números
positivos ya que el bit de la izquierda o MSB es 0, y el resultado dio negativo
(MSB =1) esto hará que V=1, dependerá si es que nosotros trabajamos o no con
números con signo o sin signo de interpretar mirar o no entonces al bit V y
actuar en consecuencia.

H: Este bit se encarga de informar cuando es que se produce un carry del


nibble bajo al nibble alto, es decir de los 4 bits menos significativos a los 4 bits
más significativos, es muy poco usado, en el único caso que se utiliza es cuando
se trabaja con números en BCD y debe realizarse la corrección apropiada a los
números BCD. Es decir se pondrá en uno cuando pase un uno del bit 3 al 4 en
una operación. Más adelante veremos instrucciones que lo utilizan para ajustar la
suma de dos números BCD.
I: Este bit es el único que no cambia debido a las operaciones aritméticas
y lógicas, sino que lo que hace es una mascara para las interrupciones, vale decir
que si el bit I está en uno están anuladas todas las posibilidades que un modulo
interrumpa al microcontrolador, por el contrario si está en 0 los módulos están
habilitados para interrumpir, si es que estos estuvieran configurados para hacerlo
y se diera la condición. Por default este bit luego de un reset amanece en uno, es
decir que inhabilita las interrupciones. Si queremos trabajar con interrupciones
tendremos que borrar este bit mediante la instrucción adecuada.

N: Este bit se encarga de decir si el resultado de la operación aritmética o


lógica dio negativo o no, es decir se coloca en uno si dio negativo y cero en caso
contrario. Recordando que el MCU utiliza el convenio de complemento a 2 para
trabajar con números negativos, y estos lo son cuando el bit más significativo,
también llamado BIT de signo esta en uno. El bit N entonces es una copia del bit
más significativo del resultado de la operación aritmética o lógica.

Z: Este bit es el indicador de que el resultado de la operación aritmética o


lógica fue cero. Si bien es extraño el pensamiento cuando este bit esté en uno
significa que fue 0 el resultado y caso contrario este bit permanecerá en 0.

C: Este bit es el encargado de marcar cuando se produjo carry en la operación


realizada anteriormente del bit 7.

Como podemos ver estos bits, también denominados flags (bandera) ya que
podemos pensar en ellos como banderas indicadoras de ciertas condiciones que
ocurrieron al hacer las diferentes operaciones. Se ponen en uno al pasar la condición
que evalúan que son: Overflow (V), Half Carry (H), Interrupt Mask(I), Negative(N),
Cero(Z), Caray(C).

Assembly

Los microcontroladores tienen en su memoria de programa una serie de unos y


ceros que al leerlos y decodificarlos mediante una tabla que poseen, “interpretan” que es
lo que deben hacer y de que forma hacerlo. El fabricante nos brinda esos códigos
(código de operación u opcode) y nos dice que es lo que interpreta el MCU respeto de
cada uno de ellos y la forma de disponerlos en memorias a fin de que el
microcontrolador haga lo que queremos que haga. Es decir que cuando el
microcontrolador lea en memoria un byte con 11000110 (C6 en hexadecimal)
interpretará que tendrá que sumar lo que hay en el acumulador con una posición de
memoria, la dirección de la posición de memoria estará en los próximos 16 bits
contiguos al byte que informa la operación.

De acuerdo al código de operación será la cantidad de operandos a buscar luego


de si mismo. Esta información, de cómo trabajar con los operandos y diferentes códigos
de operación nos la brinda el fabricante a través de la información de los modos de
direccionamiento de las diferentes instrucciones.
Si bien no es una tarea imposible, se dificulta mucho el trabajar con números
hexadecimales que no tienen ningún significado para el programador, a medida que el
programa se complica, diríamos que se torna imposible. Para esto hay un programa que
se encarga de traducir una ciertas palabritas llamadas nemonicos que son
representativos de códigos de operación, esto hace más simple la tarea del programador
a la hora de crear, evaluar y analizar un programa. Vale a esta altura aclarar que no
todas las operaciones ocupan lo mismo en memoria dado que no es lo mismo una
instrucción que recibe como valor un byte sumado a su código de operación que aquella
que necesita para poder operar una dirección (puede ser una dirección e 8 bits o 16 bits,
en donde en la de 8 bits se suponen 0 todos los bits más significativos que representan la
dirección, más adelante aclararemos este tema) así mismo no todas las instrucciones
demoran lo mismo en ser ejecutadas, estó más el poseer instrucciones que realizan
varias operaciones bajo una misma instrucción, es lo que hace que nuestro set de
instrucciones (conjunto de instrucciones) sea clasificado como CISC (Complex
Instruction Set Controller) a diferencia de su variante RISC (Reduced Instruction Set
Controller), en donde no hay instrucciones que aglomeren varias operaciones en una y
todas las instrucciones tarda lo mismo en ejecutarse, los microcontroladores PIC de
Microchip son un ejemplo de RISC, como también variantes de los núcleos ARM.
Por ejemplo el código de operación visto anteriormente que carga en el
acumulador un valor que es el contenido de una posición de memoria es el $B6, el
nemonico asociado a este opcode es el LDA (Load acomulator).
La sintaxis utilizada para la carga es la siguiente

LDA $0080

Nótese la distancia de una tabulación entre el nemonico y el operando que


utiliza. Más adelante seguiremos ampliando las formas de programación en assembly.
Modos de direccionamiento

Una vez mencionados los registros del microcontrolador mencionaremos ahora los
modos de direccionamiento, es decir las diversas formas por las cuales podemos acceder
a los datos y registros.

El CPU del HCS08 cuenta con 16 modos de direccionamiento, estos son:

 Directo (Dir)
 Extendido (Ext)
 Inmediato (Inm)
 Inherente (Inh)
 Indexado sin offeset (IX)
 Indexado con offset de 8 bits (IX1)
 Indexado con offset de 16 bits (IX2)
 Indexado a través de stack pointer con 8 bits de offset (SP1)
 Indexado a través de stack pointer con 16 bits de offset (SP2)
 Relativo (Rel)
 Memoria a Memoria (Inmediato a directo) (Inm/Dir)
 Memoria a Memoria (Directo a directo) (Dir/Dir)
 Memoria a memoria (Indexado a directo con post incremento) (IX+/Dir)
 Memoria a memoria (directo a indexado con post incremento) (Dir/IX+)
 Indexado con post incremento (IX+)
 Indexado con 8 bit de offset y post incremento (IX1+)

Modo de direccionamiento Directo

Estadísticamente está establecido que un alto porcentaje de las posiciones de


memoria a las cuales vamos a consultar por su contenido y que vamos a alterar están
ubicadas entre la posición $0000 y $00FF, debido a que la RAM o por lo menos el
comienzo de la RAM está ubicado entre estas posiciones, vale hacer la aclaración que
en algunos miembros de los HCS08 la RAM termina en $00FF, por eso ahí comienza
apuntando el SP, pero no en todos, por compatibilidad hacia atrás con el HC05 es que
se mantiene este valor de comienzo para el SP. Dado esta condición de uso de memorias
comprendidas entre $0000 y $00FF (bloque llamado página cero de memoria)
necesitamos tan solo 1 byte para pasarle la dirección ya que supondríamos que el byte
más significativo seria $00.
Con este modo de direccionamiento podemos acceder a las posiciones desde
$0000 a $00FF reduciendo en 1 byte la instrucción.
Mostraremos como utilizar una instrucción que cargara el acumulador con el
contenido de la posición $0080 utilizando el modo de direccionamiento directo

La sintaxis es la siguiente:
LDA $80

Y se lee de la forma, LOAD ACUMULATOR (carga en el acumulador) El contenido de


la posición de memoria $80

Modo de direccionamiento Extendido

Como mencionamos antes está estadísticamente probado que un alto numero de


las variables que se utilizan están entre las posiciones $0000 y $00FF. Si bien es un alto
numero no es un 100% por eso mediante este método de direccionamiento podremos
acceder a posiciones de memoria más allá de la dirección $00FF y manejarnos con todo
el mapa de memoria, es decir trabajar con los 64KB posibles que puede direccionar el
HCS08 (desde la posición $0000 hasta la $FFFF), aunque en realidad utilizaremos
desde la posición $0100, hasta la $FFFF debido a que cualquier dirección inferior a
$0100 se implementaría mediante el modo de direccionamiento directo.
A causa de esto estas instrucciones ocupan un byte más en memoria, ya que no
supondremos $00 al byte más significativo de la memoria

Veamos a la sintaxis para depositar en la posición de memoria $0100 el valor


contenido en el acumulador

STA $0100

Se lee de la siguiente forma: Guardar el contenido del acumulador (Storage


Acumulator) en la posición $0100

Modo de direccionamiento inmediato

Mediante este método no se le dice al MCU que vaya a buscar ningún dato a
ninguna posición de memoria sino que se le brinda el dato a utilizar. Es decir luego de la
instrucción INMEDIATAMENTE viene el dato a utilizar

La sintaxis es la siguiente:

LDA #$FF
Se lee de la siguiente forma: Cargar en el acumulador el valor $FF. El símbolo
numeral que precede al dato a cargarse (operando) es el indicador de que este valor es
un dato propiamente dicho y no la posición de memoria a donde debe ir a buscar el dato.

Nótese que el operando luego de la instrucción es de 8 bits ya que el acumulador


es de 8 bits, en caso de querer cargar H:X con un dato el operando será de 16 bits y la
sintaxis será:

LDHX #$023F

Se le de la siguiente forma: Cargar el registro H:X con el valor $023F, es decir


que luego de ejecutarse la instrucción el contenido de H será $02 y $3F el de X.

Modo de direccionamiento Inherente

Este modo de direccionamiento no posee operando, es decir que en la


instrucción está ya implícito que es lo que el microcontrolador debe hacer, como y con
que registro/operando. Es un ejemplo de esto la siguiente instrucción:

INCA

Se lee de la siguiente forma: Incrementar en uno el acumulador. Como se puede


apreciar no es necesario darle ningún dato más al MCU ya que todo esta implícito en la
misma instrucción.

Modo de direccionamiento Indexado sin offset

Cuando definimos al registro indicie, dijimos que este era un puntero, por lo
tanto lo hace ideal para el manejo de tablas. El registro índice tendrá contenido en sí
mismo un valor significativo que representará, una posición de memoria a apuntar. Con
este modo de direccionamiento podremos devolver a algún registro o utilizar como
operando al contenido de lo apuntado por H:X, nótese que sí bien a H:X se lo carga con
un valor de 16 bits (que representa una posición de memoria a apuntar), el valor
devuelto por este tipo de direccionamiento es de 8 bits, ya que son datos que
extraigamos de una posición de memoria.
Podemos entonces por ejemplo cargar en el acumulador el valor contenido en la
posición de memoria a puntada por H:X.

La sintaxis es la siguiente:

LDA ,X

Se lee de la siguiente forma: Cargar el acumulador con el contenido de la


posición de memoria apuntada por H:X.
El ,X es el indicativo del uso de este tipo de direccionamiento, así como lo fue el
# para el inmediato y nada para el directo o extendido.
Este modo de direccionamiento hace evidencia lo cómodo de usarlo para el
trabajo de tablas con el MCU.
Recordando el modo de direccionamiento directo, podríamos cargar H:X de la
siguiente forma.

LDHX #$03F8

Donde $03F8; podría ser el comienzo de una tabla. La instrucción anterior lo


que hace es cargar la posición de memoria $03F8 en el registro índice, y mediante el
modo de direccionamiento indexado podríamos llevar a algún otro registro el contenido
de dicha posición.

LDA ,X

Luego de ejecutar estas dos instrucciones tendremos en el acumulador el


contenido de la memoria $3F8

Modo de direccionamiento indexado con offset de 8 bits

El concepto básico es el mismo que el utilizado para el direccionamiento


indexado sin offset solo que ahora se apuntara al contenido de H:X más un valor
llamado offset de 8 bits .Lo importante aquí es que el valor de H:X no es alterado en
NINGUN MOMENTO. También cabe aclarar que el offset es si o si una constante.

La sintaxis es la siguiente:
STA offset8bits,X
Se lee de la siguiente forma: Guardar el contenido del acumulador en la posición
apuntada por H:X + Offset

Si fuere un offset de $25 seria

STA $25,X

Él, X evidencia el direccionamiento indexado.

Modo de direccionamiento indexado con offset de 16 bits

De este modo de direccionamiento al anterior, solo varia el rango numérico de la


constante de offset, mientras que antes era una constante de 0 a 255 ahora puede ser una
de 0 a 65535.
Este modo de direccionamiento es muy usado en el manejo de tablas,
conociendo la posición de comienzo N. La forma de trabajo es hacer que el offset sea
esa posición de comienzo N de la tabla, y cargar en H:X el movimiento relativo dentro
de la tabla.

La sintaxis es la siguiente:

LDA offset16bits,X

Donde el offset ahora es un número de 16 bits, notar que la sintaxis es muy


parecida a las anteriores, ya que siempre en fin es un modo de direccionamiento
indexado.

Si tuviéramos en memoria lo siguiente:

Posición de Contenido de la
memoria posición
$EC00 $03

$EC01 $08

$EC02 $07

$EC03 $A3

$EC04 $45

y quisiéramos elegir algún elemento en dicha tabla, lo podríamos hacer así

LDHX #elemento
LDA $EC00,X

Esto cargará en el acumulador el contenido de la posición ($EC00 + Elemento)

Modo de direccionamiento Indexado por SP, con 8 bits de offset

Lo que hace este modo de direccionamiento al igual que el indexado por


registro índice, es devolver el contenido apuntado por SP + un offset constante de 8 bits.
Ya que no modifica el valor efectivo que está en el registro del SP, se puede utilizar
para traer un valor que se haya almacenado hace una cantidad de instrucciones
anteriores en el stack.

La sintaxis es la siguiente:

LDA offset8bit,SP
Se lee de la siguiente forma: Cargar en el acumulador el contenido de lo apuntado por el
Stack Pointer + el offset de 8 bits.
Cuando definimos al stack dijimos que es una pila, en la que guardaremos
valores auxiliares. Supongamos ahora que hemos guardado la parte baja de un byte, en
el stack, luego la parte alta, y ahora queremos cargar en el acumulador la parte baja del
byte de vuelta pero sin perder lo que hay en la pila, para eso este modo de
direccionamiento es muy útil ya que con solo poner

LDA 2,SP

Carga en el acumulador lo que apunta el stack un valor anterior. No olvidarse


que el stack sigue apuntando a la próxima posición libre es decir que quedará igual esto
en la pila:

Modo de direccionamiento Indexado por SP, con 16 bits de offset

Al igual que pasaba con el modo de direccionamiento indexado en donde


estaban las dos opciones de poner un offset de 8 o 16 bits, pasa igual en este caso.

La sintaxis es la siguiente:

LDA offset16bits,SP
Modo de direccionamiento Relativo

Cuando hacemos alguna operación aritmética o lógica, esperamos en ciertas


ocasiones preguntar que sucedió con el resultado de estas operaciones y actuar en
consecuencia. Ya que las instrucciones aritméticas y lógicas alteran el registro CCR,
mediante las instrucciones en modo relativo se puede analizar los bits de dicho registro.
En algunas ocasiones interesará analizar un solo bit y a veces una condición logia entre
varios de ellos.
Al programador finalmente le es transparente que bits son por los cuales tiene
que preguntar para una u otro función ya que las instrucciones lo hacen lo más amigable
posible.
Una vez analizados los bits lo que crearan estas instrucciones son bifurcaciones
en nuestro programa, es decir saltar a ejecutar una porción de código si es que la
condición por la cual se pregunto fue verdad u otro código si fue falsa.
Por ejemplo puedo llegar a cargar un valor de una variable en el acumulador,
compararlo con algún otro valor en memoria, y preguntar si es mayor, menor o igual.
Hay que tener en cuenta que algunas de las condiciones de salto por mayor y menor son
signadas y otras no. Cuando veamos más adelante algunas instrucciones haremos esta
salvedad.
Seguido de la instrucción de condición que evalúa alguno/s BIT/s la próxima
instrucción a ejecutarse estará a continuación si la condición por la cual pregunto es
falsa o saltara N cantidad de posiciones que yo le diga el MCU si es que era verdadera.
Los saltos son relativos ya que desde la posición donde me encuentro ejecutando
el programa podrá saltar –128 a +127, es decir que no puedo irme a cualquier parte a
ejecutar programa.

La sintaxis es la siguiente:

BEQ Salto

Se lee de la siguiente forma: Saltar si es igual (Branch if equal) la cantidad de


“salto”.
Recordar que salto es un número signado de rango (-128 a 127)

Importante: Estas instrucciones no alteran el CCR. Ejemplo: acabo de comparar


dos números (uno en el acumulador y otro en memoria), y luego de eso pregunto si el
que estaba en el acumulador era mayor que el que estaba en memoria, para eso utilizo la
instrucción BGT (Branch if greater than), si siguió adelante por no ser mayor NO ES
NECESARIO COMPARAR DE NUEVO los números sino que puedo preguntar
directamente si el del acumulador es menor.
Más adelante cuando veamos algunos ejemplos y veamos el uso de etiquetas nos
simplifican considerablemente este trabajo de calcular a donde tenemos que saltar, y
dejaremos al compilador la dura tarea de contar cuanto tendrá que saltar para ir a alguna
parte de programa.

Modo de direccionamiento Memoria a Memoria Directo a Directo

Este modo de direccionamiento es nuevo ya que el HC05 no lo traía. Lo que


hace es pasar datos de una posición de memoria a otra sin usar ningún registro. La única
salvedad es que dichas memorias deben estar entre los valores $0000 - $00FF (por ser
MD directo). No existen MD de memoria a memoria con alguno de los dos operandos
en extendido.
Todos los MD orientados a memoria-memoria serán utilizados con instrucciones
de MOV y tampoco altera el CCR

La sintaxis es la siguiente:

MOV OP1,OP2

Se lee de la siguiente forma: Copiar el contenido de la posición de OP1 en la


posición de memoria OP2, Es decir se asigna de izquierda a derecha (como indica la
flecha). Esto es totalmente inverso a otros MCU como los de INTEL (8051), el motivo
es el de hacerlo más amigable, ya que alguien que nunca programó intuitivamente
pensaría en este tipo de dirección en cuanto al flujo de los datos.

Modo de direccionamiento Memoria a Memoria Inmediato a Directo

Este MD es igual que el anterior, es decir que establece el traspaso de datos a


memoria sin utilizar, ni alterar ninguno de los registros, la diferencia es que ahora se le
pasara un dato constante a una posición de memoria.
Como los registro que configurar al MCU y a los diferentes módulos se
encuentran mapeados en memoria en los bytes más bajos, y así también el comienzo de
la RAM, este tipo de instrucción es muy utilizado para inicializar al MCU y
configurarlo. Por lo general un programa comenzara con unas cuantas instrucciones de
este tipo que lo setean para trabajar de la forma deseada.
La sintaxis es la siguiente:

MOV #OP1,OP2

Se lee de la siguiente forma: Cargar en la posición OP2 el valor OP1.

Modo de direccionamiento Memoria a Memoria Indexado a Directo


con post incremento

Este modo de direccionamiento es muy usado para crear tablas, el concepto es el


siguiente: copia el contenido de una posición de memoria (ubicada entre $0000 y
$00FF) a la posición de memoria apuntada por H:X y luego de hacer esto incrementa
H:X en una unidad, pasando a apuntar una posición de memoria después de la que
apuntaba.
Por ejemplo podríamos ubicar H:X apuntando al comienzo de la tabla que
queremos crear. Adelantándonos un poco podemos mencionar que tenemos una
posición de memoria donde se aloja el valor de recepción del puerto serie. Podríamos
cada vez que tenemos un dato valido en dicho byte, copiar el contenido a lo apuntado
por H:X y como luego se incrementa solo, nada más deberíamos preguntar cuando
vuelva a haber un dato nuevo, y volver a copiar el contenido del byte a lo apuntado por
H:X que va a ser una unidad de memoria después de donde hizo la copia anterior. De
esta manera con muy pocas instrucciones se puede crear tablas en RAM.

La sintaxis es la siguiente:

MOV X+,OP1

En la sintaxis de arriba el X+ indica que es indexado con post incremento, y el


OP1, al ser una dirección el destino como direccionamiento directo

Modo de direccionamiento Memoria-memoria Directo a Indexado con


post incremento
Este MD es el inverso al anterior. Mencionábamos que el anterior era idóneo
para la creación de tablas, éste es ideal para el traspaso de tablas por ejemplo por un
puerto serie.
Adelantándonos un poco diremos que hay un byte que al escribirlo, estaremos
poniendo el dato a sacar por el puerto serie. Con este concepto, si apuntamos H:X al
comienzo de la tabla y copiamos lo apuntado por H:X al byte de salida del puerto serie,
luego preguntamos si es que ya fue transmitido, y volvemos a ejecutar la misma
instrucción ya que cada vez ejecutada y copiado el byte apuntado por H:X
automáticamente se pasa a apuntar al próximo debido al post incremento vemos que de
vuelta con muy pocas instrucciones hemos enviado toda una tabla por el puerto serie.
Combinando las dos formas de trabajo y algunas que otras pocas instrucciones
más podremos por ejemplo de acuerdo a lo leído por el conversor analógico digital crear
una tabla en RAM y llegado cierto tiempo enviarlo por el puerto serie, así de simple
hemos creado un Adquisidor de datos analógicos.

Modo de direccionamiento indexado con post incremento

Es un modo de direccionamiento poderoso ya que involucra lo que habíamos


hablado en los de memoria-memoria indexado con post incremento, son muy pocas las
instrucciones que lo utilizan pero así y todo muchas las veces que se utilizan en un
programa. La instrucción es CBEQ, que significa (comparar y saltar si es igual). Lo que
hará esta instrucción en este modo de direccionamiento es comparar el acumulador con
lo apuntado por H:X, y saltar si es igual, luego de hacer esto incrementa SIEMPRE en
una unidad a H:X, dejándolo apuntando al siguiente byte.
Este MD es más que nada utilizado para la comparación de datos en tabla.

La sintaxis es la siguiente:

CBEQ ,X+
Modo de direccionamiento indexado con 8 bit de offset y post
incremento

No existen más diferencias que las que el lector pueda anticipar, que es la de
utilizar un offset, sumar a H:X un valor constante de 8bits para la comparación de datos
ubicados en una tabla:

La sintaxis es la siguiente:

CBEQ Offset8bits,X+
Set de instrucciones del CPU HCS08

El set de instrucciones es nuestro diccionario en cuanto a como implementar los


nemonicos y como los interpreta el MCU, contienen todas las instrucciones y sus
formas de usarla, indica, el tiempo que tardan en ejecutarse las instrucciones, bytes que
ocupan en memoria, si es que lo hacen o no que bits alteran o no del CCR y los modos
de direccionamiento disponibles para dichas instrucciones.
Es un concepto erróneo el pensar que uno debe saberse de memoria el set de
instrucciones, lo que si es cierto que cuanto más programemos y más familiarizados
estemos con este menos lo utilizaremos, pero uno SIEMPRE debe tener el set de
instrucciones a mano cuando se encuentre programando cualquier microcontrolador de
la arquitectura y empresa que fuere. En el Anexo I se encuentra el set de instrucciones,
ahora solo comentaremos la forma en que debemos interpretarlo.
A la izquierda siempre tendremos la columna indicándonos la sintaxis, donde opr es el
operando, dependiendo el modo de direccionamiento, será un dato (Inmediato) posición de
memoria (Directo o extendido) un offset de 8 o 16 bits, o simplemente el ,X (indexado)
En la 2º columna tenemos la ampliación de lo que significa el nemonico.
En la 3º columna una descripción, donde el paréntesis indica el contenido de ( ), y la
ausencia de los mismos son el registro o posiciones de memoria especifico.
La 4º columna nos indica cual de los bits del CCR son afectados, algunas veces puede
que siempre los ponga a 1, a 0, no los altere (-) o que el resultado del bit dependa justamente de
los operandos analizados indicando esto con la flecha que puede valer 0 o 1 dependiendo de la
operación y operandos utilizados.
La 5º columna nos indica el modo de direccionamiento coincide fila a fila con la
sintaxis empleada.
La 6º Columna es la traducción a hexadecimal del nemonico al código de operación,
que utiliza en ese MD.
La 7º Columna es la cantidad de nibbles (2 nibbles = 1 byte) que utiliza como
operandos en ese MD, fijarse que de directo a extendido cambia aparte del opcode que indica
que debe ir a buscar un byte más de operando, que la dirección se la pasa en dos bytes
recordando que en MD directo suponía $00 a la parte alta.
La 8º Columna indica los ciclos de maquina que insume al MCU dicha instrucción,
obviamente está directamente ligado con el tiempo que tarde en ejecutarlo, y el mismo con la
circuiteria de reloj que veremos más adelante como calcularlo.
Formas de escribir un programa

Si bien más adelante ahondaremos más sobre el compilador en sí, cabe aclarar
que todo lo que reverenciemos ahora es sobre el entorno de programación llamado
CodeWarrior versión 6,3, si bien muchas de las normativas son comunes, puede que
alguna difieran de otra cambiando a otro compilador. Con el CodeWarrior
programaremos tanto en assembler como en C, cabe aclarar que este compilador es
relativamente nuevo para esta familia de microcontroladores ya que antes para la
programación de assembler se utilizaba otro programa (aun existente) llamado WinIde,
este es un programa muy sencillo que honestamente me parece ideal para iniciarse pero
al estar dejando de usarse y no tener renovaciones para los micros más nuevos es
preferible abordar el CodeWarrior y amigarse con un entorno muy poderoso donde nos
encontraremos para trabajar en Aseembly, en C simular y bajar código a nuestros
MCUs. Ahondaremos ahora en la forma de escribir la programación en si del MCU más
adelante nos introduciremos en el uso mas exhaustivo del programa y muchas mas
opciones que vienen con él.
La forma de escribir un programa es en base cuatro columnas que nos indicaran
que es cada cosa. La distancia entre columnas puede estar dada por una o dos
tabulaciones
En la primera columna se encontraran las etiquetas, es decir nombres amigables
para referenciarnos a lo que haya a la derecha, si hay código, entonces estas etiquetas
representaran la posición de memoria donde se encuentra el código. En esa posición de
memoria se encuentra el código que escribiremos en las columnas segunda y tercera.
Las etiquetas no pueden empezar con números ni ser ninguna de las palabras reservadas
que veremos más adelante.
El la segunda columna se encontrará el nemónico que deseamos escribir, que
luego el compilador lo traducirá a su respectivo valor hexadecimal.
En la tercera columna se encuentran si es que los hubiere los operandos de la
instrucción a ejecutar, estos indicaran el MD utilizado, por lo tanto el código de
operación a poner de acuerdo a nemónico, fijarse que el opcode cambia de un MD a
otro de un mismo nemónico.
El la cuarta columna se deberán ubicar los comentarios, no es solo necesario que
este ubicados en la 4º columna si que estén precedidos por un “;”
1ºColumna 2ºColumna 3ºColummna 4º Columna

Etiqueta LDA #$80 ;Carga 80 en A

Se lee de la siguiente forma: en la posición de memoria “etiqueta” estará la


instrucción LDA #$80
De ahora en más siempre que pongamos etiqueta estaremos referenciando a esta
posición de memoria. Es decir que si en alguna situación de salto en otra parte de
programa queremos volver si es que fue igual por ejemplo a esta posición de memoria la
sentencia será:

1ºColumna 2ºColumna 3ºColummna 4º Columna

BEQ Etiqueta

Se ve que no es necesario siempre ubicar una etiqueta, ni tampoco el comentario.


De no haber comentario igualmente el mnemónico deber ir en la segunda columna, lo
que no es necesario para los comentarios que en realidad pueden ir en cualquier parte,
precedidos por el “;”, todo lo que este a la derecha de estos será ignorado por el
compilador.
El compilador por si solo se dedicará a calcular de cuanto es el salto y el valor
que tiene que poner para que salte a Etiqueta. Si se excediera de los -128 ;+ 127 el
compilador daría un error. Más adelante veremos como solucionar este problema con
saltos absolutos (es decir no saltar sino ubicar en el PC un valor efectivo, que será el de
la próxima instrucción a ejecutar).
Todo lo que concierne a los nemónicos es del lenguaje assembly, pero si
pensamos tenemos que, de alguna manera, decirle al programa donde queremos que
ubique nuestro código de programa, es decir donde va a colocarse nuestro programa en
todo el mapa de memoria.
Esta y otras directivas más, son propias del compilador, ya que a él le decimos a
partir de donde es que cuando grabemos a nuestro MCU copie nuestro código.
La directiva ORG es la encargada de decir donde comenzara a colocar todo lo
que encuentre por debajo de esta directiva que puede ser tanto código como variables,
su nombre deriva de origen, es decir que le pasaremos la una dirección y todo lo que
siga después debe ir a partir de dicha dirección. Esta directiva siempre se debe ubicar en
la 2º columna y seguido de un espacio la dirección.
Por ejemplo si tenemos un MCU donde la memoria ROM comienza en la
$EC00, y queremos ubicar ahí nuestro código, este empezará de la siguiente forma

ORG $EC00

Comienzo: LDA #$3F


STA $80

En el ejemplo anterior a partir de la $EC00 se encuentra nuestro código, a titulo


de ayuda a la interpretación colocamos una etiqueta de Comienzo, esta al ser una
posición de memoria coincidirá con $EC00.
En la posición $EC00 tendremos el opcode de LDA en MD inmediato que es el
$A6, luego el $3F en $EC01, el opcode del STA en la siguiente y así…..
Otra directiva que nos ayuda mucho en la programación es la directiva EQU
(equate) lo que hace es reemplazar un texto, su forma de utilizarla es la siguiente:

1ºColumna 2ºColumna 3ºColummna

Etiqueta EQU valor

De ahora en más, siempre que coloque en cualquier parte del programa la


palabra “Etiqueta”, cuando el compilador ensamble el programa, cambiará esta por
valor. Esto ayuda mucho a la programación ya que podemos definir algunos valores al
principio del programa por medio de los EQU que utilizaremos a lo largo de todo
nuestro programa y con tan solo cambiar el valor lo habremos cambiado para todo el
programa.

Por ejemplo sabes que en alguna parte del programa cargamos una variable
llamada segundo con el valor del tiempo que queremos que espere la rutina X. Si
creamos un EQU con el valor de segundo de la siguiente forma:

SegVal EQU #$01

Y en nuestro programa cargamos la variable segundo de la siguiente manera:

LDA SegVal

Estaremos cargando $01 en el acumulador si usaremos muchas veces el cargar


este valor en nuestro programa, y por alguna razón nos diéramos cuenta que en realidad
son 2 segundos que tenemos que esperar, con tan solo cambiar el EQU #$01 por el
valor #$02 cambiaremos todos los valores en TODO el programa.

También es equivalente colocar:

SegVal EQU $01

*********
*********
*********
LDA #SegVal
Al utilizar el núcleo del CPU HC08 y este ser genérico e igual para todos los
miembros de la familia HC08, cuando pasemos de uno a otro miembro, puede que
cambien los módulos y las posiciones de memoria peor no el código en sí. Por eso al
comenzar a programar pondremos unos EQUs de la siguiente manera:

ROMStart EQU $EC00


RAMStart EQU $0080

Utilizamos en este caso los valores para el HC08 JK3.

Cuando comencemos nuestro programa recordemos que debemos marcar a partir


de donde queremos que ponga nuestro código por la directiva ORG. Pondremos
entonces:

ORG ROMStart

Comienzo: ;........ nuestro código

Si cambiáramos de MCU y tuviera la memoria en otras posiciones solo


tendríamos que cambiar el valor definido arriba.

Esta directiva no solo es útil si se utiliza mucho algún valor durante el programa
sino para poder ver más fácil el programa debido a que las etiquetas suelen sernos más
familiares que los números. Lo cierto que en un programa suelen haber pocos números
dentro del código y muchos EQU con los respectivos valores.

Generalmente lo que hagamos al programar los microcontroladores es utilizar


variables que alojaremos en RAM. Lo que haremos es reservar la cantidad de memoria
que necesitemos para nuestro código, sin necesidad de pensar en que posición de
memoria se encuentra dicha variable, eso se hace de la siguiente manera.

ORG Z_RAMStart ;Utilizando el EQU anterior del JK3, decimos que


;lo que sigue esta a continuación en la posición $0080
;que según el manual es donde comienza la RAM

Variable RMB 1
O totalmente equivalente:

Variable DS.B 1

Ambas pseudo operaciones, son equivalente DS.B (define space (Byte)) RMB
(reserve memory). De esta manera estaremos reservando 1 byte bajo el nombre Variable
en la posición $80

Luego en nuestro código invocaríamos de la siguiente manera

LDA Variable ;Cargamos en el acumulador el contenido


;de la variable

Si necesitáramos reservar dos bytes seria de la siguiente forma

Variable RMB 2

La forma de invocar seria:

LDA Variable ;carga el 1 Byte


LDA Variable+1 ;carga el 2 byte

También podemos reservar más de un byte utilizando las directivas

DS.W (reserva 2 bytes)


o DS.L (reserva 4 bytes)

También son equivalentes

RMD (reserva 2 bytes)


o RMQ (reserva 4 bytes)
Resumiendo:

variable RMB 1 (reserva 1 byte bajo el nombre variable)


variable DS.B 1 (reserva 1 byte bajo el nombre variable)

variable RMD 1 (reserva 2 byte bajo el nombre variable)


variable DS.W 1 (reserva 2 byte bajo el nombre variable)

variable RMQ 1 (reserva 4 byte bajo el nombre variable)


variable DS.L 1 (reserva 4 byte bajo el nombre variable)

DC. DEFINE CONSTANT BLOCK

Otra sentencia muy utilizada es la que se utiliza para establecer tablas en ROM,
se hace de la siguiente manera

ORG ROMStart ; pondremos nuestra tabla al comienzo de la flash

Tabla DC.B $01,$02,$03,$04,$05,$06

Tabla DC.B $01


DC.B $02
DC.B $03
DC.B $04
DC.B $05
DC.B $06

La sentencia se lee como define constant (definir una constante), y lo que hace
es definir el contenido de un byte (posición de memoria). Lo que hará en los casos
anteriores que son totalmente iguales es poner en el contenido de la posición $EC00 el
valor $01, en la $EC01 el valor $02, y así hasta la $EC04 con el valor $05.
El comienzo de la tabla estará marcado por la etiqueta Tabla, que es la posición
$EC00.
También es valido para completar con caracteres ASCII la siguiente forma:

Tabla DC.B “Hola mundo”


DC.B 0

Donde completara el la dirección “Tabla” con el equivalente de ‘H’ y así hasta el


último valor que será 0

Así como tenemos un define constantes de bytes y lo hicimos para reservar


memoria RAM, tenemos la forma de definir dos bytes contiguos, existe una palabra que
hace que definamos dos bytes a la vez, define constant word.

Tabla DC.W $0102


DC.W $0304
DC.W $0506

El concepto es totalmente equivalente al visto antes con define byte. Más


adelante veremos que el DC.W se utiliza para definir un sector muy importante llamado
vector de interrupciones, en donde se deberán colocar las direcciones de las rutinas a
ejecutarse en caso de ocurrir algunas de las interrupciones.

también tenemos el caso (aunque no tan usado) de definir constantes de 4 bytes

Tabla: DC.L $010203


DC.L $040506

Así como tenemos una variante al DS (RMB) tenemos la posibilidad de usar otra
expresión/directiva

FCB equivalente a: DC.B


DCL equivalente a: DC.W
FQB equivalente a: DC.L

Directiva INCLUDE
Esta directiva lo que hace es incluir el contenido de un archivo en el actual
archivo, en el lugar donde coloquemos la directiva completa con su respectiva sintaxis.
El parámetro a pasarle a esta directiva es el archivo que queremos incluir archivo. Lo
que hará será copiar todo el contenido del archivo en el que está en uso como si es que
fuera escrito en el mismo archivo.
La sintaxis es la siguiente:

INCLUDE ‘nombre del archivo con la extensión’


INCLUDE “ruta completa del archivo con la extensión”

La directiva busca el archivo, el misma carpeta donde esta el archivo actual


usando ‘’o mediante “” se le especifica la ruta completa, y lo copia literalmente en
nuestro archivo fuente. Generalmente estos archivos tienen extensión .inc de include.
El compilador nos proporciona una serie de archivos donde se encuentran ya
definidos mucho de los EQU con las direcciones de los registros que trae cada MCU.

El CodeWarrior al iniciarlo directamente nos preguntará con que micro de toda la gama
que tenemos dentro de HCS08 vamos a trabajar, por eso nos genera el archivo principal
main.asm donde nos encontraremos con la línea

INCLUDE 'derivative.inc'

Dentro del archivo derivate.inc hay otra línea de include donde en el caso de
utilizar un JK3 nos encontramos con:

INCLUDE 'MC68HC908JK3.inc'

Una vez ensamblado el programa, el compilador genera un archivo .LST que


contiene todo el código unido en un mismo archivo.

Estableciendo las bases numéricas:

BASE 10 ; Cambia a base decimal


BASE $0A ; Cambia a base decimal
BASE @12 ; Cambia a base decimal
BASE 2 ; Cambia a base binaria
BASE $02 ; Cambia a base binaria
BASE @02 ; Cambia a base binaria

BASE 16 ; Cambia a base hexadecimal


BASE $10 ; Cambia a base hexadecimal
BASE @20 ; Cambia a base hexadecimal

Estas cuatro formas ubicadas en la 1º columna indican la base estándar de los


números encontrados en el archivo. Si es que no se especifica nada por defecto se toma
como base hexadecimal.
Una vez establecida la base, si no se coloca nada delante del operando se
interpretara que es de la base definida o la base por defecto hexadecimal en caso de no
haberlo indicado.
A veces por más que en todo el programa nos convenga trabajar con una base,
para ciertas ocasiones nos interesa expresar un número en otra base diferente para eso
tenemos dos formas, poner un prefijo al operando o un sufijo:

Base Prefijo

2 %

8 @

10

16 $

Las siguientes formas son todas equivalentes

LDA #$0a
LDA #@12
LDA #%00001010
LDA #10
Salto al lugar:

Cuando queremos producir un salto al lugar, no es necesario gastar una etiqueta


de la siguiente forma:

ACA: BEQ ACA ;salta al lugar si es igual (Z=0)

Con los caracteres $ y * (cualquiera de ellos) indicamos que es un salto al lugar.

BEQ * ;salto al lugar


BEQ $ ;salto al lugar

Assembly condicional

Estas directivas del compilador indica que parte del código se debe o no
compilar. Los parámetro a evaluar se setean con SET

SET Setea a verdeara un parámetro

sintaxis:

parametro SET valor

Si el parámetro se setea con SET a un valor, entonces se ensambla el código


entre las directivas IF, ENDIF si es que el parametro vale la condición.
Toda sentencia que no este entre ninguno de estos bloques será SIEMPRE
ENSAMBLADA.
Dentro del IF condicional tenemos varias posibilidades de preguntas:

== igual
!= distinto
>= mayor igual
> mayor
<= menor igual
< menor

Ejemplo:

debug SET 1 ; Setea debug = Verdadero


test SET 0 ; Setea test = Falso
nop ; Always assembles
nop ; Always assembles
IF (debug == 1) ; si debug = Verdadero
jmp start

ELSEIF ; Si debug = Falso


jmp end
ENDIF
nop ; Siempre lo ensambla
nop ; Siempre lo ensambla

IF (test==1) ; Si test = Verdadero


jmp test
ENDIF

ABSENTRY:
Si bien hasta ahora nunca lo nombramos existen dos formas de trabajo en cuanto
a programación en assembly bajo el CodeWarrior que se definen como Absolutely
Assebly y Relocable Assembly. Para entender un poco cada una de ellas y sus
diferencias daremos alguna explicación extrapolando a otros ejemplos que no tiene que
ver con HC08 ni tampoco necesariamente assembly pero es el mismo caso. Es lógico
pensar que los sistemas operativos que conocemos hoy en día, cualquiera de ellos, no
son programamos por una sola persona, es decir que varias personas, y muchas veces a
lo largo del mundo hacen pequeñas partes, funciones, rutinas de lo que al final, en
conjunto será el programa, sistema operativo, etc. Como vimos o también nos
imaginamos puede que las funciones necesiten variables tanto propias como
provenientes de otras funciones o partes de programa, también es obvio pensar que cada
uno al hacer una función no sabe que porción de código en memoria o que ubicación va
a tener su código dentro de un microcontrolador etc., etc. Por tales cosas se trabaja de la
forma Assembly Realocable. Bajo esta forma de programación podremos hacer
mención a variables que ni siquiera hemos declarado pero bajo alguna directiva
podremos decirle al compilador que luego alguien se encargará de definirla en algún
otro archivo y alguien o algo oficiará de vinculador o linker entre todos los archivos,
definiendo al final quien declara y en que lugar una u otra variable, como así también
definirá los espacios que se le asignaran a cada porción de código, entonces ya no
hablaremos más de ORG absolutos donde solo podemos pasar una dirección de código
sino que estableceremos secciones de código para ROM, RAM, etc.. Más adelante nos
encargaremos de esta forma de programación muy útil, pero que obviamente es
utilizada para programas que requieran un nivel mucho más alto de abstracción de
hardware y donde, como ya mencionamos, trabajen varias personas. La otra forma de
programación, que es a la que nos dedicaremos por el momento es el assembler
absoluto, para ello todas las definiciones serán absolutas, es decir que con los ORG
estableceremos donde termina y empieza nuestro código o variables con direcciones
físicas ya establecidas, y nosotros definiremos todas nuestras variables en nuestro
archivo. Si bien al comenzar (ya veremos mas adelante como) definimos si trabajamos
con uno u otro tipo de programación al generarse el archivo main.h que vamos a utilizar
vemos que cerca del comienzo hay una línea que dice:

ABSENTRY _Startup

La interpretación de esta directiva es que le está indicando al compilador que


bajo el nombre (etiqueta) _Startup se encuentra la primer línea de código que tiene que
ejecutar nuestro MCU. En caso de trabajar en forma realocable esta línea no aparecerá.

XDEF:

Esta directiva también tiene que ver con los tipos de programación, si es que
nosotros dentro de nuestro proyecto usamos más de un archivo, de esta forma estamos
avisando a todos los demás archivos que nosotros somos los propietarios, donde esta
alojada la etiqueta _Startup. XDEF= xternal Definition

XDEF _Startup

XREF:
Siguiendo con las metodologías empleadas para los trabajos en los dos tipos de
programación anteriormente con XDEF mostramos como informar al resto del mundo
que nosotros somos los propietarios de una determinada etiqueta, que puede ser una
variable, porción de código, etc.
Con la directiva XREF definimos que vamos a utilizar una etiqueta que no
hemos declarado nosotros la metodología es:

XREF etiquetaglobal

De esta manera podemos utilizar etiqueta global que sin bien no la hemos
declarado el compilador sin problema compila sabiendo que alguien (el linker) luego
establecerá donde se encuentra la etiqueta, definida bajo XDEF.

Hemos descrito algunas de las más importantes y mas usuales directivas del compilador,
existen muchas otras de estas directivas aunque generalmente no son usadas dejamos al
lector que quiera referirse al manual HC08ASMRM donde podrá encontrarlas todas y
leerlas en profundidad. A lo largo del presente texto veremos más ejemplos de como
utilizar las que hemos mencionado y algunas otras más.

Sub-rutinas

Hasta ahora, habíamos trabajado con programas totalmente lineales en su


formato, pensemos por un momento que necesitamos en nuestro programa hacer la
conversión de algún numero a ASCII que tenemos en el acumulador, como sabemos
para convertir un numero del $00 a $0F, si el numero es menor que $0A, con solo
sumarle $30 tendríamos el resultado, y en caso de ser mayor que $09 sumaremos $37
($37 + $0A = $7 + $30 + $0A =$41 =’A’). La rutina a crear podría ser de la siguiente
forma(tener en cuenta que el dato ya se encuentra en el acumulador):

*******************************************
CMP #$0A ;comprar el contenido del
;acumulador con $0A
BLO MENOR ;si es menor saltar a MENOR
ADD #$07 ;si era mayor sumo $07
MENOR: ADD #$30 ;indistintamente de ser mayor o
;menor le sumo $30
*******************************************

Si ahora usáramos varias veces a este código, tendríamos que ponerlo en cada
lugar donde lo usáramos. Mediante las subrutinas podemos encapsularlo, es decir,
escribirlo una sola vez e ir a ese lugar donde se encuentra el código y retornar luego de
donde lo llamamos.
Como el código no sabe desde que parte del programa lo llame, cuando termine
de ejecutarse no sabrá a donde retornar, a menos que en el momento en el cual acudo a
ejecutar esta porción de código guarde la dirección de donde yo estaba, es decir el
contenido del PC. Recordemos cuando hablamos del stack, dijimos que era ideal para el
almacenamiento valores contenidos en los registros de forma temporal, entonces lo que
vamos a hacer cuando invoquemos a una subrutina, es guardar en el stack una copia del
contenido del PC (serán dos bytes ya que el PC se conforma por el PCL y PCH, total de
16 bits) y luego cuando retorne devolverle al PC lo contenido copiado en el stack.
Si bien suena complicado la metodología, es totalmente transparente para el
programador ya que hay dos instrucciones que se encargan de hacer todo, el salto a la
subrutina y guardar el contenido del PC y otra que se encargan de retornar al PC el
contenido guardado en el stack.

Supongamos ahora que le colocamos una etiqueta nuestra rutina para poder
invocarla, en nuestro ejemplo le pondremos rutina: “CONVERSION”. Luego mediante
la instrucción JSR (jump tu subrutina – Saltar a la subrutina) saltaremos a la rutina a
ejecutar guardándose automáticamente el contenido del PC. Luego al final de la rutina
colocaremos la instrucción RTS (Return from Subrutine - Retorno de subrutina) que
devolverla al PC el contenido de lo apuntado por el SP.

La sintaxis de invocación seria:

JSR CONVERSION

Para la rutina seria:

*******************************************
CONVERSION CMP #$0A
BLO MENOR
ADD #$07
MENOR: ADD #$30
RTS
*******************************************

Más adelante con los ejemplos se verá un poco más claro la forma de trabajo.

Explicación de algunas instrucciones y sus diversos usos

Vale aclarar que en las operaciones cuando es que la instrucción necesite un operando,
colocaremos la letra M como segundo operando que es la nomenclatura que se usa en el Set de
instrucciones, pero M no siempre es una memoria, sino que dependiendo del modo de
direccionamiento usado para la instrucción puede ser un valor inmediato, una posición de
memoria o lo apuntado por alguno de los registros (X como SP), con o sin cada uno de
los offset posibles.

ADC (Add with carry/sumar con carry):

Operación: A (A) + (M) + (C)

Descripción: Suma el contenido del acumulador con el de M, más el carry.

Modos de direccionamiento permitidos para esta instrucción:

1. Inmediato
2. Directo
3. Extendido
4. Indexado con 16 bits de offset
5. Indexado con 8 bits de offset
6. Indexado sin offset
7. Indexado por SP con 16 bits de offset
8. Indexado por SP con 8 bits de offset
Esta operación es muy usada para las operaciones de suma multibyte ya que cuando
sumamos un byte distinto del primero tendremos que tener en cuenta el bit de carry
anterior. Para dar un ejemplo mostraremos la suma mulibyte de dos datos de 3 bytes.
Los bytes del 3º al 1º del dato 1 se encontraran en las posiciones $80,$81,$82
respectivamente, y los los bytes del 3º al 1º del dato 2 en las posiciones $83,$84,$85
respectivamente. El resultado será dejado en las posiciones $86,$87 y $88

ORG RAMStart

Dato1 RMB 3 ;reservo 3 bytes para Dato1 al comienzo de la flash,


;posición $80
Dato2 RMB 3 ; reservo 3 bytes para Dato2 al final de Dato1, es decir
; en la posición $83,$84,$85
Resultado RMB 3 ;; reservo 3 bytes para el resultado al final de Dato3.
; posiciones $86,$87,$88.

ORG ROMStart

Comienzo: LDA Dato1+2 ; Cargo en el acumulador la posición


;$82
ADD Dato2+2 ;Sumo el acumulador SIN CARRY con
;el contenido de la posición de memoria
;$85
STA Resultado+2 ;Guardo el resultado en la posición $88
LDA Dato1+1 ;IDEM anterior pero una posición de
;memoria menos
ADC Dato2+1
STA Resultado+1
LDA Dato1
ADC Dato2
STA Resultado

La única consideración en cuanto a este ejemplo es que en la primer suma debemos


utilizar la instrucción de suma sin carry, o asegurarnos de poner el carry en 0, antes.

Podríamos generalizar para la suma de datos de N bytes usando el registro Indice para
apuntar a los operandos de la siguiente forma

N EQU 3 ;Cantidad de bytes a sumar

ORG RAMStart

Dato1 RMB N ;reservo N bytes para Dato1 al comienzo de la flash,


;posición $80
Dato2 RMB N ; reservo N bytes para Dato2 al final de Dato1, es decir
; en la posición $83,$84,$85
Resultado RMB N ;; reservo N bytes para el resultado al final de Dato3.
; posiciones $86,$87,$88.
Cuenta RMB 1

ORG ROMStart

Comienzo: CLC ;Borramos el bit de carry para la primer suma


MOV #N,cuenta ;movemos el valor tope de sumas que debemos
;efectuar
LDHX #(Dato1+N-1) ;Cargamos el ultimo byte de dato. Si Dato1 es
;la posición del primer byte, “TAMAÑO – 1”
;estaremos al ubicados en el último byte

loop: LDA ,X ;Cargamos en el acumulador lo apuntado por


;H:X
ADC N,X ;Lo sumamos con lo apuntado por X+N
;posiciones, (Dato2).
STA 2*N,X ;Lo dejamos en lo apuntado por X más 2*N
;posiciones es decir resultado
AIX #-1 ;Decrementamos X para apuntar al otro byte de
;los datos a sumar.
DBNZ cuenta,loop ;Decrementamos el valor de la cantidad total de
;sumas a implementar, si no es cero es porque
;nos quedan sumas por realizar, caso contrario
;hemos terminado

Note como con casi la misma cantidad de instrucciones hemos ampliado la suma a un
valor genérico N, donde solo debemos cambiar el valor del EQU superior de N para que
funcione para cualquier tipo de datos.
El código anterior demuestra lo poderoso de utilizar los EQU y de delegar ciertas tareas
de calculo al compilador como por ejemplo el 2*N y el (Dato1+N-1), que serán calculados por
el compilador cuando se lo invoque y colocará el valor especifico en su lugar ya que estas son
constantes.
Otra aclaración muy importante es notar que después del ADC, para que todo tenga
valor, ninguna de las instrucciones que se ejecuten hasta una nueva suma deberán alterar el
CCR, es decir no cambiar el bit de carry, si chequeamos la metodología usada en el set de
instrucciones vemos que tanto AIX como DBNZ no alteran el CCR, lo cual nos permite cuando
volvamos a hacer la suma tener el bit de carry de acuerdo a la suma anterior.
La única desventaja de este código es el tener que utilizar una variable para la cuenta de
las sumas realizadas hasta el momento.

AIS (Add inmediate value(signed) to Stack Pointer)

Operación: SP (SP) + (16<<M)

Descripción: Suma un valor inmediato de 8bits (signado) al Stack Pointer. EL significado del
(16<<M) es que como que Stack Pointer es un registro de 16 bits, para poder sumarle un valor
signado (como mencionamos el convenio utilizado es el complemento a 2) el bit de signo debe
ser desplazado hasta el bit Nº 16 a fin de sumarle correctamente el dato al valor contenido del
Stack Pointer.

Esta instrucción es muy utilizada para alocar (reservar espacio) en la pila. La utilizan
mucho los compiladores en C para pasar parámetros, reservar memoria en variables locales, o
eliminar bloques creados en la pila. Notar que esta instrucción NO ALTERA EL CCR.

Veamos un ejemplo donde creamos un espacio de 2 bytes en la pila que utilizaremos


para guardar resultados temporales de operaciones entre dos memorias Mem1 y Mem2, los
valores temporales nos servirán para luego sumarlos y guardar el resultado en una variable
Mem3.

AIS #-2 ;Reservamos 2 bytes en la pila


LDA Mem1
ADD Mem2
STA 1,SP ;Guardamos la suma temporalmente en
;el byte más arriba de la pila, de los dos
;reservados
ADD #5
STA 2,SP ;Guardamos la suma temporalmente en el byte
;más lejano al SP
ADD 1,SP ;Sumamos el acumulador con el primer valor –
;guardado temporalmente
STA mem3
AIS #2

En el ejemplo se ve claro como el stack nos sirve para no tener que definir variables
para todo el programa que probablemente se usen en porciones de código muy reducidas. Se
deja al lector la tarea de aplicar en concepto de usar variables temporales en el stack para ver si
se puede aplicar en el ejemplo anterior a fin de eliminar la declaración de cuenta.
También vale recalcar que para reservar memoria debemos restar el valor al SP, ya que
la pila crece cuando las posiciones de memoria decrecen.

ASL (Arithmetic Shift Left):

Operanción: M (M) <<1

Descripción: Rota hacia la izquierda M, rellenando con 0 desde por el b0 y haciendo que los bits
que desbordan, del b7 pasen al bit de carry.

Esta instrucción es muy utilizada para crear rutinas de comunicación serie sencillas.
Dejaremos sin realizar la rutina de demora entre enviar un bit y otra que establece la velocidad
de comunicación. Suponemos que ingresa en la variable “carácter” el dato a enviar por un bit N
de algún puerto.

N EQU 0
portserie EQU portb

SerialOut: LDA #8 ;Cargo en el acumulador la cantidad de bits a


;transmitir
loop: ASL caracter ;Desplazo el MSB al carry
BCS Uno ;Pregunto si el carry es uno, pondré uno en el
;bit de puerto correspondiente
BCLR n,portserie ;Caso contrario pongo un cero
BRA Sigo
Uno: BSET N,portserie
Sigo JSR DELAY ;Invoco a mi rutina de demora entre bit y bit
DBNZA loop ;Chequeo si transmití todos los bytes, de ser así
;salgo

DELAY: ……..
……..
……..
……..
RTS

Nuevamente vemos las ventajas de utilizar los EQU ya que cambiando el EQU del N
cambiaremos el bit por el cual queremos que salga y cambiando el del portserie el puerto de
salida. Más adelante retornaremos a esta rutina agregándole muy pocas cosas podremos
establecer una comunicación exitosa con la PC a 9600 bd o cualquier otra velocidad.

Otra connotación importante con respecto a esta y todas las instrucciones de


desplazamiento, es recodar que cuando desplazamos un byte hacia la izquierda estaremos al
valor contenido antes del desplazamiento multiplandolo por 2, y al desplazarlo hacia la derecha
dividiéndolo por dos.
CLR (Clear):

Operación: A $00 o M$00 o X$00 o H$00


Lleva al contenido de alguno de los registros o a M el valor $00, estas instrucciones si
bien podrían reemplazarse por LDA $00 por ejemplo para el caso del acumulador, demoran
menos tiempo y ocupan menos espacio en memoria.

Veamos un ejemplo que nos permite borrar toda la memoria RAM

TAMAÑO EQU 128T ;Tamaño de la memoria RAM

ORG ROMStart

Comienzo: LDHX #RAMStart ;Apunto con H:X al comienzo de la


RAMStart
loop: CLR ,X ;Borro el contenido de lo apuntado por H:X
AIX #1 ;Apunto al próximo Byte de RAM a borrar
CMPHX #RAMStart+TAMAÑO;Chequeo si llegue al final
BNE loop ;Sino llegué sigo borrando bytes

Cambiando solo la instrucción CLR ,X por LDA #$Valor y STA ,X


Completaremos con cualquier valor a partir de una posición de memoria (en este caso comienzo
de RAM) la cantidad de bytes que especifiquemos

TAMAÑO EQU 128T ;Tamaño de la memoria RAM


Valor EQU $FF

ORG ROMStart

Comienzo: LDHX #RAMStart ;Apunto con H:X al comienzo de la


RAMStart
LDA #Valor ;Cargo en A el valor a cargar en los Bytes
loop: STA ,X ;Copio el valor de A, en la posición de memoria
;apuntada por H:X
AIX #1
CMPHX #RAMStart+TAMAÑO;Chequeo si llegue al final
BNE loop ;Sino llegué sigo borrando bytes
DAA(Decimal Adjust Accumulator):

Descripción: Luego de haber realizado una operación con números BCD deberemos realizar o
no un ajuste sumando 6 a cada uno de los nibbles de acuerdo con los resultados de los bits de
carry, half carry o si los valores superan al numero 9. Esta instrucción automáticamente analiza
los bits C y H además de evaluar si el resultado de la operación BCD a evaluar (contenido en el
acumulador) posee alguno de sus nibbles mayores a 9.
Ejemplo:

ORG ROMStart

Valor1 DB $78
Valor2 DB $49

Comienzo: LDA Valor1 ;A= $78


ADD Valor2 ;A=$78+$49 = $C1; como H=1 y el nible alto
;es mayor a 9 suma 6 a ambos nibbles
DAA ;$C1 + $66 = A = $27 C=1

El ejemplo realiza la suma de dos números BCD 78 + 49 = 127, al sumarlos


aritméticamente sin consideraciones de suma BCD, el resultado seria $C1, al evaluar que se
produjo acarreo en del nibble bajo al alto, debemos sumar 6 este, y como el resultado del nibble
alto es mayor a 9 también debemos sumarle 6 al este, por lo tanto $C1 + $66 = $27 con C = 1 lo
que nos da el resultado $127 esperado.

DBNZ (Decrement and Branco if Not Zero):

* Si bien ya hemos utilizado en los ejemplos anteriores esta instrucción, debido a lo poderosa
que es, es bueno dedicarle una explicación y ver un ejemplo.

Descripción: Decrementa M, y salta si es que M (luego de ser decrementado) es cero.


Esta instrucción lo que nos permite es, como vimos en los ejemplos, con muy pocas
instrucciones crear un bucle en el que podemos hacer diversas operaciones.

Como nombramos al describir el Set de instrucciones vimos que cada instrucción


demora un tiempo en ejecutarse dado por su cantidad de ciclos de maquina, este valor
dependerá de la circuiteria de reloj asociada al MCU, con esta instrucción podremos crear
demoras para nuestros programas de forma muy sencilla de la siguiente manera:
N EQU $41

ORG RAMStart

Cuenta RMB 1

ORG ROMStart

Comienzo: LDA #N
Loop: DBNZ Cuenta,*
DBNZA Loop

La demora que realiza = [(Cuenta*5) + 3]*N + 2.La formula deriva de analizar la


función. Lo que hará es primero cargar en el acumulador que lo hará una vez sola, observando
el set de instrucciones (la ultima columna) vemos que cargar el acumulador con un valor en
inmediato demora 2 ciclos de maquina (cy), luego repetirá el contenido A ((A)=N) el
decrementar cuenta, decrementar una variable demora 5 cy, que repetirá cuenta veces y a su vez
todo eso lo hará N=(A) veces. Siempre para el análisis hay que ir del corazón de la rutina hacia
afuera.

Reset e Interrupciones

Si bien por ahora no los nombramos, el microcontrolador, además de


componerse de todos los elementos si mencionados anteriormente, pueden contener
algunos módulos para diversas aplicaciones. Es decir módulos que se encarguen de las
conversiones analógicas digitales, módulos de timers, comunicaciones digitales seriales
sincrónicas y/o asincrónicas, etc. Una característica importante en los
microcontroladores de NXP es la de contener diversos módulos lo que hace que
seguramente haya un MCU que satisfaga las necesidades de un proyecto a fin de
minimizar la cantidad de componentes externos en la plaqueta con todo lo que ello trae
aparejado. Los módulos que traiga como también la cantidad de ellos depende de cada
microcontrolador. Por lo general la modalidad de trabajo es la de configurarlos llevando
algunos datos a ciertos registros especiales (ubicados siempre a partir de la memoria
$0000 zona de los I/0 registers). Una vez configurados y que se pasa a la utilización,
debemos plantear dos formas de uso. Método por polling y por interrupciones.

Método polling/encuestas: Consiste en ir preguntando al modulo si es que ocurrió el


evento para el que nosotros lo programamos (Ej. en un conversos analógico digital,
esperaríamos que nos avise que termino de convertir el dato, o en una comunicación que
llego algún dato o salió del MCU)
Podemos establecer una analogía con lo que podría suceder en nuestras casas,
podríamos no colocar ningún timbre en la puerta, y cada X cantidad de tiempo ir a la
puerta y fijarnos si es que hay alguien, mientras más seguido nos fijemos menos será la
probabilidad de que alguien que estaba en la puerta esperando a ser atendido se valla
por justamente no haber sido atendido (método polling).

Interrupciones: Siguiendo con la analogía anterior podemos decir la otra forma de


trabajo colocando un timbre en la puerta y de alguna manera, olvidarnos de la puerta, es
decir, seguir con nuestras tareas, en cuanto alguien llegue a nuestra puerta y quiera ser
atendido, tocará el timbre, lo cual nos interrumpirá en nuestras tareas y nosotros iremos
a atender (método por interrupciones).
Dentro de las interrupciones tenemos dos grandes tipos, enmascarables y no
enmascarables. Pensemos en las enmascarables con la analogía anterior en desenchufar
el timbre, si bien está el timbre no va a interrumpirnos. Recordemos que en el CCR se
encuentra el bit I, que justamente lo que hace es enmascarar (bloquear) las
interrupciones, también recordemos que el bit después de un reset amanece seteado, por
lo tanto las interrupciones del tipo enmascarables luego de un reset aparecen
bloqueadas. El tipo de interrupciones no enmascarables son aquellas a las cuales no
puedo obviarlas, siempre que ocurran deberé atenderlas INDEFECTIBLEMENTE. Una
de estas interrupciones es el reset y la otra es la interrupción de software.
La diferencia entre una interrupción y una subrutina, es que a las subrutinas yo
las voy a invocar y se en que momento van a ejecutarse, así que puedo si es que en la
subrutina se altera algún registro, tener las consideraciones adecuadas de salvarlo, etc.,
etc. En cambio las interrupciones al no saber cuando van a ocurrir puede que este justo
realizando ciertas operaciones con los registros, al encontrarme en la mitad de estas,
caiga una interrupción, por lo tanto saltara a ejecutar su rutina (escrita especialmente
para ese evento), y si en dicha rutina se utilizaran algunos de los registros, al volver de
la interrupción a donde yo estaba ejecutando mi programa, los datos contenidos en los
registros puede que ya no sean los mismos. Podría entonces el programa hacer cualquier
cosa o colgarse, resetearse, en fin hacer algo totalmente imprevisto.
Recordemos cuando mencionamos el problema que teníamos si no guardábamos
el PC cuando saltábamos a ejecutar una subrutina, este caso es muy similar, debido a
que efectivamente tendrá que suceder nuevamente cada vez que se ejecute una
interrupción, pero no solo con eso alcanza sino que deberán guardarse todos los
registros a fin que sea donde sea que se produjo el quiebre del flujo normal y esperado
por nuestro programa, al retornar de la interrupción tengamos los registros de la misma
manera que antes de ir a la rutina de interrupción. En efecto todo esto lo hace solo el
MCU, es decir que cuando se produce una interrupción el microcontrolador, guarda los
registros en el stack, guarda primero el PC parte baja, después la parte alta, a
continuación guarda X, después el acumulador y por último el CCR. Nótese que cada
vez que se produce una interrupción el Stack crece en 5 bytes, si encima dentro de la
interrupción se anidan subrutinas el stack empieza a crecer considerablemente, por eso
hay que manejarlo con cuidado, ya que si crece demasiado puede llegar a sobre escribir
posiciones de memoria que se utilizaban para almacenar variables. Otro dato
importantísimo es que nombramos todos los registros que guarda el HC08 en caso de
interrupción y podemos ver que NO GUARDA H, esto pasa solamente por
compatibilidad con HC05, que no tenia registro H. Enseguida veremos una forma de
solucionar esto para no tener inconvenientes y poder utilizar el registro H en la rutina de
interrupción sin tener problemas a la hora de retornar.
El MCU, cuando ocurre una interrupción, irá a la rutina que definiremos para tal
efecto y guardara antes todos los registros de la forma planteada en el párrafo anterior.
Una vez que se finaliza la rutina de interrupción deberemos indicarlo para que entonces
retorne desde el stack, los valores a los respectivos registros y vuelva a la posición
donde estaba ejecutando el programa. Así como RTS, devolvía el PC solamente, la
instrucción RTI (return from interrupt- retorno de interrupción) devuelve todos los
registros en orden.
La sintaxis de la rutina de interrupción en la que no se usa el registro H seria de
la siguiente forma:

Mirutina_de_interrupcion ;mi código


;mi código

RTI

En el caso de utilizar el registro H dentro de la subrutina deberemos al entrar


guardar en el stack el valor, y luego antes de retornar todos los demás registros retornar
el valor de H a H. La forma seria de así:

Mirutina_de_interrupcion PSHH ;Guarda H en la pila


;mi código
;mi código
PULH ;retorna H a la pila
RTI
Mencionamos cuando empezamos a hablar de interrupciones que el MCU al
ocurrir alguna de estas deberá ir a ejecutar un cierto código que nosotros escribiremos a
fin de ejecutarse cuando ocurra la interrupción. Para ello el microcontrolador reserva las
últimas posiciones de memoria para que coloquemos allí la dirección de memoria a la
cual debe irse en caso de ocurrir una interrupción. Esta tabla que mencionamos es
conocida como el vector de interrupciones de un MCU. El fabricante nos dice en que
posición deberemos colocar la dirección para la interrupción pertinente, así tendremos
una posición donde poner la dirección del código a ejecutarse en caso de una
interrupción de timer, otra posición donde este la dirección del código a ejecutarse en
caso de interrupción del modulo de conversión analógica digital, etc,etc. La longitud del
vector dependerá de la cantidad de módulos que tenga el MCU y como dijimos antes,
esto varia de un modelo a otro.
Vamos anticipando que este comportamiento el de ir a buscar a una posición de
memoria el código a ejecutar es lo que sucede cuando hay un reset en el
microcontrolador, por esto SIEMPRE las dos últimas posiciones de memoria ($FFFE y
$ FFFF) estarán destinadas a colocar la dirección de comienzo de nuestro programa.
Cuando el MCU se resetea (por cualquiera de las seis causas que producen un reset, que
veremos más adelante) va al final del vector de interrupciones y carga la posición de
comienzo de nuestro código. Si al principio de nuestro programa colocamos la etiqueta
comienzo, una forma de completar las posiciones del vector de interrupciones referidas
al reset seria la siguiente:

ORG $FFFE
DW comienzo

De esta manera a las posiciones $FFFE y $FFFF las completa con la dirección
(parte alta y parte baja) de comienzo de nuestro programa. Notemos lo útil que es
utilizar etiquetas en nuestro programa ya que nos olvidamos por completo de las
posiciones de memoria y delegamos esa tarea al compilador.
Siempre que hagamos cualquier programa debemos definir si o si estos dos
últimos bytes, para que al arrancar el MCU sepa a donde tiene que ir. Si bien no es
necesario definir los otros lugares del vector de interrupciones si es que no se usan las
interrupciones veremos más adelante en el tema “seguridad en los HC08” que conviene
completarlos aunque sea con un valor cualquiera. Vemos a continuación la tabla del
vector de interrupciones para un JK3 y dos formas de completar el vector, en este caso
podremos una rutina para timer, el adc y completaremos con cualquier valor los demás
puntos

/////tabla
ResetVector EQU $FFDE

ORG RAMStart

Mi_Var DS.B 1

ORG ROM

Comienzo: ;Configuro el MCU, los módulos


CLI ;Habilito las interrupciones
;mi programa

Adc_int: PSHH ;Rutina de interrupción del ADC


;comienzo salvando H

;mi código (mi rutina) de interrupción

PULH ;Restauro H
RTI

Timer_int PSHH ;Rutina de interrupción del TIMER


;comienzo salvando H

;Pongo en el medio mi código de interrupción

PULH ;Restauro H
sin_uso RTI

ORG ResetVector

DC.W Adc_int ;Rutina de interrupción del ADC


DC.W sin_uso ;Rutina de interrupción de teclado
DC.W sin_uso ;Posiciones en el vector sin uso real
DC.W sin_uso ;Posiciones en el vector sin uso real
DC.W sin_uso ;Posiciones en el vector sin uso real
DC.W sin_uso ;Posiciones en el vector sin uso real
DC.W sin_uso ;Posiciones en el vector sin uso real
DC.W sin_uso ;Posiciones en el vector sin uso real
DC.W sin_uso ;Posiciones en el vector sin uso real
DC.W sin_uso ;Posiciones en el vector sin uso real
DC.W Timer_int ;Rutina de interrupción del timer
DC.W sin_uso ;Rutina de interrupción del canal 1 del timer
DC.W sin_uso ;Rutina de interrupción del canal 0 del timer
DC.W sin_uso ;Posiciones en el vector sin uso real
DC.W sin_uso ;Rutina de interrupción de IRQ
DC.W sin_uso ;Rutina de interrupción de software
DC.W Comienzo ;Posición de comienzo del programa

**********************************************************************

ORG ResetVector

DC.W Adc_int ;Rutina de interrupción del ADC


DC.W sin_uso ;Rutina de interrupción de teclado

ORG $FFF2

DC.W Timer_int ;Rutina de interrupción del timer


DC.W sin_uso ;Rutina de interrupción del canal 1 del timer
DC.W sin_uso ;Rutina de interrupción del canal 0 del timer

ORG $FFFA
DC.W sin_uso ;Rutina de interrupción de IRQ
DC.W sin_uso ;Rutina de interrupción de software
DC.W Comienzo ;Posición de comienzo del programa

En los ejemplos de arriba no solo hemos visto como establecer el vector de


interrupciones de dos maneras diferentes, sino un ejemplo de cómo sería un programa,
un template vacío, donde completando con nuestro código ya podríamos compilarlo
directamente.
En la tabla (tabla 1.2) que nos brinda Freescale acerca de que posición de
memoria equivale a que interrupción en el vector también vemos a la izquierda que las
posiciones en el vector establecen una prioridad, siendo el reset la más prioritaria y la
ultima (el ADC en este caso) la menos prioritaria. Esto es debido a que puede darse el
caso que ocurran dos interrupciones al mismo tiempo, en ese caso primero se atenderá a
la de más prioridad, y luego al salir se ejecutará el código de la de menor prioridad.

Sin bien es intuitivo ver que los diferentes módulos sin saber exactamente lo que
hace generaran interrupciones dedicadas a conectar un teclado, la conversión analógica
digital, tres diferentes formas del timer, es un poco menos fácil de imaginarse sin saber
lo que son, la interrupción de IRQ y la de software. La interrupción de IRQ (lo veremos
más en detalle en el modulo mismo), se produce cuando un pin físico dedicado a tal fin,
pasa de un estado alto a un estado bajo. Es la única interrupción en este
microcontrolador que se produce por una fuente externa como ser el cambio de un pin
del MCU. La otra interrupción, la de software es medio extraña, si bien se aleja un poco
del concepto de interrupción ya que no ocurre en cualquier momento sino que yo la
invoco como si es que estuviera llamando a una subrutina, al ir a ejecutar esa porción de
código guardará todos los registros como lo hace con cualquier otra interrupción. Es
decir que cuando el programa ejecute la instrucción SWI, ira a ejecutar una porción de
código, previamente guardando todos los registros en el stack, en la sección de ejemplos
veremos para que nos puede llegar a ser útil esta pseudo interrupción.
Nota importante: Cuando se produce una interrupción el BIT I del CCR pasa a
valer uno, por lo tanto todas las interrupciones quedan deshabilitadas, al salir de la
interrupción el bit I volverá al estado anterior o sea cero. Si quiero que dentro de una
interrupción me puedan interrumpir otros módulos puedo hacerlo colocando en mi
rutina de interrupción la instrucción CLI, no es conveniente hacer esto ya que el stack
puede crecer de forma muy rápido, y si ocurren muchas interrupciones el MCU puede
colgarse, resetearse o hacer cualquier cosa. En muy pocas ocasiones verdaderamente se
necesita borrar el bit I dentro de una interrupción, y de no ser muy necesario es
conveniente no hacerlo.

Causas de reset
Cuando hablamos de reset, inevitablemente viene a nuestras mentes el mágico
botón de reset de nuestras PC hogareñas, en donde ante cualquier evento extraño,
presionamos el botón, y todo vuelve a comenzar, es decir la computadora vuelve a un
estado conocido. Lo mismo pasa en un MCU, al producirse un reset, este ira a ejecutar
el código que nosotros indiquemos como código inicial (mencionamos antes que le la
dirección de dicho código se encuentra en las posiciones $FFFE y $FFFF). No solo el
pin cuya función asociada es la de reset (produce el reset cuando pasa de un estado alto
a un estado bajo) es quien produce esta condición, sino que en total son seis las causas
que producen un reset. Estas son:

 POR (Power on reset)


 Pin
 Opcode Ilegal
 Dirección Ilegal
 Modulo de LVI
 Modulo COP (Watch Dog)

POR: El power on reset se da ante la transición de 0V a la tensión de alimentación en el


pin VDD, es decir, cuando energizamos a nuestro microcontrolador.

Pin: Como mencionamos antes hay un pin dedicado a la función de reset, y cuando este
pin este en un estado lógico 0 el MCU producirá un reset. Los HC08 tienen la
posibilidad de configurarle un pull-up interno, por lo tanto se puede dejar al aire y con
un pulsador unir a masa este pin en caso de querer resetear nuestro sistema, al igual que
lo hacemos con la PC.

código de operación ilegal: Si bien hay muchos nemónicos que son representativos de
un valor hexadecimal, que el MCU interpretará como una instrucción para hacer algo,
hay ciertos valores que no están definidos, si el MCU lee en memoria un código de
operación invalido producirá un reset.

Dirección ilegal: Al tener un bus de datos de 16 bits, nos da la posibilidad de


direccionar 64 KB, dependiendo del mapeo de memoria de cada miembro de la familia
hay posiciones de memoria que están sin implementar o figuran como reservadas. Si
queremos acceder a alguna de estas posiciones el MCU procederá a resetearse.

Modulo LVI: Si bien analizaremos este modulo más adelante, mencionamos ahora que
podemos utilizarlo para que cuando la tensión de alimentación pase un cierto rango el
MCU se resetee y no quede funcionando con baja tensión, lo que puede producir
perdidas de datos en RAMStart.
Modulo Cop: Al igual que el LVI, veremos más adelante este módulo pero, si
mencionaremos ahora que si lo tenemos funcionando, este modulo evita que el MCU se
quede en lazos cerrados de los cuales no tenia que quedarse. Tendremos que cada cierto
tiempo por nuestro programa decir a este modulo que el MCU esta funcionando, si no lo
hacemos transcurrido ese tiempo el modulo del COP reseteará al MCU.

Si bien estamos acostumbrados a pensar en el pin de reset como una entrada el


MCU, donde en el momento que pase de un nivel bajo a un nivel alto ( es decir flanco
ascendente) produciremos un reset, esto no es 100% verdad. Cuando el MCU se
encuentra funcionando normalmente lo descrito anteriormente es de la forma en que el
pin trabaja, también, en caso de ocurrirse un reset por las causas internas (LVI, COP,
Ilegal Opcode, Ilegal Address) el pin de reset se pondrá en un nivel bajo por un tiempo
determinado, indicando al mundo exterior que internamente se produjo un reset. Esto es
muy útil ya que podríamos conectar este pin a diferentes módulos ( si es que los
hubiese) en nuestra placa y ya que generalmente será el MCU quien maneje a estos
periféricos, en caso de resetearse por algún problema en el programa, también reseteará
los demás módulos, para llevar a todo el hardware y software a un estado inicial
conocido.

Modulo SIM (Sistem Integration Module)

Junto con el CPU este modulo conforman y definen la arquitectura de los HC08.
Este modulo se encarga de reconocer los tipos de interrupciones, su trabajo es el de
derivar al MCU a ejecutar la porción de código escrita para el servicio de interrupción,
es decir administra y maneja el vector de interrupciones también controla y define los
tipos de reset dándonos la posibilidad de saber cual fue ultima causa de reset ocurrida.
Tendremos un byte, con bits específicos para cada una de las seis fuentes de reset, y
tendremos un uno en aquel BIT cuya causa fuere la que provoco el último reset. Los dos
bits faltantes para completar el byte, uno se deja sin uso, y el otro avisa una condición
particular que es el haber entrado anteriormente en “Modo Monito”, mas adelante
ampliaremos este concepto y diferenciaremos este modo del “Modo Usuario”, pero
básicamente pensemos que cuando programamos al MCU utilizamos al primero de los
modos mencionados y cuando nuestro programa “corre” en el microcontrolador será el
segundo modo el que estemos usando. El byte al que hacemos referencia es el RSR
(Reset status register / registro de estado de reset).

RSR
Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0

Lectura: POR PIN COP ILOP ILAD MODRST LVI 0

Escritura:                

POR: 1 0 0 0 0 0 0 0

  Sin implementar

El administrar las causas de reset se torna muy útil para poder por ejemplo
imprimir un cartel de “Batería baja” o “Llamar al técnico”, etc. (En el caso de tener
conectado un LCD, o algún elemento indicativo). Como regla general para saber si
tenemos o no que leer el byte que nos indica la última causa de reset diremos que todo
depende de la complejidad del proyecto, del área de aplicación del mismo, etc. Es decir,
si nosotros queremos hacer un programa para que solamente un pin del puerto cambie
entre un estado alto y un estado bajo, no tendrá mucho sentido el tener la consideración
de leer este byte y de actuar en consecuencia. Pero si estamos desarrollando un sistema
de alarma, seria prudente que ante una lectura de código ilegal, un acceso a dirección
ilegal o un reset por COP (que son indicativos de que el programa esta funcionando
mal) el sistema tuviera la “inteligencia” de avisar estas causas anómalas a fin de poder
corregirlas lo antes posible. Vale decir que nunca jamás, a menos que sea un código
muy sencillo, se escribe un programa de principio a fin de un día para el otro y funciona
perfectamente al primer intento. Generalmente se analizan las situaciones, se desarrollan
diagramas de flujo, se escriben las primeras líneas, se simulan, se vuelven a corregir,
vuelven a simular, luego se pasa a probar el programa en el hardware nuevamente se
corrigen las situaciones no contempladas e inesperadas, para finalmente dar paso al
programa final. Necesitaríamos mucho tiempo para poder probar nuestro programa y de
verdad saber si es que TODAS las situaciones contempladas suceden y son atendidas de
la forma correcta, por esto es que tener rutinas que nos sirvan para poder poner ya en
funcionamiento un producto y ante la eventualidad de que el programa tenga un Bug
(error), minimizar los efectos producidos y poder tomar las acciones correctivas lo antes
posible.
En cuanto a los módulos presentes en el MCU para atender a las interrupciones
tendremos una cantidad de bytes que bit a bit se pondrán en uno cuando la interrupción
de la cual ese Bit hace referencia (puede cambiar de un MCU a otro) se encuentre en
uno. Realmente no se usan estos registros ya que para el programador es transparente, el
microcontrolador se encarga solo de saber que fuente de interrupción fue la generadora
y de posicionarse en el vector de interrupciones para ejecutar la rutina de servicio de
interrupción que hemos escrito para tal hecho.

MODULO COP (Computer operating properly/ Computadora operando


correctamente)
Este modulo es conocido como el watch dog / perro guardián, debido a que se
puede interpretar como que se encuentra inspeccionando como funciona el
microcontrolador más precisamente el flujo del programa, y espera ser “alimentado”
cada cierta cantidad de tiempo, si no se “alimenta” durante un periodo establecido de
tiempo el modulo procederá a resetar al microcontrolador, a “mordernos”.
Describiendo técnicamente al modulo, se trata ni más ni menos de un contador
que cuando produce un overflow es decir pasa del valor máximo al valor mínimo reseta
al microcontrolador. Como no es de nuestro agrado que se este reseteando el MCU,
entonces resetearemos a este contador a fin de que no llegue a producir este overflow.
La ventaja de utilizar este modulo es la de protegernos ante código mal escrito,
es decir que si nuestro programa por alguna razón se fuere a ejecutar código no
contemplado, no ejecutara nunca las instrucciones que nosotros habíamos colocado para
“alimentar al perro”, entonces luego de un lapso el MCU se reseteara ya que interpretará
que nuestro programa no esta siguiendo el curso que nosotros habíamos pensado para él.
Como bien mencionamos arriba en cuanto a evaluar cual fue la última causa de
reset, el utilizar este modulo o no depende de nuestro la aplicación que le demos a
nuestro programa, quedará bajo la responsabilidad del programador analizar si conviene
o no habilitar este modulo.
El contador de COP se resetea tan solo escribiendo cualquier valor en la posición
de memoria $FFFF. Una instrucción de este tipo:

STA $FFFF

Resetearia el COP, ya que no importa el valor que tenga el acumulador, este será
escrito en el registro a fin de resetar el contador.
El COP se puede anular escribiendo un uno en el bit menos significativo del
registro CONFIG1, analizaremos más adelante dicho registro.
Los valores de cuenta, y por lo tanto los tiempos que el COP tardará en provocar
un reset pueden ser dos:

 218-24 ciclos de maquina


 213-24 ciclos de maquina

Estos tiempos también son configurados en el registro CONFIG1.

Por defecto el microcontrolador comienza con el COP encendido, de querer


apagarlo tendremos que mover el valor especifico al bit correspondiente en el
CONFIG1 al principio de nuestro programa.
Low Voltage Inhibit (LVI)

Como su nombre lo indica este modulo se encarga de chequear la alimentación


de microcontrolador y en caso de bajar a un valor nominal X, puede llegar a producir un
reset en el MCU.
La razón de ser de este modulo es que cuando la tensión de alimentación baja la
memoria RAMStart puede empezar a perder valores, guardar cualquier cosa, etc,etc. Lo
que indefectiblemente produciría un mal procesamiento de nuestros datos, y
consecuentemente que el microcontrolador haga cualquier cosa. Como “cualquier cosa”
es algo totalmente aleatorio y no contemplado por nuestro programa no debemos dejar
que esto pase, por eso este modulo se encarga de anular todas estas condiciones en
donde el MCU podría actuar de forma incierta y dejarlo inactivo hasta que la
alimentación vuelva a un estado que haga que el MCU funcione correctamente,
llevándolo a un lugar conocido y seguro como es el lugar de comienzo luego de un
reset.
Hablamos de un valor nominal X al principio debido a que este valor puede
variar de un microcontrolador a otro (chequear en la hoja de datos del microcontrolador
al final en el sección características eléctricas). también puede variar este valor en un
mismo MCU si es que trabajamos con 5V o con 3V, generalmente trabajando con 5V el
valor nominal estará por los 4V y para tensiones de trabajo de 3V este valor estará
cercano a los 2.4V. Como el MCU no sabe con que valor de tensión es que estamos
trabajando tendremos que establecerlo de alguna forma. Esto se lleva al cabo en el
registro CONFIG1 donde habrá un bit destinado a informar al microcontrolador con que
tensión es que vamos a trabajar.
Como todo modulo de los HC08 existe la posibilidad de apagarlo, y esto se
realiza también por un bit de CONFIG1, vale decir que por defecto el microcontrolador
arranca con el modulo de LVI funcionando, por razones obvias también arranca
funcionando con 3V, ya que si arrancara funcionando para 5V y quisiéramos usarlo en
3V ni bien arranca produciría el reset del MCU al ser 3V menor que la tensión nominal
4V para resetear al MCU.
Algunos microntroladores de la familia HC08 cuentan con la posibilidad de
dejar encendido el modulo de LVI pero deshabilitar el hecho de que este produzca un
reset (configurable también a través del CONFIG1). Esto es útil ya que nos brindara en
un registro dedicado (LVISR- en la posición $FE0C)un bit (flag) que nos indicará que
se esta trabajando por debajo de la tensión nominal. Podremos entonces estar en nuestro
programa preguntando continuamente por este bit, y en caso de la tensión encontrarse
por debajo de la tensión nominal, efectuar alguna operación, o indicar la condición en
algún visualizador, o copiar las variables en la RAM a una EEPROM, en fin las
posibilidades son muchas y los limites están dados solamente por nuestra imaginación.

Primeros programas: Conociendo el Codewarrior:

El codewarrior es un IDE( Integrated Develpmen Enveriment, entorno de


desarrollo integrado), es decir un programa que aglomera al compilador el editor de
texto, simulador, emulador y programador.
Es una herramienta profesional, la versión 6.3 SE (special edition) permite la
compilación ilimitada de código en assembler y hasta 32k de compilación en C. Ya que
ahora tenemos nocion de las instrucciones y como ubicar cada código empezemos a
trabajar con el programa y crear nuestro primer proyecto.
Una vez instalado el codewarrior, vale aclarar que la versión 6.3 solo funciona
bajo Windows de 32 bits no asi en sistemas operativos de 64bits, por eso que si tenemos
un SO de 64 bits lo mejor es instalar una maquina virtual con un Windows XP por ej. E
instalarlo ahí, la versión más reciente del codewarrior 10.0 permite instalar en Windows
de 64bits pero no permite programar en HC08, por eso es que nos quedamos con la
versión 6.3 para estos micros.
Una vez que abrimos el programa nos encontraremos con una ventana como la
siguiente:

En dicha ventana haremos click en “Create new Project”


Luego elegimos el microcontrolador a utilizar en esta caso dentro de los micros
HC08 elegimos la familia JL/JK y el MCU es el JK1, vemos a la derecha que nos pide
poner que conexión vamos a utilizar, podemos elegir Full Chip Simulation, si vamos a
simular nuestro código igualmente cuando queramos programar cambiaremos a Mon08
Interface pero lo haremos desde adentro del programa, las otras opciones son para otros
programadores o emuladores.
Ahora como todo proyecto debemos definir el nombre, que va a llevar la
extensión .mcp y el tipo de código que vamos a utilizar, el codewarrior nos da la opción
de Absolute Assembly, para poder validarla debemos deseleccionar la opción de C, esta
opción es la que empezaremos a utilizar, que es crear un archivo único de código
assembler, después tenemos Relocatable Assembly, que es utilizada cuando se trabaja
en grupo y se tienen varios archivos con códigos que vinculados terminan dando el
código final, luego la opción de código en C y C++, por ahora elegiremos la opción de
Absolute Assembly.
Hacemos click en siguiente y nos da la posibilidad de agregar archivos a nuestro
proyecto, esto es en caso de tener librerías o código ya escrito en otros archivos, por el
momento y para nuestro primero proyecto no agregaremos nada.
La última opción antes de comenzar es la de utilizar otros programas que vienen con el
codewarrior que mediante interfaces visuales nos generar los códigos de inicialización
de los diferentes modulos, suele ser útil y facilita las cosas, pero aveces suele generar un
código un poco extenso además es preferible usarlo cuando ya estamos acostumbrados
al código en assembler y directivas del compilador ya que muchas veces tenemos que
modificar el código creado así que por el momento pondremos none.
Una vez dadas todas las condiciones nos crea el proyecto, a la izquierda vamos a
ver el proyecto Proyecto1.mcp y todas las carpetas y archivos. Vamos a ver en la
carpeta Sources (fuentes) el archivo main.asm, que es el archivo principal donde
pondremos nuestro código, de este archivo nos encargaremos de describir lo que ya
contiene en unos instantes. La otra carpeta es una carpeta llamada Includes, ya el
programa como seleccionamos el micrcontroladores JK1 nos puso el archivo
M68HC908JK1.inc, si hacemos doble click vamos a poder ver el archivo dentro de él
vemos un montón de definiciones y EQU que ya están escritos para no tener que
volvernos locos memorizando donde esta cada registro y cada bit de cada registro. El
archivo derovate.inc contiene el include al archivo del micro elegido y además una
rutina para “alimentar” al COP.
Luego tenemos otra carpeta Project Settings y dentro Linker Files con el archivo
burner.bbl, al ser un programa de assembler absoluto y no relocalizable no posee linker,
más adelante cuando veamos código en C veremos el uso del linker y lo importante que
es, pero no lo utilizamos en código assembler absoluto, en el archivo burner.bbl están
algunas definiciones internas de cómo debe crear el archivo .s19 que en definitiva es el
archivo binario que se graba en el microcontrolador.
Una vez que vimos todos los archivos y carpetas que componen a nuestro
proyecto vamos a ir a main.asm donde nos encontraremos con mucho código ya escrito.
Primero hay comentarios que obviamente podemos borrar y agregar nuestro
propio comentario sobre el programa en cuestión que estamos haciendo, fecha de
creación, autor, versión etc.
Luego vemos la directiva INCLUDE ‘derivative.inc’ lo que hace esta directiva
es que todas las definiciones puestas en el archivo M68HC908JK1.inc se puedan utilizar
tal como si hubieran sido escritas en el mismo archivo.
Luego vemos XDEF como vimos anterior mente, esta directiva hace que la
palabra Startup sea visible para todos los archivos y módulos del proyecto como
trabajamos con un archivo único no es necesaria, es decir dejarla o borrarla es lo mismo,
si es utilizada en assembly relocalizable, lo mismo para ABSENTRY, que a otros
módulos que Startup es donde comienza el programa pero es una definición tan solo
para los otros módulos, recordemos que en código lo importante es la dirección que
figura en las direcciones $FFFE y $FFFF.
Luego ya tenemos un ORG RAMStart (vemos como ya dentro del
M68HC908JK1.inc está definido el comienzo de la RAM y FLASH con las etiquetas
RAMStart, luego tenemos ya definida una variable ExampleVar de 1 byte que
obviamente vamos a borrar. Ahí debajo colocaremos todaslas variables que nuestro
proyecte utilice como anteriormente vimos.
Después nos encontramos con ORG ROMStart y debajo el código de
inicialización, que hace apuntar el SP al final de la RAM, en el caso del JK1 es
innecesario ya que arranca apuntando al final de la RAM pero en otros MCU de hc08
como el GP32 o el AP32 no arranca apuntando al final de la RAM ya que el final no
esta en la $00FF, por lo tanto ese código dependiendo el microcontrolador a utilizar es
que decidiremos dejarlo o no, como no molesta y son tan solo 2 instrucciones, lo
dejaremos.
Ya tenemos luego creado un pequeño bucle que no hace nada más que alimentar al COP
(watch dog) pero por lo general al principio desactivamos al COP, es innecesario, y
luego tenemos algunas posiciones del vector de interrupciones definidas. En definitiva
nos quedará algo así:

;*******************************************************************
;*Programa para……
;*Creado por :……
;*Fecha:……
;* Version: ……
;*******************************************************************

INCLUDE 'derivative.inc'
XDEF _Startup
ABSENTRY _Startup
;Seccion de mis variables

ORG RAMStart ; Insert your data definition here

;Seccion de codigo
ORG ROMStart

_Startup: MOV #$01,config1


CLRA
CLRX
LDHX #RAMEnd+1 ; inicializa el Stack Pointer
TXS
;insertar mi código acá

spurious:
RTI

;**************************************************************
;* Vector de interrupciones Completar con todos los que se usen *
;**************************************************************
ORG $FFFA

DC.W spurious ;
DC.W spurious ; SWI
DC.W _Startup ; Reset

Vemos como debería quedarnos el código, donde insertaremos nuestro código


donde dice “insertar mi código acá” obviamente también pondremos las variables en
cuestión llenaremos el vector de interrupciones si es que utilizamos interrupciones etc
etc.

Simulando el código

Una vez que creamos nuestro código podemos hacer click en Make para que
compile el código, si todo está bien, no saldrá ningún cartel, en cambio si hay algún
error el compilador nos informará donde está el error y qué tipo de error es. Una vez
compilado hacemos click en Debug para pasar a simular el código.
Una vez que termina de abrir el simulador vamos a poder ver 7 ventanas. El
simulador es el mismo tanto para C como para assembler por lo tanto hay algunas
ventanas que no tiene mucho sentido en assembler, como es la ventana assembly,
tendremos otra ventana donde veremos nuestros registros, otra donde podemos ver toda
la memoria, una ventana para agregar variables que tengamos nosotros declaradas para
poder verlas de forma más rápida sin tener que buscar en la memoria, además que
podemos verlas en otros formatos como ser binario, hexadecimal, ASCII etc.
Obviamente tenemos la ventana donde esta nuestro código llamada source, y una
ventana más donde podremos ingresar algunos comandos a fin de simular los módulos
externos.
En la barra superior veremos una serie de botones dedicamos a poder ir
simulando los pasos que da el MCU e ir viendo que sucede tenemos entonces el botón
de Start que ejecuta muy rápido las instrucciones, un single step, que es un paso único,
un step over para ejecutar toda una rutina sin entrar en el detalle de que pasa dentro un
step out para cuando estemos dentro de la rutina y queramos que ejecute todo hasta salir
y un paso en assembly, las de step over y step out son utilizadas en C, nosotros nos
manejaremos con el Start y más que nada con sin single step, también tenemos un botón
para ejecutar un reset y que todo vuelva a comenzar.
Si hacemos click con el botón derecho del mouse sobre cualquier línea de código
se desplegaran un monton de opciones, dentro de las mas importantes tenemos Set
Breakpoint, Run To Cursor, Set Program Counter.
Set BreakPoint: Un breakpoint es un punto de quiebre, es decir cuando nosotros
ejecutaremos start, el simulador ejecuta muy rápido todas las instrucciones que va
encontrando, no es en tiempo real pero si mucho más rápido que lo que podemos llegar
nosotros a analizar, cuando el simulador se encuentra ejecutando una serie de
instrucciones en modo Start, para al encontrase con una línea que posee un breakpoint,
por eso al ubicar el breakpoint podemos ejecutar el programa de manera muy veloz
hasta que pase por alguna línea de código en donde el simulador va a detenerse.
Run To Cursor: Es muy parecido a un breakpoint lo que hace es ejecutar un start
hasta que el programa pase por esa línea de código.
Set Program Counter: El caso de no querer que el programa ejecute la secuencia
normal que tiene y tan solo queremos ver qué pasa cuando el programa pasa por algún
lado, podemos hacer que el PC, vaya directamente a una línea de código y siga
ejecutando desde allí.

Programando el MCU

Una vez que hemos simulado nuestro programa y correjido todos los problemas
que pudiera llegar a tener y queremos probarlo, vamos a donde dice Full Chip
Simulation y cuando desplegamos hacia abajo ponemos Mon08Interface y hacemos
click el debub, ahora nos abre una nueva ventada de Conection Manager. Hacemos click
en Add a Conection y seleccionamos la placa programadora que tengamos. Una vez
hecho esto hacemos click en Ok seleccionamos el puerto donde este conectada en caso
de ser una placa por puerto serie y la velocidad de conexión. Y hacemos click en
Contact Target with These Settings….Automáticamente el programa pedirá grabar el
microcontrolador y podremos hacer la emulación o apagar el simulador apagar el
programador sacar nuestro MCU y probarlo directamente en la placa con nuestro
circuito.
Explorando la familia JK/JL

Para poder comenzar a bajar todo lo que estamos aprendiendo a un


microcontrolador, nos identificaremos y haremos hincapié sobre los microcontroladores
de la familia JK/JL, estos micros, son micros que manejan formatos de 20 y 20 pines,
existen en formato DIP (dual in line) por lo que los hace muy idóneos para primeros
aprendizajes, cuentan con pocos módulos pero que cumplen necesidades básicas y
sirven para muchos proyectos, obviamente que dentro de toda la familia con núcleo
HC08 hay microcontroladores mucho mas poderosos, o con mejores prestaciones pero
para una primera aproximación es un muy buen MCU.
Dentro de esta familia denominada JK/JL tenemos los siguientes miembros:
 MC68HC908JL16
 MC68HC908JL8
 MC68HC908JL3
 MC68HC908JK8
 MC68HC908JK3
 MC68HC908JK1

Debido a que todos los microcontroladores con el mismo núcleo comienzan de con
el mismo nombre de ahora en más solamente hablaremos con las ultimas siglas que nos
indican la familia a la que pertenecen y por ultimo la cantidad aproximada de memoria
Flash. Los micros JL3, JK3, JK1. Responden al mismo manual, es decir que comparten
todas sus funcionalidades, la diferencia entre ellos es que
 JL3: Cuenta con 4KB de memoria Flash, y viene en encapsulado con 28
pines
 JK3: Cuenta con 4KB de memoria Flash, y viene en encapsulado con 20
pines
 JK1: Cuenta con 1,5KB de memoria Flash, y viene en encapsulado con 20
pines

Registros CONFIG1 y CONFIG2

Anteriormente fuimos viendo que debemos indicarle al microcontrolador


diversos aspectos a la hora de comenzar nuestro programa, uno de ellos es si vamos a
trabajar o no con el COP, también con el modulo LVI y de ser así con que tensión de
trabajo será para la cual el MCU responda ante la llamada tensión nominal.
Para marcar estos factores tendremos dos registros denominados CONFIG1 y
CONFIG2, que establecerán las pautas de trabajo a lo largo de la ejecución de nuestro
programa sin importar lo que este haga en diversas partes, es decir que estos
lineamientos se establecerán al comienzo de nuestro programa y no podrán ser alterados
nunca más hasta luego de un reset.
Una vez que decidimos que trabajáramos o no con LVI o con el COP como
también las tensiones de trabajo, no podremos volver atrás en el flujo de nuestro
programa. También vale aclarar que el contenido y factores a establecer en estos
registros puede variar de un MCU, vale decir que no es igual para todos, describiremos
los registros CONFIG1 y CONFIG2 para la familia JL/JK, más adelante mostraremos
estos registro para otros micros.

CONFIG1

Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0

Lectura: COPRS R R LVID R SSREC STOP COPD


Escritura:
Reset: 0 0 0 0 0 0 0 0

R Reservado

Vemos que tenemos una serie de bits tanto en el CONFIG1 como en el CONFIG2 que
son reservados y escribir ahí no tendrá ningún efecto sea que pongamos un uno o un
cero.

COPD (Bit0):
Este bit es el encargado de habilitar o no el modulo de COP es decir
escribiremos:
“1”: Para inhabilitar el modulo COP
“0”: Para habilitar el modulo COP
Nótese que por defecto luego de un reset este bit amanece en “0”, por lo tanto el modulo
COP luego de un reset se encuentra habilitado.

STOP (Bit1):
Este bit lo que hará es habilitar la instrucción de bajo consumo “STOP”, si no la
habilitaremos, cuando esta sea ejecutada será tratada como un ilegal opcode, y por lo
tanto producirá un reset en el MCU.
“1”: Habilita la instrucción STOP
“0”: Deshabilita la instrucción STOP y esta es tratada como un código ilegal en
caso de ser encontrada durante el flujo del programa y consecuentemente se procederá a
resetear el MCU.

SSREC (Bit2):
Este bit setea la velocidad de recuperación cuando el microcontrolador sale de
ejecutar la instrucción de bajo consumo STOP.
“1”: El tiempo de recuperación se setea en (213-24) x 2OSCOUT
“0”: El tiempo de recuperación se setea en (218-24) x 2OSCOUT

LVID (Bit4):

Este bit es el encargado de habilitar el modulo de LVI:


“1”: Deshabilita el modulo LVI
“0”: Habilita el modulo LVI

Notar que el modulo de LVI al igual que el COP luego de un reset comienzan
funcionando.

COPRS (Bit7):
Este bit setea la velocidad del contador del COP, para le cual luego de
producirse el overflow procederá al reset del MCU
“1”: El tiempo de reset se setea en (213-24) x 2OSCOUT
“0”: El tiempo de reset se setea en (218-24) x 2OSCOUT

CONFIG2

Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0

Lectura: IRQPUD R R LVIT1 LVIT0 R R R


Escritura:
Sin Sin
Reset 0 0 0 afectar afectar 0 0 0

Por: 0 0 0 0 0 0 0 0
  Reservado

LVIT0 – LVIT1 (BIT3 y BIT4):


Estos dos bits, determinarán la alimentación en la cual estemos trabajando el
MCU, por lo tanto la tensión nominal de reset del modulo LVI.

LVIT0 LVIT1 Valor nominal/ Trip Voltage Comentario

0 0 Vlvr3 (2,4V) Para alimentación = 3V

0 1 Vlvr3 (2,4V) Para alimentación = 3V

1 0 Vlvr5 (4,0V) Para alimentación = 5V

1 1 Reservado

IRQPUD (Bit7):
Este bit habilita una resistencia de pull up interna en el pin de IRQ. Si bien no
mencionamos todavía el modulo IRQ, este modulo si es que se habilita puede generar
una interrupción al pasar de estado alto a un estado bajo. Por lo tanto no es deseable que
se encuentre al aire, para eso se puede colocar una resistencia de pull up interno y
olvidarse de colocar un estado fijo en el bit mediante hardware.
“1”: Desconecta la resistencia de Pull up en el pin de IRQ.
“0”: Conecta la resistencia de Pull up en el pin de IRQ.

Vale decir que el fabricante nos brinda una gama importante de herramientas
para poder evitar que el microcontrolador efectué cualquier operación ante un programa
que pueda tener algunos errores, como bien dijimos antes, está en el programador y la
aplicación el decidir si será o no necesario gastarse un rato más en la configuración de
estos módulos.
En estos módulos se ve la versatibilidades y ventajas de tener módulos
incorporados, ya que hay integrados que sirven de LVI que se conectan al pin de reset
del MCU que aproximadamente cuestan 0,30 U$S, si bien para un determinado
proyecto no es demasiado el costo, si debemos pasar a la producción en serie de un
determinado producto sobre 1000 piezas fabricadas el costo se reduciría
instantáneamente en 300 U$S que comienza a ser un numero importante, lo mismo
sucederá con los diversos módulos como conversores y módulos de comunicación serie
que también hay integrados. Además de reducir el costo de hardware, la incorporación
de módulos reduce notablemente el tamaño del hardware y el tiempo de diseño del
mismo ya que al haber menos componentes se hace más sencillo su desarrollo. Por eso
es muy importante a la hora de elegir un microcontrolador trata de buscar aquel que
tenga la mayor cantidad de módulos que va a necesitar el proyecto. Para eso más
adelante veremos una guía que nos brinda Freescale denominada Selector Guide, que
no es más que una tabla donde se pueden visualizar rápidamente todos los
microcontroladores de las diversas familias, la memoria que contienen, pines de
entrada/salida, conversores A/D, módulos de timer, etc., etc., etc.

I/0 Ports

Al comienzo de este documento mencionamos que no existiría microcontrolador


que no posea alguna interacción con el mundo exterior. Para ello los MCU de Freescale
cuentan con una amplia gama de chips en toda su familia que van desde los más
pequeños en encapsulados de 8 patas con máximo de 6 pines de entrada y salidas
digitales, hasta microcontroladores con 50 pines de entrada y salida digitales. Todos
estos bits/pines están agrupados de a 8 debido a la capacidad de bus de datos y se los
llaman puertos, de acuerdo a la cantidad de pines de entrada y salida que tenga el MCU
tendremos entonces el Puerto A, Puerto B, C, etc., etc., etc. La forma de trabajo es
idéntica a trabajar con una memoria, es decir, los puertos están mapeados en memoria
por ejemplo el Port A se encuentra en la posición $0000, y cada vez que escribamos un
dato en esta posición estaremos colocando el dato en los pines físicos del puerto, y cada
vez que leamos estas posiciones de memoria también estaremos leyendo el estado de los
puertos.
A los pines de entradas y salidas de la familia HC08 hay que indicarles la
dirección mediante otro registro que siempre se encuentran después de todos los ports.
Tendremos en le posición $0004 el registro que determinará la dirección del
puerto A (caso JL/JK, GP), dicho registro se denomina DDRA (Data direcction Register
/ Registro de dirección de datos). Este registro establece una correspondencia bit a bit
con el bit del puerto, es decir que cuando escribamos un 1 en el bit 0 del DDRA, se
establecerá como salida el bit 0 del puerto A, si escribimos un 0 en el bit 1 DDRA
estaremos estableciendo como entrada el bit 1 del PortA y axial sucesivamente. Pasando
en limpio diremos que escribiendo un 1 en el DDRX estableceremos como salida el
correspondiente bit del puerto X y con un 0 estableceremos a dicho pin como entrada.
Vale aclarar que por defecto luego de un reset todos los pines de puerto arrancan
como entrada, dado que si arrancasen como salida podrían ocasionar dependiendo del
hardware asociado daños o condiciones no esperadas, pensemos lo drástico que seria si
la bomba atómica estaría conectada a nuestro MCU y arrancase como salida
activándola, no seria nada agradable.
Como dijimos anteriormente los microcontroladores de Freescale se caracterizan
por contener diversas cantidad de módulos como ser comunicaciones serie sincrónicas,
asincrónicas, conversores analógicos/digitales etc. estos módulos interactuaran con el
mundo exterior por los pines del puerto, es decir que compartirán sus funciones con los
bits de entrada y salidas digitales. Cuando se habilita un modulo como puede ser el
ADC (Analog to digital converter), el bit de puerto asociado a tal fin para ingresar al
MCU el valor analógico a ser convertido, deja de funcionar como entrada o salida
digital para convertirse en entrada analógica.
Las funciones de los diversos módulos y los pines que compartan con las
entradas y salidas digitales puede variar de un microcontrolador a otro por eso es que se
recomienda leer el manual antes de proceder.

Mencionaremos algunas de las funciones agregadas/compartidas en diversos


PORTS en las familias JL/JK, vale aclarar que esto puede cambiar de un MCU a otro,
por eso es conveniente chequear siempre el manual.

PORTA: además de la capacidad de manejar bits de entrada/salida digitales


como mencionamos anteriormente, el PORTA generalmente tiene la capacidad de
trabajar con corrientes del orden de los 10ma para encender leds, no hay que configurar
ningún bit en ningún registro para poder manejar estas corrientes lo que si hay que tener
en cuenta según el diagrama interno compuesto por transistores MOS lo que nos
establece el flujo de la corriente, que debe ser hacia el MCU (sumidero) y no fuente de
corriente por eso el correcto conexionado de un led seria el siguiente:

Otra de las versatilidades que del PORTA es la de poder conectar resistencias de


pull up internamente, esto se hace sencillo colocando un 1 en el bit correspondiente en
el registro PTAPUE.
PTAPUE

Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0

Lectura: PTA6EN PTAPUE6 PTAPUE5 PTAPUE4 PTAPUE3 PTAPUE2 PTAPUE1 PTAPUE0


Escritura:
Reset 0 0 0 0 0 0 0 0

Entonces escribiendo por ejemplo un “1” en el bit 7 del PTAPUE conectaremos


internamente una resistencia de pull up (30K) en el bit 7 del PORTA. Hay que tener
en cuenta que esto depende de cada MCU por ejemplo en la familia JL/JK solo hay 7
bits para el PORTA por lo tanto en el bit 7 tiene otro uso.
El bit 6 del PORTA se comparte con el pin del oscilador, si queremos trabajar
con un oscilador externo, solo necesitaremos un pin de conexión y podríamos usar el
pin sobrante que usaríamos si conectáramos un cristal, para uso general (solo entrada),
la habilitación para usar este pin como uso general u oscilador la hacemos en el registro
PTAPUE, colocando en 0 funcionara como pin de salida del oscilador(veremos mas
adelantes circuitos de oscilador), y con un 1 en este pin lo configuraremos como pin de
uso general (solamente como entrada)
Otra de las funciones que generalmente viene anexada al PORTA es el modulo
de Keyboard (teclado), veremos más adelante como se utiliza, pero lo hace ideal junto
con las resistencias de pull up para el trabajo de teclados matriciales.
Volvemos a insistir con chequear de un MCU a otro las posibilidades de pull up
como funciones compartidas, en el caso de la familia AP, el modulo de keyboard viene
implementado en el PORTD.
El PORTB y PORTD generalmente vienen anexados con el conversor analógico
digital y otros módulos que puedan estar presente en el MCU, como ser TIMERS,
Puertos series etc. En el caso de la familia AP el PORTA es el encargado de la
conversión analógica digital.

Familia JK/JL

    Puede
Funciones
Resistencias manejar
JL3 JK3/JK1 Adicionales
de pull up corrientes de
Compartidas
10 mA

PTA 0 SI NO SI SI KEYB
1 SI NO SI SI KEYB

2 SI NO SI SI KEYB

3 SI NO SI SI KEYB

4 SI NO SI SI KEYB

5 SI NO SI SI KEYB

Compartido Compartido
6 con OSC con OSC SI SI KEYB

7 NO NO -   -

PTB 0 SI SI     ADC

1 SI SI     ADC

2 SI SI     ADC

3 SI SI     ADC

4 SI SI     ADC

5 SI SI     ADC

6 SI SI     ADC

7 SI SI     ADC

PTD 0 SI       ADC

1 SI       ADC

2 SI SI     ADC

3 SI SI     ADC

4 SI SI     TCH0

5 SI SI     TCH1

6 SI SI SI SI Salida Open Drain

  7 SI SI SI SI Salida Open Drain


Existe un registro muy particular en la familia JL/JK llamado PDCR, dicho
registro es el encargado de configurar si queremos que los bits 6 y 7 del PORTD
trabajen con resistencias de 5k conectadas a pull up y si queremos que trabajen en modo
open drain o push pull, dándonos la posibilidad de trabajar con corrientes de 25ma en
open drain.

PDCR

Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0

Lectura: 0 0 0 0 SLOWD7 SLOWD6 PTDU7 PTDU6


Escritura:
Reset 0 0 0 0 0 0 0 0

SLOWDx: SLOW EDGE ENABLE:


Estos dos bits son quienes establecen el modo de funcionamiento de los bits 6 y
7 del PORTD para trabajar en modo Push pull o open drain, como dijimos
anteriormente un el modo open drain puede manejar corrientes de sustrato de hasta
25ma.

‘1’= Show Edge habilitado: Trabajo en modo open drain


‘0’= Show Edge deshabilitado: Trabajo en modo push pull

PTDPUx: Pull up enable


Estos dos bits configuran si queremos o no resistencia de pull up en los bits 6 y 7
del PORTD.
‘1’=Resistencia de pull up de 5k conectada
‘0’=Resistencia de pull up de 5k desconectada

Ejemplos de programas
Recordando para el JK/JL3 los puertos que tenemos disponibles son:

 PORTA (solo 7 bits, del bit 0 al bit 6)


 PORTB (completo)
 PORTD (completo)

Crearemos un programa ahora por el cual solamente alternemos entre un 1 y un 0 un


bit del puerto, en este caso lo haremos para el bit 0 del PORTD, pero como utilizaremos
los EQUATES podremos fácil y rápidamente cambiarlo. Mostraremos varias formas de
trabajo ya que un programa puede realizarse de diversas maneras. Si bien por ahora no
sabemos como hacer una demora de tiempo de por ejemplo 1 segundo invocaremos a
esta función que será escrita más adelante.

Estableceremos algunas pautas que debemos realizar a la hora de hacer un


programa:

 Establecer los valores de los registros CONFIG1 y CONFIG2, de coincidir


con el valor de reset no será necesario modificarlos.
 Indicar la dirección de los diferentes bis de los distintos Puertos, los bis que
no usemos podremos establecerlos como salida y dejarlos al aire, o
establecerlos como entrada teniendo en cuenta que deberá entonces
colocársele algún estado estable por hardware, sea un 1 o 0 lógico, ya que el
ruido entrante puede afectar al funcionamiento del microcontrolador.

Hecha estas aclaraciones comenzaremos con nuestro planteo para la resolución


del problema.
Primero decidiremos si trabajaremos o no con el modulo COP y LVI, dado que
en nuestro programa no es muy critico las situaciones a contemplar por estos dos
módulos procederemos a deshabilitarlos mediante los registros CONFIG 1 y 2. Luego
estableceremos la dirección de los datos mediante los registros DDRA, DDRB y
DDRD, colocaremos en un estado inicial “0” el bit 0 del PORTD, efectuaremos una
demora (invocar a la rutina DELAY), luego pondremos a 1 el bit 0 del PORTD, de
nuevo efectuaremos la demora, y volveremos a donde pusimos en 0 el bit 0 del PORTD,
quedándonos en ese lazo infinitamente.
ROMStart EQU $EC00
RESETVECTOR EQU $FFFE
CONFIG1 EQU $001F
CONFIG2 EQU $001E
PORTA EQU $0000
DDRA EQU $0004
PORTB EQU $0001
DDRB EQU $0005
PORTD EQU $0003
DDRD EQU $0007

ORG ROMStart

Comienzo: MOV #$01,CONFIG1


MOV #$00,CONFIG2
MOV #$FF,DDRA
MOV #$FF,DDRB
MOV #$FF,DDRD
CLR PORTA
CLR PORTB
LOOP: CLR PORTD
JSR DELAY
MOV #$01,PORTD
JSR DELAY
BRA LOOP
DELAY:
RTS

ORG RESETVECTOR

DC.W COMIENZO
Debido a que ya existe un archivo con casi todos los EQU que colocamos arriba
también podríamos haber puesto:

ROMStart EQU $EC00


RESETVECTOR EQU $FFFE

ORG ROMStart

Comienzo: MOV #$01,CONFIG1


MOV #$00,CONFIG2
MOV #$FF,DDRA
MOV #$FF,DDRB
MOV #$FF,DDRD
CLR PORTA
CLR PORTB
LOOP: CLR PORTD
JSR DELAY
MOV #$01,PORTD
JSR DELAY
BRA LOOP
DELAY:
RTS

ORG RESETVECTOR

DC.W COMIENZO

Si bien como fue planteado el programa funciona correctamente, por lo general


los programas que creemos utilizaremos más de un bit del puerto ya se como entradas o
como salidas, si nos fijamos bien en el programa creado anteriormente estamos no solo
poniendo a 0 el bit0 del PORTD y luego a 1 sino que estamos cambiando todos los bits
del PORTD, por lo tanto si tuviéramos conectado periféricos u otros indicadores en
dichos bits y este programa seria una porción de código perteneciente a otro programa
más largo, estaríamos modificando valores que no deberíamos cambiar. Mostraremos a
continuación un código que solo modifica el bit 0 del PORTD y no lo demás bits (como
es con fines ilustrativos no colocaremos las primeras sentencias de inicialización como
axial tampoco los EQU ni vectores de interrupciones, pero cabe aclarar que debe
colocarse todo a la hora de crear el código final a compilar para poner en el MCU). La
forma de trabajo es la de saber que una función lógica AND de cualquier valor con 0,
deja en 0 el bit correspondiente y con 1 deja pasar el valor que tenia el bit (utilizaremos
esto para forzar a 0 un bit). Para poder fijar un uno utilizaremos la función lógica OR,
ya que OR de un dato con 0 da exactamente el DATO y OR con 1 fija el bit a uno, esto
se ilustra más claro gráficamente de la siguiente manera:

XXXXXXXX
AND 1 1 1 1 1 1 1 0
XXXXXXX 0

XXXXXXXX
OR 0 0 0 0 0 0 0 1
XXXXXXX 1

LOOP: LDA PORTD ;Cargo el valor del puerto D en A


AND #$F7 ;Al contenido de A le hago la AND
;con el valor $F7 así solo pongo en 0
;el bit menos significativo
STA PORTD ;Pongo el valor en el puerto D,
;alterando solo el bit 0
JSR DELAY ;Rutina de demora
LDA PORTD ;Hago el mismo procedimiento pero
;utilizando la función lógica OR, a
ORA #$01 ;fin de ahora colocar un 1 en el bit 0

STA PORTD
JSR DELAY
BRA LOOP ;creo mi bucle infinito

Ahora que hemos realizado una versión que solo modifica 1 bit del puerto,
mejorémosla, mediante otra función lógica que lo que hace es ser un inversor
controlador, esta función es la XOR, cuya tabla de verdad mostraremos a continuación:
A B Y

0 0 0

0 1 1

1 0 1

1 1 0

Y mostrando su tabla de verdad reducida podemos apreciar mejor su condición de


inversor controlado.

A Y

0 B

Dado que nosotros lo que queremos es continuamente negar el valor del bit 0,
podríamos hacerle a este bit una XOR con el valor 1 sin importarnos por el valor en si,
sino saber que va a ser la negación del actual.

LOOP: LDA PORTD ;Cargo el valor del puerto D en A


EOR #$01 ;Al contenido de A le hago la XOR
;con el valor $01 así solo invierto
;el bit menos significativo
STA PORTD ;Pongo el valor en el puerto D,
;alterando solo el bit 0
JSR DELAY ;Rutina de demora
BRA LOOP ;Vuelvo a negar le bit0 infinitamente
Ahora recordando que en los HC08 tenemos instrucciones que directamente nos
modifican un bit de una memoria y como los puertos se trabajan igual que memoria
podríamos utilizarlas para liberar el acumulador, es decir independizar al acumulador de
esta porción de código.

LOOP: BCLR 0,PORTD


JSR DELAY
BSET 0,PORTD
JSR DELAY
BRA LOOP

Lo bueno del trabajo con instrucciones que maneja bit es que podríamos haber
colocado:

LED EQU 0
PORTLED EQU PORTD
DDRLED EQU DDRD

BSET LED,DDRLED
LOOP: BCLR LED,PORTLED
JSR DELAY
BSET LED,PORTLED
JSR DELAY
BRA LOOP

Donde cambiando solamente el valor de los tres EQU primeros se puede


cambiar a cualquier pin del puerto, ya que lo configura como salida y luego lo utiliza.
Además de la ventaja de no utilizar el acumulador este código hace que se vea más claro
el trabajo ya que el tener que enmascarar con valores constantes no hace que se vea
claro a simple viste lo que queremos hacer. En caso de tener algún error seguramente se
descubra más rápido en códigos de este tipo que en los códigos donde se utilizan
mascaras.

Hemos visto con un simple ejemplo de tan solo modificar un bit del puerto como
pueden plantearse diversas formas de resolución y para el caso particular pedido en el
ejemplo TODAS eran validas, por lo cual arribamos que si bien hay formas optimas de
programación en cuanto a emplear un algoritmo varias pueden ser las que deriven en el
mismo resultado.

Crearemos ahora un programa ahora en el cual se reciban por el los 4 bits menos
significativos (bits 0-1-2-3) del PORTB una palabra que llamaremos genéricamente
DATO1, también en los 4 bits menos significativos del PORTD otra palabra que
llamaremos DATO2, y debemos realizar la AND-OR o XOR lógica entre estos dos
datos, dependiendo del los bits 4 y 5 del PORTA. Sacando el resultado también por los
bits menos significativos del PORTA.

BIT4 BIT5 FUNCION LOGICA

0 0 NO MODIFICA LA
SALIDA

0 1 AND

1 0 OR

1 1 XOR

Desarrollemos ahora un diagrama de flujo genérico sobre lo que deberá hacer


nuestro programa. Luego llevaremos esto a un diagrama de flujo más especifico para
luego recién comenzar con nuestro código.
Ahora una vez hecho un diagrama de flujo debemos especificar esas partes, y
establecer como es que la vamos a llevar a cabo. Primero debemos establecer todos los
puntos que configuraremos en los registros CONFIG 1 y 2. Como antes trabajaremos
sin LVI ni COP, por lo tanto solo moveremos $01 al CONFIG1. En cuanto a la
dirección e los bits conviene tomarse el trabajo de hacer una tablita con las funciones y
así asignar a cada bit la dirección correspondiente de la siguiente forma (recordar que un
1 en el DDR establece el bit como salida y un 0 como entrada):

PORTA:

BIT7 BIT6 BIT5 BIT4 BIT3 BIT2 BIT1 BIT0

Control Control Salida Salida Salida


de la de la Salida de de de
Sin función función Sin de datos datos datos datos
FUNCION asignar lógica lógica asignar Bit3 Bit2 Bit1 Bit0

Dirección: Salida Entrada Entrada Salida Salida Salida Salida Salida

DDRA 1 0 0 1 1 1 1 1 Valor Hexadecimal: $9F

PORTB:

BIT7 BIT6 BIT5 BIT4 BIT3 BIT2 BIT1 BIT0

Entrada Entrada Entrada


Entrada
Sin Sin Sin Sin de de de
FUNCION de Dato1
asignar asignar asignar asignar Dato1 Dato1 Dato1
Bit3
Bit2 Bit1 Bit0

Dirección: Salida Salida Salida Salida Entrada Entrada Entrada Entrada

DDRB 1 1 1 1 0 0 0 0 Valor Hexadecimal: $F0

PORTD:

BIT7 BIT6 BIT5 BIT4 BIT3 BIT2 BIT1 BIT0

Entrada Entrada Entrada


Entrada
Sin Sin Sin Sin de de de
FUNCION de Dato2
asignar asignar asignar asignar Dato2 Dato2 Dato2
Bit3
Bit2 Bit1 Bit0

Dirección: Salida Salida Salida Salida Entrada Entrada Entrada Entrada

DDRD 1 1 1 1 0 0 0 0 Valor Hexadecimal: $F0

Una vez definidos los valores que debemos asignar los registros de dirección de
los puertos (DDR). Según nuestro diagrama de flujo debemos leer los bits 5 y 6 del
PORTA, Podemos hacerlo de la forma establecida en el diagrama de flujos preguntando
bit a bit, o podríamos leer todo el byte correspondiente al PORTA (llevarlo al
acumulador) efectuarle una AND lógica a fin de poner a 0 todos los demás bits menos
los bits 5 y 6, es decir hacerla la AND con el valor binaria %01100000 = $60, y
preguntar si el resultado es $20 (hacer la AND) o si es $40 (hacer la OR) o si es $60
(hacer la XOR), de no ser ninguno de esos valores solo resta que sea $00, y por lo tanto
no hacer nada. Procedamos ahora a hacer el programa y ver como vamos resolviendo
las diversas cuestiones:

DATODDRA EQU $9F


DATODDRB EQU $F0
DATO DDRD EQU $F0
MASCARA EQU $60
LOGICAAND EQU $20
LOGICAOR EQU $40
LOGICAXOR EQU $60
LSB EQU $0F
RESETVECTOR EQU $FFFE

ORG ROMStart

Comienzo: MOV #$01,CONFIG1


MOV #DATODDRA,DDRA
MOV #DATODDRB,DDRB
MOV #DATODDRD,DDRD
CLR PORTA
CLR PORTB
CLR PORTD
VUELTA: LDA PORTA
AND #MASCARA
CBEQA #LOGICAAND,ANDLOGICA
CBEQA #LOGICAOR,ORLOGICA
CBEQA #LOGICAXOR,XORLOGICA
BRA VUELTA

ANDLOGICA: LDA PORTB


AND #LSB
AND PORTD
STA PORTA
BRA VUELTA

ORLOGICA: LDA PORTB


AND #LSB
ORA PORTD
STA PORTA
BRA VUELTA

XORLOGICA: LDA PORTB


AND #LSB
EOR PORTD
STA PORTA
BRA VUELTA

ORG RESETVECTOR

DC.W COMIENZO

Si se quisiera se podría eliminar la variable temp, si recién comienza a


programar rogamos que saltee este ejemplo, y vuelva a él un poco más adelante.
Dijimos que el stack es un lugar en donde podemos guardar valores temporalmente que
no usaremos a lo largo de todo el programa. Explicamos para la ANDLOGICA, ya que
para los demás seria prácticamente igual cambiando la función a realizar solamente.

ANDLOGICA: LDA PORTB


AND #LSB
PSHA ;Guardo A en el stack
LDA PORTD
AND 1,SP ;Hago la función lógica con el valor
;del stack guardado recién
STA PORTA
PULA ;Retorno el stack a su lugar
BRA VUELTA

Recordar que siempre se deben hacer tantos PUL como PSH se hayan hecho, es
decir dejar siempre al stack pointer donde lo encontramos luego de usarlo.

Seguiremos avanzando ahora comenzando a trabajar con tablas, para eso lo que
haremos es un conversor de binario a código 7 segmentos. En el programa que
realizaremos a continuación leeremos los 4 bits menos significativos del PORTB y los
sacaremos convirtiéndolos código 7 segmentos por el PORTA (utilizaremos al
segmento A conectado al bit0, B al bit 1.. y axial hasta G en el bit 6). Si el valor
ingresado por el PORTB superar al valor 9 expresarlo colocando una línea en el display.
De electrónica básica sabemos que para poder prender un LED tendremos que
suministrarle una corriente de aproximadamente 10ma, debido a que el PORTA
mediante una configuración como mencionamos antes podemos extraerle esta corriente
es el porque lo utilizamos en este ejemplo). Recordar que la forma de conectarlos es la
siguiente:

Donde colocando un 0 en el Bit 0 del PORTA(para este caso) encenderemos el


LED y lo apagaremos con un 1.

En nuestro caso simbolizamos a los segmentos como leds y tendríamos:


5V

330
PTA0
330
A
PTA1
330
B
PTA2
330
C
PTA3
330
D
PTA4
330
E
PTA5
330
F
PTA6

Recordar que la forma en que se visualizan es la siguiente:

Valor a
Segmentos a encender
representar

  G F E D C B A

0 No Si Si Si Si Si Si

1 No No No No Si Si No

2 Si No Si Si No Si Si

3 Si No No Si Si Si Si

4 Si Si No No Si Si No

5 Si Si No Si Si No Si

6 Si Si Si Si Si No No
7 No No No No Si Si Si

8 Si Si Si Si Si Si Si

9 Si Si No Si Si Si Si

- Si No No No No No No

Recordando que un 0 encendemos el segmento, y haciendo el reemplazo tenemos

Valor a Valor
Segmentos a encender
representar en hexa

  G F E D C B A  

0 1 0 0 0 0 0 0 $01

1 1 1 1 1 0 0 1 $E9

2 0 1 0 0 1 0 0 $24

3 0 1 1 0 0 0 0 $30

4 0 0 1 1 0 0 1 $19

5 0 0 1 0 0 1 0 $12

6 0 0 0 0 0 1 1 $03

7 1 1 1 1 0 0 0 $78

8 0 0 0 0 0 0 0 $00

9 0 0 1 0 0 0 0 $10

- 0 1 1 1 1 1 1 $3F

Es decir que cuando queramos escribir un 0 tendremos que sacar por el PORTA
el valor $01, para un 1 tendremos que sacar el valor $E9 y axial sucesivamente.
Como los datos son totalmente aleatorios es decir que no siguen una
correspondencia uno con otros lo que haremos es crear una tabla en memoria ROMStart
y mediante algún algoritmo convertiremos los datos.
La forma de trabajo es la siguiente, nos posicionaremos en el comienzo de la
tabla y nos moveremos una cantidad de posiciones como el numero a convertir, es decir
si queremos convertir el numero “0”, sumaremos 0 a la posición de la tabla y
extraeremos el contenido de dicho byte que es el valor de conversión, si queremos
convertir el numero “1”, sumaremos a la posición de comienzo de la tabla una unidad lo
que nos ubicara en el segundo parámetro, y axial sucesivamente, como el valor de
comienzo de la tabla es una constante de 16 bits(por ser una dirección) podremos
utilizar el modo de direccionamiento indexado para trabajar con posiciones de memoria
y contenido de dichas posiciones con offset de 16 bits que será justamente el comienzo
de la tabla, sumaremos en X el valor a convertir.

Veamos el ejemplo y después lo seguiremos comentando:

El valor de dirección de los puertos viene dado por :

PORTA:

BIT7 BIT6 BIT5 BIT4 BIT3 BIT2 BIT1 BIT0

Sin
G F E D C B A
FUNCION asignar

Dirección: Salida Entrada Entrada Salida Salida Salida Salida Salida

DDRA 1 1 1 1 1 1 1 1 Valor Hexadecimal: $FF

PORTB:

BIT7 BIT6 BIT5 BIT4 BIT3 BIT2 BIT1 BIT0

Entrada Entrada Entrada Entrada


Sin Sin Sin Sin
FUNCION de Dato de Dato de Dato de Dato
asignar asignar asignar asignar
Bit3 Bit2 Bit1 Bit0

Dirección: Salida Salida Salida Salida Entrada Entrada Entrada Entrada

DDRB 1 1 1 1 0 0 0 0 Valor Hexadecimal: $F0

PORTD:

BIT7 BIT6 BIT5 BIT4 BIT3 BIT2 BIT1 BIT0

Sin Sin Sin Sin Sin Sin Sin Sin


FUNCION
asignar asignar asignar asignar asignar asignar asignar asignar
Dirección: Salida Salida Salida Salida Entrada Entrada Entrada Entrada

DDRD 1 1 1 1 1 1 1 1 Valor Hexadecimal: $FF

LIMITE EQU $0A


RESETVECTOR EQU $FFFE

ORG ROMStart

TABLA: DC.B $01,$E9,$24,$30,$19,$12,$03,$78,$00,$10,$3F

COMIENZO: MOV #$01,CONFIG1


MOV #$FF,DDRA
MOV #$F0,DDRB
MOV #$FF,DDRD
CLR PORTA
CLR PORTB
CLR PORTD
CLRH
LOOP: LDX PORTB
CMPX #LIMITE
BLS NORMAL
LDX #LIMITE
NORMAL: LDA TABLA,X
STA PORTA
BRA LOOP

ORG RESETVECTOR

DC.W COMIENZO
Empezamos lentamente a notar que lo que en verdad dificulta la tarea de
programar y lo que hace que se tarde más a la hora de resolver un problema es la etapa
de establecer los modos de trabajo, ya que el código puede ser muy reducido pero puede
llevar mucho tiempo llegar a él.
Si ahora le agregamos las funciones de Latch Enable, Lamp Test, y Black Input,
hemos creado en un microcontrolador un decodificador como el CD4511 para display
ánodo común. Recordemos un poco las funciones (en nuestro caso todas serán activas
altas):

Black Input: Apaga todos los segmentos del display sin importar el valor binario
entrado.

Lamp Test: Prende todos los segmentos del display sin importar el valor binario
entrado.

Latch Enable: Cuando esta entrada se encuentra activa no importa el valor de entrada
siempre se mantendrá el ultimo valor decodificado.

LIMITE EQU $0A


RESETVECTOR EQU $FFFE
LT EQU 0
LE EQU 1
BI EQU 2
CONTROLPORT EQU PORTD

ORG ROMStart

TABLA: DC.B $01,$E9,$24,$30,$19,$12,$03,$78,$00,$10,$3F

COMIENZO: MOV #$01,CONFIG1


MOV #$FF,DDRA
MOV #$F0,DDRB
MOV #$FF,DDRD
BCLR LE,CONTROLPORT
BCLR BI,CONTROLPORT
BCLR LT,CONTROLPORT
CLR PORTA
CLR PORTB
CLR PORTD
CLRH
LOOP: BRSET LT,CONTROLPORT,LAMPTEST
BRSET BI,CONTROLPORT,BLANCKINPUT
BRSET LT,CONTROLPORT,LOOP
LDX PORTB
CMPX #LIMITE
BLS NORMAL
LDX #LIMITE
NORMAL: LDA TABLA,X
STA PORTA
BRA LOOP

LAMPTEST: MOV #$FF,PORTA


BRA LOOP

BLANCKINPUT: CLR PORTA


BRA LOOP

ORG RESETVECTOR

DC.W COMIENZO

En fin vemos como agregando tan solo pocas líneas vamos cambiando nuestro
programa y modificándolo para que haga lo que nosotros queramos, nuevamente
recalcamos las bondades del los EQUATES que cambiando arriba la definición del bit
de puerto que queramos usar para LT, LE y BI basta para que el programa se modifique
totalmente.
A medida que vayamos avanzando con los módulos seguiremos mostrando
códigos en lo posible completos mostrando como programar el MCU para realizar
diversas opciones y las formas de llevarlas a cabo.

Modulo ADC (Conversor analógico a digital)

Debido a que vivimos en un mundo netamente analógico necesitamos en un


principio trasductores de magnitudes físicas a variables eléctricas, los trasductores más
simples lo que harán es darnos una relación de tensión o resistencia de acuerdo a la
variación física de algún parámetro como puede ser temperatura, humedad, etc. Como
nuestro microcontrolador maneja solamente variables digitales, habría que incorporar un
conversor de estas magnitudes que nos entregan los trasductores a nuestras variables
digitales, el hecho de incorporar conversores externos nos traería aparejado costos de
hardware que se repetirían de un producto a otro como un costo fijo, además de
complicar el hardware, alargar los tiempos de diseño, etc. Para ello Freescale nos
proporciona la ventaja de poder entrar directamente por algún pin del microcontrolador
una magnitud analógica (desde 0V a 5V) y poder internamente convertirla en un valor
digital. Los primeros microcontroladores de la familia HC08 poseían integrados
módulos conversores de 8 bits (caso familia JK/JL- que veremos ahora), con el tiempo
los nuevos módulos que se van incorporando tienen una posibilidad de convertir
magnitudes analógicas a magnitudes digitales con 10 a 12 bits de resolución mas
adelante cuando hablemos sobre otros microcontroladores de freescale de la familia
HC08 mostraremos los conversores de 10 bits.
La metodología de trabajo es muy simple, vale decir que cuando el modulo nos
devuelva de alguna forma un byte (para el caso de conversión de 8 bits) y tengamos los
todos los bits en “1” es decir el valor máximo = 255 = $FF, significará que la variable
ingresada por el pin del MCU valía su valor máximo analógico, es decir 5V. Por regla
de 3 simple sale cualquier valor posible para el modulo conversor, siendo la diferencia
entre valores mínima:

Si:
255 5V
1 X= 5V/255 aprox. = 19mv

El MCU entonces una vez programado en el caso de 8 bits depositará el valor


correspondiente a la conversión en un byte destinado a tal efecto denominado ADR
(generalmente ubicado en la posición $003D).
Antes de seguir mostrando la implementación y forma de uso de los conversores
analógicos-digitales en la familia HC08, comentaremos un poco la teoría que rige a
estos conversores.
Existen diversas maneras de generar un valor digital correspondiente a un valor
analógico, es decir muchas formas de implementar conversores analógicos. Dentro de
estos nombramos algunos:

 Conversor de comparador en paralelo


 Conversor por contadores
 Conversor por cuenta continúa
 Conversor con integrador
 Conversor Sigma Delta
 Conversor Flash
 Conversor rampa y doble rampa
 Conversor por aproximaciones sucesivas

Dentro de todos los conversores que mencionados anteriormente el último es el


que nos interesa y es como Freescale implementó sus conversores en los
microcontroladores, vale decir que este tipo de conversores son los mas usados en casi
todos los tipos de conversores, sirven para muchas de las aplicaciones con una
velocidad moderada y una resolución normal, es decir 8, 10, 12 bits de resolución. Si
deseamos conversores más veloces, estaríamos hablando de conversores Flash, en
cambio si deseamos conversores con elevada relación señal ruido, alta resolución pero
no muy rápidos elegiríamos conversores con tecnología Sigma-Delta.
Para poder entender la forma de trabajo de un conversor analógico-digital por
aproximaciones sucesivas vamos a hacer una analogía y llevarlo a la vida cotidiana.
Supongamos que tenemos un libro de 500 paginas y queremos acceder a la pagina 43,
una de las metodologías seria la de ir pagina a pagina comparando si el valor de la
pagina actual coincide con el valor de la pagina buscada, es decir 43. Pero si para poder
realizar este análisis tuviéramos la posibilidad de saber de no encontrarnos en la pagina
deseada si la buscada esta por encima o por debajo podríamos emplear el siguiente
algoritmo:
Abrimos el libro en la mitad (pagina 250).Comparamos si es que nuestra pagina a
buscar es la que acabamos de abrir, si esta por encima o si se encuentra por debajo. (en
nuestro caso por debajo). Entonces al estar por debajo ya descartamos las 250 paginas
superiores y a las 250 paginas restantes las dividimos en dos, en nuestro caso seria en la
pagina 125, volvemos a aplicar el mismo procedimiento, donde nuevamente en nuestro
ejemplo nos daría que estamos por debajo, yendo ahora a la pagina 62, con el mismo
procedimiento, nos quedamos con las 62 inferiores y nos vamos hacia la pagina 31,
donde ahora detectamos para nuestro ejemplo que la buscada se encuentra por encima
de la pagina 31 pero por debajo de la pagina 62 por lo tanto nos ubicamos en la mitad es
decir 46 [(31+62)/2], después de hacer este procedimiento algunas veces llegaremos a
la pagina que buscábamos, en muchas menos preguntas que las 43 preguntas si
hubiéramos ido pagina a pagina, por lo tanto este método es mas rápido que el plateado
inicialmente.
Ahora veremos un diagrama que ilustra electrónicamente la forma de generar un
conversor por aproximaciones sucesivas. Para ello necesitamos un comparador, un
registro de desplazamiento, y un conversor digital analógico.

///////////CAMBIAR ESQUEMA!!!!!!!!!!!
Vamos ahora a hacer un análisis del funcionamiento del esquema anterior. Ni
bien se da comienzo a un proceso de conversión se carga en el registro de
desplazamiento el valor 10000000, es decir 128 (mitad de 256), dicho valor es
convertido analógicamente y es ingresado al comparador que lo usa para contrarrestar
este valor contra el valor de la tensión de entrada. De ser mayor, la salida del
comparador será un uno, ingresando este valor al registro y quedando el valor 11000000
(192[mitad de 256 y 128]), en el caso de ser menor, el comparador dará en su salida un
‘0’, que ingresado en el registro de desplazamiento quedará el valor 01000000
(64[mitad de 128]). El proceso es repetitivo ingresando ‘0’ si la comparación es menor
y ‘1’ si es mayor, llegando así en 8 desplazamientos al valor deseado.

Registros asociados al ADC


El primer registro es bastante obvio, es el registro que nos devolverá el valor de
conversión analogica digital, que hemos realizado, veremos que un bit en otro registro
se pondrá a ‘1’ cuando se termine de hacer una conversión lo que significa que lo que
leamos de este registro será coherente con el valor analogico en el pin del
microcontrolador.
ADR Posición $003D

Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0

Lectura: ADC8 ADC7 ADC6 ADC5 ADC4 ADC3 ADC2 ADC1


Escritura:

Si bien al inspeccionar rápidamente los microcontroladores con este modulo


veremos que tienen varios pines que comparten su función de entrada/salida digital con
la función de conversor analógico digital, hay que aclarar que el modulo solo podrá
realizar una conversión de dicho pines a la vez, y siempre deposita el resultado en el
mismo byte, es decir que el modulo efectuará a la vez de multiplexor en donde en un
byte de control le indicaremos sobre cual de los pines deberá efectuar la conversión, una
vez efectuada podremos cambiar a otro pin, apagar el modulo o seguir convirtiendo
sobre el mismo pin de acuerdo a las necesidades cada uno de estos pines capaces de
convertir y ser seleccionados por el modulo se denominan canales del timer.
Las formas de trabajo del conversor pueden ser:
Única conversión: En este modo el conversor analógico digital, una vez configurado,
efectúa la conversión del pin asociado a la función, y se detiene.

Conversión Continua: En este modo el conversor, una vez efectuada una conversión se
reinicia y luego de un tiempo vuelve a realizar otra conversión de la variable presente en
el pin del puerto del MCU, sobre escribiendo el valor de la conversión anterior sin
importar si es que fue o no procesado el dato anterior.
Por medio de un byte podremos leer el estado del conversor como también
configurarlo. Este byte es el denominado ADSCR.

ADSCR

Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0

Lectura: COCO AIE ADCO ADCH4 ADCH3 ADCH2 ADCH1 ADCH0


Escritura: 0
Reset: 0 0 0 1 1 1 1 1

COCO (Bit7):
El bit de COCO (Conversión Complete /Conversión completa) se podrá en 1
cuando se el modulo haya terminado de realizar la conversión, es un indicativo de que el
valor contenido en el registro ADR, es un valor coherente con el valor analógico
presente en el pin configurado para tal efecto. Con tan solo leer el byte del ADR este bit
automáticamente se volverá a 0, no se lo puede escribir/forzarlo a valer 0.

AIE (Bit6):
Como a los diferentes módulos que componen al MCU podemos trabajarlos por
los métodos de Polling e interrupciones, si trabajamos en el primero estaremos por
software estaremos preguntado por el bit de COCO, en cambio si trabajamos por
interrupciones, al terminarse una conversión automáticamente si ira a ejecutar código
escrito para esta ocasión. Este bit lo que hace es habilitar el trabajo por interrupciones
del modulo
“1”: El modulo trabaja por interrupciones
“0”: El modulo trabaja por polling.

ADC0(bit5):
Como mencionamos anteriormente podemos trabajar al modulo en las formas de
conversión única y conversión continua, este bit es el que establece cual de estos dos
diferentes modos de trabajo utilizaremos.
“1”: El modulo se encuentra trabajando en el modo conversión continua
“0”: El modulo se encuentra trabajando en el modo única conversión

ADCH4-ADCH0 (Bits 4 a 0):


Estos bits se encargan de establecer cual de los diferentes canales del timer será
la fuente para el conversor.

La tabla varía de un MCU a otro, mostramos a continuación la tabla para un JL3.


Nótese que el valor 11111 en los bits de ADCH4 a ADCH0 apagan el modulo, y por
defecto este es el valor que contienen después de un reset, es decir que al comienzo de
nuestro programa el modulo amanecerá apagado.

Canal de
CH4 CH3 CH2 CH1 CH0 Selección del pin
ADC

0 0 0 0 0 ADC0 PTB0

0 0 0 0 1 ADC1 PTB1

0 0 0 1 0 ADC2 PTB2

0 0 0 1 1 ADC3 PTB3

0 0 1 0 0 ADC4 PTB4

0 0 1 0 1 ADC5 PTB5

0 0 1 1 0 ADC6 PTB6

0 0 1 1 1 ADC7 PTB7

0 1 0 0 0 ADC8 PTD3
0 1 0 0 1 ADC9 PTD2

0 1 0 1 0 ADC10 PTD1

0 1 0 1 1 ADC11 PTD0

0 1 1 0 0
. . . . . ------- Sin usar (Nota 1)
. . . . .
1 1 0 1 0
1 1 0 1 1 ------- Reservado

1 1 1 0 0 ------- Sin usar

1 1 1 0 1   VDDA (Nota 2)

1 1 1 1 0   VSSA (Nota 2)

1 1 1 1 1   ADC apagado

Nota1: Si alguno de estos canales se utiliza el valor devuelto por la conversión será
incierto
Nota2: Estos valores son usados para comprobaciones tanto en producción como para el
usuario, en el caso de la familia JL/JK estos pines están conectados internamente a
VDD y VSS respectivamente, en otras familias como GP o AP se encuentran
disponibles en el exterior para ser conectados a VSS y VDD.

El último registro que debemos conocer para poder utilizar el conversor es el


ADCLK, que es el encargado de establecer la velocidad de conversión, es decir el
tiempo que demore el modulo en convertir un valor analógico a digital y depositarlo en
el ADR.

ADCLK

Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0

Lectura: 0 0 0 0 0
ADIV2 ADIV1 ADIV0
Escritura:          

Reset 0 0 0 0 0 0 0 0

  Reservado
Según los valores que carguemos en estos bits tendremos:

Velocidad de clock
ADIV2 ADIV1 ADIV0
del ADC

Entrada de clock del


0 0 0
ADC / 1

Entrada de clock del


0 0 1
ADC / 2

Entrada de clock del


0 1 0
ADC / 4

Entrada de clock del


0 1 1
ADC / 8

Entrada de clock del


1 X X
ADC / 16

Según el manual, la velocidad del conversor debería setearse a fin de trabajar a


1Mhz. La entrada del clock es igual a la frecuencia interna de trabajo del MCU,
denominada: frecuencia de BUS.

La metodología de trabajo es primero la de configurar el byte asociado a la


velocidad de trabajo del modulo (ADCLK) y luego el ADSCR. Para posteriormente
utilizarlo.

Método por Polling conversión única, con el canal 0:

MOV #valordevelocidad,ADCLK ;Configuramos la


;velocidad para trabajar a 1 MHZ
MOV #%00000000,ADSCR ;Configuramos utilizar por
;polling, conversión única, y canal 0
BRCLR 7,ADSCR,* ;Esperamos a que COCO=1, es decir
;que realice la conversión
LDA ADR ;Leemos el ADR a fin que COCO se
;vuelva a 0, y de tener el valor
;convertido en el acumulador

Si quisiéramos hacer que nuestro MCU sea un conversor analógico digital


podríamos hacer los siguientes ejemplos donde ingresamos por el canal 0 (PTB0 para el
JL3) un valor analógico y sacamos por el PORTD el valor convertido a digital,
utilizaremos conversión continua y mostraremos los métodos de trabajo por polling e
interrupciones. Supondremos una velocidad de BUS de 8 Mhz

COCO EQU 7
RESETVECTOR EQU $FFFE

ORG ROMStart

COMIENZO: MOV #$01,CONFIG1


MOV #$FF,DDRA
MOV #$FE,DDRB
MOV #$FF,DDRD
CLR PORTD
CLR PORTA
CLR PORTB
MOV #$60,ADCLK
MOV #%00100000,ADSCR
LOOP: BRCLR COCO,ADSCR,*
LDA ADR
STA PORTD
BRA LOOP

ORG RESETVECTOR

DC.W COMIENZO
ROMStart EQU $EC00
COCO EQU 7
RESETVECTOR EQU $FFFE
ADCVECTOR EQU $FFDE

ORG ROMStart

COMIENZO: MOV #$01,CONFIG1


MOV #$FF,DDRA
MOV #$FE,DDRB
MOV #$FF,DDRD
CLR PORTD
CLR PORTA
CLR PORTB
CLR ADR2
MOV #$60,ADCLK
MOV #%01100000,ADSCR
CLI
BRA *

ADC_INT: PSHH
LDA ADR
STA PORTD
PULH

ORG ADCVECTOR
DW ADC_INT

ORG RESETVECTOR
DC.W COMIENZO

En los ejemplos anteriores mostramos como trabajar con el modulo de dos


maneras, para los casos mostrados no es critico utilizar un método u otro pero
dependiendo de la aplicación será si podremos estar mirando continuamente el bit de
COCO o no para saber si trabajaremos con interrupciones o por polling.
También es bueno recalcar que no eran necesarias las dos instrucciones de
PSHH y PULH en la rutina de interrupción ya que no se utiliza ni se modifica el registro
H en dicha rutina, pero fueron colocadas a fin que el usuario que recién incursiona en la
programación por interrupciones se acuerde de colocarla a fin de no provocar en otros
casos errores por no haber tenido en cuenta esta condición
Si bien en el ejemplo anterior el código de atención a la interrupción de adc es
muy corto, si tuviera que manipularse de laguna manera el dato leído del conversor es
conveniente copiarlo a alguna variable y manipularlo fuera de la interrupción dada la
alta velocidad de conversión el modulo, eso lo haríamos de la siguiente manera:

ROMStart EQU $EC00


COCO EQU 7
RESETVECTOR EQU $FFFE
ADCVECTOR EQU $FFDE

ORG RAMStart

ADR2 RMB 1

ORG ROMStart

COMIENZO: MOV #$01,CONFIG1


MOV #$FF,DDRA
MOV #$FE,DDRB
MOV #$FF,DDRD
CLR PORTD
CLR PORTA
CLR PORTB
CLR ADR2
MOV #$60,ADCLK
MOV #%01100000,ADSCR
CLI
LOOP: MOV ADR2,PORTD
BRA LOOP

ADC_INT: PSHH
LDA ADR
STA ADR2
PULH

ORG ADCVECTOR
DC.W ADC_INT
ORG RESETVECTOR
DC.W COMIENZO
IRQ (Interrupción externa)

Este tipo de interrupción es producida por cambios en el mundo exterior al


MCU, es decir que se producirá una interrupción y por lo tanto se irá a ejecutar código
escrito para tal efecto, si en un pin dedicado a tal efecto (PIN de IRQ) (de acuerdo al
modo programado):

 Se produce un flanco descendiente (paso de nivel alto a nivel bajo).


 Se produce un flanco descendiente o se mantiene en un nivel bajo el pin de IRQ.
Las dos causas externas al MCU son las que producirán un reset del tipo IRQ, para
ello tendremos un registro dedicado a configurar el modo de trabajo (INTSCR).

INTSCR

Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0

Lectura: 0 0 0 0 IRQF1   IMASK1 MODE1


Escritura:           ACK1
Reset 0 0 0 0 0 0 0 0

  Sin implementar

IRQF1: Este bit se pondrá en 1 cuando haya una interrupción de IRQ pendiente.
“1”= interrupción de IRQ pendiente
“0”= interrupción de IRQ no pendiente

ACK1: Escribiendo este bit con un “1” se borra el latch que indica una interrupción de
IRQ pendiente, siempre que se lea este bit se leerá como un “0”.

IMASK1: Este bit es la mascara de interrupción del MODULO IRQ.

“1”= Interrupciones de IRQ deshabilitadas


“0”= Interrupciones de IRQ deshabilitadas

Luego de un reset las interrupciones de IRQ amanecen habilitadas


MODE1: Mediante este bit estableceremos alguno de los dos modos mencionados al
comienzo para el pedido de interrupción de IRQ
“1”= Se produce un flanco descendiente (paso de nivel alto a nivel bajo).
“0”= Se produce un flanco descendiente o se mantiene en un nivel bajo el pin de
IRQ.

Recordar que en el registro CONFIG2 el bit7 establece la posibilidad de


conectar una resistencia de pull up interna en el pin de IRQ (por defecto luego de un
reset esta resistencia se encuentra conectada).

Hemos mencionado al pin de IRQ como aquel que lo único que hace es invocar
a una rutina de interrupción en el caso (obviamente con su previa configuración) el este
pin se encuentre en 0 o haya ocurrido un cambio de estado desde un nivel alto hacia un
nivel alto. También podemos en estos microcontroladores trabajar al pin de IRQ como
un pin de entrada más, ya que tendemos dos instrucciones que nos permitirán crear
bifurcaciones, saltos, en el caso que el pin de IRQ se encuentre en uno o cero, estas
instrucciones son: BIH y BIL que preguntaran por el estado alto o bajo del pin de irq
respectivamente.
Cuando avancemos y veamos el modulo de SCI, que permite la comunicación
serie asincrónica, veremos como podemos usando el bit de IRQ implementar una rutina
de comunicación serie por interrupción cuando trabajemos con módulos que no tengan
modulo de SCI.
Importante!!!: Una vez producida una interrupción debemos nosotros
manualmente poner a 0 el bit de IRQF1, para así poder dar paso a que otra interrupción
del modulo ocurra si es que sigue habilitado. De lo contrario, no bajar el bit de IRQF1
puede producir que el programa se quede eternamente en la interrupción, es decir, que
sale de la interrupción pero al encontrar este bit en 1 al salir vuelve a entrar a la misma
quedando en un bucle infinito donde no haría más nada.

Keyboard Interrupt

El modulo de interrupción por keyboard es muy similar al trabajo con el bit de


IRQ, con la salvedad de que ahora no trabajaremos con un solo bit sino con varios bits
(pudiendo configurar cual de ellos funcionaran bajo este modulo) que nos llevarán a
ejecutar una rutina (distinta de la de IRQ) escrita para tal efecto. Tan similar es el
trabajo con el modulo de IRQ que tendremos un byte de configuración que es
exactamente igual al de configuración del modulo anterior. La diferencia radicara en
otro byte en donde seleccionaremos poniendo un uno en el bit correspondiente que bits
del puerto, asociados a tal efecto, nos producirán la interrupción.
KBSCR

Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0

Lectura: 0 0 0 0 KEYF   IMASKK MODEK


Escritura:           ACKK
Reset 0 0 0 0 0 0 0 0

  Sin implementar

KEYF: Este bit se pondrá en 1 cuando haya una interrupción de KIB pendiente. Por
alguno de los bits habilitados
“1”= interrupción de IRQ pendiente
“0”= interrupción de IRQ no pendiente

ACKK: Escribiendo este bit con un “1” se borra el latch que indica una interrupción de
KEY pendiente, siempre que se lea este bit se leerá como un “0”.

IMASKK: Este bit es la mascara de interrupción del MODULO KEY.

“1”= Interrupciones de KEY deshabilitadas


“0”= Interrupciones de KEY deshabilitadas

Luego de un reset las interrupciones de KEY amanecen habilitadas

MODEK: Mediante este bit estableceremos alguno de los dos modos mencionados al
comienzo para el pedido de interrupción de KEY
“1”= Se produce un flanco descendiente (paso de nivel alto a nivel bajo).
“0”= Se produce un flanco descendiente o se mantiene en un nivel bajo el / los
pines de KEY.

Importante!!!: Una vez producida una interrupción debemos nosotros


manualmente poner a 0 el bit de KEYF, para así poder dar paso a que otra interrupción
del modulo ocurra si es que sigue habilitado. De lo contrario, no bajar el bit de IRQF1
puede producir que el programa se quede eternamente en la interrupción, es decir, que
sale de la interrupción pero al encontrar este bit en 1 al salir vuelve a entrar a la misma
quedando en un bucle infinito donde no haría más nada.
KBIER

Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0

Lectura: 0 KBIE6  KBIE5 KBIE4 KBIE3 KBIE2 KBIE1 KBIE0


Escritura:  
Reset 0 0 0 0 0 0 0 0

  Sin implementar

En el modulo de IRQ era un solo bit el que producía la interrupción, en este


modulo cualquier bit del puerto A (Familia JL/JK, verificar para cada micro) puede
producir una interrupción o no, para eso en este registro habilitamos cual bit del modulo
puede producir una interrupción, cabe volver a aclarar que esta interrupción es distinta
que la de IRQ, pero es la misma para cada bit del puerto, es decir que va a ir a la misma
función si interrumpe el bit 0 1,2,3,4,5,o 6, si fuera necesario seremos nosotros por
software que detectaremos que bit del puerto fue el que produjo la interrupción.

TIMER

La magnitud física del tiempo es algo que rige nuestra vida cotidiana por eso el
timer junto con los ports son los módulos más usados. Generalmente las aplicaciones
con microcontroladores en algún momento llevan alguna demora, alguna cuenta de
tiempo, etc.
Su funcionamiento básico no es complejo, vale decir que la forma básica en
técnicas digitales de generar demoras es utilizando contadores, como contar demora un
tiempo de acuerdo a que contemos más o menos será la demora que nosotros
generemos. Obviamente para que sea algo estable y que los valores de cuentas no varíen
aleatoriamente, tendremos una base de tiempo que nos brindará el tiempo mínimo, el
tiempo entre cuenta y cuenta, es decir entre un valor y su próximo valor. Para darle un
poco más de flexibilidad al timer la base de tiempo que ingresa al mismo, en este caso la
frecuencia de clock, se la puede dividir por un factor, a fin de generar demoras mas
largas, ya que el máximo o mínimo de la demora que queramos crear esta limitado
justamente por el máximo/mínimo valor de cuenta como así también por la frecuencia
base a la cual podemos contar.
Una vez hecha esa breve e importante introducción veremos más en profundidad
como es que funciona el timer en las familias HC08, recordamos que cada miembro de
la familia puede tener uno o más timers, como siempre decimos es recomendable que se
vea el manual con el microcontrolador que vamos a trabajar antes de empezar cualquier
tarea. Como mencionamos anteriormente tenemos una frecuencia base, que es la
frecuencia de bus (por el momento y para el JL3 la frecuencia de bus = Fxtal/4), que es
dividida por un factor configurable por tres bits. Es decir que tenemos una tabla donde
de acuerdo al valor de esos bits podemos dividir la frecuencia base por 1,2,4,8,16,32 y
64. Una vez realizada la división obtenemos a la salida de dicho bloque la frecuencia de
cuenta del timer. Nuestro timer es de 16 bits, por lo tanto tenemos un rango de 0 a
65535, es decir 65536 valores diferentes, al arrancar de 0 el timer (bajo las variables de
cuenta TCNTH y TCNTL) se incrementa en una unidad con el tiempo 1/Fcuenta. Con
cada incremento el contador se comparará con otras dos variables TMODH y TMODL,
como es supuesto TMODH se compara con TCNTH y TMODL con TCNTL, y en caso
de ser iguales dichos valores se produce un overflow (un desborde) es decir que un bit
(TOF: Timer overflorw flan) se pone en 1 indicándonos esta condición, en caso de estar
habilitadas puede este modulo bajo esta condición generarnos una interrupción por
overflow del timer. Automáticamente luego del overflow el timer vuelve a 0 y todo el
proceso vuelve a empezar, seremos nosotros los que por interrupciones o por polling
tendremos que poner el TOF a 0 para poder detectar un nuevo overflow del timer.

¿Como calcular una demora?

Recordando que el timer funciona con un contador y sabiendo que el timer


demora 1/fcuenta en pasar de un valor al próximo

Cuenta* (1/fcuenta)= tiempototal

Donde tiempototal es el tiempo que nosotros queremos establecer de nuestra


demora expresado en segundos.
Es decir que a lo largo de haber contado todo el valor de cuenta pasó el tiempo
total, ya que para nuestro caso fcuenta es igual a Fbus/PS (PS= Prescaler, divisor de
frecuencia base)

Cuenta* (PS/Fbus)= tiempototal

despejando:

Tiempototal*Fbus/PS=cuenta

Recordar que Fbus= Fxtal /4, pero solo trabajando con cristal y sin PLL, (siempre
ocurre en el JL3/JK3/JK1, mas adelante veremos cuando eso no pasa)
Osea: Tiempototal*Fxtal/4*PS= cuenta

Donde el tiempo total es la demora que nosotros queremos calcular en segundos,


Fxtal es la frecuencia del cristal que estemos usando en Hz, Ps el valor del prescaler y
cuenta el valor a cargar en TMODH y TMODL para producirse el desborde.

Como vemos en la ecuación obtenida tenemos dos incógnitas, cuenta y PS. Pero
es fácil solucionar esto. Primero calculamos suponiendo PS=1, y vemos que resultado
nos da cuenta, si trabajado así cuenta es un valor menor a 65535 PS debe ser =1 si
cuenta es mayor a 65535 debemos dividir por otro valor de PS a fin que nos de menor.
Ya que PS solo admite unos pocos valores no se demora mucho en hacer las prueba,
también puede suceder que con más de un valor o juego de valores entre cuenta y PS se
produzca la misma demora.
Es recomendable empezar dividiendo por el valor mas alto, es decir 64 para
ahorrar tiempo.

Ej.: Demora de 1 seg.:

Tiempototal=1seg
Fxtal=9830400 Hz

De ahí cuenta=24576000

Dado que cuenta es un valor mayor a 65535 probamos dividiendo por 64

Cuenta = 38400 ---> ahora si es un valor menor que 65535 aquí vemos que PS
debe ser = 64.

Si probamos con PS= 32 vemos que cuenta= 76800 por lo tanto es un valor muy
grande imposible de usar.

En el caso de crear una demora de 0,5 s vemos que con los valores

Cuenta= 38400 PS=32


y
Cuenta =19200 PS=64
Alcanzamos la misma demora, en este caso utilizar cualquiera de las dos es
indistinto.

Registros asociados al timer overflow:

TSC

TSC

Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0

Lectura: TOF TOIE TSTOP PS2 PS1 PS0


Escritura: 0 TRST
Reset 0 0 1 0 0 0 0 0

Sin implementar

TOF: Timer Overflow Flag, este bit es el indicador de que se ha producido un


desborde en el timer, es decir TCNTH y TCNTL son iguales a TMODH y TMODL
respectivamente, En caso de habilitarse las interrupciones cuando este bit se ponga en 1
producirá una interrupción de timer overflow

“1”= El timer ha desbordado


“0”= El timer no ha desbordado aun

TOIE: Timer overflow interrupt enable, este bit es el habilitador de


interrupciones propio del timer.

“1”= Interrupciones del timer habilitadas


“0”= Interrupciones del timer no habilitadas

TSTOP: Timer Stop, este bit es el encargado de parar o dejar correr el timer
Luego de un reset este bit se pone a 1, por lo tanto el timer amanece parado.

“1”= Timer parado


“0”= Timer funcionando

PS[0-2]: Prescaler del timer, de acuerdo a los valores divide la frecuencia base
del timer para así obtener a la salida la frecuencia de cuenta.

PS2 PS1 PS0 Frec de cuenta

0 0 0 Fbus/1

0 0 1 Fbus/2

0 1 0 Fbus/4

0 1 1 Fbus/8

1 0 0 Fbus/16

1 0 1 Fbus/32

1 1 0 Fbus/64

1 1 1 No disponible

TCNTH

Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0

Lectura:
Escritura:
Reset 0 0 0 0 0 0 0 0

TCNTL
Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0

Lectura:
Escritura:
Reset 0 0 0 0 0 0 0 0

Estos dos registros TCNTH y TCNTL son los registros de cuenta del timer, que
continuamente se comparan con TMODH y TMODL. Leer el registro TCNTH latchea
el contenido de TCNTL a fin de obtener una medición coherente al tiempo de hacerla, y
no leer una variable a medias ya que entre lectura de uno y otro registro podría cambiar
la parte baja de dicha palabra en algún valor. El bit TRST del TOF pone estos dos
registros a 0.

TMODH

TMODH

Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0

Lectura: Bit7 Bit6 Bit5 Bit4 Bit3 Bit2 Bit1 Bit0


Escritura:
Reset 1 1 1 1 1 1 1 1

TMODL

TMODL

Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0

Lectura: Bit7 Bit6 Bit5 Bit4 Bit3 Bit2 Bit1 Bit0


Escritura:
Reset 1 1 1 1 1 1 1 1

Como hemos comentado anteriormente TMODH y TMODL son los valores que
continuamente se van comparando con las variables de cuenta TCNTH y TCNTL,
cuando estos registros sean iguales se producirá un desborde en el timer, por default
estos registros valen $FF es decir que se compara con el valor máximo de cuenta 65535.
Hay que tener cuidado porque escribir solo uno de estos registros inhabilita el timer
hasta que se hayan escrito ambos registros, siempre escribir primero TMODH y
TMODL luego.

Formas de cargar TMODH, TMODL Y TSC:

Supongamos una demora de 1 seg., para lo cual según nuestros cálculos


anteriores cuenta= 38400= $9600 y PS=64.

Primero nos aseguramos que el modulo este apagado (después de un reset el


timer arranca apagado)

MOV #%00100000,TSC
LDHX #$9600 ;cargamos HX con $9600
STHX TMODH ;Cargamos TMODH con H
;y TMODL con X
MOV #%00010110,TSC ;usamos el timer por polling
;configuramos PS y reseteemos el timer
BCLR 5,TSC ;Arrancamos el timer.

Otra forma:

MOV #%00100000,TSC
MOV #$96,TMODH ;cargamos TMODH con $96
MOV #$00,TMODL ;cargamos TMODL con $00
MOV #%00010110,TSC ;usamos el timer por polling
;configuramos PS y reseteemos el timer
BCLR 5,TSC ;Arrancamos el timer.

Vamos ahora a ver un ejemplo por el cual toogleamos el valor de un pin del puerto,
específicamente el bit 0 del puerto B, vemos que con los equs podemos modificar esto
muy fácilmente.
Por polling:

BITPUERTO EQU 0
PUERTOLED EQU PORTB
DDRLED EQU DDRB
MASCARA EQU 1

Comienzo: MOV #$01.CONFIG1 ;anulamos el cop


BSET BITPUERTO,DDRLED ;establecemos el bit como
;salida
BCLR BITPUERTO,PORTB ;establecemos valor inicial
MOV #%00100000,TSC
LDHX #$9600
;cargamos HX con $9600
STHX TMODH ;Cargamos TMODH con H
;y TMODL con X
MOV #%00010110,TSC ;usamos el timer por polling
;configuramos PS y
;resetemos el timer
BCLR 5,TSC ;Arrancamos el timer.
LOOP: BRCLR 7,TSC,$
BCLR 7,TSC
LDA PORTLED
EOR MASCARA
STA PORTLED
BRA LOOP

sin_uso: RTI

ORG ResetVector

DC.W sin_uso ;Rutina de interrupción del ADC


DC.W sin_uso ;Rutina de interrupción de teclado

ORG $FFF2

DC.W Timer_int ;Rutina de interrupción del timer


DC.W sin_uso ;Rutina de interrupción del canal 1 del timer
DC.W sin_uso ;Rutina de interrupción del canal 0 del timer

ORG $FFFA

DC.W sin_uso ;Rutina de interrupción de IRQ


DC.W sin_uso ;Rutina de interrupción de software
DC.W Comienzo ;Posición de comienzo del programa

Por interrupciones:

Comienzo: MOV #$01.CONFIG1 ;anulamos el cop


BSET BITPUERTO,DDRLED ;establecemos el bit como
;salida
BCLR BITPUERTO,PORTB ;establecemos valor inicial
MOV #%00100000,TSC
LDHX #$9600
;cargamos HX con $9600
STHX TMODH ;Cargamos TMODH con H
;y TMODL con X
MOV #%01010110,TSC ;usamos el
;timer por interrupciones
;configuramos PS y
;resetemos el timer
BCLR 5,TSC ;Arrancamos el timer.
LOOP: BRA *
Timer_int: BCLR 7,TSC
LDA PORTLED
EOR MASCARA
STA PORTLED
RTI

sin_uso: RTI

ORG ResetVector

DC.W sin_uso ;Rutina de interrupción del ADC


DC.W sin_uso ;Rutina de interrupción de teclado

ORG $FFF2

DC.W Timer_int ;Rutina de interrupción del timer


DC.W sin_uso ;Rutina de interrupción del canal 1 del timer
DC.W sin_uso ;Rutina de interrupción del canal 0 del timer

ORG $FFFA

DC.W sin_uso ;Rutina de interrupción de IRQ


DC.W sin_uso ;Rutina de interrupción de software
DC.W Comienzo ;Posición de comienzo del programa

CANALES DEL TIMER


Nuevos módulos conversores de 10 BITS.

Los nuevos miembros que se van integrando a la familia de microcontroladores


HC08, ya dejan de traer el conversor estudiado anteriormente para traer un modulo
nuevo con ciertas mejores, además de tener la posibilidad de convertir en modulo de 10
bits, puede realizar conversiones de 8 bits, también puede automáticamente barrer
varios canales y a dejar los resultados en diferentes bytes destinados para tal evento,
veremos a continuación este nuevo modo.
Modo de Auto Scan: Esta modalidad está solamente disponible en los conversores de
10 bits, lo que hace es ir barriendo secuencialmente desde el canal 0 hasta el canal 3 (se
puede programar que pare en el canal 1-2 o 3), depositando el resultado de la conversión
en una serie de bytes destinados a tal hecho (ADRL0-ADRL1-ADRL2-ADRL3). En
este modo el modulo nuevo solo puede realizar conversiones de 8 bits.
Para utilizar este modo se debe establecer configurar la palabra correspondiente
en el byte ADASCR.

ADASCR

Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0

Lectura: 0 0 0 0 0
Escritura:           AUTO1 AUTO0 ASCAN
Reset 0 0 0 0 0 0 0 0

  Sin implementar

AUTO[1:0]: Estos bits setean la cantidad de canales a convertir en el modo de


auto scan.

AUTO1 AUTO0 Canales seleccionados

0 0 ADC0

0 1 ADC0 a ADC1

1 0 ADC0 a ADC2

1 1 ADC0 a ADC3

Los valores devueltos de las conversiones de los respectivos canales serán


devueltos en los registros ADRL0-ADRL1-ADRL2-ADRL3, donde se ubicara en el
primero el resultado de la conversión del canal 0, en el segundo la del canal 1 y así hasta
el último en el canal 3.

ASCAN:
Este bit lo que hace es habilitar el modo de Auto Scan
“1” Modo de Auto Scan habilitado
“0” Modo de Auto Scan deshabilitado

Es conveniente no habilitar el modo de auto scan si se encuentra


habilitado el modo de conversión continua.

Devolución de la conversión.

Justificación de resultado:

1 Justificado a la izquierda
2 Justificado a la derecha
3 Justificado a la izquierda con signo
4 Devolución en modulo 8 bits

1 El justificado a la izquierda lo que hace es volcar los 10 bits hacia el bit más
significativo de ADRH de la siguiente forma

ADRH

Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0

Lectura: ADC9 ADC8 ADC7 ADC6 ADC5 ADC4 ADC3 ADC2


Escritura:

ADRL

Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0

Lectura: ADC1 ADC0 0 0 0 0 0 0


Escritura:

2 El justificado a la izquierda lo que hace es volcar los 10 bits hacia el bit menos
significativo de ADRL de la siguiente forma

ADRH

Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0

Lectura: 0 0 0 0 0 0 ADC9 ADC8


Escritura:

ADRL
Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0

Lectura: ADC7 ADC6 ADC5 ADC4 ADC3 ADC2 ADC1 ADC0


Escritura:

3 Este formato es igual al justificado a la izquierda con la salvedad que


complementa el bit más significativo de la conversión, convirtiéndolo en el bit
de signo, este tipo de conversión es utilizada para conversiones de magnitudes
signadas donde se parte a la mitad de la escala para utilizar convenciones de
signo.

ADRH

Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0

Lectura: SIGNO ADC8 ADC7 ADC6 ADC5 ADC4 ADC3 ADC2


Escritura:

ADRL

Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0

Lectura: ADC1 ADC0 0 0 0 0 0 0


Escritura:

4 Si bien el modulo es capaz de convertir en 10 bits este método trunca la


conversión a 8 bits haciendo que pueda trabajar el modulo en ambos módulos
libremente.

ADRH

Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0

Lectura: 0 0 0 0 0 0 0 0
Escritura:
ADRL

Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0

Lectura: ADC8 ADC7 ADC6 ADC5 ADC4 ADC3 ADC2 ADC1


Escritura:

Los modos descriptos anteriormente se pueden setear en un byte que es muy


similar al ADCLK que tenia el conversor de 8 bits, donde también se setea de la misma
manera la velocidad del conversor y adhiere 3 bits más, uno con respecto a la fuente de
reloj del modulo y otro para configurar el modo de trabajo.

ADICLK

Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0

Lectura: 0 0
ADIV2 ADIV1 ADIV0 ADICLK MODE1 MODE0
Escritura:   R

Reset 0 0 0 0 0 0 0 0

  Sin implementar

MODE1 MODE0 Justificación de los datos

0 0 Devolución en modulo 8 bits

0 1 Justificado a la derecha

1 0 Justificado a la izquierda

Justificado a la izquierda
1 1
con signo

El bit que queda por describir es el 4: ADICLK: Este bit establece la fuente de reloj que
luego será dividía por ADIV2-0 para establecer un reloj de entre 500Khz y 2Mhz:
“1”= La velocidad del bus es quien establece la velocidad del ADC
“0”= El oscilador externo es quien establece la velocidad del ADC
Para los bits 7 a 5 referirse a la tabla de configuración para el ADLCK del
modulo de 8 btis.
COMUNICACIONES SERIE

Hasta ahora si queríamos conectar algún dispositivo a nuestro microcontrolador


lo hacíamos por medio de los ports, y si se deseaba transmitir un dato a por ejemplo un
CD4511 en sus bits de dato A, B, C y D lo hacíamos conectado un bit del puerto a cada
uno de estos bits del otro integrado, esta forma de comunicación entre dos dispositivos
es denominada comunicación paralela, ya que toda la información que se desea
transmitir desde un punto a otro (no es necesario que sean microcontroladores) es
transmitida en un instante, y se tienen tantos hilos (bits) de comunicación como longitud
del dato a transmitir, es decir que si deseamos hacer una transmisión para comunicarle a
un destino un byte debemos emplear 8 bits es decir 8 cables desde el destino a la fuente.
Si bien esta forma de comunicación, funciona, y es rápida, tiene ciertas desventajas, al
tener que exclusivamente utilizar n cables el formato generalmente son cables con
perdidas sin ninguna configuración en especial como ser líneas bifilares o coaxiles
(donde la perdida de señal o distorsión a lo largo de la distancia es menos considerable)
la perdida de datos y errores es mucho mas importante, igualmente el impedimento
mayor de una comunicación paralela es el hecho de tener que cablear todas las líneas o
incluso en un PCB tener que dificultar el diseño de la plaqueta.
La contrapartida de la comunicación paralela de la que hemos hecho una
breve introducción en el párrafo anterior es la comunicación serie, para la cual
generalmente se utilizan 2 hilos de comunicación uno para transmitir y otro para recibir
que generalmente llamaremos rx y tx para receptor y transmisor respectivamente
(haremos las salvedades para cuando en un protocolo especifico esta s líneas tomen
otros nombres, también veremos más adelante que esto puede modificarse según el tipo
de comunicación y llegar a utilizarse más o menos líneas) mediante estos dos hilos se
transmite la información desde un punto a otro, al contrario de la comunicación paralela
que en un tiempo transmitía la cantidad n de bits necesaria para la comunicación a la
comunicación serie le tomará m tiempos transmitir la cantidad n de bits necesario para
completar la comunicación.
Existen muchos métodos y protocolos de comunicación serie algunos de
los más comunes son:

 RS-232 (SCI)
 RS-485
 Ethernet
 USB
 I2C
 SPI
 CAN
 SERIAL ATA
 IRDA

Clasificación de las comunicaciones serie según la forma de transmitir o recibir:

 Full-duplex
 Half-duplex
 Simplex

Full-Duplex: Este tipo de comunicación se caracteriza por poder enviar al mismo


tiempo que recibe. El teléfono común que tenemos en nuestras casas es un claro
ejemplo ya que podemos hablar y escuchar a la vez.

Half-Duplex: Se caracteriza por poder enviar y recibir pero solo una de estas tareas a la
vez, es decir que en algún momento se encontrará recibiendo y en otro momento
transmitiendo pero JAMAS podrá hacer la dos cosas a la vez. El sistema de handys que
se usan en la actualidad es un ejemplo ya que se debe esperar a que el canal se libere
para poder transmitir información.

Simplex: En este tipo de comunicación solo se puede establecer el flujo de datos desde
un punto llamado fuente a otro llamado destino y nunca se podrán intercambiar los
roles, siempre la fuente será transmisora y el destino receptor. Los controles remotos (de
televisores por ejemplo) son un ejemplo donde emiten señales hacia el dispositivo a
controlar pero los dispositivos a ser controlados no indican al control su estado.

Describiremos alguna de las comunicaciones series mas utilizadas en el área de


los microcontroladores que poseen los MCU de Freesscale.
MODULO DE COMUNICACIÓN SERIE SINCRÓNICA SPI

Si bien dijimos anteriormente que la comunicación serie se lograba a través de


un hilo para transmitir y otro para recibir, no siempre suele ser así, la comunicación
sincrónica es una de estas excepciones. Debido al concepto fundamental de
comunicación serie en que debemos enviar uno a uno los bits que conforma la palabra
de comunicación ambos dispositivos (fuente y destino) deberán saber en que momento
se cambia entre un valor y su próximo valor dado que puede que dos valores
consecutivos sean iguales por lo tanto no se provocaría un cambio eléctrico o físico en
la línea pero si lógico al interpretarse en tiempos diferentes y encasillar a ese dato
proveniente de la lectura del hilo de comunicación. Para evitar este inconveniente lo que
se ideo es sincronizar a ambos intervinientes en la comunicación mediante una línea
extra denominada SPSCK (SPI Serial Clock) los cambios en esta línea (flancos) serán
ahora los indicadores de que un nuevo dato VALIDO y correspondiente al próximo
valor lógico del dato a transmitir se encuentra en la línea de datos.
Algo que no nombramos hasta ahora y NO EXISTE EN TODAS las
comunicaciones series es la diferencia entre los diversos dispositivos que componen la
red de comunicación, en este tipo de comunicación SPI, vamos a diferenciar a los
dispositivos participantes de la comunicaciones entre Maestros y Esclavos, aunque es
falso hablar de maestros ya que en un red de comunicación SPI solo admite UN solo
maestro y múltiples esclavos. El maestro es SIEMPRE el encargado de manejar la línea
de CLOCK por eso es siempre el encargado de empezar una comunicación. Nunca un
esclavo podrá comenzar una comunicación si el maestro no mueve la línea de clock.
Si bien en el párrafo hablamos de red de comunicación no especificamos la
metodología para que el maestro hable con alguno de los esclavos y solamente ese sea
quien escuche esa comunicación e interprete como propio el mensaje que el maestro le
envíe. Para que esto pueda suceder se agrega N líneas como esclavos haya que son
manejadas por el maestro para elegir con quien quiere hablar llamados SS (Slave
Select), esta línea además sirve para identificar (vía hardware) si vamos a trabajar como
maestros o esclavos ya que si esta configurada en uno solo podemos trabajar como
maestros y si la línea esta en 0 trabajaremos como esclavos.
Particularmente la líneas de datos que fluyen del maestro a los esclavos y al
revés tienen un nombre en particular para no confundirlas y saber quien de cada
dispositivo se conecta con quien, así es que tenemos las dos líneas y de acuerdo a si
somos maestros o esclavos es con quien nos conectaremos.

MISO (Master input- Slave Output): Esta línea funciona como entrada si
funcionamos como maestros o como salida si es que funcionamos como esclavos, es
decir que MISO del maestro se conecta con MISO del esclavo.
MOSI (Master output – Slave Input): Esta línea funciona como salida si
funcionamos como maestros o como entrada si es que funcionamos como esclavos, es
decir que MOSI del maestro se conecta con MOSI del esclavo.

Es esquema de comunicación seria el siguiente:


La comunicación serie sincrónica no termina mas que siendo un intercambio de
bytes donde primero se envía el MSB (Bit mas significativo, bit 7) y al mismo tiempo se
recibe el LSB (Bit menos significativo, bit 0), dado esto que acabamos de decir
podemos claramente ver que este formato utiliza la comunicación full-duplex.

Configuraciones del clock

Como bien sabemos el clock es una señal que se encuentra alterando desde un
estado lógico alto “1” a otro estado lógico bajo “0”, el hecho que vaya alterando y que
tengamos que interpretar datos antes los cambios en esta señal nos da cuatro
posibilidades de captación de los datos en la línea, estas cuatro posibilidades las
podemos a su vez dividir en dos rasgos, segundo su fase, y según su polaridad. Como
mencionamos antes la señal cuadrada que usamos para la generación del reloj cambia
continuamente entre dos estados, lo que nos deja dos fases, una fase inicial y su fase
posterior cuando cambia hacia el otro estado. De acuerdo a la polaridad es que
definiremos a la primer fase como una fase en estado alto o estado bajo y la inversa para
su fase posterior.
Podemos entonces como mencionamos antes captar los datos de acuerdo a la
fase y la polaridad, donde podemos captarlos en la fase inicial sea alta o baja de acuerdo
a la polaridad o en la segunda fase de cuerdo de nuevo a la polaridad alta o baja que
inevitablemente es la inversa de la primera fase.

De acuerdo a lo anterior se nos presentan estos cuatro casos:

Errores en la comunicación

El modulo de SPI que presentan los microcontroladores de la línea de Freescale,


poseen incorporado en ellos la detección de dos tipos de errores.

Errores
 Por overflow
 Por Mode Fault
Overflow: Ni bien se termina la una comunicación, es decir la transacción de un
byte entre el maestro y el esclavo, los datos esta disponibles para que el usuario,
pueda utilizarlos, pero también el modulo queda listo (a menos que se lo apague)
para que llegue algún nuevo dato desde le maestro a esclavo, si antes de que
nosotros leamos los datos que nos llegaron llega un nuevo dato que obviamente
borra (pisa) los datos que antes había llegado se produce un error de overflow.

Mode Fault: Este error se produce cuando hay una incoherencia entre la línea de
SS y lo que el microcontrolador tiene configurado ya sea como maestro o
esclavo. El error de Mode fault se produce cuando:

 El MCU esta configurado internamente como maestro y tiene el


pin de SS en estado bajo en cualquier momento.

 El MCU esta configurado internamente como esclavo y durante


una transmisión de datos el pin de SS pasa de estado bajo a estado
alto.

Estos dos tipos de errores pueden si se desea generar una interrupción aparte de
la del modulo que produce cuando se recibe o envía un dato, y ser muy útil por ejemplo
para acelerar el proceso de obtención de datos si se produce un error de overflow o
avisar en algún dispositivo como ser un LCD que se daño el hardware al desconectarse
la línea de SS, etc. Etc.

MODULO DE COMUNICACIÓN SERIE ASINCRÓNICA SCI

Hemos descripto en el apartado anterior la funcionalidad de la comunicación


serie y mostramos la comunicación serie sincrónica, la cual se basa en utilizar una línea
de clock para establecer cuando es que los datos puestos sobre la línea de Datos, son
validos, es decir, se leen coherentemente con lo que el transmisor quiere informar el
receptor. El tipo de comunicación de la cual nos vamos a encargar ahora, es justamente
lo contrario a la comunicación anterior, denominada sincrónica.
Capa física:
Definiremos ahora las siglas por lo general utilizadas para definir los diferentes
pines del MCU que utilizaremos, para así saber que conectar con que.
Hablamos ahora de altos y bajos ya que a niveles de tensión de un
microcontrolador las entradas y salidas de el (en términos digitales) son de 1 y 0, es
decir 5V y 0V respectivamente. Ya veremos que muchas veces estos niveles son
cambiados, como es el caso de la norma RS-232 que utiliza la computadora, cabe
aclarar que hay que separar entre la capa física, es decir los niveles de tensión que
adopta la norma a la comunicación en sí, la misma comunicación asincrónica puede ser
transmitida en forma RS-232, TTL etc.

La comunicación sincrónica, como bien se puede llegar a prever por su nombre,


es el tipo de comunicación para el cual no se utiliza físicamente una línea o conexión
que transporte el clock, o la indicación de cuando hay que leer un dato de la línea de
Datos sino que tanto el receptor como el transmisor tiene que saber de antemano cada
cuanto tiempo se va a transmitir un dato, por lo tanto con esa información una vez
iniciada la transacción de los datos, el transmisor tiene que si o si mantener estable el
cambio de su información en el bit de Datos (coherente a la información a transmitir) de
acuerdo a lo establecido por ambos que de ahora en mas vamos a definir como
velocidad de comunicación o Baud Rate. Así mismo, el receptor para poder obtener
coherentemente los datos que el transmisor quiere enviarle debe leer la conexión de
Datos con la misma periodicidad que el transmisor se los envía (Baud Rate).
El problema sub-siguiente en este tipo de esquema es el saber cuando comienza
la transmisión de datos para poder establecer luego el tiempo que tengo que esperar para
leer el próximo bit, correspondiente a la información. Para eso se establece como
primera medida un estado denominado Idle. Este estado es el que consideramos como
estado de inactividad, es decir mientras estemos en esta condición que ahora pasaremos
a describir se interpretará que no estamos transmitiendo nada ni tenemos intenciones de
hacerlo. La condición Idle por lo general se establece con un alto de tensión.
Hasta ahora si nadie cambia esta condición de idle no podrá iniciarse ninguna
comunicación, obviamente como estamos hablando de comunicaciones digitales para
detectarse que se inicia una comunicación, se deberá cambiar de la condición de idle a
nivel bajo, este cambio, es el encargado de informar a al otro dispositivo que vamos a
empezar a transmitirle algo, por ello se lo llama Start bit.
Una vez pasado el start bit, obviamente que tendrá una duración de 1/BaudRate.
Se transmitirán cada uno de los bits correspondientes a los datos a transmitir
obviamente la duración de todos estos bits de datos, deberá ser también constante e
igual a 1/BaudRate.
En cuanto a la cantidad de datos considerados como “datos” hay dos
posibilidades, pueden incluirse 8 bits de datos o 9, teniendo en cualquiera de las dos
opciones la posibilidad de que el ultimo bit sea bit de paridad, es decir de acuerdo a lo
que nosotros elijamos y tengamos como datos el ultimo bit puede ser 1 o 0 para
completar par de unos, en caso de paridad par, o completar con 1 o 0 a fin de tener
impar de unos, paridad impar.

7 bits de byte con bit de paridad


datos par impar
0000000 00000000 00000001
1000101 10001011 10001010
1111011 11110110 11110111
1111111 11111111 11111110
Por último una vez transmitido los datos, con o sin el bit de paridad, se transmite
el bit de stop, dicho bit se logra forzando un nivel bajo durante un tiempo 1/BaudRate,
una vez terminado el bit de stop se vuelve al valor Idle, y se continua así esperando el
próximo start bit y que todo comience de nuevo.
Vale destacar que hablamos de diferentes posibilidades en cuanto a lo que
incluye a los bits entre el bit de start y el de stop, lo importante es que ambos equipos,
fuente y destino deben estar de acuerdo sobre la forma en la que van a transmitir, en
cuanto a cantidad de datos y la presencia o no del bit de paridad. De ahí en más nuestro
software será el encargado de interpretar los datos de la manera que deba hacerlo.
Mas alla de lo que puede llegar a pasar en el medio y de todas las formas que
nosotros tomemos de interpretación de nuestro código, es decir nuestros bits que
llamamos datos, pueden no ser puramente datos a transmitir y recibir sino códigos, que
nosotros implementemos. Por ejemplo si transmitimos 8 bits de datos sin paridad,
podemos definir los 2 bits más significativos para algún tipo de mensaje y los otros 6
restantes para la información en sí, cuando sea necesaria. De esta forma para un ejemplo
podríamos definir como.

Bits Orden

00 Datos

01 Ok

10 Error

11 Retransmisión

De acuerdo a este código los 6 bits restantes serán interpretados como datos en
el primer caso, o no se tomaran en cuanta en los otros casos. Nuestro código puede
establecer un protocolo mínimo donde cada vez que se transmite algo debe enviar el
receptor al transmisión del mensaje, otro mensaje donde le diga si llegaron los con error,
si pide retransmisión o si todo llego bien. Así como ahora implementamos básicamente
algo pueden hacerse miles de protocolos, algunos propios o algunos que pueden ser
producto de implementaciones de terceros o normas ya establecidas. Vale separar ahora
el protocolo montado sobre la capa lógica de transmisión. Es decir que ahora ya
hablamos de tres capas distinguibles,

 Capa Física
 Capa Lógica
 Capa de protocolo
Estas capas que ahora hemos nombrado no son más que capas definidas en un
modelo de comunicación llamado Modelo OSI, que se hacen imprescindibles a la hora
de montar comunicaciones cada vez más y más sofisticadas.

Errores en las comunicaciones asincrónicas


PLL CGMC OSC qy (demoras por tiempo)
TMB
AWU

Trabajando en C.

Ahora que hemos aprendido como funciona nuestro MCU, que ya lo hemos
programado en assembly, nos hemos familiarizado con lo que realmente pueden hacer
toda esta gama de microcontroladores con el núcleo HC08.
Primero presentaremos el
“This is a dummy entry for the map file.
The correct input will be placed after the first link process.”
#include <hidef.h> /* for EnableInterrupts macro */
#include "derivative.h" /* include peripheral declarations */

void main(void) {

EnableInterrupts; /* enable interrupts */


/* include your code here */

for(;;) {
__RESET_WATCHDOG(); /* feeds the dog */
} /* loop forever */
/* please make sure that you never leave main */
}

También podría gustarte