Documentos de Académico
Documentos de Profesional
Documentos de Cultura
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
¿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
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.
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.
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.
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 Hardvard
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 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.
Acumulador [A]
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.
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
LDA $0080
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.
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+)
La sintaxis es la siguiente:
LDA $80
STA $0100
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.
LDHX #$023F
INCA
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
LDHX #$03F8
LDA ,X
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
STA $25,X
La sintaxis es la siguiente:
LDA offset16bits,X
Posición de Contenido de la
memoria posición
$EC00 $03
$EC01 $08
$EC02 $07
$EC03 $A3
$EC04 $45
LDHX #elemento
LDA $EC00,X
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
La sintaxis es la siguiente:
LDA offset16bits,SP
Modo de direccionamiento Relativo
La sintaxis es la siguiente:
BEQ Salto
La sintaxis es la siguiente:
MOV OP1,OP2
MOV #OP1,OP2
La sintaxis es la siguiente:
MOV X+,OP1
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
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
BEQ Etiqueta
ORG $EC00
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:
LDA SegVal
*********
*********
*********
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:
ORG ROMStart
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.
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
Variable RMB 2
Otra sentencia muy utilizada es la que se utiliza para establecer tablas en ROM,
se hace de la siguiente manera
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:
Así como tenemos una variante al DS (RMB) tenemos la posibilidad de usar otra
expresión/directiva
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:
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'
Base Prefijo
2 %
8 @
10
16 $
LDA #$0a
LDA #@12
LDA #%00001010
LDA #10
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
sintaxis:
== igual
!= distinto
>= mayor igual
> mayor
<= menor igual
< menor
Ejemplo:
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
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
*******************************************
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.
JSR CONVERSION
*******************************************
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.
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.
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
ORG ROMStart
Podríamos generalizar para la suma de datos de N bytes usando el registro Indice para
apuntar a los operandos de la siguiente forma
ORG RAMStart
ORG ROMStart
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.
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.
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.
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
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.
ORG ROMStart
ORG ROMStart
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
* 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.
ORG RAMStart
Cuenta RMB 1
ORG ROMStart
Comienzo: LDA #N
Loop: DBNZ Cuenta,*
DBNZA Loop
Reset e Interrupciones
RTI
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
PULH ;Restauro H
RTI
PULH ;Restauro H
sin_uso RTI
ORG ResetVector
**********************************************************************
ORG ResetVector
ORG $FFF2
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
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:
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.
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.
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
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.
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:
;*******************************************************************
;*Programa para……
;*Creado por :……
;*Fecha:……
;* Version: ……
;*******************************************************************
INCLUDE 'derivative.inc'
XDEF _Startup
ABSENTRY _Startup
;Seccion de mis variables
;Seccion de codigo
ORG ROMStart
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
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
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
CONFIG1
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):
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
Por: 0 0 0 0 0 0 0 0
Reservado
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
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
PDCR
Ejemplos de programas
Recordando para el JK/JL3 los puertos que tenemos disponibles son:
ORG ROMStart
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:
ORG ROMStart
ORG RESETVECTOR
DC.W COMIENZO
XXXXXXXX
AND 1 1 1 1 1 1 1 0
XXXXXXX 0
XXXXXXXX
OR 0 0 0 0 0 0 0 1
XXXXXXX 1
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
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.
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
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.
0 0 NO MODIFICA LA
SALIDA
0 1 AND
1 0 OR
1 1 XOR
PORTA:
PORTB:
PORTD:
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:
ORG ROMStart
ORG RESETVECTOR
DC.W COMIENZO
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:
330
PTA0
330
A
PTA1
330
B
PTA2
330
C
PTA3
330
D
PTA4
330
E
PTA5
330
F
PTA6
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
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.
PORTA:
Sin
G F E D C B A
FUNCION asignar
PORTB:
PORTD:
ORG ROMStart
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.
ORG ROMStart
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.
Si:
255 5V
1 X= 5V/255 aprox. = 19mv
///////////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.
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
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
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 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.
ADCLK
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
COCO EQU 7
RESETVECTOR EQU $FFFE
ORG ROMStart
ORG RESETVECTOR
DC.W COMIENZO
ROMStart EQU $EC00
COCO EQU 7
RESETVECTOR EQU $FFFE
ADCVECTOR EQU $FFDE
ORG ROMStart
ADC_INT: PSHH
LDA ADR
STA PORTD
PULH
ORG ADCVECTOR
DW ADC_INT
ORG RESETVECTOR
DC.W COMIENZO
ORG RAMStart
ADR2 RMB 1
ORG ROMStart
ADC_INT: PSHH
LDA ADR
STA ADR2
PULH
ORG ADCVECTOR
DC.W ADC_INT
ORG RESETVECTOR
DC.W COMIENZO
IRQ (Interrupción externa)
INTSCR
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”.
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
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”.
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.
Sin implementar
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.
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
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.
Tiempototal=1seg
Fxtal=9830400 Hz
De ahí cuenta=24576000
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
TSC
TSC
Sin implementar
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.
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.
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
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
TMODL
TMODL
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.
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
sin_uso: RTI
ORG ResetVector
ORG $FFF2
ORG $FFFA
Por interrupciones:
sin_uso: RTI
ORG ResetVector
ORG $FFF2
ORG $FFFA
ADASCR
Lectura: 0 0 0 0 0
Escritura: AUTO1 AUTO0 ASCAN
Reset 0 0 0 0 0 0 0 0
Sin implementar
0 0 ADC0
0 1 ADC0 a ADC1
1 0 ADC0 a ADC2
1 1 ADC0 a ADC3
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
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
ADRL
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
ADRL
Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0
ADRH
ADRL
ADRH
Lectura: 0 0 0 0 0 0 0 0
Escritura:
ADRL
ADICLK
Lectura: 0 0
ADIV2 ADIV1 ADIV0 ADICLK MODE1 MODE0
Escritura: R
Reset 0 0 0 0 0 0 0 0
Sin implementar
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
RS-232 (SCI)
RS-485
Ethernet
USB
I2C
SPI
CAN
SERIAL ATA
IRDA
Full-duplex
Half-duplex
Simplex
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.
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.
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.
Errores en la comunicación
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:
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.
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.
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) {
for(;;) {
__RESET_WATCHDOG(); /* feeds the dog */
} /* loop forever */
/* please make sure that you never leave main */
}