ING. LUIS G. URIBE C.Computer Architecture: The MC9S08 Primera Edición Septiembre de 2014, V1.3d ING. LUIS GUILLERMO URIBE CATAÑO Computer Architecture: The MC9S08 2014 Ing. Luis G. Uribe C. Caracas, Venezuela i Contenido PRÓLOGO ........................................................................................................................................................................... 5 ABSTRACT............................................................................................................................................................. 5 INTRODUCCIÓN ................................................................................................................................................... 6 ARQUITECTURA DEL COMPUTADOR: QUÉ DEBEMOS ESTUDIAR .................................................................. 6 Enfoque ......................................................................................................................................................................... 7 Observaciones sobre algunos términos empleados ...................................................................................................... 7 VON NEUMANN COMPUTERS....................................................................................................................................... 8 INTRODUCCIÓN ................................................................................................................................................... 8 QUÉ ES UNA INSTRUCCIÓN ..................................................................................................................................... 8 MÁQUINAS DE 3, 2, 1 Y 0 DIRECCIONES ................................................................................................................... 10 HCS08 ARCHITECTURE: PROGRAMMING MODEL...................................................................................... 11 MODOS DE DIRECCIONAMIENTO EN EL MC9S08 (CPU08RM Reference Manual)........................................... 11 VARIACIONES Y OPTIMIZACIONES ....................................................................................................................... 12 CODIDAC, VON URIBE’S COMPUTER.................................................................................................................... 14 LA ARQUITECTURA ................................................................................................................................................. 14 LAS INSTRUCCIONES............................................................................................................................................... 14 LOS MODIFICADORES ............................................................................................................................................ 14 EL SUBSISTEMA DE ENTRADA Y SALIDA.............................................................................................................. 14 CÓDIGOS DE OPERACIÓN ..................................................................................................................................... 14 EL EDITOR ................................................................................................................................................................ 15 EL FORMATO DE LOS NÚMEROS .......................................................................................................................... 15 LA CALCULADORA................................................................................................................................................... 15 LA ESTRUCTURA ...................................................................................................................................................... 15 DATOS ESTADÍSTICOS............................................................................................................................................. 15 CIRCUITOS IMPRESOS ............................................................................................................................................ 15 FOTOS........................................................................................................................................................................ 16 ASSEMBLER HCS08 ........................................................................................................................................................ 37 EJERCICIOS BÁSICOS EN ASSEMBLY LANGUAGE .......................................................................................... 37 1) Primer Programa para el HCS08: MINIMUM Program for HCS08..................................................................... 37 COMENTARIOS a ["Laboratorios\Lab1\00a_L1.asm"]:........................................................................................... 38 2) Segundo Programa para el HCS08: Init COP & STACK ...................................................................................... 40 COMENTARIOS a ["Laboratorios\Lab1\00c_L1.asm"]:........................................................................................... 41 3) Programación en Lenguaje de Máquina (HEX) para el HCS08:........................................................................... 42 COMENTARIOS a ["Laboratorios\Lab1\01a_L1.asm"]:........................................................................................... 43 4) Invente su propio Instruction Set para el HCS08:.................................................................................................. 46 COMENTARIOS a ["Laboratorios\Lab1\01b_L1.asm"]:........................................................................................... 47 5) Usando los OpCodes del Fabricante para el HCS08:............................................................................................ 47 COMENTARIOS a ["Laboratorios\Lab1\01c_L1.asm"]:........................................................................................... 48 6) Forma DEFINITIVA para programar el HCS08: .................................................................................................. 49 COMENTARIOS a ["Laboratorios\Lab1\01d_L1.asm"]:........................................................................................... 50 7) CODE SECTION, DATA SECTION, en el HCS08:................................................................................................ 50 COMENTARIOS a ["Laboratorios\Lab1\01e_L1.asm"]:........................................................................................... 51 PROGRAMAS AVANZADOS EN ASSEMBLY LANGUAGE, PARTE I ......................................................... 54 8) Serie de Fibonacci.................................................................................................................................................. 54 COMENTARIOS a ["Laboratorios\Lab1\02Fibonacci.asm"]:................................................................................... 56 9) Empleo de Subrutinas; Identificación de Modos de Direccionamiento.................................................................. 56 COMENTARIOS a ["Laboratorios\Lab1\Lab1_M\03a_L1_M.asm"]:....................................................................... 58 10) Introducción a las INTERRUPCIONES ............................................................................................................... 58 COMENTARIOS a ["Laboratorios\Lab1\Lab1_M\03b_L1_M.asm"]:....................................................................... 61 11) Ejercicio con más Aspectos Nuevos: .................................................................................................................... 63 COMENTARIOS a ["Laboratorios\Lab1\Lab1_M\03c_L1_M.asm"]:....................................................................... 65 12) Ejercicio con más Aspectos Nuevos aún: ............................................................................................................. 67 COMENTARIOS a ["Laboratorios\Lab1\Lab1_M\03d_L1_M.asm"]:....................................................................... 70 13) Aspectos Cosméticos en el trato de los Vectores de Interrupción:....................................................................... 71 14) Ejemplo: Rutina de MULTIPLICAR, por Sumas Sucesivas: ................................................................................ 72 COMENTARIOS a ["Laboratorios\Lab1\Lab1_M\05a_L1_M.asm"]:....................................................................... 73 15) "SUBRUTINA" de MULTIPLICAR, por sumas sucesivas:................................................................................... 74 COMENTARIOS a ["Laboratorios\Lab1\Lab1_M\05b_L1_M.asm"]:....................................................................... 75 16) Ejemplo PRIMORDIAL del uso de Variables Dinámicas: ................................................................................... 76 COMENTARIOS a ["Laboratorios\Lab1\Lab1_M\05d_L1_M.asm"]:....................................................................... 80 PROGRAMAS AVANZADOS EN ASSEMBLY LANGUAGE, PARTE II........................................................ 84 17) Torres de Hanoi, MÍNIMO, en C: ........................................................................................................................ 84 COMENTARIOS a ["Temarios\Ex#1-HanoiMin\HanoiMin--.c"]: .......................................................................... 85 18) Torres de Hanoi, MÍNIMO, en ASSEMBLY LANGUAGE: .................................................................................. 86 A) Programa HanoiMin--.asm: ................................................................................................................................. 87 B) Include File HanoiMin_.inc:................................................................................................................................. 88 COMENTARIOS a ["Temarios\Ex#1-HanoiMin\HanoiMin--.asm"]: ........................................................................ 90 COMENTARIOS a ["Temarios\Ex#1-HanoiMin\hanoiMin_.inc"]: ........................................................................... 91 TIMERS.................................................................................................................................................................. 93 NÚMERO DE TIMERS: ............................................................................................................................................. 93 OPERACIÓN Y USO DE LA BIBLIOTECA DE TIMERS:......................................................................................... 94 19) Timers for Windows (timers.cpp): ........................................................................................................................ 95 COMENTARIOS a ["Laboratorios\Lab2\_New\timers_.h"]: ..................................................................................... 99 COMENTARIOS a ["Laboratorios\Lab2\_New\timers.cpp"]: ................................................................................. 100 COMENTARIOS a ["Laboratorios\Lab2\_New\timtst2.cpp"]: ................................................................................ 101 20) Librería de TIMERS para el HC9S08 (timers8HS.inc): ..................................................................................... 101 COMENTARIOS a ["Laboratorios\Lab2\Timers8HS\timers8HS.inc"]:................................................................... 104 21) Ejemplo del uso de la Librería de TIMERS (timer8HS.asm): ............................................................................ 108 COMENTARIOS a ["Laboratorios\Lab2\Timers8HS\timer8HS.asm"]: .................................................................. 111 22) "WaitISR_on" Interactuando con la "IRQISR" .................................................................................................. 111 COMENTARIOS a ["Laboratorios\Lab2\Timers8HS\tim8_IRQ-HS.asm"]:............................................................ 114 23) Enciende los 8 LEDs con Diferentes Intervalos ................................................................................................. 116 COMENTARIOS a ["Laboratorios\Lab2\Timers8HS\TimedFlash8.asm"]:............................................................. 119 24) Enciende ORDENADAMENTE los 8 LEDs con Diferentes Intervalos .............................................................. 120 Finalización Tema TIMERS...................................................................................................................................... 123 PROGRAMAS AVANZADOS EN ASSEMBLY LANGUAGE, PARTE III .................................................... 123 COLAS DE DATOS .............................................................................................................................................. 126 25) BIBLIOTECA de COLAS: Que.inc ..................................................................................................................... 129 COMENTARIOS a ["Laboratorios\Lab3\SciComm\Que.inc"]: ............................................................................... 133 "STRUCT" in ASSEMBLY LANGUAGE................................................................................................................... 134 26) Programa Elemental para Probar la Librería de COLAS ................................................................................. 139 COMENTARIOS a ["Laboratorios\Lab3\SciComm\4quetst.asm"] .......................................................................... 141 COMUNICACIONES SERIALES......................................................................................................................... 143 27) LIBRERÍA de COMUNICACIONES SERIALES: SciComm.inc ......................................................................... 143 COMENTARIOS a ["Laboratorios\Lab3\SciComm\SciComm.inc"]:....................................................................... 147 28) Transmisión. ....................................................................................................................................................... 158 COMENTARIOS a ["Laboratorios\Lab3\SciComm\1tstXmt.asm"].......................................................................... 160 29) ECHO. ................................................................................................................................................................ 160 COMENTARIOS a ["Laboratorios\Lab3\SciComm\2echo1.asm"]:......................................................................... 162 30) Transmisión por INTERRUPCIONES. ............................................................................................................... 163 COMENTARIOS a ["Laboratorios\Lab3\SciComm\3echoInt-1.asm"]: ................................................................... 165 31) Cuarto Programa de COMUNICACIONES: Flash LEDs.................................................................................. 168 COMENTARIOS a ["Laboratorios\Lab3\SciComm\3echoInt-1Leds.asm"]:............................................................ 170 32) Programas de COMUNICACIONES que USTED Debe Hacer: ........................................................................ 173 PROGRAMAS VARIOS EN ASSEMBLY LANGUAGE .................................................................................. 174 33) BITCOPY, MACRO y Ejemplo. .......................................................................................................................... 174 COMENTARIOS a ["Laboratorios\Proy\Buzzer\BitCopy.asm"]: ............................................................................ 178 34) USO Básico del ADC, y Display en LEDs.......................................................................................................... 180 COMENTARIOS a ["Laboratorios\Proy\Buzzer\ADC.asm"]................................................................................... 182 35) Uso Básico del ADC, Reversar Datos Antes de ir a los LEDs. .......................................................................... 184 COMENTARIOS a ["Laboratorios\Proy\Buzzer\ADC_Reverse.asm"]:................................................................... 187 36) Contador de UNOS............................................................................................................................................. 189 COMENTARIOS a ["Books\Interfacing-HCS08\Examples\bitcount3.asm"]: .......................................................... 190 37) Programación de un TONO en el BUZZER. ...................................................................................................... 191 COMENTARIOS a ["Laboratorios\Proy\Buzzer\Buzzer.asm"]: .............................................................................. 193 38) Un "WALKMAN" Elemental............................................................................................................................... 195 COMENTARIOS a ["Laboratorios\Proy\Buzzer\Walkman.asm"]: .......................................................................... 199 39) "SWITCH", vía "COMPUTED GOTO".............................................................................................................. 202 COMENTARIOS a ["Laboratorios\FSM-FiniteStateMachines\ComputedGoTo.asm"]:.......................................... 204 40) La FÁBRICA de CHOCOLATES........................................................................................................................ 207 41) TABLAS de DATOS; Implementación del "FOR". ............................................................................................. 209 COMENTARIOS a ["Laboratorios\Tables\Tables0.asm"] ....................................................................................... 211 42) Last, but not Least: 8 Bits Rotate Left. ............................................................................................................... 212 COMENTARIOS a ["Evaluaciones\2013-01Ene\Evaluaciones\Ex#1\Rotate8.asm"]:............................................. 213 PROGRAMACIÓN EN "C"........................................................................................................................................... 215 NOTAS INICIALES ............................................................................................................................................ 215 DÓNDE ESTAMOS ............................................................................................................................................ 215 GENERALIDADES ............................................................................................................................................ 216 43) Programa INTRODUCTORIO en C, para HCS08, Comparativo con ASM. ..................................................... 216 COMENTARIOS a ["Labs-C\Fibonacci\060Fibonacci.c"]:..................................................................................... 217 ["Labs-C\Fibonacci\Fibonacci_.h"]: ....................................................................................................................... 219 COMENTARIOS a ["Labs-C\Fibonacci\Fibonacci_.h"]: ........................................................................................ 220 44) FIBONACCI PROGRAMADO EN C, NORMALMENTE, PARA COMPARAR ................................................. 221 COMENTARIOS a ["Labs-C\Fibonacci\090FibonacciOK.c"]: ............................................................................... 221 45) EXPONENCIACIÓN POR MULTIPLICACIONES Y SUMAS SUCESIVAS: WHILE ....................................... 222 COMENTARIOS a ["Labs-C\Lab1\010Lab0e-1.c"]: ............................................................................................... 222 46) EXPONENCIACIÓN, MULTIPLICACIONES Y SUMAS SUCESIVAS: FUNCIONES ..................................... 222 COMENTARIOS a ["Labs-C\Lab1\020Lab0e-2Sub.c"]: ......................................................................................... 223 47) EXPONENCIACIÓN, FUNCIONES: PARA VISUAL STUDIO ......................................................................... 223 COMENTARIOS a ["Labs-C\Lab1\030Lab0e-2SubVisualStudio.c"]: ..................................................................... 224 48) PRIMER PROGRAMA PARA HCS08: MINIMUM C PROGRAM..................................................................... 224 COMENTARIOS a ["Labs-C\lab1\00a_l1.c"]:......................................................................................................... 224 49) PROGRAMA EN C PARA HCS08, UN POCO MÁS ÚTIL ................................................................................ 225 COMENTARIOS a ["Labs-C\Lab1\01c_L1.c"]:....................................................................................................... 226 50) "INTERRUPTS" EN EL LENGUAJE C.............................................................................................................. 227 COMENTARIOS a ["Labs-C\Lab1\03b_L1_M.c"]: ................................................................................................. 229 EL INCLUDE FILE "SEVERAL_U.H":.................................................................................................................... 232 COMENTARIOS a ["Labs-C\Lab1\several_U.h"]: .................................................................................................. 232 OBSERVACIÓN: ...................................................................................................................................................... 233 51) "Interrupts" en el lenguaje C, variación............................................................................................................. 234 MANEJO DE TIMERS ......................................................................................................................................... 234 52) LIBRERÍA DE TIMERS: timersJ.c ..................................................................................................................... 234 COMENTARIOS a ["Labs-C\Lab2\TimersJ\timersJ.c"]: ......................................................................................... 238 COMENTARIOS a ["Labs-C\Lab2\TimersJ\timersJ_.h"]:....................................................................................... 242 53) EJEMPLO #1 USANDO LIBRERÍA DE TIMERS .............................................................................................. 243 COMENTARIOS a ["Labs-C\Lab2\TimersJ\100TimedFlash8.c"] ........................................................................... 245 54) EJEMPLO #2 USANDO LIBRERÍA DE TIMERS .............................................................................................. 247 COMENTARIOS a ["Labs-C\Lab2\TimersJ\110TimedFlash8X4.c"]: ..................................................................... 249 SERIAL COMMUNICATIONS & DATA QUES IN C............................................................................................ 250 55) LIBRERÍA DE COMUNICACIONES SERIALES............................................................................................... 250 COMENTARIOS a ["Labs-C\Lab3\SciComm\SciComm.h"]:................................................................................... 253 56) Send 'A' to 'z' letters for ever, to PC ................................................................................................................... 257 COMENTARIOS a ["Labs-C\Lab3\SciComm\1tstXmt.c"]: ...................................................................................... 258 57) SEND STEP BY STEP (IRQ) 'A' TO 'Z' LETTERS TO PC & SHOW IN LEDS ................................................. 258 COMENTARIOS a ["Labs-C\Lab3\SciComm\1tstXmt-Leds.c"]: ............................................................................. 260 58) ECHO ................................................................................................................................................................. 262 COMENTARIOS a ["Labs-C\Lab3\SciComm\2echo1.c"]:....................................................................................... 263 59) ECHO USANDO INTERRUPCIONES............................................................................................................... 263 COMENTARIOS a ["Labs-C\Lab3\SciComm\3echoInt-1.c"]: ................................................................................. 265 60) ECHO CON INTERRUPCIONES, ASCIZ STRINGS ......................................................................................... 266 COMENTARIOS a ["Labs-C\Lab3\SciComm\3EchoInt-2Z.c"]: .............................................................................. 268 61) COLAS DE DATOS ............................................................................................................................................ 268 COMENTARIOS a ["Labs-C\Lab3\SciComm\Que.h"]: ........................................................................................... 270 62) COLAS DE DATOS: DEMOSTRACIÓN............................................................................................................ 274 COMENTARIOS a ["Labs-C\Lab3\SciComm\4quetst.c"]:....................................................................................... 276 63) CHAT POR INTERRUPCIONES, USANDO COLAS......................................................................................... 277 COMENTARIOS a ["Labs-C\Lab3\SciComm\6ChatInt.c"]: .................................................................................... 280 COMENTARIOS a ["Labs-C\Lab3\SciComm\6ChatInt_.h"]:.................................................................................. 282 COMENTARIOS a ["Labs-C\Lab3\SciComm\4InputPushButtons_.h"]:.................................................................. 283 64) BIG CHAT, INTERRUPCIONES, COLAS ......................................................................................................... 284 COMENTARIOS a ["Labs-C\Lab3\SciComm\6ChatInt-4.c"]:................................................................................. 286 FIN DE LA OBRA..................................................................................................................................................... 288 PRÓLOGO CoDiDac, Computador Digital Didáctico: In Memoriam. ABSTRACT H ABIENDO dictado clases de Arquitectura del Computador a Ingenieros, sin solución de continuidad desde 1971 (43 años y contando) y –especialmente desde 2003 en la USB– donde se emplea para las prácticas la familia de 8 bits de Motorola/Freescale, he acumulado notas y ejercicios que resultan de utilidad en mis clases, pues mi propósito es el de ir más allá de los conocimientos teóricos impartidos en los planes de estudio, que suelen tratar estos temas a nivel de sistemas, y omiten referirse en estricto detalle y en profundidad, al hardware, al software, a los algoritmos y a los problemas prácticos. No hay forma tan eficaz como Aprender Haciendo (¡ah!), pero ese método no está exento de problemas; por ejemplo, para aplicarlo al estudio de los microcontroladores se requiere manejar con precisión una enorme cantidad de detalles. Esa es la diferencia fundamental entre el enfoque del aula y la aproximación del laboratorio; y entre comprender los conceptos u ostentar destrezas de Maestro. Estas notas pretenden mostrar una pequeña pero importante fracción de esos detalles, que se necesitan para la lograr Maestría en la praxis, y que no se consiguen con facilidad ni en claustros ni en bibliotecas. De hecho, en relación a los últimos exponentes de la familia de microcontroladores que nos ocupa –la MCS08– solo se ha identificado en el mercado un de libro de texto, de calidad precaria. Desde luego, siendo ilimitado el ámbito de lo que desconocemos, este aporte siempre resultará insuficiente, con independencia de su extensión… y otro autor habrá, que aparezca calificando también nuestro esfuerzo de insuficiente. De eso estoy conciente; este es un trabajo en evolución. Habrá que añadir la utilización de más periféricos (aunque los tiempos en mis asignaturas no dan para más dispositivos…) y técnicas avanzadas que incluyan la aproximación a los Sistemas Operativos… 5 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 INTRODUCCIÓN ARQUITECTURA DEL COMPUTADOR: QUÉ DEBEMOS ESTUDIAR Es bastante conocida mi polémica opinión en relación al objetivo de nuestro plan de estudios. Para comenzar, permítaseme incluir una paráfrasis: ¿Qué diríamos de un médico eminente que aplica los últimos desarrollos de la instrumentación electrónica para diagnosticar acertadamente la plétora de pacientes que lo agobia, y que prescribiendo los más modernos antibióticos de última generación logra mantener a raya difíciles y dolorosas enfermedades; que interviene mediante técnicas no invasivas a quienes no responden a los medicamentos apropiadamente, para rescatarlos de la enfermedad y devolverles la salud… pero que no tiene sino una superficial – aunque a todas vistas suficiente– idea de los increíbles principios físicos que hacen funcionar el “scanner”… y sus conocimientos de la maravillosa química que se sintetiza en las cápsulas no le permitirían dirigir un laboratorio químico, y que es completamente lego en relación al genial concurso que hacen la micromecánica, la óptica y la electrónica, aplicadas a las miniaturas que usa como utensilios quirúrgicos? Casi todos podríamos convenir en que es un excelente médico; sus pacientes lo veneran, los enfermos hacen lo imposible por ser atendidos por él pero, probablemente… de sus manos no salga el próximo CAT ni el tan esperado antibiótico final, y tampoco lo contratarán para diseñar el Escalpelo del Futuro. No será un investigador, ni electrónico, ni químico, ni micro-mecánico. Eso sí, está al tanto de conseguir y emplear las últimas y más certeras herramientas que se ofrecen para el ejercicio de su profesión Médica. Bueno, subyace aquí un dilema que en realidad parece sencillo: Qué queremos como egresado, ¿un científico, o un Ingeniero? Es claro que el título que concedemos es el de Ingeniero... Un ingeniero resuelve problemas en la industria (ojalá que los de la USB lo hagan en Venezuela), empleando dispositivos especializados y complejos, como el Core i7 de Intel, por ejemplo, que cuando lo aplicamos no tenemos sino una muy aproximada idea de cómo está hecho un quadra-core. Un ingeniero hace eso: aprende a usar la tecnología como herramienta en su trabajo diario, y sé que no hay muchos de nosotros que pudiéramos diseñar un Core i7 de Intel, ni ello resultaría tampoco en gran beneficio, pues Intel no adolece de profesionales en esa área… y Venezuela no tiene como prioridad preparar ingenieros para trabajar en Intel… Quiero hacer las siguiente citas: "A good Scientist is a person with original ideas. A good ENGINEER is a person who makes a design that works with as few original ideas as possible. There are no prima donnas in engineering. - Freeman Dyson (Nanotechnology father)" cfr en.wikipedia.org/wiki/Freeman_Dyson Uno puede estar de acuerdo con esta afirmación o rechazarla, pero todos debemos convenir en que, sí, es cierto que el ámbito de acción de un científico no es el mismo que el de un Ingeniero, y que nuestra carrera es la de Ingeniero Electrónico, no la de científico; ni siquiera la de profesor o docente. Parafraseando a Martin Grimheden, del Royal Institute of Technology, que habla de la “legitimidad” de la profesión: “The question of legitimacy is defined as ‘the relation between the actual outcome of the educational efforts undertaken by the university, and the actual demands that are put on the students’ abilities by the society and/or industry at the end of the students’ education.’ To simplify: ‘Why teach [electronics]?’ and ‘What does the industry want?’ ” (Y yo preguntaría: qué quiere NUESTRA industria, para empezar...) El artículo profundiza sobre “knowledge and skills” (teoría y práctica): “industries hiring [electronics] engineers, search for functional skills rather than formal knowledge” 6 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 En nuestro país, y en Latinoamérica, probablemente también sea así. Y, a lo mejor, ¡con los INGENIEROS, en general, ocurra de la misma manera en todas partes! Solo cuando pretendemos que los egresados funjan más bien como Docentes o Investigadores, todo queda medio al revés. O cuando ignoramos el patrón que –para Ingenieros– usan tan prestigiosas instituciones como Stanford y el MIT. En nuestra profesión NO se puede conocer un poco de todo; hay que saber USAR ese “todo”. En 40 años de ejercicio profesional, y en mi transición desde profesor hasta el área de proyectos: ingeniero, gerente, vicepresidente de R&D, y en el perfil de empleado y de empresario (! y, de nuevo, a profesor !), en las compañías en las que me desenvolví, contraté –o trabajé con ellos– a más de 100 ingenieros, casi todos electrónicos, la mayoría de la USB (NB: Cuando la profesora Trina Adrián de Pérez hizo la última encuesta externa para cambio de currículum, en mi empresa yo empleaba, simultáneamente, 44 ingenieros electrónicos, 40 de la USB)… Algo conozco del tema, y mi reales que me ha costado. OBSERVACIÓN: Yo podría ser el profesor activo de la USB, que más egresados de electrónica ha contratado…) Para mí, el que diseñaba amplificadores operacionales, microprocesadores, modems, e incluso dispositivos de más alto nivel… no estaba capacitado para lo que yo necesitaba. Yo requería siempre al que sabía ENCONTRAR y EMPLEAR las herramientas más apropiadas de la tecnología. p Como integrador de sistemas, nunca reinventé la rueda, y me fue bastante bien... ¡en Venezuela! Con lo anterior como premisa, yo abogo por un estudio de la Arquitectura (y… los Sistemas Operativos, y TODO lo que enseñamos) orientado hacia la APLICACIÓN de la tecnología a la solución de problemas. Si alguien quiere diseñar (micro)procesadores en Intel, se encontrará con que las plazas ya están copadas, que ese desarrollo lo hacen científicos –rara vez Ingenieros– y que son como 1,000 especialistas en todo el mundo. Enfoque Este libro no estudia cómo diseñar una computadora, y hace énfasis en cómo están diseñadas, no en por qué están diseñadas así. Observaciones sobre algunos términos empleados Siendo el Inglés la lingua mater de la investigación electrónica, resulta en ocasiones difícil elaborar en esa materia un texto en Castellano que no incurra en anglicismos, y aquellos que lo hacen pueden terminar en un galimatías. Recuerdo algún impreso que traducía “Master Clear” como Limpieza Maestra o, peor, “buffer” como Memorias Tampón, y “flip-flops” por básculas. Además, he empleado la primera forma verbal en vez de la tercera, clásica y neutra, porque esto no es una tesis ni un reporte de investigación, y puedo darme el lujo de acercarme al lector hablando en primera persona. El estudiante se abstendrá de imitarme hasta que tenga tantos años como yo. Ing. Luis G. Uribe C. Caracas, marzo de 2014. 7 1 Capítulo I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 VON NEUMANN COMPUTERS “Quien no conoce la historia está condenado a repetirla”, Jorge Santayana. INTRODUCCIÓN L AS computadoras actuales (a mí me encanta nombrarlas en femenino) pueden separarse en términos prácticos en dos clases: Harvard y Von Neumann. Muchas otras divisiones coexisten, como CISC y RISC, siendo las más utilizadas en la actualidad, por su cantidad, las CISC tipo Von Neumann, aunque yo he trabajado con algunos RISC tipo Harvard: los Microchip de la familia 16Fxxx. La familia HC9S08 que nos ocupa es CISC de tipo Von Neumann, con instrucciones primordialmente de una dirección, orientadas a Acumulador, como ya veremos. QUÉ ES UNA INSTRUCCIÓN A las computadoras hay que programarlas, o instruirlas para que realicen sus actividades. Entienden, para ese fin, un idioma propio de cada una de ellas, o Lenguaje de Máquina (Machine Language). El programador emplea INSTRUCCIONES, compuestas de: a) una Voz Imperativa o COMANDO, que le indica a la computadora qué operación hacer en un instante determinado y b) la identificación de los operandos que deben participar en la acción. Desde que estudiábamos aritmética aprendimos que un cálculo largo, que incluía una cierta cantidad de valores (operandos) y distintas operaciones aritméticas sobre ellos, podía descomponerse en una secuencia más simple de operaciones que incluyeran, como máximo, dos (2) operandos; así, por ejemplo: F = A*B‐C/D se realiza como: T1 = A*B; T2 = C/D y F = T1 ‐ T2. o: F = A*B; T = C/D y F = F ‐ T (se emplea una variable temporal menos) A las operaciones aritméticas elementales se las conoce como “binarias”, o “diádicas”, porque emplean dos operandos sobre los cuales actúan para producir un (1) resultado. Lo mismo ocurre con los operadores booleanos. Y aquellos que requieren un solo operando, como “-5”, o “~B” (unarias o monádicas) son un subconjunto y pueden recomponerse como binarios o diádicos así: “0-5” o “~(0|B)”. 8 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 Una computadora tiene una unidad Lógico Aritmética (ALU: Arithmetic-Logic Unit) cuyos circuitos realizan operaciones de suma y resta; otras incluyen multiplicación y división y, las menos, soportan -además de números Enteros– representación en “punto flotante” para números Reales. Para identificar los Comandos hay que codificarlos. Eso significa que si hubiera, por ejemplo, solo dos operaciones (dos Comandos), podrían representarse con un bit que, estando en 0 designaría al primero de ellos, y si valiera 1 señalaría al otro. Si la Unidad de Control debe identificar 250 comandos dentro de instrucciones (esa es la cantidad que tenía el antecesor del HCS08) se necesitarían 8 bits, lo cual resulta muy conveniente pues 8 son los bits que definen UN Byte, que es la cantidad mínima de bits que se manipulan de una sola vez en las computadoras modernas. Cuando tenemos procesadores con una cantidad superior a 256 Comandos (tal es el caso del nuevo HCS08) podrían codificarse con dos (2) bytes pero, como con 16 bits pueden individualizarse 65,536 entidades diferentes, se ve que habría una holgura innecesaria (dado que los computadores actuales no tienen más de alrededor de 1,000 y pico de códigos de operación, como orden de magnitud). Entonces se usa un truco que codifica los Comandos más empleados con un (1) byte, y para el resto usa dos (2): uno llamado “Escape”, que advierte al procesador que, a continuación, viene otro byte que es el código que en realidad identifica ese comando que por tanto requiere 2 bytes. Esta es la aproximación que se usa en el HCS08, y desde luego también en Intel. Así que para ver cuántos bits conforman una Instrucción tenemos que, para el Código de Operación, o Comando (OpCode), se necesita un (1) byte. Veamos ahora qué se requiere para codificar los operandos, que residen en la Memoria. La Unidad de Control debe instruir a la memoria para que le suministre a la ALU cada operando, y para que reciba y almacene el resultado. La Memoria o unidad de almacenamiento puede caracterizarse como un dispositivo con dos dimensiones: qué se va a guardar (el Valor de la variable, consideradas ellas en el sentido que se les atribuye al estudiar algún lenguaje de programación), y dónde se va a almacenar ese valor: Valor y Dirección. Sobre el Valor ya nos hacemos una idea de a qué corresponde. En cuanto a la Dirección, las celdas o posiciones de memoria se numeran normalmente comenzando desde 0, en incrementos comúnmente de a uno, y cada una resulta así identificada mediante ese número, que se conoce como la Dirección de la celda. Cuando decimos que debemos leer una variable (para hacer un cálculo), en lenguajes de programación solemos identificarla mediante un Nombre, pero a la computadora hay que señalársela mediante un número, la Dirección sobre la cuál la memoria debe operar para entregarnos dicho Valor. Así, una Variable identificada por el programador mediante un Nombre, desde el punto de vista de la computadora equivale a una Dirección y un Valor. Entonces, ¿cuántos bits se requieren para identificar el Valor, y la Dirección? Ese número no tiene por qué ser el mismo para ambos casos. Para el Valor, la mayoría de los MCUs usan un (1) byte. Máquinas más nuevas emplean dos (2) bytes (16 bits), cuatro (4) bytes (32 bits); ahora se van imponiendo ocho (8) bytes (64 bits) y ya llegarán hasta 16 bytes (128 bits). Ya eso me parece muy improbable. Ahora, con una máquina común de 32 bits, si cada dato es un número, éste puede representar enteros hasta 2^32 = 4,294,967,296 o, si llevan signo: de +2,147,483,647 a –2,147,483,648. 9 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 En el caso de las Direcciones se necesitan 4 bytes. Si tenemos dos (2) operandos que suministran valores, y uno (1) para el resultado, y cada uno de ellos precisa cuatro (4) bytes para su identificación, el número de bytes de una instrucción sería de: 1 (OpCode) + 3 * 4 (Addresses) = trece (13) bytes. Note que como son tres (3) operandos, a este tipo de computadoras se las conoce como Máquinas de Tres Direcciones. Hay Instrucciones que tienen menos direcciones, como “BORRE” (Clear; V=0), que tiene UNA sola dirección; y “HALT”, que no tiene ninguna. MÁQUINAS DE 3, 2, 1 Y 0 DIRECCIONES T RECE (13) bytes son demasiados. Cada vez que se ejecuta una Instrucción, el CPU necesita leer todos los bytes que la componen. Para obtener un alto rendimiento se requiere que se lea la Instrucción con la menor cantidad de pasos posible; ojalá UNA sola operación. Para esto, el “Bus” en el que se transfiere la información tendría un ancho exagerado, lo que resultaría costoso, además de oponerse a los espacios pequeños que requieren los dispositivos modernos. Por tanto, es importante ver cómo pueden eliminarse “campos” o elementos de la Instrucción. Recuerden que con la codificación del Comando hay poco que hacer, según acabamos de describir. El primer paso en este sentido se dio haciendo que uno de los 3 operandos de la Instrucción fuera, al mismo tiempo, fuente de información y resultado; algo así como: A = A*B. Eso, en realidad, elimina un operando (cuatro [4] bytes). Para que el sistema funcione hay que agregar una instrucción que permita inicializar una variable con el contenido de otra; esta es un MOVE. F = A*B‐C/D se realiza ahora como: T = C (MOVE TC; una sola Dirección); T = T/D; F = A (MOVE FA); F = F*B; F = F‐T A este tipo de computadora bien podría llamársela Máquina de Dos Direcciones. El segundo paso para eliminar de la Instrucción otro operando consistió en inventarse un dispositivo de almacenamiento que al mismo tiempo fuera fuente de información y albergue del resultado; algo así como: Acc = Acc * B en donde Acc, conocido como Accumulator, W [Working Register], H [Holding Register…] es un registro dentro del CPU, y es un elemento TÁCITO, que no requiere identificarse porque es el único que hay, con lo cual, la instrucción anterior sólo tiene UNA Dirección. A estas computadoras se las conoce como Máquinas de Acumulador, o, naturalmente, Máquinas de UNA Dirección. Para que funcione el sistema hay que hacer que el Move anterior pueda llevar datos DESDE la memoria hasta el Acumulador, y desde éste HACIA la memoria. Como ahora son dos operaciones, reciben nombres diferentes: LOAD (AccMemory) y STORE (Memory Acc). Nuestro ejemplo de siempre quedaría resuelto de la siguiente manera: F = A*B‐C/D, en una máquina de Acumulador (Una Dirección): LOAD C; (Acc = C). Obsérvese que no se menciona el Acc; solo “C” DIVIDE D; (Acc = Acc/D: C/D) STORE T; (T = Acc: C/D) LOAD A; (Acc = A) MULTIPLY B; (Acc = Acc*B: A*B) SUBTRACT T; (Acc = Acc‐T: A*B‐C/D) STORE F; (F = Acc: A*B‐C/D) 10 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 Éste último resultó ser el sistema más eficiente en relación a los parámetros referidos al principio, y es el que más se usa en la actualidad: Instrucciones de Una Dirección; Máquinas de Acumulador. Finalmente, se forzó la barra y existen Máquinas de Cero Direcciones. Los computadores HP fueron sus exponentes más sobresalientes. Una aplicación muy generalizada de esta aproximación de Cero Direcciones la encontramos en la evaluación automática de expresiones, principalmente como parte de los Compiladores. La notación más divulgada se conoce como RPN, Reverse Polish Notation. Los tipos generales de Direcciones: inmediata, directa, indirecta, modo registro, indirección con registros, desplazamiento, stack, basadas, segmentadas, deben consultarse en las referencias genéricas. HCS08 ARCHITECTURE: PROGRAMMING MODEL E L correspondiente diagrama puede verse en el Reference Manual del microcomputador: CPU08RM.pdf, página 15. Se tiene el Acumulador A, un registro de 8 bits con las características que acabamos de ver para las Máquinas de Una Dirección. Existe un registro Índice (H:X) de 16 bits, que fundamentalmente sirve como apuntador (pointer, como en C); es decir, alberga: no el valor de una variable, sino la dirección de esa variable (exactamente como en C). Se pueden realizar operaciones que mimeticen las equivalentes de alto nivel, tales como: (*p se refiere al H:X): *p = Acc; Acc = *p; *p++ = Acc; Acc = *p++; p = *p; jump *p; ... y similares. Está también el Stack Pointer SP de 16 bits, que permite realizar el protocolo para la ejecución de Subrutinas o Funciones, así como el de las Interrupciones, y sirve para establecer la existencia de variables dinámicas, como en el C, que se materializan con la llamada de una función y desaparecen a su finalización. Finalmente se encuentran otros 2 registros de uso más específico que los 3 anteriores: el PC o Program Counter, de 16 bits, que en principio identifica la instrucción que habrá de ejecutarse a continuación, y el Processor Status Word, cuyo nombre fue cambiado a CCR: Condition Code Register; es de 16 bits y almacena banderas (flags) individuales que identifican el estado del procesador en un instante dado, y facilitan el control del flujo del programa (saltos condicionales, habilitación de interrupciones). El uso en detalle de todos estos elementos del MCU lo veremos más adelante como parte del análisis y desarrollo de la secuencia de programas, objetivo principal de este texto. MODOS DE DIRECCIONAMIENTO EN EL MC9S08 (CPU08RM Reference Manual) El microprocesador HCS08 que se escogió en la USB para incluirlo como elemento de trabajo en sus laboratorios de electrónica es casi perfecto para estudiar la Arquitectura de las Computadoras, por sus 11 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 enriquecidas características, casi todas tomadas de las minicomputadoras DEC de los 70s y siguientes. (Motorola no fue la única en adoptar tan bella tecnología de las PDP-11; Intel incluye más conceptos aún en sus microcomputadores). Ya vimos que, a diferencia de muchos dispositivos similares de sus competidores, tienen un repertorio de instrucciones poderoso, variado y numeroso (no como las 35 instrucciones de los 16F8Xx). En cuanto a los modos en que emplea las direcciones, hay 17 distintos. Aquí solo se enumeran; enfóquese para su estudio en el Manual de Referencia del CPU: “CPU08RM”. El uso en detalle de todos los modos de direccionamiento lo veremos más adelante como parte del análisis y desarrollo de la secuencia de programas que presentaremos. 1) Inherent (no addresses) 2) Immediate, 8 bit constants 3) Immediate, 16 bits constants 4) Direct 8 bits (ZERO page) 5) Extended 16 bits; "NATURAL" addressing mode: ABSOLUTE 6) Indexed no offset 7) Indexed, 8‐bit offset 8) Indexed, 16‐bit offset 9) Indexed no offset with (Index register) post increment 10) Indexed, 8‐bit offset with (Index register) post increment 11) SP, 8‐bit offset 12) SP, 16‐bit offset (no existe SIN offset) 13) Relative (not for DATA! Only for CODE Branches) Memory‐to‐Memory, M‐M (Only advanced++ ): 14) M‐M: Immediate to Direct 15) M‐M: Direct to Direct 16) M‐M: Indexed to Direct, with (Index register) post increment 17) M‐M: Direct to Indexed, with (Index register) post increment VARIACIONES Y OPTIMIZACIONES Para optimizar el rendimiento del HCS08, maximizando velocidad y minimizando uso de memoria, se incluyeron varias excepciones a la arquitectura convencional; primero, en el manejo de la memoria. Normalmente la memoria de una computadora tipo Von Neumann consiste, como ya indicamos, en una colección de celdas o elementos almacenadores, de información y programas, colocados en Secuencia Lineal. (La unidad de almacenamiento de las máquinas tipo Harvard tiene la particularidad de que la sección que podemos usar para guardar datos emplea su propio conjunto de direcciones, separado del grupo de direcciones de la memoria en la que se guardar el programa. Cada una de ellas puede verse como un arreglo lineal, pero no están contiguos el uno junto al otro. Es decir, que si por ejemplo, se recorre la RAM o memoria de almacenamiento para variables, y se llega hasta el final, la próxima posición NO es la del código del programa. En las máquinas tipo von Neumann hay UN solo 12 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 campo de direcciones. Al recorrerse todas las posiciones en secuencia, se habrán visitado tanto las variables como el programa). Si hay 2^16 = 65536 posiciones (64 Kbytes) necesitamos desde luego 16 bits de Direccionamiento para cada unidad elemental de almacenamiento, que en nuestro caso es el Byte (8 bits). Motorola dividió la memoria, y a la primera sección, compuesta de 256 posiciones (0x0000 a 0x00FF) la llamó Direct Page (Z RAM). Desde luego, si únicamente hubiera este grupo de unidades de memoria, sólo se necesitaría 1 Byte (8 bits) para identificarlas, en lugar de los 16 bits que se requerirían si la memoria fuera mayor. El truco consiste en multiplicar por 2 los códigos de operación, de tal manera que se use, por ejemplo, Add0 para identificar la suma (Add) de una variable que está en Direct Page, y que por lo tanto puede identificarse con un (1) solo Byte, y Add1 para indicar la suma de una variable que no está localizada en Direct Page, y para la cual se necesitan dos (2) Bytes. Así, si se coloca en Direct Page la información más utilizada, se puede lograr una economía en el tamaño del programa, que requiere menos bytes para accesar esas posiciones, y tiene mayor velocidad, porque hay que cargar un byte menos. Si normalmente las instrucciones tienen un (1) byte para el OpCode y dos (2) para la dirección (total: 3), que demorarían en su manipulación “3 tiempos de lectura de Byte, tB”; el ahorro, al llevarlo a solo 2 bytes es de 1/3 en “tB”; así la velocidad aumenta en 50%: en el mismo intervalo de tiempo se ejecuta un 50% más instrucciones, si todos los accesos son en Direct Page. Así que podríamos decir que dentro del Código de Operación para el Comando Add, hay un bit que vale 0 o 1 e identifica si a continuación de ese OpCode vienen 1 o 2 bytes de dirección. Es interesante notar que el programa que nos ayuda a codificar a tan bajo nivel (inferior al C), llamado el Assembler, se encarga de averiguar la posición de las variables, de forma que el programador pueda usar un solo código, Add en este caso, y el Ensamblador incluye Add0 o Add1, según corresponda a la ubicación de la información, en Page-0 o no. El programador queda así liberado de llevar la pista a las variables. 13 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 CODIDAC, VON URIBE’S COMPUTER LA ARQUITECTURA Consola de operador, con teletipo o télex (Olivetti, Siemens), para el suministro de datos y programa mediante el teclado, e impresión de resultados en papel. Automatización vía lector adjunto de cinta de papel. Tablero para despliegue de información interna: registros, direcciones, instrucciones, botonera para encendido y demás funciones no realizables desde el teletipo. LAS INSTRUCCIONES 14 instrucciones: Comandos para lectura de datos, impresión, 4 operaciones aritméticas, toma de decisiones y bifurcación en el flujo de control, desplazamientos de información a derecha e izquierda, movimiento de datos entre diversas posiciones de memoria, borrado, espera y parada… codificadas en castellano. El CODIDAC debía realizar operaciones similares a las del FORTRAN. El CODIDAC se diferencia de otros computadores porque tiene Clases de Información definidas a nivel de máquina. En los computadores tipo Von Neumann la naturaleza de un elemento de información no le es inherente; el significado se lo proporcionan las instrucciones que lo manipulan. Así, al sumar A con B, la instrucción de suma les otorga significación numérica a las variables; si hubiera a lo mejor texto en ellas, el computador no tendría forma de saberlo… Por ejemplo, para el CODIDAC la instrucción que desplaza información lo hace de una manera si el operando es numérico y de otra muy diferente si representa un texto. En caso de números, el resultado siempre es un número; desplazar un número negativo produce siempre otro número negativo. En caso de textos, el resultado siempre es un texto. ¡Incluso las Instrucciones pertenecen a una Clase! lo que permite realizar sobre ellas operaciones aritméticas, en los campos de las direcciones de los operandos (numéricos), para facilitar el manejo de subíndices, estructuras de datos, etc. LOS MODIFICADORES Un modificador cambia las operaciones, de simples a Repetitivas. Una sola instrucción de máquina, como IM: IMprima, arroja un único valor, o un Rango de resultados (IR: Imprima Repetitivamente). EL SUBSISTEMA DE ENTRADA Y SALIDA Forma parte esencial de la Arquitectura del CPU, con instrucciones específicas, más poderosas que el INPUT o PRINT del Basic y superiores al IN y al OUT de los microprocesadores. CÓDIGOS DE OPERACIÓN Como el aspecto didáctico es preeminente, los códigos tienen una base mnemónica de fácil comprensión y recordación: Cuando dos palabras designan un comando, se toma la primera letra de cada una para conformar dicho código. Por ejemplo, "Acepte Información" se codifica como "AI", y las letras 'A' e 'I' se almacenan en la memoria, en código baudot de 5 bits. 14 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 Si el comando tiene una sola voz imperativa, como "SUME", el código se conforma como "SU", utilizando sus 2 primeras letras. Si se requiere el modificador de iteración, se reemplaza la segunda letra por una R: AR es Acepte información de manera Repetitiva (desde, hasta), SR totaliza un área completa de memoria, etc. EL EDITOR Para cargar el programa hay un editor de texto compuesto por circuitos (no es software). Para invocarlo existe el interruptor "Función Programa", y el "Botón de Corrección" para corregir errores tipográficos. EL FORMATO DE LOS NÚMEROS La información numérica se representa en BCD, en “signo y magnitud”, y es de "formato fijo": El número de decimales se preestablece mediante selectores en la consola: desde 0 hasta 11 decimales. Cada palabra de memoria almacena 12 dígitos BCD, o 16 letras. La unidad de salida imprime la “Coma” de los números en la posición decimal designada, y obviaba la escritura de ceros no significativos, es decir, ¡el FORMAT está también incluido en la electrónica! LA CALCULADORA El CODIDAC puede operarse como calculadora, gracias al selector de Operaciones Inmediatas. Así, no hay que elaborar un programa para realizar operaciones aritméticas. Ningún otro computador tiene esta característica. LA ESTRUCTURA La máquina es del tipo serial, con lógica secuencial convencional, no microprogramada. Posee selectores que permiten trabajar a máxima velocidad (¡600 KHz!) o en animación lenta para permitir el seguimiento del flujo de información. Tiene un interruptor de paso a paso; uno para Detener la operación del programa en cualquier instante, otro para Continuarla, y el Master Reset. DATOS ESTADÍSTICOS Está compuesto por más de 4.000 circuitos integrados TTL, 300 transistores, 200 tarjetas de circuito impreso; 160.000 puntos de soldadura, 155.000 agujeros perforados con taladro de mano; 16.000 cablecitos de alambre telefónico (2.500 metros) y más de 5 kilos de soldadura de estaño. Diez fuentes de alimentación, de 5 Voltios a 10 amperios, más fuentecitas variadas para manejar las luces de neón, el teleimpresor, etc., alojadas en un gabinete independiente. Dos disipadores principales tienen como 1,70 metros de altura c/u. El consumo está por encima de los 3 kilovatios y para controlar la generación de calor hay una pequeña turbina eléctrica que fuerza la circulación de aire en el gabinete principal, así como un poderoso ventilador situado en la base del gabinete de alimentación. CIRCUITOS IMPRESOS En los primeros dibujos para obtener los negativos fotográficos se emplearon "rapidógrafos"; luego se usaron cintas adhesivas, como “pistas”, y “ojetes” para las perforaciones. 15 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 FOTOS 1) VISTA GENERAL A la izquierda se ve la consola formada por la mesa, el teclado y el impresor (télex o teletipo) al que se le alcanza a ver el rollo de papel de impresión y el perforador de cinta de papel, adosado al lado izquierdo. Además, el lector de papel. A la derecha se observa el panel de control (botonera) y despliegue de información (luces). El gabinete principal es el que está a la derecha, con 4 racks (se ven dos, uno arriba del otro). En el de abajo alcanza a verse la memoria de núcleos magnéticos de ferrita. El tercer gabinete, pequeño, situado entre los otros dos, alberga las fuentes de alimentación. 16 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 2) VISTA GENERAL DESDE OTRO ÁNGULO En el gabinete de las fuentes se ve un panel de control, con interruptores y borneras de interposición, así como las protecciones para sobrevoltaje, tipo Crowbar (un SCR quema un fusible). 17 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 3) ACERCAMIENTO En esta foto no aparece el lector de cinta de papel. 18 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 4) MAYOR ACERCAMIENTO A LA CONSOLA 19 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 5) PRESENTACIÓN Le enseño el proyecto a mi padre, y a mi hermano que es el fotógrafo. 20 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 6) CONSOLA DE CONTROL Y TELETIPO 21 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 7) CONSOLA DE CONTROL, VISTA DE FRENTE Abajo a la izquierda, los contactores para encender el computador, y una luz piloto. A la derecha, en dos grupos, los interruptores; los 7 primeros de la izquierda sirven para determinar el número de decimales (0 a 11) con que trabajará un programa. A la derecha, la botonera de control: De izquierda a derecha, los principales son Inicie; Función Programa; Corrija una línea (borra y repite la entrada); Arranque; Pare; Continúe; Función Calculadora. Alcanzan a verse un botón para máxima velocidad, otro para baja velocidad, y uno para paso a paso (el ícono es una persona caminando). En la parte baja del panel vertical, el despliegue de bits de cada registro. Con el selector de la izquierda se escoge entre los registros A, B o C, o el de Instrucciones, para presentarlo mediante las luces de neón. La información entra por la derecha y se desplazaba secuencialmente hacia la izquierda. Más arriba se indica el número del paso del micro-secuenciador. La instrucción más larga tiene 18 pasos. La siguiente franja tiene los íconos que representan los catorce comandos: Acepte Información, Compare por Igualdad, Compare por Magnitud, Lleve, Sume, Reste, Divida, Multiplique, Desplace a la Derecha, Desplace a la Izquierda, Imprima, Borre, Espere y Pare. Los cuatro íconos de la izquierda muestran errores de overflow, división por cero y demás. Y a la derecha, la dirección de memoria que se está usando (000-999. Se está visualizando un 022) 22 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 8) GABINETE PRINCIPAL, VISTA ANTERIOR Cuatro Racks con conectores (peines). Caben hasta 17 por fila, pero no todas están completamente pobladas Algunas tarjetas son de doble altura (dos conectores). El rack inferior derecho es el de la memoria: los bastidores con los núcleos (entre láminas protectoras de acrílico), las tarjetas controladoras (corrientes y sensores), y una fila con 100 palabras de almacenamiento semiconductor, hechas con registros de desplazamiento normales que quedaron sin utilizar al terminar el diseño. 23 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 9) GABINETE PRINCIPAL, VISTA POSTERIOR Indicativo de la cantidad de cables de interconexión (no se utilizó “Mother Board”). Resaltan los sensores remotos (cinco y cinco, en acrílico blanco) que leen a distancia el valor de las 10 alimentaciones y los llevan hasta los reguladores. Así se garantizan el valor de los voltajes en el destino, a pesar de la considerable separación entre el gabinete de las fuentes y el de la electrónica. 24 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 10) GABINETE DE MEMORIA Los núcleos están localizados en cuatro (4) planos. Cada bastidor se alambró a mano, cruzando un hilo de Excitación (corriente) horizontal, llamado X, otro de Excitación vertical, Y, uno de Inhibición, para cancelar la excitación en las posiciones no seleccionadas, y el de Lectura. Los núcleos magnéticos son de 20 mils (milésimas de pulgadas de diámetro)… 25 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 11) ACERCAMIENTO A LA MEMORIA DE NÚCLEOS MAGNÉTICOS 26 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 12) DETALLE DE LA UNIDAD DE CONTROL Rack situado arriba a la izquierda en el gabinete. 27 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 13) DETALLE (POSTERIOR) 28 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 14) GABINETE DE FUENTES (VISTA ANTERIOR) Se ven abajo los transformadores (¡nada pequeños!); un poco más arriba, el ventilador para forzar el aire y, a continuación, las 10 fuentes. 29 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 15) GABINETE DE FUENTES (ACERCAMIENTO; VISTA ANTERIOR) 30 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 16) GABINETE DE FUENTES (VISTA POSTERIOR) Se aprecia uno de los dos disipadores, con 5 transistores de potencia (de los 10 que eran en total). Detrás de él se encuentra el segundo disipador. Puede observarse sus tamaños… 31 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 17) MÉTODO DE ELABORACIÓN DE CIRCUITOS IMPRESOS (1) Como ejemplo puede verse una sección de la Unidad de Control. 32 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 18) MÉTODO DE ELABORACIÓN DE CIRCUITOS IMPRESOS (2) Se escoge una parte de la sección de la Unidad de Control, la que va a colocarse en una tarjeta de circuito impreso. Se muestra la parte seleccionada, el dibujo tinta china y plumilla, hecho sobre lámina flexible de acrílico (de las que se usan para el retroproyector), y el negativo fotográfico que se aplica sobre la tarjeta de cobre para realizar la exposición, utilizando lámparas de luz muy fuerte. 33 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 19) MÉTODO DE ELABORACIÓN DE CIRCUITOS IMPRESOS (3) Ejemplo de una tarjeta ya poblada de componentes (master clock, 16 fases…). Vista anterior. 34 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 20) MÉTODO DE ELABORACIÓN DE CIRCUITOS IMPRESOS (4) Master clock, vista posterior. Todas las tarjetas son de dos caras. 35 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 21) FORMATO DE LAS INSTRUCCIONES Puede verse la codificación de algunas instrucciones (así se escriben en el programa y así quedan en la memoria): Acepte Información (AI), Acepte información de manera Repetitiva (AR), Imprima (IM), Imprima Repetitivamente (IR), Sume (SU), Sume Repetitivamente (SR, que totaliza un bloque de datos contiguos), Reste (RE), Multiplique (MU), Divida (DV; es la única excepción de la codificación, porque DI es Desplace a la Izquierda…). Ing. Luis Guillermo Uribe Cataño Revisión de Diciembre de 2012. Resumen de lo publicado por la Javeriana, 2011, y por Tekhne, en la UCAB. 36 2 Capítulo I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 ASSEMBLER HCS08 "Perfection is finally attained, not when there is no longer anything to add, but when there is no longer anything to take away..." (Antoine De Saint-Exupéry) EJERCICIOS BÁSICOS EN ASSEMBLY LANGUAGE P ARA comprender los aspectos más importantes de una computadora es imprescindible dar una mirada a la manera como el programador le transmite lo que ella debe hacer, cómo se la programa en forma nativa: su Conjunto de Instrucciones, cómo direcciona la memoria, cómo usa el Stack, qué recursos tiene, tales como registros índice, Stack. En nuestro empeño por apoyarnos en el MC9S08QE128 de Freescale instalado en la tarjeta de desarrollo DEMOQE128, hemos elaborado una serie de programas ilustrativos que presentamos a continuación. Estos ejercicios se dividen en 4: Introductorios; referentes a Timers, en donde explico la librería que suministro siempre con mis cursos, para estudiarla y usarla; Comunicaciones con el PC, usando el SCI (RS-232); aquí también analizamos una librería que desarrollé para mis cursos, la estudiamos con detalle y también debemos poder usarla en nuestros ejercicios y proyectos. Finalmente, ejercicios varios. 1) Primer Programa para el HCS08: MINIMUM Program for HCS08 Este es el programa más pequeño que puede escribirse en el ambiente de desarrollo CodeWarrior (la versión que uso en la actualidad es: CodeWarrior 10.5. Tiene sus Bugs, algunos reportados por mí [enero de 2014], sin corregir, pero es lo que hay... NB: MUCHOS ambientes de desarrollo, en el área de MCUs, son de JUGUETE) El objetivo de este programa es verificar que su instalación del ambiente de desarrollo funciona, y los mínimos aspectos que debe incluir en sus programas. ["Laboratorios\Lab1\00a_L1.asm"] 01 ;******************************************************************** 02 ; 00a_L1.asm, MINIMUM Program for HCS08, Rev. F, V08J2012 M29E2013 03 ; Luis G. Uribe C., D27Y2007 J09A2009. For CodeWarrior 10.2 04 ; =================================================================== 05 ; Begin of Code Section 06 ABSENTRY Main ; Export symbol (DEBUGGER entry) 07 08 ORG $2080 ; ORG? Origin. Next code (bra) is located 09 ; ..at this address! 37 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 10 ; $2080 is HCS08 ROMStart (Flash) 11 ; Main: symbolic for address $2080 (bra) 12 Main: bra * ; Branch to himself (BRA: Branch Always; *: here!) 13 14 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 15 nop ; <<<NEEDED by CodeWarrior 10.1&2 (not 6.3). INCREDIBLE<<< 16 ; This 'nop' MAY be removed for CW 6.3 ... 17 ;******************************************************************** 18 ; Interrupt Vectors 19 20 ORG $FFFE ; VECTOR holding address for HCS08 startup 21 DC.W $2080 ; ...(RESET) DC.W: Define CONSTANT Word 22 END ; END and next lines, removed by Assembler 23 24 Note: Nothing is read after END... (cfr. 'main.dbg') COMENTARIOS a ["Laboratorios\Lab1\00a_L1.asm"]: La FORMA y la PRESENTACIÓN del Código en nuestra área de trabajo o en cualquier otra, es FUNDAMENTAL para la vida de los proyectos. Los programas deben poder entenderse, por quien los hace, cuando vuelva a mirarlos, y por las personas que vengan después a modificarlos o repararlos. Para eso se requieren comentarios útiles, resaltantes y claros. Lo primero que hay que incluir siempre, en un programa, es el nombre del mismo, la fecha o fechas en los que se hizo, o modificó el programa. En este libro empleo abreviaturas y como las variaciones han sido cosméticas casi nunca las relaciono: 01 ;******************************************************************** 02 ; 00a_L1.asm, MINIMUM Program for HCS08, Rev. F, V08J2012 M29E2013 03 ; Luis G. Uribe C., D27Y2007 J09A2009. For CodeWarrior 10.2 En la primera línea: ; 00a_L1.asm, MINIMUM Program for HCS08, Rev. F, se coloca el nombre del programa, propósito del mismo, para qué plataforma y qué revisión (identificación que permite llevar la pista de las modificaciones que se le han hecho) Además, las fechas indican el comienzo del programa, cuándo se lo modificó y la datación de la revisión actual. Cuando es importante, hay que agregar una lista de fechas y cosas que se añadieron, quitaron o modificaron. También, las iniciales de la persona que hizo cambios en el código, para saber a quién preguntar si fuera necesario. 02 ; ..V08J2012 M29E2013 03 ; ..D27Y2007 J09A2009. For CodeWarrior 10.2 Este programa se comenzó el Domingo 27 de maYo de 2007; el jueves 09 de Abril de 2009 se lo adaptó al CodeWarrior 10.2 (ahora se sabe que funciona bien bajo la versión 10.5). Luego, el viernes 08 de Junio de 2012 se le hizo alguna modificación cosmética (porque no hay mayor reporte), y la última edición fue el martes 29 de Enero de 2013, hace ya un año. Nótese también la columna casi perfecta donde comienzan los comentarios. En Assembly Language se recomienda que el ";" que marca el comienzo de los comentarios se coloque EN la columna 32. A veces esto no es factible y hay que moverla, hacia atrás o 38 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 adelante, pero en general debe utilizarse esa regla. SUS PROGRAMAS TIENEN QUE HACERSE SIGUIENDO ESTE Y TODOS LOS LINEAMIENTOS. Cuando un programa se va a ejecutar (o, como todo el mundo dice, se va a CORRER), hay que indicarle al CPU dónde comienza el código. Cada procesador tiene su propio protocolo para esto. Por ejemplo, cuando se energizan los procesadores Intel, el PC (Program Counter) asume el valor 0xFFFF, para los micros de 16 bits, y 0xFFFFFFFF para los de 32 bits, como parte de la definición de su Estado Inicial (un valor de 0x0000, puros ceros, o 0xFFFF, puros unos, son cómodos de lograr, mediante las líneas apropiadas de los registros correspondientes: Clear o Preset, respectivamente). De esa dirección toma el CPU LA PRIMERA INSTRUCCIÓN que ha de ejecutarse. Como esa ubicación corresponde a la última posición de la memoria, seguramente que allí tiene el programador que colocar un JUMP hacia su verdadera primera instrucción. (Cómo puede colocarse una instrucción en la memoria, si recién se está energizando la máquina, y el estado inicial de la RAM es normalmente indeterminado, forma parte de la solución al problema denominado IPL: Initial Program Loader. Revise en la literatura del curso en qué consiste con exactitud ese problema, y cuáles son las soluciones. Una de ella consiste en que en la parte superior de la memoria de trabajo, volátil, se coloca una ROM, o memoria de sólo lectura, que contiene su programa, o un código destinado a “buscar” su programa y localizarlo en un sitio adecuado) En el HCS08, usted tiene que decidir dónde va a colocar la primera instrucción de su programa. Eso se hace así: Se le indica al CodeWarrior que la primera dirección de su programa es 'Main' (tiene que estar seguro de que esa sea la etiqueta apropiada dentro de su código) 06 ABSENTRY Main ; Export symbol (DEBUGGER entry) Luego se le dice que coloque el código de su primera instrucción, en la dirección Hexadecimal $2080: 08 ORG $2080 ; ORG? Origin. Next code (bra) is located ORG, por Origin, le indica al programa Assembler que el código que a continuación usted va a escribir, debe colocarlo de manera secuencial a partir de la dirección indicada; $2080 en nuestro caso (que para el HC9S08QE128 que empleamos, es la PRIMERA dirección de la memoria FLASH, que es su almacenamiento no volátil. Para una identificación precisa de las áreas ocupadas por la RAM y la Flash, remítase al manual MC9S08QE128RM (Reference Manual) Luego, el programador tiene que colocar su 'Main' (usted escoge a su gusto el nombre; no tiene que ser Main, siempre que use el mismo en todas aquellas partes en las que yo lo incluí en este ejemplo...) 11 ; Main: symbolic for address $2080 (bra) 12 Main: bra * ; Branch to himself (BRA: Branch Always; *: here!) Note que se enfatiza en que el símbolo mnemónico, alfabético, 'Main', tiene una equivalencia con el número $2080. Usted emplea 'Main'; el CPU usa $2080 (16 BITS EN BINARIO, ¡QUE ES LO ÚNICO QUE LAS COMPUTADORAS ENTIENDEN!) Ahora observe la línea 15; se ha colocado ese NOP porque se requiere en las versiones 10.x (al menos hasta la 10.5; NO era necesario en la versión 6.x y no recuerdo haberlo usado en versiones anteriores, las que comencé a usar por allá por la 3.x, hace eones) 15 nop ; <<<NEEDED by CodeWarrior 10.1&2 (not 6.3). INCREDIBLE<<< 16 ; This 'nop' MAY be removed for CW 6.3 ... 39 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 Si esta línea se omite, el Debugger produce un error al tratar de ejecutar el 'bra *' (salto infinito a sí mismo) de la línea 12. Esto es un error del CodeWarrior actual. Finalmente vienen los Interrupt Vectors de la línea 20. Resulta que, a diferencia de Intel que, como ya dijimos, inicializa sus PC para leer LA PRIMERA INSTRUCCIÓN de su programa SIEMPRE desde la última dirección de memoria, el Motorola/Freescale inicializa también su PC hacia la última dirección WORD (16 bits) de la Flash (16 bits que ocupan $FFFE y $FFFF), PERO ALLÍ LO QUE ESPERA encontrar es LA "DIRECCIÓN" DE LA PRIMERA INSTRUCCIÓN DE SU PROGRAMA. El mecanismo que se emplea para establecer este vínculo se forma de la siguiente manera: 06 ABSENTRY Main ; Export symbol (DEBUGGER entry) 08 ORG $2080 ; ORG? Origin. Next code (bra) is located ... 12 Main: bra * ; Branch to himself (BRA: Branch Always; *: here!) ... 20 ORG $FFFE ; VECTOR holding address for HCS08 startup 21 DC.W $2080 ; ...(RESET) DC.W: Define CONSTANT Word En 06 usted define la Etiqueta (Main) en la que va a comenzar su programa, e indica en qué posición (válida) de Flash la va a colocar (el resto de su código va a continuación!); luego en la 12 está su 'Main' y, en la 20 y siguientes se culmina el vínculo: cuando el PC asume su valor inicial, $FFFF, el CPU, que está en la fase de inicialización, usa ese PC para cargar un valor de 16 bits, que corresponde a la dirección de su primera instrucción. Reemplaza su $FFFF por lo que está almacenado allí (el número $2080 en el ejemplo) y de esa posición toma la primera instrucción que va a ejecutar, de SU programa. Al espacio en donde se colocan los valores de arranque (o RESET), y otros que luego estudiaremos, se lo conoce como el área de Vectores de Interrupción. Comienzan en la ÚLTIMA posición de memoria, y va creciendo HACIA ABAJO, de a dos posiciones, porque cada elemento de información allí almacenado corresponde a direcciones (de arranque, o de rutinas de interrupción: ISR), y las direcciones siempre tienen 16 bits. Todo programa finaliza con un END: 22 END ; END and next lines, removed by Assembler 24 Note: Nothing is read after END... (cfr. 'main.dbg') Se ha incluido ex professo la línea 24, con texto común y corriente, para enfatizar que, después del END del programa principal, el Ensamblador NO lee absolutamente nada más. Puede usarse, por tanto, esa área para incluir texto cualquiera, como comentarios. Tenga CUIDADO: Algunos ensambladores suspenden el análisis cuando encuentran CUALQUIER END. A veces, si a un INCLUDE FILE, similar al que ya conoce del C, se le coloca inadvertidamente un END, el programa queda procesado hasta allí, y con mucha probabilidad eso no es lo que el programador desea... 2) Segundo Programa para el HCS08: Init COP & STACK Hay un código mínimo que SIEMPRE debe incluir en sus programas, y se indica a continuación: 40 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 ["Laboratorios\Lab1\00c_L1.asm"] 01 ;******************************************************************** 02 ; 00c_L1.asm, MINIMUM Program for HCS08, V08J2012 03 ; Luis G. Uribe C., J09A2009. For CodeWarrior 10.2 04 ; =================================================================== 05 ; Begin of Code Section 06 ABSENTRY Main ; Export symbol (DEBUGGER entry) 07 08 ORG $2080 ; HCS08 ROMStart (Flash) 09 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 10 ; *** ALWAYS *** include the following 4 instructions 11 12 Main: lda #$42 ; $42(%0100_0010)? COPE(SOPT1b7)=0 ($1802) 13 sta $1802 ; (SOPT1 Pup init to $C2 = %1100_0010) 14 ldhx #$1800 ; Init Stack Pointer SP (HCS08) 15 txs ; $1800? RAMEnd + 1. SP = RAMEnd: $17FF 17 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 18 bra * ; Branch to himself (BRA: Branch Always; *: here!) 19 20 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 21 nop ; <<<NEEDED by CodeWarrior 10.1&2 (not 6.3). INCREDIBLE<<< 22 ; This 'nop' MAY be removed for CW 6.3 ... 23 ;******************************************************************** 24 ; Interrupt Vectors 25 26 ORG $FFFE ; "Vector" holding starting address, 27 DC.W $2080 ; ...(RESET) DC.W: Define CONSTANT Word 28 END COMENTARIOS a ["Laboratorios\Lab1\00c_L1.asm"]: Cuatro líneas se han añadido por las siguientes dos razones: PRIMERA: Casi todos los microcontroladores (MCU) incluyen un dispositivo que por hardware lo inicializan (resetean) si no se ha cumplido cierto protocolo con él; se lo conoce como el "Watch Dog". Su propósito es el de restaurar la máquina, tal como si se hubiera activado la señal de RESET (o se hubiera encendido el MCU en ese momento). Bien instrumentado, puede evitar costosísimos desplazamientos a reinicializar nuestros equipos, en caso de que por alguna fluctuación en la energía eléctrica, o impacto con un rayo gamma o partículas alpha, el flujo del programa tome un derrotero no previsto. En el MC9S08QE128 al Watch Dog se lo conoce como COP: Cpu Operating Properly. En los programas que se cubren en esta publicación NO se utiliza el COP, pero hay que DESACTIVARLO porque el MCU lo tiene activo al aplicársele la energía. Ese es el propósito del código de las líneas: 12 Main: lda #$42 ; $42(%0100_0010)? COPE(SOPT1b7)=0 ($1802) 13 sta $1802 ; (SOPT1 Pup init to $C2 = %1100_0010) Se han incluido valores en Hexadecimal, pero luego veremos que hay un mecanismo que nos permite hacer referencias SIMBÓLICAS, que ofrecen mayor claridad a quien codifica y a quien luego quiere entender el programa. 41 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 En el registro SOPT1 (SYSTEM OPTION 1, ubicado en la posición $1802) se deshabilita el COP: COPE=0: COP Enable=0. Como esta es una máquina de Acumulador, se necesitan dos instrucciones para hacer el MOVE de un dato a una variable: LOAD Accumulator con #$42 y STORE Accumulator a la posición $1802 (líneas 12 y 13). Nótese la sangría de un espacio que he incluido en la línea 13 con respecto a la 12. Sirve para enfatizar que las dos instrucciones físicas realizan una sola operación lógica. Usted debe tratar de hacer lo mismo. Luego aprenderemos un mecanismo: MACROs, que le permiten definir sus propias instrucciones, como MOVE, que reemplazan a la secuencia de LDA y STA señaladas. SEGUNDA razón: Al encender el MCU el Stack Pointer (SP), al igual que el PC, se inicializa en un valor útil para todos los modelos de MCU de las familias HC05 y HC08, algunos de los cuales que pueden tener muy pequeña RAM. En nuestro caso no es así; en el HC9S08QE128 hay mucha RAM (8064 bytes reporta el Reference Manual), así que el manufacturante recomienda inicializar el stack (vía el SP) así: 14 ldhx #$1800 ; Init Stack Pointer SP (HCS08) 15 txs ; $1800? RAMEnd + 1. SP = RAMEnd: $17FF Observen de nuevo la sangría. Debido a ciertas peculiaridades de la Arquitectura de este MCU, la instrucción TXS (Transfer indeX to Sp) RESTA un UNO al registro índice H:X ANTES de transferir el valor original al SP. Por eso, dado que la última posición de RAM es la $17FF, y que el Stack arrancará ahí (y crecerá hacia ABAJO), hay que colocar en H:X el valor $1800. 3) Programación en Lenguaje de Máquina (HEX) para el HCS08: Se presenta la forma más elemental en la que se le pueden dar las instrucciones al Microprocesador. Como ya dijimos, los comandos de las instrucciones se individualizan y diferencian unos de otros, identificándolos mediante la asignación de un número, o Código de Operación (OpCode). El diseñador del MCU hace la asignación, y hay una tabla en la que se colocan los comandos y su Código de Operación. Así, si queremos borrar una variable que hemos localizado en la primera posición de la Memoria de Acceso Directo o Página 0 (ZRam) necesitaremos buscar el código del CLEAR y escribir algo así: Main: DC.B $3F ; Define Constant Byte. $3F $80? CLR $80 DC.B $80 ; $80? Direct Address Por suerte el programa Assembler permite referirnos a los OpCode mediante los SÍMBOLOS que el manufacturante definió para ellos (CLR para BORRAR memoria, etc.), y no tenemos ni qué memorizar sus códigos hexadecimales ni buscar en tablas las equivalencias. Se trata de emplear, al MÁXIMO, símbolos alfanuméricos, los de las instrucciones, y a las variables y ciertas posiciones de código también les damos NOMBRES, ojalá con significado claro y preciso, tal como se hace en C. El objetivo es el de hacernos más fácil la composición, lectura e interpretación del código, y ese mecanismo pretende ELEVAR EL VALOR SEMÁNTICO DEL LENGUAJE, que ya de por sí, es bastamte BAJO… Puede verse completo este tercer ejemplo a continuación: 42 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 ["Laboratorios\Lab1\01a_L1.asm"] 01 ;******************************************************************** 02 ; 01a_L1.asm, MINIMUM HCS08 Program Rev.E, V08J2012 >> HEXADECIMAL << 03 ; Luis G. Uribe C., D27Y2007 J09A2009. For CodeWarrior 10.2 L21N2013 04 ; Minimum aspects needed to write a program: 05 ; 1.‐ Define program ORG ($2080 and up) 06 ; 2.‐ Write the program (look up for each instruction code in Hex) 07 ; 3.‐ Define RESET Address (vector $FFFE) 08 ; NOTE: Look up "SortedHCS08_ISet2013‐U.pdf" for Op. Codes 09 ; =================================================================== 10 ; Begin of Code Section 11 ABSENTRY Main ; Export symbol (DEBUGGER entry) 12 13 ORG $2080 ; HCS08 ROMStart (Flash) 14 Main: DC.B $3F ; Define Constant Byte. $3F $80? CLR $80 15 DC.B $80 ; $80? Direct Address 16 DC.B $3C ; LOOP: $3C,$80? INC $80 (direct variable) 17 DC.B $80 ; $80? Direct Address 18 DC.B $20 ; BRA LOOP 19 DC.B $FC ; $FC? Relative offset. BRA *: $20‐2 ($FE) 20 ; $FC?‐4. PC points to NOP. PC‐4 is 'LOOP' 21 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 22 nop ; <<<NEEDED by CodeWarrior 10.1&2 (not 6.3). INCREDIBLE<<< 23 ; This 'nop' MAY be removed for CW 6.3 ... 24 ;******************************************************************** 25 ; Interrupt Vectors 26 ORG $FFFE 27 DC.B $20 ; Reset Vector HIGH 28 DC.B $80 ; Reset Vector LOW (HCS08 is BIG ENDIAN) 29 30 END 31 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 32 Byte‐addressable processors may be categorized as big endian, 33 little endian, or bi‐endian. 35 A multibyte numerical value stored with the MOST significant 36 byte in the LOWEST numerical address is stored in big‐endian 37 fashion. 39 The little‐endian style stores the MOST significant byte in the 40 HIGHEST numerical address. Intel microprocessor are LITTLE ENDIAN. 41 42 A bi‐endian processor can handle both styles (Motorola's PowerPC). COMENTARIOS a ["Laboratorios\Lab1\01a_L1.asm"]: Hay varios documentos en los que pueden encontrar la asociación entre Comandos y OpCodes. Por ejemplo: 08 ; NOTE: Look up "SortedHCS08_ISet2013‐U.pdf" for Op. Codes 43 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 Primero se define, como siempre, la asociación para el vector de RESET: 11 ABSENTRY Main ; Export symbol (DEBUGGER entry) 13 ORG $2080 ; HCS08 ROMStart (Flash) 14 Main ... El resto del programa inicializa la variable en cero, la incrementa y retorna a incrementarla por siempre. Puede verificar el comportamiento del programa mediante el Debugger, que permite correrlo paso a paso, e ir viendo cada vez el contenido de la variable en la posición $80: 14 Main: DC.B $3F ; Define Constant Byte. $3F $80? CLR $80 15 DC.B $80 ; $80? Direct Address 16 DC.B $3C ; LOOP: $3C,$80? INC $80 (direct variable) 17 DC.B $80 ; $80? Direct Address 18 DC.B $20 ; BRA LOOP 19 DC.B $FC ; $FC? Relative offset. BRA *: $20‐2 ($FE) 20 ; $FC?‐4. PC points to NOP. PC‐4 is 'LOOP' En cuanto a las líneas 18 y 19, que codifican un salto incondicional (BRA: Branch Always) al "Loop" de Incremento, es interesante notar lo siguiente, que es de uso común entre casi todo los procesadores: Para realizar un salto a alguna posición de código dentro de la memoria, el OpCode es el que corresponda, en nuestro caso un BRA, y luego hay que identificar la posición a la cual saltar. Como el programa en una máquina de Von Neumann puede estar en cualquier parte de la memoria, se necesita identificar una dirección de 16 bits, en nuestro micro, que direcciona en forma natural, 64KBytes. De esa manera, las instrucciones de ramificación, que modifican el flujo del programa, y que forman uno de los grupos más usados, tendrían 3 bytes: uno para el OpCode y dos para la dirección. Una forma de optimización en esta materia se logra mediante la siguiente observación: la gran mayoría de saltos se realizan en la "LOCALIDAD", que es un concepto que identifica de manera genérica, las posiciones cercanas a la dirección del salto; sus VECINAS. Y esa localidad es muy numerosa en un rango de 127 posiciones adelante, y hasta 128 atrás. Lo cual es muy conveniente, ya que esa información es la que se codifica mediante bytes con signo (signed chars). Así, se logra que las instrucciones de ramificación que más se emplean tengan solo DOS bytes: uno para el OpCode, y uno para el DESPLAZAMIENTO (no se indica a dónde, sino que se señalan cuántas posiciones adelante o atrás, mediante un Offset). Desde luego, hay un grupo de instrucciones que permiten saltar a cualquier lado. En esta máquina, los BRANCHES son RELATIVOS, es decir, emplean Desplazamientos que indican el offset en RELACIÓN a la instrucción. Y los JUMPS son absolutos, emplean direcciones de 16 bits. Lo interesante es el mecanismo para calcular la dirección efectiva, destino del salto: A la posición actual (registrada siempre mediante el PC), se le debe sumar el Offset con signo para obtener así la dirección de la próxima instrucción. Se dice bien pero hay dos cosas sutiles que aclarar. La primera es en relación al uso del PC. Lo segundo, ver cómo las direcciones, que son números SIN signo, suman bien con los “Offset”, que son números CON signo. 44 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 La operación del PC, en todos los micros que conozco, es como sigue: se comienza con el PC apuntando a la próxima instrucción que se va a ejecutar. Cuando se trata de instrucciones de varios bytes, como es el caso del BRANCH, apunta al OpCode. Cuando se comienza a ejecutar la instrucción, en el ciclo de Fetch, el CPU lee, usando la dirección almacenada en el PC, el OpCode, y apunta hacia el siguiente elemento. Esto lo hace incrementando el PC. Es decir, para cada lectura se produce la siguiente secuencia: InstructionRegister = *PC++; // se carga el OpCode y se apunta // ..al siguiente byte: el Offset *DataBuf = *PC++; // se carga el Offset y el PC, en este caso, // ..apunta al próximo byte: Next OpCode. Ahora, si la dirección del comienzo de la instrucción es N, al finalizar la secuencia anterior el PC alberga N + 2. Así que una instrucción como: BR *, en la que se desea hacer un ciclo infinito de saltos a sí mismo, el Offset que hay que colocar es... ‐2. De esa manera el próximo PC será PC = N + 2 ‐ 2 = N, que era la dirección original. O sea, es importante al calcular el Offset, recordar que para el momento de la aplicación de PC + Offset, el PC apunta dos bytes hacia abajo. ESTA ES LA FUENTE DEL ERROR EN EL CODEWARRIOR, para solucionar el cual tuve que agregar el NOP del que ya hemos hablado. Cuando se ejecuta el BRANCH, el PC apunta MÁS ALLÁ DE LA MEMORIA QUE LE HA SIDO ASIGNADA AL PROGRAMA. Allí NO hay código, y el Assembler cree que se ha cometido un error, cuando en realidad no es así. De hecho, la versión 6.3 funciona muy bien sin ese NOP. En relación al segundo punto, uno de los problemas del cuestionario pide demostrar que a un número sin signo (una dirección) se le puede sumar uno CON signo (el Offset), y el resultado es perfecto: la dirección requerida, adelante o atrás, de la dirección original. ESTA PROPIEDAD DE LOS NÚMEROS CON SIGNO, CODIFICADOS EN COMPLEMENTO A DOS, ES LA RAZÓN PRIMORDIAL POR LA CUAL SE USA ESTA REPRESENTACIÓN NUMÉRICA, y no se emplean el complemento a 1 (ni mucho menos otras más engorrosas, como Signo y Magnitud). En la parte final del ejercicio se ejemplifica el orden en que una información de 16 bits (2 bytes) debe representarse en una máquina BIG ENDIAN, como es el caso del HC08: Yendo desde las direcciones inferiores hasta las superiores (de $FFFE a $FFFF), primero se coloca el byte de Mayor valor y a continuación, de último, va el final: la parte Menos significativa del número. La información termina (END) en la dirección mayor (BIG); de ahí el término ”BIG ENDIAN”. (Si este fuera un microprocesador Intel, en la misma secuencia de direcciones los valores almacenados serían, primero, $80 y luego, $20: LITTLE ENDIAN; es decir el número termina [END; $80] en la parte mas baja de memoria [LITTLE]): 25 ; Interrupt Vectors 26 ORG $FFFE 27 DC.B $20 ; Reset Vector HIGH 28 DC.B $80 ; Reset Vector LOW (HCS08 is BIG ENDIAN) 45 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 4) Invente su propio Instruction Set para el HCS08: Invente SU propio Instruction Set. Esto casi nunca se usa, pero ilustra la manera como trabaja el Assembler al traducir los códigos que usted le suministra. Además hay al menos una manera más avanzada de hacer lo que este ejercicio nos muestra, a base de MACROs, pero eso vendrá después. ["Laboratorios\Lab1\01b_L1.asm"] 01 ;******************************************************************** 02 ; 01b_L1.asm Symbolic Program (User defined) Rev. F V08J2012 M29E2013 03 ; Luis G. Uribe C., J09A2009 04 ; Aspectos mínimos necesarios al escribir un programa: 05 ; 1.‐ Definir posición inicial del código. $8000 up 06 ; 2.‐ Escribir el programa (buscar código HEX de c/instrucción) 07 ; Hacer SU propio conjunto de instrucciones en Simbólico. 08 ; 3.‐ Definir Dirección de inicio de ejecución luego 09 ; de Reset (vector $FFFE) 11 ; ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 12 ; Parameter Definitions (User defined SYMBOLS via 'EQU') 13 ; You may define whatever symbols you like... Your own Assembly Language! 14 15 START: EQU $2080 16 STARTh:EQU $20 17 STARTl:EQU $80 18 XCLR: EQU $3F ; Instruction clr, tomada del Reference Manual 19 XOP1: EQU $80 ; Operand (1st variable) for clr and inc 20 XINC: EQU $3C ; Instruction inc, del Reference Manual 21 XBRA: EQU $20 ; Instruction bra, del Reference Manual 22 XOFF: EQU ‐4 ; Offset for instruction bra 23 RESET: EQU $FFFE ; Vector de Interrupciones 24 25 ; =================================================================== 26 ; Begin of Code Section 27 ABSENTRY Main ; Export symbol (DEBUGGER entry) 28 29 ORG $2080 ; HCS08 ROMStart (Flash) 30 Main: DC.B XCLR ; Later TRAY THIS: Step some, Insert BREAK 31 DC.B XOP1 ; ..POINT in Main. >>RUN<< CW will stop.. 32 loop: DC.B XINC ; ..here. WHY? **BECAUSE COP is ENABLED**! 33 DC.B XOP1 ; loop 34 DC.B XBRA 35 DC.B XOFF 36 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 37 nop ; <<<NEEDED by CodeWarrior 10.1&2 (not 6.3). INCREDIBLE<<< 38 ; This 'nop' MAY be removed for CW 6.3 ... 39 ;******************************************************************** 40 ; Interrupt Vectors 42 ORG RESET 44 DC.B STARTh ; Reset Vector HIGH 45 DC.B STARTl ; Reset Vector LOW (HCS08 is BIG ENDIAN) 46 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 46 47 ; NOTE: If you remove ALL references to RESET, STARTh and STARTl... 48 ; the program continues to be "debugged" fine! 49 ; The DEBUGGER uses the 'ABSENTRY Main' instead... 50 ; The actual CPU NEEDs the RESET Vector (Vreset) COMENTARIOS a ["Laboratorios\Lab1\01b_L1.asm"]: Lo primero es generar SUS propios OpCodes. Los hemos comenzado con una 'X' para ayudarnos con la diferenciación entre sus códigos y los del manufacturante. 18 XCLR: EQU $3F ; Instruction clr, tomada del Reference Manual 19 XOP1: EQU $80 ; Operand (1st variable) for clr and inc 20 XINC: EQU $3C ; Instruction inc, del Reference Manual 21 XBRA: EQU $20 ; Instruction bra, del Reference Manual 22 XOFF: EQU ‐4 ; Offset for instruction bra 23 RESET: EQU $FFFE ; Vector de Interrupciones La parte siguiente es muy similar al ejercicio anterior: 01a_L1.asm: 29 ORG $2080 ; HCS08 ROMStart (Flash) 30 Main: DC.B XCLR ; Later TRAY THIS: Step some, Insert BREAK 31 DC.B XOP1 ; ..POINT in Main. >>RUN<< CW will stop.. 32 loop: DC.B XINC ; ..here. WHY? **BECAUSE COP is ENABLED**! 33 DC.B XOP1 ; loop 34 DC.B XBRA 35 DC.B XOFF 40 ; Interrupt Vectors 42 ORG RESET 44 DC.B STARTh ; Reset Vector HIGH 45 DC.B STARTl ; Reset Vector LOW (HCS08 is BIG ENDIAN) 5) Usando los OpCodes del Fabricante para el HCS08: El mismo ejercicio anterior, 01a_L1.asm, todo escrito en lenguaje SIMBÓLICO. Ya usted no usa SUS propios OpCodes, sino los de Motorola. Usted (casi) NUNCA debe emplear números; los símbolos alfanuméricos, como en el C, son más o menos descriptivos, dependiendo de lo bien que usted se aplique a hacer las definiciones. Pero, un número como $40, difícilmente será recordado como la cantidad numérica que se usa para deshabilitar el COP, cuando se lo almacena en el $1802 (SOPT1) ["Laboratorios\Lab1\01c_L1.asm"] 01 ;******************************************************************** 02 ; 01c_L1.asm Symbolic Program (User defined), Rev. E V08J2012 03 ; Luis G. Uribe C., J09A2009 04 ; Aspectos mínimos necesarios al escribir un programa: 05 ; 1.‐ Definir posición inicial del código. $2080 up 06 ; 2.‐ Escribir el programa (buscar código SIMBÓLICO de c/instrucción) 07 ; 3.‐ Definir Dirección de inicio de ejecución luego 47 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 08 ; de RESET (vector $FFFE) 09 10 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 11 ; Parameter Definitions 12 13 START: EQU $2080 ; comienzo de la Flash 14 RESET: EQU $FFFE ; vector para indicar comienzo del código 15 16 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 17 ; Definición de variables 18 19 Var: EQU $80 ; una forma de identificar variables 20 21 ;==================================================================== 22 ; Begin of Code Section 23 ABSENTRY Main ; Export symbol (DEBUGGER entry) 24 25 ORG START ; HCS08 ROMStart (Flash) 26 Main: clr Var ; Ensayar: clr VAR (mayúsculas. Da error) 27 loop: inc Var 28 bra loop 29 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 30 nop ; <<<NEEDED by CodeWarrior 10.1&2 (not 6.3). INCREDIBLE<<< 31 ; This 'nop' MAY be removed for CW 6.3 ... 32 ;******************************************************************** 33 ; Interrupt Vectors 34 35 ORG RESET 36 DC.W Main ; RESET: HCS08 Power On (PON) procedure. 37 END COMENTARIOS a ["Laboratorios\Lab1\01c_L1.asm"]: Los elementos importantes son: 11 ; Parameter Definitions 13 START: EQU $2080 ; comienzo de la Flash 14 RESET: EQU $FFFE ; vector para indicar comienzo del código Asocian números a símbolos alfanuméricos. Dice mucho más RESET, que $FFFE. 17 ; Definición de variables 19 Var: EQU $80 ; una forma de identificar variables Mucho mejor el símbolo de alto nivel 'Var', y no tener que recordar que su dirección es la $80, que además puede cambiar si se agregan o eliminan variables. 25 ORG START ; HCS08 ROMStart (Flash) 26 Main: clr Var ; Ensayar: clr VAR (mayúsculas. Da error) 27 loop: inc Var 28 bra loop 48 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 Luego usaremos el símbolo ROMStart, que es el definido por el manufacturante. Las etiquetas de programa lucen como en C. (Con ustedes es un problema hacer referencias al C, cuando con toda seguridad NINGUNO usó nunca etiquetas para identificar partes de su código en C. Así, no puedo basarme en sus (des)conocimientos de C para ayudarme en las explicaciones de este curso. Lástima...) 33 ; Interrupt Vectors 35 ORG RESET 36 DC.W Main ; RESET: HCS08 Power On (PON) procedure. Todo en simbólico. Tampoco usaremos 'RESET', pues hay una serie de símbolos para la tabla de Interrupt Vectors, definidos por el manufacturante. 6) Forma DEFINITIVA para programar el HCS08: FULL Use of Motorola's Symbolic Language Ahora sí; esta es LA forma definitiva que usted debe usar para escribir sus programas. ["Laboratorios\Lab1\01d_L1.asm"] 01 ;******************************************************************** 02 ; 01d_L1.asm Use of Motorola's Symbolic Language, Rev. E V08J2012 03 ; Luis G. Uribe C., J09A2009 04 ; Aspectos mínimos necesarios al escribir un programa: 05 ; 1.‐ Definir posición inicial del código. $8000 up 06 ; 2.‐ Escribir el programa (buscar código SIMBÓLICO de c/instrucción) 07 ; 3.‐ Definir Dirección de inicio de ejecución luego 08 ; de RESET (vector $FFFE) 09 10 ; ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 11 ; Include files: Work exactly like in "C"; define processor symbols: 12 ; .. Z_RAMStart, ROMStart, INT_RESET, registers, I/O ports... 13 ; ..derivative.inc is prepared by the Project Wizzard, for each CPU. 14 15 INCLUDE 'derivative.inc' 16 ; ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 17 ; Definición de variables 18 19 ORG Z_RAMStart ; $80 20 21 Var: DS.B 1 ; DS.B? 1 byte variables Name&Data Storage 22 23 ; =================================================================== 24 ; Begin of Code Section 25 ABSENTRY Main ; Export symbol (DEBUGGER entry) 26 27 ORG ROMStart ; HCS08 ROMStart (Flash) 28 Main: clr Var ; ensayar: clr VAR (mayúsculas. Da error) 29 loop: inc Var 30 bra loop 31 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 49 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 32 nop ; <<<NEEDED by CodeWarrior 10.1&2 (not 6.3). INCREDIBLE<<< 33 ; This 'nop' MAY be removed for CW 6.3 ... 34 ;******************************************************************** 35 ; Interrupt Vectors 36 37 ORG Vreset 38 DC.W Main ; RESET: HCS08 Power On (PON) procedure. 39 END 40 41 SEE 'MC9S08QE128.inc' for Symbols Definitions like: 42 ‐ Z_RAMStart ; $80 43 ‐ ROMStart ; HCS08 ROMStart (Flash) 44 ‐ Vreset 45 46 'MC9S08QE128‐U.inc' was ***COMPLETLY*** REARRANGED and BEAUTIFIED by 47 Luis G. Uribe C., J07J2012, for DOCUMENTATION PURPOSES ONLY. Do NOT 48 REPLACE the original... COMENTARIOS a ["Laboratorios\Lab1\01d_L1.asm"]: Este código no necesita explicación adicional. 7) CODE SECTION, DATA SECTION, en el HCS08: Ya hemos visto cómo, para comenzar a escribir nuestro programa, comenzamos definiendo un ORG START (START, o su equivalente que cada vez usaremos menos: $2080). En CodeWarrior esto no recibe una identificación particular, pero en otros ensambladores, y dado que allí se está definiendo código, se lo denomina CODE SECTION. Y hay, por lo mismo, un DATA SECTION, cuando queremos definir variables de manera simbólica (que todavía no hemos visto). Un uso muy atractivo es que nos permite comenzar escribiendo código en el CODE SECTION y luego, cuando identificamos la necesidad de usar una o más variables, cambiamos a DATA SECTION, definimos la variable, y nos devolvemos al sitio donde íbamos con otro CONTROL SECTION. (Estas parejas reciben el nombre genérico de PROGRAM SECTIONS. Un lenguaje de alto nivel muy famoso que hizo uso de estas secciones fue el Cobol, copiado luego por el PL‐1 de IBM, en el que yo trabajé cuando hacía mi maestría en Sistemas, hay como un millón de años...) Si quisiéramos tener facilidad de hacer lo mismo en CodeWarrior (lo cual está en un nivel un tanto más avanzado de lo que corresponde a este curso, que NO es un curso de Lenguajes, sino de ARQUITECTURA), la forma es la que se indica en el ejercicio a continuación, único en tocarse este tema. ["Laboratorios\Lab1\01e_L1.asm"] 01 ;******************************************************************** 02 ; 01e_L1.asm Symbolic Assembly Language, Rev. E V08J2012 03 ; Luis G. Uribe C., J09A2009 04 ; ADVANCED way to define variables... 05 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 06 ; Include files 08 INCLUDE 'derivative.inc' 09 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 10 ; Parameter definitions 50 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 12 ram: set Z_RAMStart ; Set 'ram' to $80 13 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 14 ; Variables Definición (ADVANCED) 15 16 ORG ram ; Start in $80 17 18 Var: DS.B 1 ; DS.B? 1 byte variables Name&Data Storage 19 Varend: 20 ram: SET Varend ; $81 21 22 ORG ram ; Re‐start in $81 23 Var2: DS.B 1 24 Var2end: 25 ram: SET Var2end ; $82 26 27 ORG ram ; Re‐start in $82 28 Var3: DS.B 1 29 Var3end: 30 ram: SET Var3end ; $83... and SO ON. 31 32 ;==================================================================== 33 ; Begin of Code Section 34 ABSENTRY Main ; Export symbol (DEBUGGER entry) 36 ORG ROMStart ; HCS08 ROMStart (Flash) 37 Main: ldhx #Var 38 ldhx #Var2 39 ldhx #Var3 40 bra * 41 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 42 nop ; <<<NEEDED by CodeWarrior 10.1&2 (not 6.3). INCREDIBLE<<< 43 ; This 'nop' MAY be removed for CW 6.3 ... 44 ;******************************************************************** 45 ; Interrupt Vectors 46 47 ORG Vreset 48 DC.W Main ; RESET: HCS08 Power Up (Pup) procedure. 49 END COMENTARIOS a ["Laboratorios\Lab1\01e_L1.asm"]: La primera novedad es el uso de INCLUDE files: 06 ; Include files 08 INCLUDE 'derivative.inc' Esta parte deberá incluirse SIEMPRE en sus ejercicios, de ahora en adelante. El ambiente de desarrollo particulariza (customize) o parametriza una serie de definiciones, a fin de hacer lo más portable posible los programas. Y nuestra interfaz con ese resultado es a través de esa línea 08: INCLUDE 'derivative.inc' Luego, la definición de variables, empleando un ORG, tal como para el código, pero en las direcciones correspondientes a las variables: la Random Acces Memory, RAM: 51 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 ORG ram: 10 ; Parameter definitions 12 ram: set Z_RAMStart ; Set 'ram' to $80 13 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 14 ; Variables Definición (NORMAL, LA QUE UD. DEBE EMPLEAR) 16 ORG ram ; Start in $80 18 Var: DS.B 1 ; DS.B? 1 byte variables Name&Data Storage 23 Var2: DS.B 1 28 Var3: DS.B 1 Hasta allí la parte común de la definición de variables, que usted SIEMPRE usará en sus programas. Ahora, si se deseara definir una variable, y más adelante otra, y luego otra, el truco consiste en REDEFINIR la posición 'RAM' para que lleve la cuenta hasta adónde hemos llegado definiendo variables. Por eso, en el programa que estamos analizando el código NO va como las 8 líneas anteriores, sino que en realidad es como se ilustra a continuación. Comencemos diciendo que en el HC9S08QE128, las áreas de RAM son: Z_RAMStart: equ $00000080 Z_RAMEnd: equ $000000FF RAMStart: equ $00000100 RAMEnd: equ $000017FF RAM1Start: equ $00001880 RAM1End: equ $0000207F NOTE que hay un AGUJERO, un espacio no definido dentro de ese campo de direcciones; lo emplean REGISTROS usados en periféricos. En la $1800, por ejemplo, se encuentra el registro SRS ‐System Reset Status Register; 0x00001800 ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ Entonces, el código bajo análisis es, en realidad, como sigue: 10 ; Parameter definitions 12 ram: set Z_RAMStart ; Set 'ram' to $80 13 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 14 ; Variables Definición (ADVANCED) 16 ORG ram ; Start in $80 17 18 Var: DS.B 1 ; DS.B? 1 byte variables Name&Data Storage 19 Varend: 20 ram: SET Varend ; $81 21 22 ORG ram ; Re‐start in $81 23 Var2: DS.B 1 24 Var2end: 52 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 25 ram: SET Var2end ; $82 26 27 ORG ram ; Re‐start in $81 28 Var3: DS.B 1 29 Var3end: 30 ram: SET Var3end ; $83... and SO ON. En realidad, en esta secuencia contigua SOLO HACE FALTA lo que ya se ilustró en las líneas inmediatamente anteriores: 10, 12, 14, 16, 18, 23, 28. Pero usted puede hacer algo como esto: ORG ram ; Start in $80 Var: DS.B 1 ; DS.B? 1 byte variables Name&Data Storage Varend: ram: SET Varend ; ram = $81 ORG rom ; Start in $2080 ; ... CÓDIGO, CÓDIGO, CÓDIGO ActualRom: rom: SET ActualRom ; rom = $20XX (donde su programa vaya) ORG ram ; Re‐start in $81, en este ejemplo Var2: DS.B 1 Var2end: ram: SET Var2end ; ram = $82 ORG rom ; Start in $20XX (donde su programa iba) ; ... MÁS_CÓDIGO1, MÁS_CÓDIGO1, MÁS_CÓDIGO1 ActualRom1: rom: SET ActualRom1 ; rom=$20XX (donde su programa vaya ahora) ORG ram ; Re‐start in $82 Var3: DS.B 1 Var3end: ram: SET Var3end ; $83... and SO ON. ORG rom ; Start in $20XX (donde su programa iba) ; ... MÁS_CÓDIGO2, MÁS_CÓDIGO2, MÁS_CÓDIGO2 ActualRom2: rom: SET ActualRom2 ; $20XX (donde su programa vaya ahora...) y ya se hacen una idea. "THE POOR'S MAN PROGRAM SECTION aproach"... El resto del programa no tiene, en este ejemplo, mayores dificultades: 36 ORG ROMStart ; HCS08 ROMStart (Flash) 37 Main: ldhx #Var 38 ldhx #Var2 39 ldhx #Var3 40 bra * 53 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 Trate usted de cambiar el programa para incluir código de verdad, entremezclado con definición de variables. Aun cuando le parezca arrevesado este modo de programación (y, en realidad, así es), lo que ocurre es que cuando estamos haciendo uso de MACROs, que son rutinas que se escriben en otras partes, o librerías, y en donde puede necesitarse agregar variables, que usted no ha previsto en el programa principal, esas MACROs pueden hacer uso de este mecanismo, si se establece un protocolo (ojalá de aplicación automática) que permita marcar el sitio en donde el programa principal iba al momento de llamar a la MACRO, definir las variables a las que hubiera lugar, y luego dejar el CODE SECTION en el preciso valor en donde el programa lo necesita. Programa y conceptos muy importantes, pero que al nivel de este curso, en donde NO se está estudiando en realidad el Assembler Language, no es tan relevante. Pero para mí, que debo escribirles rutinas y bibliotecas de ellas, es fundamental. PROGRAMAS AVANZADOS EN ASSEMBLY LANGUAGE, PARTE I 8) Serie de Fibonacci. Este programa lo adapté del manual del Assembler Eclipse (la nueva versión 10.x se basa en Eclipse, un IDE gratuito, construido por IBM, y regalado a la comunidad. Es usado por Microchip, y ahora por Freescale, y otros. Está siendo adaptado en muchas plataformas. Por ejemplo, el desarrollo para celulares ANDROID se basa en la plataforma Eclipse...) No hay mayores comentarios sobre este programa. Usted debe ser capaz de decir cómo funciona y, en caso de duda, acuda al Debugger del CodeWarrior, siga el programa paso a paso, y podrá hacerse una idea del funcionamiento). ["Laboratorios\Lab1\02Fibonacci.asm"] 01 ;******************************************************************** 02 ; 02Fibonacci.asm, Luis G. Uribe C., V10A2009 V08J2012 03 ; ADAPTED from: HCS08‐RS08_Assembler_MCU_Eclipse.pdf, Listing 4.1 04 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 05 ; Include files 06 NOLIST 07 INCLUDE 'derivative.inc' 08 LIST 09 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 10 ; Parameter definitions 11 12 ram: SET Z_RAMStart ; $80 13 initStack: EQU $1800 14 COP_Disable: EQU %01000010 ; $42 15 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 16 ; Definición de variables (ds: byte por defecto) 54 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 17 ORG ram 18 19 Counter: DS.B 1 20 FiboRes: DS.B 1 ; Aquí va la respuesta 21 ; =================================================================== 22 ; Begin of Code Section 23 ABSENTRY Main ; Export symbol (DEBUGGER entry) 24 25 ORG ROMStart ; HCS08 ROMStart (Flash) 26 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 27 ; *** ALWAYS *** include the following 4 instructions 28 29 Main: lda #COP_Disable 30 sta SOPT1 ; System Options 1 31 ldhx #initStack ; Init SP 32 txs 33 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 34 mainLoop: 35 clra ; Use Accumulator as a counter 36 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 37 cntLoop: 38 inca 39 cbeqa #14, mainLoop; Larger values cause overflow 40 sta Counter ; Update global variable 41 bsr CalcFibo 42 sta FiboRes ; Store result 43 lda Counter ; ..Activate BREAKPOINT here to see... 44 ; .. 1 2 3 5 8 13 21 34 55 89 144 233 45 bra cntLoop ; Next round 46 ;==================================================================== 47 ; Function to compute Fibonacci numbers. Argument is in A 48 49 CalcFibo: 50 dbnza fiboDo ; Fibonacci Do 51 inca 52 rts 53 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 54 fiboDo: 55 psha ; The counter 56 clrx ; Second last = 0 57 lda #$01 ; Last = 1 58 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 59 FiboLoop: 60 psha ; Push last 61 txa 62 add 1, sp 63 pulx 64 dbnz 1, sp, FiboLoop 65 66 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 67 FiboDone: 68 pulh ; Release counter 55 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 69 rts ; Result in A 70 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 71 nop ; <<<NEEDED by CodeWarrior 10.1&2 (not 6.3). INCREDIBLE<<< 72 ; This 'nop' MAY be removed for CW 6.3 ... 73 ;******************************************************************** 74 ; Interrupt Vectors 75 76 ORG Vreset 77 DC.W Main ; RESET: HCS08 Power Up (Pup) 78 END COMENTARIOS a ["Laboratorios\Lab1\02Fibonacci.asm"]: Un aspecto a resaltar: 05 ; Include files 06 NOLIST 07 INCLUDE 'derivative.inc' 08 LIST NOLIST es una Pseudo operación que indica al Assembler que el contenido del include file NO lo incluya en los listados que se producen como resultado del proceso de ensamblaje. De esta manera, sólo queda en esos listados, para su documentación, información que usted considere relevante. Debajo del include file es menester rehabilitar la generación de los listados, con LIST, ya que de lo contario, TAMBIÉN SU CÓDIGO será eliminado. 9) Empleo de Subrutinas; Identificación de Modos de Direccionamiento. Este programa enfatiza en el uso elemental de llamadas a funciones, o subrutinas (BSR). Además, se identifican algunos modos de direccionamiento empleados. La tabla de Vectores de Interrupción se ha ampliado para incluir IRQ (Interrupt ReQuest, un pin externo del MCU que al activarlo genera una interrupción), SWI (una instrucción diseñada para servir de interfaz con el Sistema Operativo: SoftWare Interrupt, muy valiosa) y el consabido RESET. Se vuelve a mencionar el tema de suspensión del PROGRAM SECTION para volver después. IRQ y SWI en realidad NO juegan ningún papel en este programa; se escribió su entrada en la tabla de Interrupt Vectors simplemente para ver cómo se incluirían. ["Laboratorios\Lab1\Lab1_M\03a_L1_M.asm"] 01 ;******************************************************************** 02 ; Programa 03a_L1_M.asm, HCS08_CPU, Luis G. Uribe C., M29E2013 03 ; 04 ; Nuevos Aspectos: 05 ; 06 ; Empleo de una SUBRUTINA dentro del programa, mediante "bsr" para el 07 ; ..llamado y "rts" para el retorno. Se usan los direccionamientos: 08 ; ‐ Inherente ‐ Directo ‐ Inmediato ‐ Relativo 09 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 56 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 10 ; Include files 11 NOLIST 12 INCLUDE 'derivative.inc' 13 LIST 14 15 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 16 ; Parameter definitions 17 ram: SET Z_RAMStart ; $80. Cfr. 'MC9S08QE128‐U.inc' 18 rom: SET ROMStart ; $2080 19 initStack: EQU $1800 20 COP_Disable: EQU $42 21 22 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 23 ; Definición de variables (DS: DATA STORATE; Byte por defecto) 24 ORG ram 25 26 var: DS 1 ; Equivalent to DS.B 27 28 ;==================================================================== 29 ; Begin of Code Section 30 ABSENTRY Main ; Export symbol (DEBUGGER entry) 31 32 ORG rom ; $2080: HCS08 ROMStart (Flash) 33 34 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 35 ; *** ALWAYS *** include the following 4 instructions 36 Main: lda #COP_Disable 37 sta SOPT1 ; System Options 1 38 ldhx #initStack ; Init SP 39 txs ; .. 40 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 41 loop: clr var ; Direct Addressing Mode 42 bsr rutina ; Relative Addressing Mode 43 bra loop ; .. 44 45 ;==================================================================== 46 ; Subrutina de ejemplo 47 rutina: 48 lda var 49 cbeqa #10, fin ; Termina cuando var llega a 10 50 inc var 51 bra rutina 52 fin: rts ; COLOCAR BREAKPOINT AQUÍ (Debugger) 53 54 55 ;******************************************************************** 56 ;* Spurious Interrupt Service Routine (Unwanted Interrupt) * 57 ;******************************************************************** 58 spurious: 59 rti 60 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 61 ; Si va a cambiar de ORG, y luego debe continuar con el Código: 57 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 63 endrom1: 64 rom: SET endrom1 66 ;******************************************************************** 67 ;* Interrupt Vectors * 68 ;******************************************************************** 69 70 ORG Virq 71 DC.W spurious ; IRQ 72 DC.W spurious ; SWI 73 DC.W Main ; RESET 74 75 ;******************************************************************** 76 ; IF you need to continue with the program...: 77 78 ORG rom ; rom points to: endrom1. Not needed here 79 80 END COMENTARIOS a ["Laboratorios\Lab1\Lab1_M\03a_L1_M.asm"]: Las pocas cosas nuevas serían, una rutina DUMMY (no hace nada), para efectos del ejemplo: 58 spurious: 59 rti La marca para cambiar de CODE SECTION: 61 ; Si va a cambiar de ORG, y luego debe continuar con el Código: 63 endrom1: 64 rom: SET endrom1 La asignación para la tabla de Vectores de Interrupción: 67 ;* Interrupt Vectors * 70 ORG Virq 71 DC.W spurious ; IRQ 72 DC.W spurious ; SWI 73 DC.W Main ; RESET Y, por si necesitara seguir escribiendo el programa, aquí está la continuación del manejo del CODE SECTION: 76 ; IF you need to continue with the program...: 78 ORG rom ; rom points to: endrom1. Not needed here ... (Continúa...) 10) Introducción a las INTERRUPCIONES Este es nuestro primer ejemplo, aunque muy simple, en el que se toca el tema de las Interrupciones, que son una gran adición al modelo del computador, sin las cuales serían impensables las computadoras modernas, y que a pesar de ello son el PEOR DOLOR 58 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 DE CABEZA de un programador de sistemas Embebidos... Así que HAY QUE ANDARSE CON MUCHO CUIDADO cuando incluyamos Interrupciones en nuestros códigos. Tiene que hacerse de una manera MUY DISCIPLINADA. Los Sistemas Operativos así lo hacen. ["Laboratorios\Lab1\Lab1_M\03b_L1_M.asm"] 001 ;******************************************************************** 002 ; Programa 03b_L1_M.asm, HCS08_CPU, Luis G. Uribe C., M29E2013 V22N13 003 ; 004 ; Nuevos Aspectos: 005 ; 006 ; ‐ Uso de Rutinas de Interrupción: 007 ; a) Inclusión del código de la rutina de Interrupción (Interrupt 008 ; Service Routine, ISR), usando "RTI" para retornar de ésta. 009 ; b) Definición de los Vectores de Interrupción de acuerdo con la 010 ; fuente de la interrupción; en este caso: "IRQ". Su vector de 011 ; interrupciones está en Virq ($FFFA‐$FFFB). Se ha usado la 012 ; etiqueta "IRQISR" para designar la dirección de la rutina 013 ; c) Habilitación del periférico para Interrumpir 014 ; d) Habilitación del CPU (CLI) para que acepte Interrupciones 015 ; (aparte de la del RESET) 016 017 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 018 ; Include files 019 NOLIST 020 INCLUDE 'derivative.inc' 021 LIST 022 023 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 024 ; Parameter definitions 025 ram: SET Z_RAMStart ; $80. Cfr. 'MC9S08QE128‐U.inc' 026 rom: SET ROMStart ; $2080 027 initStack: EQU $1800 028 COP_Disable: EQU $42 029 030 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 031 ; Definición de variables (ds: byte por defecto) 032 ORG ram 033 034 var: DS.B 1 ; Equivalent to DS 035 036 ; =================================================================== 037 ; Begin of Code Section 038 ABSENTRY Main ; Export symbol (DEBUGGER entry) 039 040 ORG rom ; $2080: HCS08 ROMStart (Flash) 041 042 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 043 ; *** ALWAYS *** include the following 4 instructions 044 Main: lda #COP_Disable 045 sta SOPT1 ; System Options 1 046 ldhx #initStack ; Init SP 047 txs ; .. 59 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 048 049 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 050 ; Enable Interrupts for IRQ 051 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 052 BSET IRQSC_IRQPE, IRQSC ; Enable IRQ PIN 053 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 054 ; .. Clear any possible previous, false, interrupts by Setting 055 ; .. IRQACK bit in IRQSC (IRQ Status and Control register) 056 BSET IRQSC_IRQACK, IRQSC 057 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 058 BSET IRQSC_IRQIE, IRQSC ; IRQ pin Interrupt Enable 059 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 060 061 cli ; CPU Interrupt ENABLE 062 063 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 064 loop: clr var ; Direct Addressing Mode 065 bsr rutina ; Relative Addressing Mode 066 bra loop 067 068 ;==================================================================== 069 ; Subrutina de ejemplo 070 rutina: 071 lda var 072 cbeqa #10, fin ; Termina cuando llega a 10 073 inc var 074 bra rutina 075 fin: rts ; COLOCAR BREAKPOINT AQUÍ (Debugger) 076 077 078 ;==================================================================== 079 ; EJEMPLO de Rutina de Interrupción (ISR for IRQ signal) 080 ; NOTE: Es necesario realizar un ACKNOWLEDGE para que la interrupción 081 ; vuelva a suceder (Interrupt Handshaking... different for each 082 ; peripheral equipment. Need to read Manual for each one... :‐( 083 IRQISR: 084 clr var 085 BSET IRQSC_IRQACK, IRQSC ; Acnowledge IRQ Interrupt 086 ; .. (rearm IRQ Interrupts) 087 rti 088 089 ;******************************************************************** 090 ;* Interrupt Vectors * 091 ;******************************************************************** 092 ; (Esta parte es más o menos estándar, 093 ; ..excepto que normalmente se incluyen TODOS los periféricos...) 094 spurious: 095 rti 097 ORG Virq ; Change ORG 098 DC.W IRQISR ; <<< IRQ Defined 099 DC.W spurious ; SWI 100 DC.W Main ; RESET 60 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 101 102 END 103 104 NOTE: If in CW 10.2, IRQ does NOT have any effect if you are 105 debugging in Full Chip Simulation (FCS) and you are using step by 106 step (F5). You need to make a break point in the ISR and then RUN at 107 full speed: F8. Now, IRQ works... 108 109 GURIBE, M29E2013 COMENTARIOS a ["Laboratorios\Lab1\Lab1_M\03b_L1_M.asm"]: Cuando se trata del MCU hay varios pasos que se deben seguir al trabajar con periféricos; a lo mejor hay dispositivos a los que no se aplican todos los pasos... Lo primero es, a veces, definir ciertos valores o escoger, por ejemplo, la fuente de CLOCK (como en el caso de las comunicaciones, SCI: Serial Communication Interface; periférico al que hay que definirle primero las características del canal de comunicaciones: velocidad, número de bits, paridad, número de stop bits... ANTES de hacer nada más). Luego, hay que "ENERGIZAR" el periférico. Muchos dispositivos no están conectados a la alimentación eléctrica, dentro del MCU, mientras no se los necesite, con el propósito de ahorrar en consumo de energía. Esto es lo que yo denominé "ACTIVATE" en el tratamiento del SCI. Nótese que cuando operamos con periféricos de comunicaciones en realidad cada uno de ellos puede verse como DOS componentes: el de transmisión y el de recepción. A lo mejor hay que ACTIVAR cada uno de ellos por separado (Revisar vez por vez el manual). En el caso del SCI es así, y en nuestros ejemplos yo ACTIVO ambos ‐‐transmisor y receptor‐‐ al mismo tiempo, mediante la operación 'XmtRcvActivate'. Después, hay que habilitar las interrupciones (Peripheral Interrupt Enable) de los dispositivos necesarios, que ya han sido apropiadamente inicializados y ACTIVADOS. Por último se debe habilitar al CPU para que procese interrupciones. Esa es la secuencia genérica de actividades para habilitar periféricos para que interrumpan el procesamiento lineal de datos del CPU. Además de todo, hay que establecer la identificación de: QUÉ rutina debe atender CUÁL interrupción. El mecanismo que se emplea es mediante un link en el Vector de Interrupciones, que tiene una posición reservada para cada periférico, o parte del dispositivos que esté en capacidad de interrumpir. Por ejemplo, en el caso del SCI, el transmisor tiene su propio vector de interrupciones; también lo tiene el receptor, y asimismo lo posee el Control de Errores de Recepción. En otros equipos, como los PC compatibles IBM/AT, hay un vector adicional para el manejo del Modem, o dispositivo de comunicación con el canal telefónico convencional. Finalmente, hay que escribir por aparte la rutina ISR que atenderá cada dispositivo habilitado para hacerlo. Cada ISR tiene que responder de la manera convencional; por ejemplo, una rutina de interrupción de Recepción en el SCI, tiene al menos que leer el dato que está llegando, y que ha ocasionado la interrupción, colocarlo en el área de memoria dispuesto para eso (normalmente una COLA, o QUEUE), indicarle al periférico que ya se lo atendió (Interrupt Acknowledge; esta funcionalidad en equipos menos primitivos la suministra el hardware, pero en la mayoría de los microcontroladores, el software tiene que participar en mayor o menor grado en el protocolo de Aceptación de Interrupciones). 61 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 Los conceptos novedosos aquí son: el Enable del IRQ (que NO de sus Interrupts, sino del PIN de IRQ): 052 BSET IRQSC_IRQPE, IRQSC ; Enable IRQ PIN 054 ; .. Clear any possible previous, false, interrupts by Setting 055 ; .. IRQACK bit in IRQSC (IRQ Status and Control register) 056 BSET IRQSC_IRQACK, IRQSC Note que se le está dando, de entrada, un Acknowledge al periférico, por si alguien había activado el interruptor ANTES de que lo hubiésemos habilitado para el proceso... Esta es una de las partes MAS COMPLICADAS en los sistemas reales (no de laboratorio). Por ejemplo, cuando conectamos una Estación Maestra (un PC) a un grupo de dispositivos remotos que están operando (RTUs, Remote Terminal Units), probablemente, al habilitar la interfaz de comunicaciones del PC, ¡ya las RTUs estén comunicándose! Comenzar una comunicación desde el PC, a la mitad de una transmisión de la RTU, puede dar lugar a los más espinosos problemas... Luego viene la habilitación de las INTERRUPCIONES, propiamente dicha: 058 BSET IRQSC_IRQIE, IRQSC ; IRQ pin Interrupt Enable Y, finalmente, la habilitación del CPU para que acepte las interrupciones externas: 061 cli ; CPU Interrupt ENABLE ESTOS SON LOS PASOS, EN ESE ORDEN, QUE DEBEN SEGUIRSE SIEMPRE PARA ACTIVAR LAS INTERRUPCIONES DE LOS PERIFÉRICOS QUE NECESITEMOS. La rutina principal es muy simple en este ejercicio, con el ánimo de no oscurecer el problema de las interrupciones; consiste en un ciclo que incrementa la variable 'var' de 0 a 10, y repite... 064 loop: clr var ; Direct Addressing Mode 065 bsr rutina ; Relative Addressing Mode 066 bra loop 068 ;==================================================================== 069 ; Subrutina de ejemplo 070 rutina: 071 lda var 072 cbeqa #10, fin ; Termina cuando llega a 10 073 inc var 074 bra rutina 075 fin: rts ; COLOCAR BREAKPOINT AQUÍ (Debugger) Cuando se interrumpe el proceso oprimiendo el botón de IRQ, la IRQISR (rutina de interrupción del periférico IRQ) en este sencillo ejemplo, simplemente coloca la variable 'var' a cero: 083 IRQISR: 084 clr var 62 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 Después, le indica al periférico IRQ que ha sido atendido (protocolo de Ack de Interrupciones), de tal manera que el dispositivo periférico pueda volver a interrumpir, si fuera necesario (lo que se conoce como REARMAR la interrupción), y retorna al flujo principal del programa (RTI): 085 BSET IRQSC_IRQACK, IRQSC ; Acnowledge IRQ Interrupt 086 ; .. (rearm IRQ Interrupts) 087 rti Por último se culmina con la parte estándar de las definiciones de los vectores de interrupción, ya vista varias veces con anterioridad. 11) Ejercicio con más Aspectos Nuevos: ‐ Definición de constantes en la ROM (Flash, memoria de programa) ‐ Direccionamiento Indexado con Post‐incremento (X+) en KBI1 para leer consecutivamente de 'Table'. ["Laboratorios\Lab1\Lab1_M\03c_L1_M.asm"] 001 ;******************************************************************** 002 ; Programa 03c_L1_M.asm, HCS08_CPU, Luis G. Uribe C., M29E2013 003 ; 004 ; Nuevos Aspectos: 005 ; 006 ; ‐ Definición de constantes en la ROM (Flash, memoria de programa): 007 ; Una tabla de valores consecutivos en una zona de la memoria de 008 ; programa DIFERENTE a la del código (instrucciones). Para esto se 009 ; ha utilizado DC (define Constant), Bytes por default. Los valores 010 ; corresponden al código ASCII de los caracteres 'Hola', seguidos 011 ; de un "nul byte" (0) 012 ; 013 ; ‐ Direccionamiento Indexado con Post‐incremento (X+) en KBI1 para 014 ; leer consecutivamente de 'Table'. 015 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 016 ; Include files 017 NOLIST 018 INCLUDE 'derivative.inc' 019 LIST 020 021 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 022 ; Parameter definitions 023 ram: SET Z_RAMStart ; $80. Cfr. 'MC9S08QE128‐U.inc' 024 rom: SET ROMStart ; $2080 025 initStack: EQU $1800 026 COP_Disable EQU $42 027 ROMTable: EQU $3000 ; Posición arbitraria, encima del 028 ; ..programa; debajo del area ocupada 029 ; ..(VER Mapa de Memoria) 030 031 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 032 ; Definición de variables (ds: byte por defecto) 033 ORG ram 63 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 034 TPtr: DS.W 1 035 out: DS 1 ; 'DS' y 'DS.B' son sinónimos 036 var: DS.B 1 ; Verifique comparando Project.abs.s19 037 038 ; =================================================================== 039 ; Begin of Code Section 040 ABSENTRY Main ; Export symbol (DEBUGGER entry) 041 042 ORG rom ; $2080: HCS08 ROMStart (Flash) 043 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 044 ; *** ALWAYS *** include the following 4 instructions 045 Main: lda #COP_Disable 046 sta SOPT1 ; System Options 1 047 ldhx #initStack ; Init SP 048 txs ; .. 049 050 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 051 ; Enable Interrupts for IRQ 052 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 053 BSET IRQSC_IRQPE, IRQSC ; Enable IRQ PIN 054 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 055 ; .. Clear any possible previous, false, interrupts by Setting 056 ; .. IRQACK bit in IRQSC (IRQ Status and Control register) 057 BSET IRQSC_IRQACK, IRQSC 058 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 059 BSET IRQSC_IRQIE, IRQSC ; IRQ pin Interrupt Enable 060 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 061 cli ; CPU Interrupt ENABLE 062 063 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 064 loop: clr var ; Direct Addressing Mode 065 bsr rutina ; Relative Addressing Mode 066 bra loop 067 068 ;==================================================================== 069 ; Subrutina de ejemplo 070 rutina: 071 lda var 072 cbeqa #10, fin ; Termina cuando llega a 10 073 inc var 074 bra rutina 075 fin: rts ; COLOCAR BREAKPOINT AQUÍ (Debugger) 076 077 ;==================================================================== 078 ; Transfer ALL table complete to 'out', with each IRQ activation. 079 ; NOTE: En una rutina de interrupciones **JAMÁS** use un registro sin 080 ; ..SALVAR previamente su valor en el stack. Al finalizar, debe 081 ; ..RESTAURAR su valor desde el stack. 082 IRQISR: 083 pshh ;Save H reg; Hardware does NOT do this 084 ldhx #Table ; Index reg. points to 'Table' start. 085 ; (Cuánto vale 'Table'? Y '#Table'?) 64 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 086 loop2: mov X+, out 087 beq done ; Activate BREAKPOINT here to see... 088 bra loop2 089 090 done: pulh 091 BSET IRQSC_IRQACK, IRQSC ; Acnowledge IRQ Interrupt 092 ; .. (rearm IRQ Interrupts) 093 rti 094 095 ;==================================================================== 096 ; Table 097 ORG ROMTable 098 Table: DC.B 'H' 099 DC.B 'O' 100 DC.B 'L' 101 DC.B 'A' 102 DC.B 0 104 ;******************************************************************** 105 ;* Interrupt Vectors * 106 ;******************************************************************** 107 ; (Esta parte es más o menos estándar, 108 ; ..excepto que normalmente se incluyen TODOS los periféricos...) 109 spurious: 110 rti 112 ORG Virq ; Change ORG 113 DC.W IRQISR ; <<< IRQ Defined 114 DC.W spurious ; SWI 115 DC.W Main ; RESET 116 117 END 118 119 NOTE: If in CW 10.2, IRQ does NOT have any effect if you are 120 debugging in Full Chip Simulation (FCS) and you are using step by 121 step (F5). You need to make a break point in the ISR and then RUN at 122 full speed: F8. Now, IRQ works... 123 124 GURIBE, M29E2013 COMENTARIOS a ["Laboratorios\Lab1\Lab1_M\03c_L1_M.asm"]: El programa comienza de la manera ordinaria. Defina la ROMTable arbitrariamente en la posición $3000, que queda bien por encima del área de Flash utilizada por el programa: 027 ROMTable: EQU $3000 ; Posición arbitraria Define un APUNTADOR a la tabla que, por tanto, tiene que tener 16 bits (DS.W: Define Storage WORD): 032 ; Definición de variables (ds: byte por defecto) 033 ORG ram 034 TPtr: DS.W 1 65 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 Hace luego la habilitación que ya hemos realizado para el IRQ, y el CLI para habilitar las interrupciones del CPU. La rutina de ejemplo es el mismo código simple que empleamos en el ejercicio anterior. La parte novedosa consiste en transferir TODA la tabla (que en el ejemplo contiene solo las letras HOLA y un '\0' que estamos usando como terminador de strings, igual que en C), de un solo golpe, cada vez que se interrumpa el CPU mediante el botón de IRQ. La advertencia más importante es: 079 ; NOTE: En una rutina de interrupciones **JAMÁS** use un registro sin 080 ; ..SALVAR previamente su valor en el stack. Al finalizar, debe 081 ; ..RESTAURAR su valor desde el stack. Analicemos la IRQISR: 082 IRQISR: 083 pshh ;Save H reg; Hardware does NOT do this 084 ldhx #Table ; Index reg. points to 'Table' start. 085 ; (Cuánto vale 'Table'? Y '#Table'?) Lo primero es decir que el protocolo de atención de interrupciones de este CPU, almacena los cinco registros, con excepción de la parte superior del H:X (o sea, el H). Así que es INDISPENSABLE SALVAR ESE REGISTRO POR PROGRAMA; de ahí el PSHH (Push H) Luego se coloca la dirección de la tabla en el registro índice H:X A continuación viene el ciclo que mueve toda la tabla, letra por letra, a la variable 'out' (que, como veremos luego, si no fuera una variable sino el registro de salida del periférico de comunicaciones, ¡enviaría el mensaje al PC!) 086 loop2: mov X+, out 087 beq done ; Activate BREAKPOINT here to see... 088 bra loop2 Para finalizar, HAY QUE RESTAURAR POR PROGRAMA LA PARTE ALTA DEL REGISTRO ÍNDICE H:X ( o sea, H, que resguardó en Stack al comienzo). Luego se culmina con el protocolo ya conocido de Interrupt Acknowledge para este periférico, y la rutina retorna de la interrupción. Si se vuelve a oprimir el botón, vuelve a trasmitirse la tabla: 090 done: pulh 091 BSET IRQSC_IRQACK, IRQSC ; Acnowledge IRQ Interrupt 092 ; .. (rearm IRQ Interrupts) 093 rti Definición de la tabla: 096 ; Table 097 ORG ROMTable 098 Table: DC.B 'H' 099 DC.B 'O' 100 DC.B 'L' 101 DC.B 'A' 66 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 102 DC.B 0 ...que también puede escribirse como: Table: DC.B "HOLA", 0 La parte final, de los vectores de interrupción, es la misma que ya hemos visto. 12) Ejercicio con más Aspectos Nuevos aún: Este programa es similar al anterior, pero no envía la tabla completa cada vez que se oprime el IRQ, sino que mueve una letra a la vez, por cada interrupción de IRQ. ["Laboratorios\Lab1\Lab1_M\03d_L1_M.asm"] 001 ;******************************************************************** 002 ; Programa 03d_L1_M.asm, HCS08_CPU, Luis G. Uribe C., M29E2013 003 ; 004 ; Nuevos Aspectos: 005 ; 006 ; ‐ Uso de una variable 'pointer' (TPtr) para transferir la tabla 008 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 009 ; Include files 010 NOLIST 011 INCLUDE 'derivative.inc' 012 LIST 014 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 015 ; Parameter definitions 016 ram: SET Z_RAMStart ; $80. Cfr. 'MC9S08QE128‐U.inc' 017 rom: SET ROMStart ; $2080 018 initStack: EQU $1800 019 COP_Disable EQU $42 020 ROMTable: EQU $3000 ; Posición arbitraria, encima del 021 ; ..programa; debajo del area ocupada 022 ; ..(VER Mapa de Memoria) 023 024 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 025 ; Definición de variables (ds: byte por defecto) 026 ORG ram 027 TPtr: DS.W 1 028 out: DS 1 ; 'DS' y 'DS.B' son sinónimos 029 var: DS.B 1 ; Verifique comparando Project.abs.s19 030 031 ; =================================================================== 032 ; Begin of Code Section 033 ABSENTRY Main ; Export symbol (DEBUGGER entry) 034 035 ORG rom ; $2080: HCS08 ROMStart (Flash) 036 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 037 ; *** ALWAYS *** include the following 4 instructions 038 Main: lda #COP_Disable 039 sta SOPT1 ; System Options 1 67 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 040 ldhx #initStack ; Init SP 041 txs ; .. 042 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 043 044 ldhx #Table ; Init pointer to Table 045 sthx TPtr ; .. 046 047 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 048 ; Enable Interrupts for IRQ 049 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 050 BSET IRQSC_IRQPE, IRQSC ; Enable IRQ PIN 051 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 052 ; .. Clear any possible previous, false, interrupts by Setting 053 ; .. IRQACK bit in IRQSC (IRQ Status and Control register) 054 BSET IRQSC_IRQACK, IRQSC 055 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 056 BSET IRQSC_IRQIE, IRQSC ; IRQ pin Interrupt Enable 057 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 058 cli ; CPU Interrupt ENABLE 059 060 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 061 loop: clr var ; Direct Addressing Mode 062 bsr rutina ; Relative Addressing Mode 063 bra loop 064 065 ;==================================================================== 066 ; Subrutina de ejemplo 067 rutina: 068 lda var 069 cbeqa #10, fin ; Termina cuando llega a 10 070 inc var 071 bra rutina 072 fin: rts 073 074 ;==================================================================== 075 ; Transfer ALL table to 'out', CHAR BY CHAR with each IRQ activation. 076 ; NOTE: En una rutina de interrupciones **JAMÁS** use un registro sin 077 ; ..SALVAR previamente su valor en el stack. Al finalizar, debe 078 ; ..RESTAURAR su valor desde el stack. 079 IRQISR: 080 pshh ; Salvar H (el Hardware no lo hace !) 081 ldhx TPtr ; Cargar registro índice con Pointer 082 mov X+, out ; Mueve letra; apunta a la siguiente 083 beq resetPtr 084 sthx TPtr ; ..Actualiza el pointer en memoria 085 bra done 086 087 resetPtr: 088 ldhx #Table ; Re‐inicializa el pointer de Table 089 sthx TPtr ; .. 090 done: pulh 68 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 091 BSET IRQSC_IRQACK, IRQSC ; Acnowledge IRQ Interrupt 092 ; .. (rearm IRQ Interrupts) 093 rti ; Activate BREAKPOINT here to see... 094 095 ;==================================================================== 096 ; Table 097 ORG ROMTable 098 Table: DC "HOLA", 0 ; QUÉ ES EL '0' QUE APARECE AL FINAL? 099 ; (DC es lo mismo que DC.B) 100 ;******************************************************************** 101 ; Interrupt Vectors 102 ; ALWAYS INCLUDE VERBATIM COPY, EXCEPTO FOR VECTORS YOU USE... 103 dummy_isr: ; This must be placed in ROM Space 104 rti 105 106 org Vtpm3ovf ; Increasing priority from bottom up 107 DC.W dummy_isr ;Vtpm3ovf: 108 DC.W dummy_isr ;Vtpm3ch5: 109 DC.W dummy_isr ;Vtpm3ch4: 110 DC.W dummy_isr ;Vtpm3ch3: 111 DC.W dummy_isr ;Vtpm3ch2: 112 DC.W dummy_isr ;Vtpm3ch1: 113 DC.W dummy_isr ;Vtpm3ch0: 114 DC.W dummy_isr ;Vrtc: 115 DC.W dummy_isr ;Vsci2tx: 116 DC.W dummy_isr ;Vsci2rx: 117 DC.W dummy_isr ;Vsci2err: 118 DC.W dummy_isr ;Vacmpx: 119 DC.W dummy_isr ;Vadc: 120 DC.W dummy_isr ;Vkeyboard: 121 DC.W dummy_isr ;Viicx: 122 DC.W dummy_isr ;Vsci1tx: 123 DC.W dummy_isr ;Vsci1rx: 124 DC.W dummy_isr ;Vsci1err: 125 DC.W dummy_isr ;Vspi1: 126 DC.W dummy_isr ;Vspi2: 127 DC.W dummy_isr ;Vtpm2ovf: 128 DC.W dummy_isr ;Vtpm2ch2: 129 DC.W dummy_isr ;Vtpm2ch1: 130 DC.W dummy_isr ;Vtpm2ch0: 131 DC.W dummy_isr ;Vtpm1ovf: 132 DC.W dummy_isr ;Vtpm1ch2: 133 DC.W dummy_isr ;Vtpm1ch1: 134 DC.W dummy_isr ;Vtpm1ch0: 135 DC.W dummy_isr ;Vlvd: 136 DC.W IRQISR ;Virq: 137 DC.W dummy_isr ;Vswi: 138 DC.W Main ;Vreset: 139 140 END 69 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 COMENTARIOS a ["Laboratorios\Lab1\Lab1_M\03d_L1_M.asm"]: Usa una variable 'pointer' (TPtr) para transferir la tabla, letra por letra. En el ejercicio 11) también se usó la variable, pero se transfería TODA la tabla por cada IRQ interrupt. La inicialización de TPtr se hace ahora en el programa principal (Main): 044 ldhx #Table ; Init pointer to Table 045 sthx TPtr ; .. La habilitación de las interrupciones para el IRQ queda igual. La 'rutina' y el 'loop' siguen siendo los mismos que antes. La IRQISR cambia un poco, para transferir letra por letra, no tabla por tabla: 079 IRQISR: 080 pshh ; Salvar H (el Hardware no lo hace !) 081 ldhx TPtr ; Cargar registro índice con Pointer El Pointer TPtr fue inicializado en Main. Ahora, usando el modo de direccionamiento indexado con autoincremento: se mueve una letra 082 mov X+, out ; Mueve letra; apunta a la siguiente se aprovecha la propiedad de Conjunto de Instrucciones Enriquecido, que fundamentalmente hace que muchas instrucciones realicen más funciones de las que indica su nombre. Fundamentalmente, la transferencia de información, como en la línea 082: mov X+, out, aprovecha para realizar una comparación TÁCITA con el operando movido, lo que permite tomar una decisión si lo que acaba de transferirse es un '\0', que es el símbolo que estamos usando para marcar el final del string, como en C: 083 beq resetPtr Si no se ha terminado, se guarda el nuevo valor de Pointer, que ha sido incrementado para apuntar a la próxima letra que debe transferirse: 084 sthx TPtr ; ..Actualiza el pointer en memoria y se termina la interrupción 085 bra done Si la instrucción 083 beq resetPtr, determina que ya se transfirió el NULL Byte, se reinicializa el pointer para comenzar de nuevo la transferencia de la tabla, cuando haya más interrupciones: 087 resetPtr: 088 ldhx #Table ; Re‐inicializa el pointer de Table 089 sthx TPtr ; .. 70 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 La parte final es la misma háyase terminado la transmisión o no. Se restaura el valor del registro H, no salvado por el mecanismo de interrupciones y se ejecuta el protocolo de Ack para la interrupción y la rutina retorna al programa principal: 090 done: pulh 091 BSET IRQSC_IRQACK, IRQSC ; Acnowledge IRQ Interrupt 092 ; .. (rearm IRQ Interrupts) 093 rti ; Activate BREAKPOINT here to see... La tabla tiene la misma estructura de antes: 097 ORG ROMTable 098 Table: DC "HOLA", 0 ; QUÉ ES EL '0' QUE APARECE AL FINAL? 099 ; (DC es lo mismo que DC.B) Como ejemplo, se incluyen TODOS los vectores de interrupción, con los nombres definidos por el manufacturante. No la repito aquí para conservar el espacio. 13) Aspectos Cosméticos en el trato de los Vectores de Interrupción: La tabla con todos los valores de los vectores de interrupción, que se incluyó en el ejercicio anterior, se acomoda dentro de un INCLUDE FILE que se invoca al final del problema. El ejercicio es muy simple y no desarrolla ningún código especial. ["Laboratorios\Lab1\Lab1_M\04b_L1_M.asm"] 01 ;******************************************************************** 02 ; Programa 04b_L1_M.asm, HCS08_CPU, Luis G. Uribe C., M29E2013 03 ; 04 ; Nuevos Aspectos: 06 ; ‐ Se emplea un Include File: 'InitU_M.inc' (ALL Interrupt Vectors) 07 08 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 09 ; Include files 10 NOLIST 11 INCLUDE 'derivative.inc' 12 LIST 13 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 14 ; Parameter definitions 15 16 ram: SET Z_RAMStart ; $80 17 rom: SET ROMStart ; $2080 18 initStack: EQU $1800 19 COP_Disable: EQU $42 20 ROMTable: EQU $3000 ; Posición arbitraria, encima del 21 ; ..programa; debajo del area ocupada 22 ; ..(VER Mapa de Memoria) 23 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 24 ; Definición de variables (ds: byte por defecto) 25 26 ORG ram 27 28 TPtr: DS.W 1 71 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 29 out: DS.B 1 30 var: DS.B 1 31 32 ; =================================================================== 33 ; Begin of Code Section 34 ABSENTRY Main ; Export symbol (DEBUGGER entry) 35 36 ORG rom ; $2080: HCS08 ROMStart (Flash) 37 38 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 39 ; *** ALWAYS *** include the following 4 instructions 40 Main: lda #COP_Disable 41 sta SOPT1 ; System Options 1 42 ldhx #initStack ; Init SP 43 txs ; .. 44 45 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 46 ; Branch for ever 47 48 bra * 49 50 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 51 ; InitU 52 NOLIST 53 INCLUDE 'InitU_M.inc' ; Interrupt Vectors; Restart in 'Main' 54 LIST 55 56 END 14) Ejemplo: Rutina de MULTIPLICAR, por Sumas Sucesivas: El HCS08 TIENE una instrucción de multiplicar..., así que no se precisaría hacer la operación mediante sumas. Es solo un ejemplo: ["Laboratorios\Lab1\Lab1_M\05a_L1_M.asm"] 01 ;******************************************************************** 02 ; Programa 05a_L1_M.asm, HCS08_CPU, Luis G. Uribe C., M29E2013 03 ; Luis G. Uribe C., D12A2009 04 ; 05 ; EJEMPLO: Rutina de MULTIPLICAR, por sumas sucesivas 07 ; NOTE: El HCS08 TIENE una instrucción de multiplicar... 08 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 09 ; Include files 10 NOLIST 11 INCLUDE 'derivative.inc' 12 LIST 13 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 14 ; Parameter definitions 15 ram: SET Z_RAMStart ; $80 16 rom: SET ROMStart ; $2080 72 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 17 initStack: EQU $1800 18 COP_Disable: EQU $42 19 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 20 ; Definición de variables (DS: Define Storage BYTE, por defecto) 21 ORG ram 22 m_ando: DS.B 1 ; Multiplicando 23 m_ador: DS 1 ; Multiplicador 24 prod: DS.B 1 ; Producto (RESULTADO) 25 cnt: DS 1 ; Contador 26 ; =================================================================== 27 ; Begin of Code Section 28 ABSENTRY Main ; Export symbol (DEBUGGER entry) 29 ORG rom ; $2080: HCS08 ROMStart (Flash) 31 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 32 ; *** ALWAYS *** include the following 4 instructions 33 Main: lda #COP_Disable 34 sta SOPT1 ; System Options 1 35 ldhx #initStack ; Init SP 36 txs ; .. 37 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 38 ; Inicialización de parámetros 39 mov #5, m_ando ; Main: Etiqueta del programa ppal. 40 mov #3, m_ador ; Pasar parámetros a la rutina 41 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 42 ; Rutina de Multiplicación por sumas sucesivas 43 clr prod 44 clr cnt 45 mply: lda m_ador 46 cbeq cnt, fin 47 lda prod 48 add m_ando 49 sta prod 50 inc cnt 51 bra mply 52 53 fin: bra * ; Simula un HALT 54 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 55 nop ; <<<NEEDED by CodeWarrior 10.1&2 (not 6.3). INCREDIBLE<<< 56 ; This 'nop' MAY be removed for CW 6.3 ... 57 58 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 59 ; InitU 60 ; NOLIST 61 INCLUDE 'InitU_M.inc' ; Interrupt Vectors; Restart in 'Main' 62 ; LIST 63 64 END COMENTARIOS a ["Laboratorios\Lab1\Lab1_M\05a_L1_M.asm"]: Las partes nuevas comienzan con la Inicialización de parámetros. Se multiplicarán, como ejemplo, 5 * 3 (constantes): 73 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 39 mov #5, m_ando ; Main: Etiqueta del programa ppal. 40 mov #3, m_ador ; Pasar parámetros a la rutina La Rutina (que aún NO Subrutina) de Multiplicación por sumas sucesivas es como sigue: 43 clr prod 44 clr cnt 45 mply: lda m_ador 46 cbeq cnt, fin 47 lda prod 48 add m_ando 49 sta prod 50 inc cnt 51 bra mply 53 fin: bra * ; Simula un HALT 54 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 55 nop ; <<<NEEDED by CodeWarrior 10.1&2 (not 6.3). INCREDIBLE<<< 56 ; This 'nop' MAY be removed for CW 6.3 ... El ejercicio termina con en la línea 53 fin: bra *, que simula un HALT, para permitirnos mirar el resultado con el Debugger. 15) "SUBRUTINA" de MULTIPLICAR, por sumas sucesivas: Es un ejemplo muy parecido al anterior, pero el código se ha colocado dentro de una SUBRUTINA que el programa principal llama. Esta es la forma más corriente de codificación. ["Laboratorios\Lab1\Lab1_M\05b_L1_M.asm"] 01 ;******************************************************************** 02 ; Programa 05b_L1_M.asm, HCS08_CPU, Luis G. Uribe C., M29E2013 03 04 ; EJEMPLO: Rutina de MULTIPLICAR, por sumas sucesivas 06 ; Nuevos Aspectos: 07 ; 08 ; ‐ Incluye una Subrutina 09 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 10 ; Include files 11 NOLIST 12 INCLUDE 'derivative.inc' 13 LIST 14 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 15 ; Parameter definitions 16 ram: SET Z_RAMStart ; $80 17 rom: SET ROMStart ; $2080 18 initStack: EQU $1800 19 COP_Disable: EQU $42 20 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 21 ; Definición de variables (DS: Define Storage BYTE, por defecto) 22 ORG ram 24 m_ando: DS.B 1 ; Multiplicando 74 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 25 m_ador: DS 1 ; Multiplicador 26 prod: DS.B 1 ; Producto (RESULTADO) 27 ; =================================================================== 28 ; Begin of Code Section 29 ABSENTRY Main ; Export symbol (DEBUGGER entry) 30 ORG rom ; $2080: HCS08 ROMStart (Flash) 31 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 32 ; *** ALWAYS *** include the following 4 instructions 33 Main: lda #COP_Disable 34 sta SOPT1 ; System Options 1 35 ldhx #initStack ; Init SP 36 txs ; .. 37 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 38 ; Inicialización de parámetros 39 mov #5, m_ando ; Main: Etiqueta del programa ppal. 40 mov #3, m_ador ; Pasar parámetros a la rutina 41 42 bsr mply ; Branch to Subroutine mply 43 final: bra final ; Halt 44 45 ; =================================================================== 46 ; Rutina de Multiplicación por sumas sucesivas 47 mply: clr prod 48 lda m_ador ; RICH Instruction Set: Load and make 49 ; ..tacit & automatic COMPARE with '0' 50 beq fin 51 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 52 ; Loop 53 mply2: lda prod 54 add m_ando 55 sta prod 56 dec m_ador 57 beq fin 58 bra mply2 59 fin: rts 60 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 61 nop ; <<<NEEDED by CodeWarrior 10.1&2 (not 6.3). INCREDIBLE<<< 62 ; This 'nop' MAY be removed for CW 6.3 ... 63 64 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 65 ; InitU 66 NOLIST 67 INCLUDE 'InitU_M.inc' ; Interrupt Vectors; Restart in 'Main' 68 LIST 70 END COMENTARIOS a ["Laboratorios\Lab1\Lab1_M\05b_L1_M.asm"]: Las novedades comienzan aquí: 42 bsr mply ; Branch to Subroutine mply 43 final: bra final ; Halt 75 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 Se trasladó el código del programa principal a la SUBRUTINA MPLY: 47 mply: clr prod 48 lda m_ador ; RICH Instruction Set: Load and make 49 ; ..tacit & automatic COMPARE with '0' 50 beq fin 52 ; Loop 53 mply2: lda prod 54 add m_ando 55 sta prod 56 dec m_ador 57 beq fin 58 bra mply2 59 fin: rts 16) Ejemplo PRIMORDIAL del uso de Variables Dinámicas: Las variables dinámicas, en C, son aquellas que no existen antes de llamarse la subrutina en donde se van a utilizar; se generan como parte del mecanismo de llamada a la función (Stack FRAME), duran hasta que la subrutina termine, y desaparecen con la finalización de la función, liberando el espacio utilizado. Este mecanismo tiene dos aspectos importantes: Primero, emplea de una manera muy eficiente el espacio, según acabo de explicar. Las variables estáticas o globales, conservan el espacio desde su asignación hasta la terminación del programa. Nunca lo liberan hasta entonces. Segundo, permiten la codificación de FUNCIONES RECURSIVAS, al estilo de FACTORIAL, o las TORRES DE HANOI y similares. Es MUY importante entender y poder aplicar las técnicas que aquí expongo. El ejemplo que usaremos como vehículo para aplicar y demostrar la forma de definir variables dinámicas será otra vez el Ejemplo de EXPONENCIACIÓN, por multiplicaciones sucesivas que a su vez se hacen por sumas sucesivas... Pero TODAS las variables estarán en el Stack. Es importante, para su comprensión, que se haga paso a paso un dibujo del Stack y el valor del SP, a medida que se ejecuta el programa. Puede ayudarse con el Debugger, siguiendo el programa paso a paso y monitoreando el SP y el área asignada al Stack. ["Laboratorios\Lab1\Lab1_M\05d_L1_M.asm"] 001 ;******************************************************************** 002 ; Programa 05d_L1_M.asm, HCS08_CPU, Luis G. Uribe C., M29E2013 003 004 ; EJEMPLO: EXPONENCIACIÓN, por multiplicaciones sucesivas; 005 ; ..que a su vez se hacen por sumas sucesivas... 006 ; 007 ; Nuevos Aspectos: 008 ; 009 ; ‐ **USA SOLO EL STACK; NO VARIABLES LOCALES** 010 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 76 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 011 ; Include files 012 NOLIST 013 INCLUDE 'derivative.inc' 014 LIST 016 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 017 ; Parameter definitions 018 mantisa: EQU 2 ; Resultado: 2^5=32 (0x20 en el ACC) 019 exponente: EQU 5 ; Cambie aquí si se desa más valores 020 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 021 ram: SET Z_RAMStart ; $80 022 rom: SET ROMStart ; $2080 023 initStack: EQU $1800 024 COP_Disable: EQU $42 025 026 STD: EQU 4 ;Posición STandarD de la 1a variable 027 028 ; =================================================================== 029 ; DEFINICION DE VARIABLES LOCALES: 031 ; En lugar de las variables (globales), se especifica el "Offset" 032 ; ..que cada una tendrá en el Stack (Frame) al momento de llamar a 033 ; ..una subrutina. De esta manera, tal como hace el IJVM, cada 034 ; ..subrutina o función puede direccionar "simbólicamente" sus 035 ; ..variables locales, y de idéntica forma, sus parámetros. Se emplea 036 ; ..para ello, de manera extensiva, el direccionamiento indexado. 037 ; 038 ; Observe que este direccionamiento puede hacerse en relación al SP, 039 ; ..pero resulta preferible en función del H:X, ya que el SP va 040 ; ..moviéndose cada vez que se hace un PUSH, un llamado de subrutina, 041 ; ..y esto complica el seguimiento de la posición de cada variable. 042 ; 043 ; Los números, tales como: "result EQU STD", indican a cuántos bytes 044 ; ..se encuentra la variable equivalente. Así, "result" está situada 045 ; ..4 bytes por encima de X, al llamar la subrutina 'exp' 046 ; 047 ; Háganse un gráfico que señale la posición de c/variable en el stack 048 ; ..Eso ayuda bastante a la comprensión de este código 049 ; 050 ; ========================== 051 ; subrutina 'exp' (exponenciar) 052 ; 053 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 054 ; (1) Variables LOCALES (En el STACK): 055 056 result: EQU STD ; Única variable "local" de 'exp' 057 ; Otras variables se definirían aquí. Por ejemplo: 058 ; var2: EQU result+1 ; 1 es el tamaño en bytes de 'result' 059 ; var3: EQU var2+1 ; 1 es el tamaño en bytes de 'var2', etc. 060 061 EXP_Vars: EQU result‐STD+1 ; Cantidad de variables locales en 'exp' 062 ; La expresión es: PRIMERA_variable‐STD+1 063 ; (*) NOTA: 064 ; Si fuera 'result' la que midiera 2 bytes, var2 sería: 77 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 065 ; var2: EQU result+2 ; etc. 066 067 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 068 ; (2) PARÁMETROS (EN EL STACK, en órden INVERSO a como se apilan. 069 ; ..Si se hace *Push BASE* y luego *Push EXP*, el orden es el que se 070 ; ..indica a continuación; primero EXP y luego BASE): 071 072 exp: EQU result+1 ; segundo parámetro 073 base: EQU exp+1 ; PRIMER parámetro. Aplica (*) NOTA arriba 074 075 EXP_Stack: EQU base‐STD+1 ; uso del stack (vars + param) 076 ; La expresión es ÚLTIMO_parámetro‐STD+1 077 078 ; ========================== 079 ; subrutina 'mply' 080 ; (1) VARIABLES LOCALES (En el STACK): 081 082 prod: EQU STD ; Única variable "local" de 'mply' 083 084 MUL_Vars: EQU prod‐STD+1 ; Cantidad de variables locales en 'mply' 085 086 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 087 ; (2) PARÁMETROS (EN EL STACK): 088 089 m_ador: EQU prod+1 ; segundo parámetro 090 m_ando: EQU m_ador+1 ; primer parámetro 091 092 MUL_Stack: EQU m_ando‐STD+1 ; uso del stack (vars + param) 093 094 ; =================================================================== 095 ; Begin of Code Section 096 ABSENTRY Main ; Export symbol (DEBUGGER entry) 097 098 ORG rom ; $2080: HCS08 ROMStart (Flash) 099 100 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 101 ; *** ALWAYS *** include the following 4 instructions 102 Main: lda #COP_Disable 103 sta SOPT1 ; System Options 1 104 ldhx #initStack ; Init SP 105 txs ; .. 106 107 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 108 ; InitU 109 ; NOLIST 110 INCLUDE 'InitU_M.inc' ; Interrupt Vectors; Restart in 'Main' 111 ; LIST 112 113 ; ============================================================ 114 ; Pasa parámetros a la rutina "expo" 115 lda #mantisa ; base 116 psha 78 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 118 lda #exponente ; exp (2^5 = 32: 0x20) 119 psha 120 121 ais #‐EXP_Vars ; reserva VARIABLES locales en "expo" 122 bsr expo ; JSR expo. Resulado en ACC 123 ais #EXP_Stack ; recupera stack *** Resulado en ACC *** 124 125 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 126 bra * ; Simula un HALT. 2^5 = 32: $20 (see ACC) 127 128 ; =================================================================== 129 ; Rutina de Exponenciación por multiplicaciones sucesivas 130 expo: pshx ; guarda stack frame anterior 131 pshh 132 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 133 tsx ; Mark Variable Frame in stack with H:X 134 135 lda #1 136 sta result, x 137 138 lda exp, x 139 beq ex_fin 140 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 141 ; Pasar parámetros a la rutina "mply" 142 expo2: lda result, x ; pasa parámetros a la rutina "mply": 143 psha ;.. m_ando 144 145 lda base, x ; m_ador 146 psha 147 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 148 ais #‐MUL_Vars ; reserva area para VARIABLES locales 149 bsr mply ; *** Resulado en ACC 150 ais #MUL_Stack ; recupera stack 151 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 152 sta result, x 153 lda exp, x 154 deca 155 sta exp, x 156 beq ex_fin 157 bra expo2 158 ex_fin: 159 lda result, x ; Result in ACC 160 pulh ; recupera stack frame anterior 161 pulx 162 rts 163 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 164 ; Rutina de Multiplicación por sumas sucesivas 165 mply: pshx ; guarda stack frame anterior 166 pshh 167 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 168 tsx ; Mark Variable Frame in stack with HX 169 clra 79 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 170 sta prod, x 171 lda m_ador, x 172 beq s_fin 173 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 174 ; Loop 175 mply2: lda prod, x 176 add m_ando, x 177 sta prod, x 178 lda m_ador, x 179 deca 180 sta m_ador, x 181 beq s_fin 182 bra mply2 183 s_fin: lda prod, x ; Result in ACC 184 pulh ; recupera stack frame anterior 185 pulx 186 rts 187 188 END COMENTARIOS a ["Laboratorios\Lab1\Lab1_M\05d_L1_M.asm"]: La primera modificación al ejercicio que ya hemos presentado con anterioridad, es la definición de los valores que van a multiplicarse. Hasta ahora se usaron como constantes numéricas: #3 y #5. Aquí se les da una identificación simbólica: 017 ; Parameter definitions 018 mantisa: EQU 2 ; Resultado: 2^5=32 (0x20 en el ACC) 019 exponente: EQU 5 ; Cambie aquí si se desa más valores Y luego viene la diferencia fundamental: DEFINICION DE VARIABLES LOCALES: En lugar de las variables (globales) con las que se trabajó en los ejercicios anteriores, se especifica aquí el "Offset" que cada una tendrá en el Stack (Frame) al momento de llamar a una subrutina. De esta manera, cada subrutina o función puede direccionar "simbólicamente" sus variables locales, y de idéntica forma, sus parámetros. Se emplea para ello, de manera extensiva, el direccionamiento indexado. El hecho de que variables y parámetros de una función, puedan manipularse DE LA MISMA MANERA (en este caso, ambos con NOMBRES), fue una de las cosas importantes de los lenguajes de alto nivel, como el C. Cuando hacemos algo así como: int example( int a, int b) { int tmp; tmp = a * b; // mirando esta líne no puede diferenciarse entre los // ..parámetros a y b y la variable local tmp. Es el ... // ..mismo tratamiento para variables y parámetros! } Este direccionamiento de variables en el Stack puede hacerse en relación al SP, pero resulta preferible en función del registro índice H:X, ya que el SP va moviéndose cada vez que se hace un PUSH, o un llamado de subrutina, y esto complica el seguimiento de 80 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 la posición de cada variable. En cambio, una vez marcado el STACK FRAME, con el registro índice, el H:X queda estático y los desplazamientos en consecuencia no se mueven. En el ejercicio, los números como el de la línea 056: 056 result: EQU STD ; Única variable "local" de 'exp' indican a cuántos bytes se encuentra la variable equivalente dentro del Stack (o dentro del STACK FRAME, que es el área marcada por el registro índice H:X). Así, "result" está situada 4 bytes por encima de H:X, al llamar y ejecutar la subrutina 'exp'. Recuerde que STD ha sido definido como: 026 STD: EQU 4 ;Posición STandarD de la 1a variable Haga un gráfico que señale la posición de c/variable y cada parámetro en el stack, siguiendo el orden de los push de los parámetros, la reserva del área de trabajo en stack, las posiciones que se lleva guardar el PC, según el protocolo de llamada a subrutinas y, finalmente, el espacio que se requiere para salvar el valor anterior del registro índice: H:X; eso ayuda bastante a la comprensión de este código. La definición de variables y parámetros para la primera subrutina, 'exp' (exponenciar) es como sigue: 051 ; subrutina 'exp' (exponenciar) 054 ; (1) Variables LOCALES (En el STACK): 056 result: EQU STD ; Única variable "local" de 'exp' Si hubiera más variables se definirían aquí así: 058 ; var2: EQU result+1 ; 1 es tamaño de 'result' 059 ; var3: EQU var2+1 ; 1 es tamaño de 'var2', etc. Se ha asumido que var2 y var3 tendrían un tamaño de un byte. Si las variables fueran WORDS, de 2 bytes, las definiciones serían así: 058 ; var2: EQU result+1 ; 1 es tamaño de 'result' 059 ; var3: EQU var2+2 ; 2 es tamaño de 'var2' Medición del espacio de Stack ocupado por la subrutina 'exp': 061 EXP_Vars: EQU result‐STD+1 ; Cantidad de VARIABLES locales en 'exp' 062 ; La expresión es: PRIMERA_variable‐STD+1 A continuación se definen los PARÁMETROS (es costumbre ponerlos en el Stack, en orden INVERSO a como se apilan. Así se hace en C, de tal manera que una expresión como printf( "%d", i ); introduce 'i' en el Stack y luego el FORMAT: %d. De esta manera, como el número de parámetros de printf es VARIABLE, se puede averiguar con cuántos parámetros se la llamó, inspeccionando el String de FORMAT, que convenientemente es el que está en el techo del Stack (apuntado directamente por el SP‐1. Recuerde que el SP en esta máquina se diseñó para que apuntara a la próxima posición VACÍA dentro del Stack, a diferencia de todas las demás máquinas del orbe...) 81 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 Recuerde que STD ha sido definido como: 026 STD: EQU 4 ;Posición STandarD de la 1a variable Repito por claridad la línea 56: 056 result: EQU STD ; Única variable "local" de 'exp' 060 061 EXP_Vars: EQU result‐STD+1; Cantidad de variables locales en 'exp' 062 ; La expresión es: PRIMERA_variable‐STD+1 067 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 068 ; (2) PARÁMETROS (EN EL STACK, en órden INVERSO a como se apilan. 069 ; ..Si se hace *Push BASE* y luego *Push EXP*, el orden es el que se 070 ; ..indica a continuación; primero EXP y luego BASE): 072 exp: EQU result+1 ; segundo parámetro. 073 base: EQU exp+1 ; PRIMER parámetro. Aplica (*) NOTA arriba 075 EXP_Stack: EQU base‐STD+1 ; uso del stack (vars + param) El segundo símbolo auxiliar para la liberación de espacio en esta subrutina es: 075 EXP_Stack: EQU base‐STD+1 ; uso del stack (vars + param) En la expresión de la línea 072: 072 exp: EQU result+1 ; 'result' es la ÚLTIMA VARIABLE del método, ANTES de los parámetros. (Aquí, además de ser la última, también es la única...) Para la subrutina 'mply' se sigue el mismo procedimiento: 079 ; subrutina 'mply' 080 ; (1) VARIABLES LOCALES (En el STACK): 082 prod: EQU STD ; Única variable "local" de 'mply' 084 MUL_Vars: EQU prod‐STD+1 ; Cantidad de variables locales en 'mply' La línea 084 mide el número de variables locales en esta subrutina, 084 MUL_Vars: EQU prod‐STD+1 Luego se definen los parámetros, similarmente a como se hizo en la subrutina anterior: 087 ; (2) PARÁMETROS (EN EL STACK): 089 m_ador: EQU prod+1 ; segundo parámetro 090 m_ando: EQU m_ador+1 ; primer parámetro 092 MUL_Stack: EQU m_ando‐STD+1 ; uso del stack (vars + param) Este valor de la línea 092 mide el USO del stack para esta subrutina: 092 MUL_Stack: EQU m_ando‐STD+1 ; uso del stack (vars + param) Note: Los vectores de Interrupción están definidos para arrancar en Main, en el INCLUDE file: 'InitU_M.inc', en: 82 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 110 INCLUDE 'InitU_M.inc' ; Interrupt Vectors; Restart in 'Main' Observemos cómo se pasan los parámetros a 'expo', y cómo se reservan las variables locales: 113 ; ============================================================ 114 ; Pasa parámetros a la rutina "expo" 115 lda #mantisa ; base 116 psha 117 118 lda #exponente ; exp (2^5 = 32: 0x20) 119 psha A estas alturas, se han acomodado los dos parámetros en el Stack. Ahora se reserva el espacio para las variables locales, decrementando el SP por la cantidad definida arriba como la cantidad de bytes de las variables locales: 121 ais #‐EXP_Vars ; reserva VARIABLES locales en "expo" Luego se salta a la subrutina, lo cual aleja nuestra área de almacenamiento, DOS (2) bytes del SP (porque el salto a subrutina implica guardar el PC de retorno en el Stack, y el PC mide 2 bytes) 122 bsr expo ; JSR expo. Resulado en ACC Al retornar, de una sola operación se recupera todo el Stack, tanto el correspondiente a los parámetros como el ocupado por las variables: 123 ais #EXP_Stack ; recupera stack *** Resulado en ACC *** Ahora analicemos la primera subrutina, 'expo'. Lo primero que hace es guardar el Stack Frame Anterior, que se encuentra marcado por el registro índice: H:X. La cantidad de bytes que ocupa el registro índice guardado en el Stack (2 bytes) nos aleja justo 2 posiciones más de nuestros datos, PARA UN TOTAL DE CUATRO (4) que es el valor definido arriba para el símbolo 'STD'. 130 expo: pshx ; guarda stack frame anterior 131 pshh Ahora procede a generar un NUEVO STACK FRAME: 132 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 133 tsx ; Mark Variable Frame in stack with H:X De ahí en adelante la secuencia de operaciones es bastante legible: 135 lda #1 136 sta result, x ; definido en 056 result: EQU STD 138 lda exp, x ; definido en 072 exp: EQU result+1 139 beq ex_fin 83 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 Recuerden que el modo de direccionamiento indexado con desplazamiento de 8 bits, suma el registro índice H:X con el desplazamiento para obtener la posición de la variable, por encima de los 4 bytes (STD) que están ocupando el PC y el antiguo H:X. Recuerde también que cada push DECREMENTA el SP. Por eso para reservar el espacio de las variables, se le sumó a SP un número negativo (repito la línea 121): 121 ais #‐EXP_Vars ; reserva VARIABLES locales en "expo" El comportamiento de la siguiente subrutina, "mply", es tan similar al anterior, que no voy a comentar más sobre él. PROGRAMAS AVANZADOS EN ASSEMBLY LANGUAGE, PARTE II A continuación voy a presentar un ejemplo clásico de la literatura computacional, en donde se muestra el poderío de la PROGRAMACIÓN RECURSIVA a la solución de aquellos problemas que pueden expresarse de esa manera, como son el cálculo de factoriales, búsqueda de Palíndromos, exploración de árboles de información (como los directorios de un sistema de archivos), la generación de combinaciones y permutaciones entre elementos (no el cálculo de cuántas son, sino cuáles son), el cálculo del Máximo Común Divisor, según el algoritmo de Euclides, por mencionar algunos de los más conocidos. En primer lugar mostraré el ejemplo en C, para Windows (compilar con Visual Studio 10+) y luego en Assembly Language para el HCS08. Cuando veamos todos estos programas reescritos en C para el HC9S08QE128, volveremos a verlo. El ejercicio de las Torres de Hanoi supone 3 torres, en las que se han colocado N discos, comenzando por el más grande abajo, y colocando encima discos cada vez más pequeños. El ejercicio requiere pasar todos los discos de la torre 1 a la torre 3, empleando la torre 2 como auxiliar. La única regla es que no se puede colocar nunca un disco grande sobre uno más pequeño. La solución es muy simple: pasar N‐1 discos de la torre 1 a la 2; pasar EL disco restante de la torre 1 a la 3, y finalmente pasar los N‐1 que están en la torre 2, a la 3. LISTO. Desde luego, para pasar N‐1 discos de una torre a otra, se pasan N‐2 a la que sirva ahora de torre auxiliar, empleando el MISMO procedimiento... de manera recursiva. Es decir, cuando sólo haya UN disco, habrá que pasarlo desde la fuente hasta el destino final. En caso contrario, llamar el procedimiento de manera recursiva. 17) Torres de Hanoi, MÍNIMO, en C: A) Programa HanoiMin‐‐.c ["Temarios\Ex#1‐HanoiMin\HanoiMin‐‐.c"] 01 #include "hanoimin_.h" // hanoimin‐‐.c LGUribeC L12D2011 J12D2013 02 void main ( int argc, char ** argv ) /* HANOImin‐‐.C */ /*()*/ 03 { uint N; 04 get( NDisks ); 84 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 05 hanoi( N, 1, 3 ); // MOVE N disks from tower 1 to 3. 06 } /* main() */ // ..That's all! 07 // ================================================================== 08 void hanoi ( uint N, uint a, uint b ) /*()*/ 09 { 10 if( N == 1 ) // GOT ONLY 1 DISK? IT'S EASY! JUST MOVE IT ON 11 PrintW( "Pase el disco de la torre '%d' a la '%d'", a, b ); 12 hanoi( N ‐ 1, a, TowerTmp ); // SEVERAL DISKS? CALL ITSELF RECURSIVELY: 13 hanoi( 1 , a, b ); 14 hanoi( N ‐ 1, TowerTmp, b ); 15 } /* hanoi() */ B) Include File hanoiMin_.h: ["Temarios\Ex#1‐HanoiMin\hanoiMin_.h"] // HANOImin_h.h, Luis G. Uribe C.: Play with paper disks! // S12N2005 J23M2006 L29G2011 J12D2013 16 #include <assert.h> 17 #include <stdio.h> 18 #include <stdlib.h> 19 enum { DEFAULT = 3, MAX = 10 }; 20 typedef unsigned int uint; 21 void hanoi( uint n, uint a, uint b ); 22 #define get(NDisks) {if( argc < 2 ) N = DEFAULT; \ 23 else if((N = atoi(argv[1])) == 0) N = DEFAULT;\ 24 if( N > MAX ) { \ 25 puts("#Discos debe ser <= 10. Asume 3\n"); \ 26 N = DEFAULT; } } 27 // Printf message, and Wait the user to hit ENTER, to continue 28 #define PrintW(s, a, b) { printf( s, a, b ); getchar(); return; } 29 // Magic Number! ie: for a=1 & b=3, => TowerTmp=2 ETC!Sleep on it... 30 #define TowerTmp ( 6 ‐ a ‐ b ) COMENTARIOS a ["Temarios\Ex#1-HanoiMin\HanoiMin--.c"]: Desde luego, me esforcé en presentar un algoritmo MÍNIMO. Difícilmente pueden colocarse menos instrucciones. De 'main' podemos olvidarnos: NO forma parte del algoritmo; sirve para leer del usuario el número de discos, N, y llamar a la respectiva función 'hanoi()', que es la que realmente materializa el algoritmo: Mover N discos desde la torre 'a' hasta la torre 'b': 08 void hanoi ( uint N, uint a, uint b ) /*()*/ 10 if( N == 1 ) // GOT ONLY 1 DISK? IT'S EASY! JUST MOVE IT ON 11 PrintW( "Pase el disco de la torre '%d' a la '%d'", a, b ); 85 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 Como ya dijimos, si en un momento dado hay que mover UN disco (N==1), se lo pasa simplemente de la torre FUENTE (SRC) al la torre DESTINO (DST). Esa parte es fácil: 'a' y 'b' SON las torres SRC y DST. Ahora, si hay más de un disco para mover entre la torre 'a' y la torre 'b', pues también es muy sencillo, como se expresa a continuación: 12 hanoi( N ‐ 1, a, TowerTmp ); // SEVERAL DISKS? CALL ITSELF RECURSIVELY: 13 hanoi( 1 , a, b ); 14 hanoi( N ‐ 1, TowerTmp, b ); ...mover N ‐ 1, de la torre 'a' a la 'TowerTmp', mover el disco que queda sólo (1 disco), obviamente de la torre SRC, 'a', a la DST, 'b' y, finalmente mover las N ‐ 1 que teníamos en la temporal, desde esa 'TowerTmp' hasta la 'b'. LISTO. ESO ES TODO. Juéguenlo con 12 cartas (4095 movimientos) y verán que inventarse un algoritmo NO RECURSIVO, no es nada sencillo... B) Include File En este archivo se han colocado las cosas accesorias, que no forman parte del algoritmo. Lo único resaltante es: // HANOImin_h.h, Luis G. Uribe C.: Play with paper disks! J12D2013 29 // Magic Number! ie: for a=1 & b=3, => TowerTmp=2 ETC!Sleep on it... 30 #define TowerTmp ( 6 ‐ a ‐ b ) El Magic Number, según se señala, se obtiene de una fórmula sencilla que nos permite saber: ‐ Si vamos de la torre 1 a la 3, la temporal será la 2. ‐ Para ir de la 1 a la 2, la temporal será la 3. ‐ Y, de la 2 a la 3, la temporal será la 1. Esa fórmula produce el número de la torre TEMPORAL, dados los otros dos números: SRC y DST. 18) Torres de Hanoi, MÍNIMO, en ASSEMBLY LANGUAGE: Es un tema recursivo de mi tratamiento acerca de este curso, la separación que debe hacerse entre POLÍTICA y MECANISMOS. Las POLÍTICAS definen LO QUE HAY QUE HACER, sin hacer énfasis en el CÓMO, que son precisamente los MECANISMOS. Cuando veamos los Timers, las rutinas de comunicaciones, este ejercicio... siempre resalto la definición de las POLÍTICAS y, aparte, la implementación de los mismos: los MECANISMOS. Otro factor importante que debe tenerse en cuenta es el diseño: TOP‐DOWN, es decir, las funcionalidades deben incluirse desde el nivel más alto del SISTEMA, e ir bajando hasta llegar a definir lo más elemental que se requiere. Y luego, probablemente la 86 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 IMPLEMENTACIÓN se realice de abajo hacia arriba: BUTTOM‐UP, codificando primero las cosas más sencillas, o elementales, que quedaron colocadas en la base jerárquica del diseño, y luego se va subiendo con los módulos más complejos, hasta llegar a los niveles superiores. A) Programa HanoiMin--.asm: ["Temarios\Ex#1‐HanoiMin\HanoiMin‐‐.asm"] 001 ; hanoimin‐‐.asm, Luis G. Uribe C. Hanoi Minimum V27D2013 002 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 003 ; Include files; Parameter definitions 004 NOLIST 005 INCLUDE 'derivative.inc' 006 INCLUDE 'hanoimin_.inc' 007 LIST 008 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐; <<< <<< Change here 'ND', Number of Disks 009 ND EQU 3 ; TODO: Get "ND" from push buttons, or from PC, via SCI 010 ;==================================================================== 011 ; GLOBAL Variables 012 ORG ram 013 tmpAcc: DS 1 ; helper to calculate Magic Number 014 ;******************************************************************** 015 ; MAIN PROGRAM HEADER: 016 ABSENTRY Main ; Export 'Main' symbol 017 ORG rom 018 Main: InitSys 019 HanoiM #ND, #1, #3 ; MOVE ND disks from tower 1‐>3 THAT'S IT! 020 BRA * ; Simulate "C" EXIT from Main. 021 ;==================================================================== 022 ;byte Hanoi_ (byte N,byte Ta,byte Tb) // N from Tower_a to Tb /*()*/ 023 Hanoi_: ; Called from HanoiM (Main), and Hanoi (below) recursively 024 CreateNewStackFrame 025 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 026 lda N, X ; Acc = "N", number of discs (N is ND 027 cmp #1 ; ..pushed by HanoiM above) 028 bne Several ; ..if( N != 1 ) goto Several 029 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 030 OnlyOne: ; Only 1 disk to move from a‐>b? MOVE IT, show the ANSWER: 031 MoveOneFromSrcToDst ; Acc = 00Ta_00Tb 032 bra fin ; <<< Break here to see RESULTS in Acc 033 ;ND=3 Seq: 1_3 1_2 3_2 1_3 2_1 2_3 1_3 034 ; TODO: Display ACC in LEDs or show it somehow in the PC (SCI) 035 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 036 Several: ; Got Several disks? Call itself recursively! 037 EvalTowerTmp Src, Dst ; Acc = Temp. Tower number: 6‐a‐b 038 sta TowerTmp, X ; ..TowerTmp = 6‐a‐b 039 lda N, X ; n_1 = n ‐ 1 040 deca ; .. 87 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 041 sta n_1, X ; .. 042 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 043 Hanoi n_1, Src, TowerTmp 044 HanoiK #1, Src, Dst 045 Hanoi n_1, TowerTmp, Dst 046 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 047 fin: RestoreOldStackFrame 048 RTS 049 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 050 nop ; <<<NEEDED by CodeWarrior 10.1‐5 (not 6.3). INCREDIBLE<<< 051 ; This 'nop' MAY be removed for CW 6.3 ... 052 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 053 ; Interrupt Vectors 054 ORG Vreset 055 DC.W Main ; RESET. Maximum priority. Asynch. 056 END B) Include File HanoiMin_.inc: ["Temarios\Ex#1‐HanoiMin\HanoiMin_.inc"] 057 ; HANOImin_.inc, Luis G. Uribe C.: V27D2013 M27Y2014 058 rom: SET ROMStart ; $2080 059 ram: SET RAMStart ; Put THIS in Main program 060 initStack: EQU $1800 061 COP_Disable: EQU %01000010; $42 062 STD EQU 4 ; 1st variable position in Stack 063 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 064 ; Macros 065 InitSys MACRO 066 lda #COP_Disable ; RSTE=0: PTA5 is NOT for ~RESET 067 sta SOPT1 ; ..System Options 1 068 ldhx #initStack ; Set up SP (and H:X for Stac Frame) 069 txs ; ... 070 ENDM 071 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 072 CreateNewStackFrame MACRO 073 pshx ; Save previous Stack Frame, order X,H 074 pshh 075 tsx ; use H:X to point to Stack Frame 076 ENDM 077 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 078 RestoreOldStackFrame MACRO ; into HX. Order: H,X 079 pulh ; *RECOVER* old Stack Frame 080 pulx 081 ENDM 082 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 083 EvalTowerTmp MACRO A, B ; "ACC=6‐A‐B": MAGIC NUMBER, i.e, for 084 lda \1, X ; tmpAcc = A 085 sta tmpAcc ; .. 086 lda \2, X ; Acc = B 087 add tmpAcc ; Acc = B + A 88 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 088 sub #6 ; Acc = B + A ‐ 6 089 nega ; Acc = 6 ‐ A ‐ B 090 ENDM 091 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 092 Push MACRO PushValue ; i.e: Push #3 (Acc <= #3; push Acc) 093 lda \1 094 psha 095 ENDM 096 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 097 Pop MACRO Var ; i.e: Pop var: pop Acc; var <= Acc 098 pula 099 sta \1 100 ENDM 101 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 102 PushH MACRO PushValue ; i.e: Push dst (Acc <= dst; push Acc) 103 lda \1, X 104 psha 105 ENDM 106 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 107 PopH MACRO Var ; i.e: Pop var: pop Acc; var <= Acc 108 pula 109 sta \1, X 110 ENDM 111 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 112 HanoiM MACRO n, SrcTower, DstTower ; Use in Main 113 Push \3 ; Dst Tower 114 Push \2 ; Src Tower 115 Push \1 ; n Disks 116 ais #‐HANOI_Vars ; *RESERVA* VARS. locales en "Hanoi_" 117 bsr Hanoi_ 118 ais #HANOI_Stack ; Restore Stack. *** Result in ACC *** 119 ENDM 120 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 121 Hanoi MACRO n, SrcTower, DstTower ; Use Recursively 122 PushH \3 ; Dst Tower 123 PushH \2 ; Src Tower 124 PushH \1 ; n Disks 125 ais #‐HANOI_Vars ; *RESERVA* VARS. locales en "Hanoi_" 126 bsr Hanoi_ 127 ais #HANOI_Stack ; Restore Stack. *** Result in ACC *** 128 ENDM 129 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 130 HanoiK MACRO n, SrcTower, DstTower ; 1st parameter is Constant #1 131 PushH \3 ; Dst Tower 132 PushH \2 ; Src Tower 133 Push #1 ; ONE Disk 134 ais #‐HANOI_Vars ; *RESERVA* VARS. locales en "Hanoi_" 135 bsr Hanoi_ 136 ais #HANOI_Stack ; Restore Stack. *** Result in ACC *** 137 ENDM 138 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 89 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 139 MoveOneFromSrcToDst MACRO 140 lda Src, X ; ..(Acc) and RETURN. Acc = 0000_00Ta 141 nsa ; ..Nibble Swap Acc.: Acc = 00Ta_0000 142 sta tmpAcc ; Tmp = 00Ta_0000 143 lda Dst, X ; Acc = 0000_00Tb 144 ora tmpAcc ; .. Acc = 00Ta_00Tb 145 ENDM 146 ;============================== 147 ; Subroutine 'hanoi_' 148 ; In "C" it would be: BYTE hanoi_( BYTE N, BYTE Src, BYTE Dst ); 149 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 150 ; (1) LOCAL Variables (in STACK): 151 TowerTmp EQU STD ; remember number of temporal tower 152 n_1 EQU TowerTmp+1 ; store "n ‐ 1" 153 HANOI_Vars EQU n_1‐STD+1 ; # de BYTES locales en 'hanoi_' 154 ; (2) PARAMETERS (IN STACK, in >>REVERSE<< order to stack. 155 ; ..If you make *Push DST*, *Push SRC* and then *Push N*, order is as 156 ; ..follows: first N, then SRC and last DST 157 N EQU n_1+1 ; tercer parámetro (1 byte) 158 Src EQU N+1 ; segundo parámetro (1 byte) 159 Dst EQU Src+1 ; PRIMER parámetro. (1 byte) 160 HANOI_Stack EQU Dst‐STD+1 ; Uso del Stack (vars + param) COMENTARIOS a ["Temarios\Ex#1-HanoiMin\HanoiMin--.asm"]: Bueno, analicemos primero el programa principal. Esta es la parte donde se define cuántos discos van a jugar (alambrado dentro del código, para hacerlo simple...) 009 ND EQU 3 ; TODO: Get "ND" from push buttons, or from PC, via SCI Ya hemos dicho que los métodos recursivos se basan fundamentalmente en variables tipo dinámicas (como en el C), que son las que se definen en el Stack (ya hicimos ejercicios de eso). Pero aquí se necesita una variable global, visible por todas las instancias del procedimiento recursivo, y que sirve para definir cuál es la torre temporal, en un instante dado: 013 tmpAcc: DS 1 ; helper to calculate Magic Number El programa se define expresando al máximo SÓLO las políticas. Veamos Main, que como en el ejemplo codificado en C, sirve solo como inicialización y llamado a la rutina que realiza de manera recursiva, todo el trabajo: 018 Main: InitSys 019 HanoiM #ND, #1, #3 ; MOVE ND disks from tower 1‐>3 THAT'S IT! Y, ahora sí, veamos la rutina recursiva: 021 ;==================================================================== 022 ;byte Hanoi_ (byte N,byte Ta,byte Tb) // N from Tower_a to Tower_Tb /*()*/ 023 Hanoi_: ; Called from HanoiM (Main), and Hanoi (below) RECURSIVELY 024 CreateNewStackFrame 90 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 Se requiere definir un nuevo Stack Frame cada vez que llamamos la rutina. Ese es el propósito de la Macro 'CreateNewStackFrame' (ver su implementación en el INCLUDE file). Ahora, mira si la cantidad de discos que tiene que transferir es UNO. Recuerde que ese caso es el sencillo; si hay un solo disco para ser movido de la torre 1 a la 3, LISTO: Moverlo... con 'MoveOneFromSrcToDst' 026 lda N, X ; Acc = "N", number of discs (N is ND 027 cmp #1 ; ..pushed by HanoiM above) 028 bne Several ; ..if( N != 1 ) goto Several 029 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 030 OnlyOne: ; Only 1 disk to move from a‐>b? MOVE IT, show the ANSWER: 031 MoveOneFromSrcToDst ; Acc = 00Ta_00Tb 032 bra fin ; <<< Break here to see RESULTS in Acc 033 ;ND=3 Seq: 1_3 1_2 3_2 1_3 2_1 2_3 1_3 De lo contrario, aplicar el mismo procedimiento de manera recursiva. Primero obtenga la identificación de la torre temporal (EvalTowerTmp), dadas las actuales torres SRC y DST: 036 Several: ; Got Several disks? Call itself RECURSIVELY! 037 EvalTowerTmp Src, Dst ; Acc = Temp. Tower number: 6‐a‐b 038 sta TowerTmp, X ; ..TowerTmp = 6‐a‐b Luego obtenga el actual N‐1, que son los discos que deben moverse a la torre temporal 039 lda N, X ; n_1 = n ‐ 1 040 deca ; .. 041 sta n_1, X ; .. Y, finalmente, aplique el procedimiento recursivo: 043 Hanoi n_1, Src, TowerTmp 044 HanoiK #1, Src, Dst 045 Hanoi n_1, TowerTmp, Dst Por último, reestablezca el valor del antiguo Stack Frame, y RETORNE: 047 fin: RestoreOldStackFrame 048 RTS B) Include File hanoiMin_.inc: COMENTARIOS a ["Temarios\Ex#1-HanoiMin\hanoiMin_.inc"]: 057 ; HANOImin_.inc, Luis G. Uribe C.: V27D2013 Las primeras líneas no necesitan especial mención. Push y Pop se entiende con facilidad. Están las equivalentes que usan el registro índice H:X para apuntar al valor al que se requiere hacer push o pop: 91 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 102 PushH MACRO PushValue ; i.e: Push #3 (Acc <= #3; push Acc) 103 lda \1, X 104 psha 105 ENDM 106 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 107 PopH MACRO Var ; i.e: Pop var: pop Acc; var <= Acc 108 pula 109 sta \1, X 110 ENDM La próxima Macro guarda los parámetros: DST, SRC y N en es stack, reserva (AIS) el espacio para las variables, llama a la subrutina y al regresar, libera el espacio ocupado en el Stack (AIS). Esta se diseñó para su uso en MAIN (HanoiM): (hace referencia a los parámetros en forma directa: Push): 112 HanoiM MACRO n, SrcTower, DstTower ; Use in Main 113 Push \3 ; Dst Tower 114 Push \2 ; Src Tower 115 Push \1 ; n Disks 116 ais #‐HANOI_Vars ; *RESERVA* VARS. locales en "Hanoi_" 117 bsr Hanoi_ 118 ais #HANOI_Stack ; Restore Stack. *** Result in ACC *** Hanoi es la que se usa de manera recursiva (hace referencia a los parámetros empleando el registro índice: PushH): 121 Hanoi MACRO n, SrcTower, DstTower ; Use Recursively 122 PushH \3 ; Dst Tower 123 PushH \2 ; Src Tower 124 PushH \1 ; n Disks 125 ais #‐HANOI_Vars ; *RESERVA* VARS. locales en "Hanoi_" 126 bsr Hanoi_ 127 ais #HANOI_Stack ; Restore Stack. *** Result in ACC *** 128 ENDM Finalmente, cuando se requiere pasar el número 1 (#1) como disco, uno de los PushH de 'Hanoi' tiene que cambiarse por Push simple: 130 HanoiK MACRO n, SrcTower, DstTower ; 1st parameter is Constant #1 131 PushH \3 ; Dst Tower 132 PushH \2 ; Src Tower 133 Push #1 ; ONE Disk 134 ais #‐HANOI_Vars ; *RESERVA* VARS. locales en "Hanoi_" 135 bsr Hanoi_ 136 ais #HANOI_Stack ; Restore Stack. *** Result in ACC *** 137 ENDM Estas 3 Macros se necesitan porque al programa Assembler del CodeWarrior le falta EXPRESIVIDAD. En otros ensambladores, bastaría una Macro, y ella determinaría con facilidad el curso de acción, si es el que se requiere en Main, o en las llamadas recursivas, o si se va a incluir una constante como el número #1. 92 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 La Macro MoveOneFromSrcToDst es la que realiza el movimiento (que, en nuestro ejemplo, consiste simplemente en mostrar en el ACUMULADOR los números de las torres que participan en cada jugada. (Esta parte debería modificarse para presentar la información en los LEDs de la tarjeta DEMOQE128, o enviarla por el canal de comunicación serial al PC del laboratorio, para su presentación por pantalla. 139 MoveOneFromSrcToDst MACRO 140 lda Src, X ; ..(Acc) and RETURN. Acc = 0000_00Ta 141 nsa ; ..Nibble Swap Acc.: Acc = 00Ta_0000 142 sta tmpAcc ; Tmp = 00Ta_0000 143 lda Dst, X ; Acc = 0000_00Tb 144 ora tmpAcc ; .. Acc = 00Ta_00Tb 145 ENDM Además de las Macros, está la subrutina, que es el VERDADERO MOTOR DEL PROCESO RECURSIVO, gracias a su manejo de variables dinámicas en Stack: 147 ; Subroutine 'hanoi_' 148 ; In "C" it would be: BYTE hanoi_( BYTE N, BYTE Src, BYTE Dst ); 149 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 150 ; (1) LOCAL Variables (in STACK): 151 TowerTmp EQU STD ; remember number of temporal tower 152 n_1 EQU TowerTmp+1 ; store "n ‐ 1" 153 HANOI_Vars EQU n_1‐STD+1 ; # de BYTES locales en 'hanoi_' 154 ; (2) PARAMETERS (IN STACK, in >>REVERSE<< order to stack. 155 ; ..If you make *Push DST*, *Push SRC* and then *Push N*, order is as 156 ; ..follows: first N, then SRC and last DST 157 N EQU n_1+1 ; tercer parámetro (1 byte) 158 Src EQU N+1 ; segundo parámetro (1 byte) 159 Dst EQU Src+1 ; PRIMER parámetro. (1 byte) 160 HANOI_Stack EQU Dst‐STD+1 ; Uso del Stack (vars + param) Si usted entiende la importancia del ejercicio de las Torres de Hanoi, y es capaz de reescribirlo por su cuenta, habrá logrado culminar la parte genérica del Curso de Arquitectura del Computador. TIMERS Gran cantidad de problemas requieren 'temporizadores' que permitan establecer en un momento determinado, si transcurrió o no cierto lapso. Los "timers" son el corazón de los Sistemas Operativos y de los sistemas Embebidos... NÚMERO DE TIMERS: ¿Por qué no hacer una rutina que active y desactive el Timer: RTC, TPM (o TIMER0 en los PIC) de acuerdo a la cantidad de milisegundos que se quiere medir? 93 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 Pues porque, como normalmente sólo hay *UN* módulo temporizador básico (RTC), y siempre se requieren VARIOS (o MUCHOS) contadores, se necesitarían ¡más recursos de hardware de los que existen! La solución común ofrece varios contadores por software, basados en un sólo dispositivo de hardware. Anexo hay un conjunto de rutinas para manejar 8 temporizadores diferentes (que son un número razonable pero, si no le alcanzan, ¡usted puede aumentarlos con relativa facilidad para ajustarlos a sus necesidades!) El conjunto de Rutinas de Soporte para 8 Timers está en el archivo 'timers8HS.inc', versión para CodeWarrior 10.6, MC9S08QE128, DEMOQE128 (H es la secuencia, contada desde la versión A que hice en Noviembre del 2003. S es la actualización para el MC9S08. Estas rutinas fueron publicadas en EDN, en la versión que realicé para MICROCHIP PIC16F84A, operando a 4 MHz. En Internet: http://edn.com/design/URIBE:Use‐eight‐timers‐with‐PIC16Fxxx‐microcontrollers OPERACIÓN Y USO DE LA BIBLIOTECA DE TIMERS: 1. Se activa el proceso de los 8 temporizadores empleando la Macro: Init8Timers La manera como operan es la siguiente: 'Init8Timers' genera 8 variables, desde 'Timer0' hasta 'Timer7', que ocupan DOS (2) bytes cada una, (16 bits, sin signo). Cada 'Timer' cronometrará una cierta cantidad de tiempo, en milisegundos. Por tanto, podrán medirse intervalos entre 1 ms y 65,536 segundos (un poco más de un minuto; si necesita extenderlo más aún, usted tiene que programar contadores de minutos...) Existe también un byte en el que CADA BIT representa el estado de UN timer, del 0 al 7: la variable llamada 'TimerFlags', y 8 SÍMBOLOS que facilitan la lectura del estado de cada uno de esos bits: 'Timer0Ready' a 'Timer7Ready' 2. Para inicializar un temporizador (en MiliSegundos) se usa la macro SetimerMS. Uno puede decir: SetimerMS 0, #2 ;active temporizador 0 con la constante #2: 2 milisegundos, o: SetimerMS 0, var ;active el timer 0 con el contenido de la variable var, en milisegundos En el programa de prueba timer8HS.asm, incluido más adelante, se emplean así: SetimerMS 0, #50 ; Timer0: 50 mSeconds through CONSTANT SetimerMS 1, var ; Timer1: 25 mSeconds through VARIABLE (var holds #25) 94 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 3. La macro SetimerMS inicializa un timer, pero el flujo de programa NO SE DETIENE A ESPERAR a que pase el tiempo programado. La macro WaitMS_on hace ambas cosas: Inicializa un timer _Y_ espera hasta que transcurra el lapso indicado en MiliSegundos: es una rutina BLOQUEANTE. 4. En el primer caso (SetimerMS), para averiguar subsecuentemente si ya se cumplió o no, el tiempo deseado, podrían emplearse las instrucciones 'brset' o 'brclr', de la siguiente forma: brset Timer0Ready, TimerFlags, labelYES ; Si el bit está SET, es porque YA se cumplió el tiempo brclr Timer1Ready, TimerFlags, labelNOT ; Si el bit está CLR, es porque NO se ; cumplió el tiempo Sin embargo usted no tiene que hacer necesariamente eso, ya que he incluido esta funcionalidad, perfectamente encapsulada en las dos macros adicionales, que se muestran a continuación: TimeOut timer, gotoAddress ; (JUMP to gotoAddress if “timer” expired. Return with jump/br) TimeOutS timer, bsrAddress ; (BRANCH TO SUBROUTINE bsrAddress if “timer” expired. Return with rts) Estas Macros inspeccionan el temporizador “timer” y, en caso de haber expirado (Timeout), ejecutan la rutina correspondiente, saltando con BRANCH a gotoAddress, o con BSR a bsrAddress, según corresponda. También se incluyen las Macros Complementarias, para su conveniencia: NTimeOut timer, gotoAddress ; .. gotoAddress IF timer has *NOT* expired NTimeOutS timer, callAddress ; .. branch to Subroutine callAddress IF timer has NOT expired Antes de mostrar los programas en Assembly Language, voy a ilustrar el tema en "C" (Windows). Luego tendrán la oportunidad de ver que las funcionalidades definidas en Ensamblador son EXACTAMENTE LAS MISMAS. En esencia lo único que cambia es el lenguaje de programación. 19) Timers for Windows (timers.cpp): Como ya hemos visto la aproximación a la inclusión de funcionalidades, en forma de Top‐ Down Design, voy a presentar en primer lugar el include file timers_h.h, que define los MECANISMOS. Después, el programa que materializa la librería, Timers.cpp, y que implementa los MECANISMOS. Por último, un ejemplo para que se pueda visualizar el empleo de las diferentes funcionalidades. timtst2.cpp: 95 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 ["Laboratorios\Lab2\_New\timers_h.h"] 001 /* *************************************************************** */ 002 // timers_h.h: Windows, time usage; Luis G. Uribe C. 003 // L13M2006 L16O06 MSVC J01Y08 C31G11 V09S11 S05E2013 L21O2013 004 /* ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ */ 005 // Include Files 006 #include <windows.h> 007 #include <conio.h> 008 #include <stdio.h> 009 using namespace System; 010 using namespace System::Threading; 011 #define NTIMERS 8 // This MUST be a power of 2: 2, 4, 8, 16... 012 /* ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ */ 013 // Function Prototypes 014 #define Timeout TimeouT // Avoid System::Timeout conflict 015 void IniTimers( int ntimers ); 016 void SetimerMS( int timer, int timeMS ); 017 int Timeout( int timer ); 018 void WTimer( int timer ); 019 void WaitMS_on( int timer, int timeMS ); ["Laboratorios\Lab2\_New\timers.cpp"]: 020 /* *************************************************************** */ 021 // Timers.cpp, Windows Luis G. Uribe C. S21S2013 022 // .. L13M2006 L16O6 VC C31G2011 J17E2013 (_kbhit) V08F2013 023 // MSVisual Studio: Change config. properties, general, to include: 024 // .. Common Language Runtime Support (/clr) 025 // .. Program File Extensions MUST be CPP 026 /* ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ */ 027 // Include Files 028 #include "timers_h.h" 029 /* ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ */ 030 // Global Variables 031 static volatile long timersMS[ NTIMERS ]; 032 /* =============================================================== */ 033 void IniTimers ( int ntimers ) /*()*/ 034 { // 'ntimers' must be 8 in this implementation. It is NOT used... 035 if( NTIMERS & (NTIMERS ‐ 1) ) { 036 fprintf( stderr, 037 "NTIMERS (%d) *MUST* be a Power of 2", NTIMERS ); 038 abort(); 039 } 96 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 040 // LINK init ALL timers ( timersMS[ NTIMERS ] ) to ZERO 041 } /* IniTimers() */ 042 /* ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ */ 043 void SetimerMS ( int timer, int timeMS ) /*()*/ 044 { 045 long tc = (long)Environment::TickCount; // ticks (milliSeconds) 046 timersMS[ timer & ( NTIMERS ‐ 1 ) ] = 047 tc + (long)timeMS; // ticks (milliSeconds) 048 } /* SetimerMS() */ 049 /* ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ */ 050 int Timeout ( int timer ) /*()*/ 051 { 052 long tc = (long)Environment::TickCount; // ticks (milliSeconds) 053 _kbhit(); // Enable Ctrl‐C testing! 054 if( timersMS[ timer & ( NTIMERS ‐ 1 ) ] >= ( tc ) ) 055 return( 0 ); 056 return( 1 ); 057 } /* Timeout() */ 058 /* ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ */ 059 void WTimer ( int timer ) /*()*/ 060 { 061 long tc = (long)Environment::TickCount; // ticks (milliSeconds) 062 if( timersMS[ timer & ( NTIMERS ‐ 1 ) ] ‐ tc > 0 ) 063 Thread::Sleep( timersMS[ timer & ( NTIMERS ‐ 1 ) ] ‐ tc ); 064 } /* WTimer() */ 065 /* ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ */ 066 void WaitMS_on ( int timer, int timeMS ) /*()*/ 067 { 068 SetimerMS( timer, timeMS ); 069 WTimer( timer ); 070 } /* WaitMS_on() */ ["Laboratorios\Lab2\_New\timtst2.cpp"]: 071 char *_id = 072 "timtst2.cpp testing routines (Lite, WINDOWS). Luis G. Uribe C.," 073 " C07S2011 L31O2013\n"; 074 // char *_usage = "timtest2 \n"; 075 // cc timtst2.c timers.c 076 /* ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ */ 077 /* Include Files */ 97 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 078 #include <stdio.h> 079 #include <stdlib.h> 080 #include "timers_h.h" 081 /* ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ */ 082 /* Global Variables */ 083 char text[] = "1234567890"; 084 /* =============================================================== */ 085 void main ( void ) /*()*/ 086 { char *p = text; 087 puts( _id ); 088 IniTimers( 8 ); // only 8 timers allowed at this time 089 fputs( "\n0) Press 'ENTER' to print w/o delays________ ",stdout ); 090 _getch(); 091 for( p = text; *p; p ++ ) { // Impresión SIN retardos 092 _putch( *p ); // putchar may be buffered; use _putch 093 } 094 /* ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ */ 095 // RTC Test: Show 3 ways for calling Timer Routines. See: // <<< 096 fputs( "\n1) Press 'ENTER' to continue at 300 ms/char_ ",stdout ); 097 _getch(); 098 for( p = text; *p; p ++ ) { // Impresión con Wait_on a 300 mS. 099 _putch( *p ); 100 WaitMS_on( 0, 300 ); // <<< BLOCK CPU for 300 ms 101 } 102 fputs( "\n2) Press 'ENTER' to continue at 750 ms/char_ ",stdout ); 103 _getch(); 104 for( p = text; *p; p ++ ) { // Impresión con while a 750 mS. 105 _putch( *p ); 106 SetimerMS( 7, 750 ); // <<< Setup timer 107 // <<< Do here any process you need 108 WTimer( 7 ); // <<< ..wait for remaining time 109 } 110 fputs( "\n3) Press 'ENTER' to continue at 200 ms/char_ ",stdout ); 111 _getch(); 112 p = text; // Impresión con while a 200 mS. 113 ploop: 114 while( *p ) { 115 _putch( *p ++ ); 116 SetimerMS( 5, 200 ); // <<< Setup timer 98 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 117 // <<< Do here any process you need 118 while( 1 ) { 119 if( Timeout( 5 ) ) { // <<< Test condition later... 120 goto ploop; // <<< ..finish? print next char 121 } // <<< You won't need GOTOs ! 122 } 123 } 124 fputs( "\n4) Press 'ENTER' to continue at 500 ms/char_ ",stdout ); 125 _getch(); 126 for( p = text; *p; p ++ ) { // Impresión con while a 500 mS. 127 _putch( *p ); 128 WaitMS_on( 0, 500 ); // <<< 129 } 130 fputs( "\n5) Press 'ENTER' to continue at 1000 ms/char ",stdout ); 131 _getch(); 132 for( p = text; *p; p ++ ) { // Impresión con while a 500 mS. 133 _putch( *p ); 134 WaitMS_on( 0, 1000 ); // <<< 135 } 136 fputs( "\n6) Press 'ENTER' to Return to full speed____ ",stdout ); 137 _getch(); 138 puts( text ); 139 puts( "\nEnd... (Press 'ENTER' to finish)" ); 140 getchar(); 141 exit( 0 ); 142 } /* main() */ COMENTARIOS a ["Laboratorios\Lab2\_New\timers_.h"]: El include file no tiene nada extraño; básicamente define las funciones que se implementarán a continuación. 011 #define NTIMERS 8 // This MUST be a power of 2: 2, 4, 8, 16... El número de Timers se ha fijado a 8 en esta implementación, pero puede cambiarse y recompilar la librería. Los valores tienen que ser potencias de 2. Las funcionalidades que se ofrecen son casi idénticas a las que mencionamos en la introducción, y a las que implementaremos en lenguaje ensamblador: 015 void IniTimers( int ntimers ); 016 void SetimerMS( int timer, int timeMS ); 017 int Timeout( int timer ); 018 void WTimer( int timer ); 019 void WaitMS_on( int timer, int timeMS ); 99 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 COMENTARIOS a ["Laboratorios\Lab2\_New\timers.cpp"]: La variable timersMS es global, por conveniencia: 031 static volatile long timersMS[ NTIMERS ]; // Global Variable IniTimers( 8 ) (el parámetro no interesa; pero se valida que NTIMERS sea potencia de 2) 033 void IniTimers ( int ntimers ) /*()*/ 034 { // 'ntimers' must be 8 in this implementation. It is NOT used... 035 if( NTIMERS & (NTIMERS ‐ 1) ) { 036 fprintf( stderr, 037 "NTIMERS (%d) *MUST* be a Power of 2", NTIMERS ); 038 abort(); 039 } No se inicializan los timers en esta implementación, porque el LINKER realiza esta función para programas en C. 040 // LINK init ALL timers ( timersMS[ NTIMERS ] ) to ZERO 041 } /* IniTimers() */ Para activar un timer: 043 void SetimerMS ( int timer, int timeMS ) /*()*/ 044 { long tc = (long)Environment::TickCount; // ticks (milliSeconds) 046 timersMS[ timer & ( NTIMERS ‐ 1 ) ] = 047 tc + (long)timeMS; // ticks (milliSeconds) 048 } /* SetimerMS() */ Se toma una de las representaciones de la hora de Windows: TickCount (en Milisegundos); se le suma el tiempo requerido, timeMS, en milisegundos también, y se lo almacena en la variable correspondiente al temporizador deseado, timer. La funcionalidad para establecer si ya venció el plazo seleccionado para algún temporizador, timer, es 'Timeout': 050 int Timeout ( int timer ) { 052 long tc = (long)Environment::TickCount; // ticks (milliSeconds) 053 _kbhit(); // Enable Ctrl‐C testing! 054 if( timersMS[ timer & ( NTIMERS ‐ 1 ) ] >= ( tc ) ) 055 return( 0 ); 056 return( 1 ); 057 } /* Timeout() */ Timeout() lee en 'tc' la representación de la hora, TickCount en MS, vigente para el momento en que se lo ejecuta; y compara si el valor almacenado para el temporizador 'timer' es mayor o igual a 'tc'. En caso de ser así significa que el tiempo actual, 'tc', no ha sobrepasado aún al memorizado para el temporizador 'timer'. En ese caso, se retorna un '0', que indique que aún no está Listo ese temporizador. 100 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 Si, por el contrario, el tiempo almacenado NO es superior al tiempo actual, 'tc', es porque el tiempo ya ha sobrepasado el valor esperado por el temporizador deseado, 'timer', y se devuelve un '1' indicando que ese lapso YA EXPIRÓ. WTimer consiste en detener el flujo del programa (bloquearlo) y esperar hasta que el tiempo del temporizador 'timer', que ha sido previamente activado mediante un SetimerMS, expire. Emplea una funcionalidad ad‐hoc del Windows: Sleep(). Esta implementación tiene la ventaja de que al programa lo suspende el sistema operativo hasta que se cumpla la condición, y Windows puede adjudicar el CPU y otros recursos, como la memoria, el disco, etc., a algún proceso que se encuentre en disponibilidad de trabajar (esté Ready). 059 void WTimer ( int timer ) /*()*/ 060 { long tc = (long)Environment::TickCount; // ticks (milliSeconds) 062 if( timersMS[ timer & ( NTIMERS ‐ 1 ) ] ‐ tc > 0 ) 063 Thread::Sleep( timersMS[ timer & ( NTIMERS ‐ 1 ) ] ‐ tc ); 064 } /* WTimer() */ Cuando hay que activar un temporizador, y el flujo del programa no puede continuar hasta que transcurra el intervalo seleccionado, la funcionalidad BLOQUEANTE que se emplea es WaitMS_on: 066 void WaitMS_on ( int timer, int timeMS ) /*()*/ 067 { SetimerMS( timer, timeMS ); 069 WTimer( timer ); 070 } /* WaitMS_on() */ COMENTARIOS a ["Laboratorios\Lab2\_New\timtst2.cpp"]: El programa de prueba, timtst2.cpp, se explica por sí mismo. 20) Librería de TIMERS para el HC9S08 (timers8HS.inc): La librería 'timers8HS.inc' es FUNDAMENTAL para desarrollar sus proyectos. Entender cómo está hecha es imprescindible, y usted tiene que ser capaz de codificar sus propias funciones, como demostración de que entendió todo el proceso. ["Laboratorios\Lab2\Timers8HS\timers8HS.inc"] 001 ; Timers8HS.inc: Luis G. Uribe C., Basic 8 Timers Support L30D2013 002 ;==================================================================== 003 ; THE FOLLOWING MACROS ARE DEFINED IN THIS LIBRARY 004 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 005 ; Init8Timers: Init 8Timers library 006 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 007 ; SetimerMS: MACRO timer, time ; time is either CONSTANT or VARIABLE 008 ; .. Set timer and continue to work 009 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 010 ; WaitMS_on: MACRO timer, time ; time is either CONSTANT or VARIABLE 011 ; .. Set timer and BLOCK until timer expires 012 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 013 ; TimeOut: MACRO timer, gotoAddress ; timer is always a CONSTANT 014 ; .. gotoAddress IF timer has expired 101 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 015 ; NTimeOut: MACRO timer, gotoAddress ; timer is always a CONSTANT 016 ; .. gotoAddress IF timer has *NOT* expired 018 ; TimeOutS: MACRO timer, callAddress ; timer is always a CONSTANT 019 ; .. branch to Subroutine callAddress IF timer has expired 020 ; NTimeOutS: MACRO timer, callAddress ; timer is always a CONSTANT 021 ; .. branch to Subroutine callAddress IF timer has NOT expired 022 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 023 ; WaitISR_on: MACRO timer, time ; time is either CONSTANT or VARIABLE 024 ; .. Set timer and BLOCK until timer expires, INSIDE some ISR 025 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 026 ; TIMERS8ISR: ISR routines to update 8 timers (user need to INCLUDE 027 ; ..this Macro in MAIN but it is of no further concern) 028 ;==================================================================== 029 ; DATA DEFINITION. 030 ; Define 8 timers Global Variables 031 ; Note: Macro directives (DW, $IF...) cannnot go into macros... GOK! 033 ; NOTE: REQUIRES 'ORG ram' **BEFORE** INCLUDE 'timers8HS.inc' 034 ; ..in main program, to make this data definitions work! 035 ; *** ALL following variables >>>MUST<<< reside on ZERO page! *** 036 ; ..(Timerflags use bset and bclr...) 037 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 038 ; TimerDef: MACRO and Variables DEFINITION 039 TimerDef: MACRO timer 040 Timer\1: DS.W 1 ; 16 bit counters: 65536 mS: more than 041 Timer\1.Rdy EQU \1 ; .. one minute each timer (1mS Tick) 042 ENDM 043 TimerDef 0 044 TimerDef 1 045 TimerDef 2 046 TimerDef 3 047 TimerDef 4 048 TimerDef 5 049 TimerDef 6 050 TimerDef 7 051 TimerFlags: DS.B 1 ; 8 bit flags: Timer0,Timer1.. 052 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 053 ; DEFINES 054 ;ram: SET Z_RAMStart ; <<<TIMERS8: Put THIS into Main program<<< 055 rom: SET ROMStart ; $2080 056 initStack: EQU RAMEnd + 1 ; $1800=$17FF+1. Stack begins on $17FF 057 COP_Disable: EQU $42 058 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 059 ; Defines Bits for RTC Programming 060 ; ..RTCSC ($1830) bits. 02MC9S08QE128RM(ReferenceManual)‐U.pdf, p.246 061 RTCPS_BY_ONE EQU mRTCSC_RTCPS3 ; ..Bit #8 Divide by 1, gives 1mS Tick 062 RTCIE: EQU RTCSC_RTIE ; ..Bit #4 RealTime Interrupt Enable 063 RTCIE.bit EQU mRTCSC_RTIE ; ..Bit #4 RealTime Clock Int. Enable 064 NOT_RTCIE.bit EQU (~RTCIE.bit & $00FF) 065 RTCIF: EQU RTCSC_RTIF ; ..Bit #7 RealTime Interrupt Enable 066 RTCIF.bit EQU mRTCSC_RTIF ; ..Bit #7 RealTime Clock Int. Enable 067 ONE_KHZ EQU 0 ; Use internal default 1Khz clock src. 068 RTC_FLAGS EQU (RTCIF.bit | ONE_KHZ | RTCIE.bit | RTCPS_BY_ONE) 102 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 069 ;==================================================================== 070 ; More MACRO DEFINITIONS 071 TimeOut: MACRO timer, gotoAddress ; timer is always a CONSTANT 072 brset \1, TimerFlags, \2 073 ENDM 074 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 075 NTimeOut: MACRO timer, gotoAddress ; timer is always a CONSTANT 076 brclr \1, TimerFlags, \2 077 ENDM 078 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 079 TimeOutS: MACRO timer, callAddress ; timer is always a CONSTANT 080 brclr \1, TimerFlags, \@cont 081 bsr \2 082 \@cont: 083 ENDM 084 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 085 NTimeOutS: MACRO timer, callAddress ; timer is always a CONSTANT 086 brset \1, TimerFlags, \@cont 087 bsr \2 088 \@cont: 089 ENDM 090 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 091 SetimerMS: MACRO timer, time ; time is either CONSTANT or VARIABLE 092 bset \1, TimerFlags ; Make timer X Ready... Mutex !!! 093 pshh ; save H:X 094 pshx ; .. 095 ldhx \2 ; time in milliseconds (50) <<< 096 sthx Timer\1 ; .. <<< 097 pulx ; restore H:X 098 pulh ; .. 099 bclr \1, TimerFlags ; Make timer X Not Ready... 100 ENDM 101 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 102 WaitMS_on: MACRO timer, time ; time is Constant or Variable 103 SetimerMS \1, \2 104 brclr \1, TimerFlags, * ; Wait 'time' mseconds on 'timer' 105 ENDM 106 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 107 WaitISR_on: MACRO timer, time ; time is Constant or Variable 108 psha ; save ACC 109 tpa ; push CCR Saves IMask status 110 psha ; .. 111 sei ; DISABLE INTERRUPTS (IMask=1): Let 112 SetimerMS \1, \2 ; ..Setimer used inside Int. Routines 113 cli ; REENABLE INTERRUPTS: Let RTC Count 114 brclr \1, TimerFlags, * ; Wait 'time' mseconds on 'timer' 115 pula ; Restore Interrupt Flag Mask 116 tap ; ..to its Saved value 117 pula ; Restore ACC 118 ENDM 119 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 120 ; Ancillary MACRO DEFINITIONS 103 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 121 UpdateTmr: MACRO timer 122 brset Timer\1.Rdy, TimerFlags, \@Next ; Mutex !!! 123 ldhx Timer\1 ; load H:X 124 aix #‐1 ; decrement TimerXX 125 sthx Timer\1 ; .. 126 bne \@Next 127 bset Timer\1.Rdy, TimerFlags ; 0?: Mark TimerXX READY 128 \@Next: 129 ENDM 130 ;==================================================================== 131 ; RTC INIT. This is the base for Timer Support 132 Init8Timers: MACRO 133 clrx 134 clrh 135 sthx Timer0 ; Init Timer0 to 0 136 sthx Timer1 ; Init Timer1 to 0 137 sthx Timer2 ; Init Timer2 to 0 138 sthx Timer3 ; Init Timer3 to 0 139 sthx Timer4 ; Init Timer4 to 0 140 sthx Timer5 ; Init Timer5 to 0 141 sthx Timer6 ; Init Timer6 to 0 142 sthx Timer7 ; Init Timer7 to 0 143 mov #$FF, TimerFlags ; Set ALL 8 bit timers flags: Done! 144 lda #RTC_FLAGS ; Use 1KHz internal clock/1: 1mS tick 145 sta RTCSC ; ..Clear RTCIF; IntEnable RTC 146 ENDM 147 ;==================================================================== 148 ; RTC Interrupt Service Routine (ISR). 90 cycles... 149 TIMERS8ISR: MACRO 150 pshh ; H Not saved by Interrupt process 151 UpdateTmr 0 ; Protected from Setimers by Mutex 152 UpdateTmr 1 ; ... 153 UpdateTmr 2 154 UpdateTmr 3 155 UpdateTmr 4 156 UpdateTmr 5 157 UpdateTmr 6 158 UpdateTmr 7 159 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 160 RTCISRexit: 161 lda #RTC_FLAGS ; Use 1KHz internal clock/1: 1mS tick 162 sta RTCSC ; ..Clear RTCIF; IntEnable RTC 163 pulh ; H not restored by RTI process 164 rti ; ..Return from Interrupt 165 ENDM COMENTARIOS a ["Laboratorios\Lab2\Timers8HS\timers8HS.inc"]: Los aspectos más sobresalientes son: 003 ; THE FOLLOWING MACROS ARE DEFINED IN THIS LIBRARY 005 ; Init8Timers: Init 8Timers library 104 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 007 ; SetimerMS: MACRO timer, time ; time is either CONSTANT or VARIABLE 008 ; .. Set timer and continue to work 010 ; WaitMS_on: MACRO timer, time ; time is either CONSTANT or VARIABLE 011 ; .. Set timer and BLOCK until timer expires 013 ; TimeOut: MACRO timer, gotoAddress ; timer is always a CONSTANT 014 ; .. gotoAddress IF timer has expired 015 ; NTimeOut: MACRO timer, gotoAddress ; timer is always a CONSTANT 016 ; .. gotoAddress IF timer has *NOT* expired 018 ; TimeOutS: MACRO timer, callAddress ; timer is always a CONSTANT 019 ; .. branch to Subroutine callAddress IF timer has expired 020 ; NTimeOutS: MACRO timer, callAddress ; timer is always a CONSTANT 021 ; .. branch to Subroutine callAddress IF timer has NOT expired 023 ; WaitISR_on: MACRO timer, time ; time is either CONSTANT or VARIABLE 024 ; .. Set timer and BLOCK until timer expires, INSIDE some ISR 026 ; TIMERS8ISR: ISR routines to update 8 timers (user need to INCLUDE 027 ; ..this Macro in MAIN but it is of no further concern) 029 ; DATA DEFINITION. 033 ; NOTE: REQUIRES 'ORG ram' **BEFORE** INCLUDE 'timers8HS.inc' 034 ; ..in main program, to make this data definitions work! Esta es el primer REQUISITO IMPORTANTE para emplear esta librería: en Main, ANTES de INCLUIR 'timers8HS.inc', debe haberse hecho un: 'ORG ram' Esto se debe a que esta librería define sus propias variables, y requiere encontrarse en el DATA SEGMENT a la hora de hacerlo. 039 TimerDef: MACRO timer 040 Timer\1: DS.W 1 ; 16 bit counters: 65536 mS: more than 041 Timer\1.Rdy EQU \1 ; .. one minute each timer (1mS Tick) 043 TimerDef 0 ... etc., hasta 7. Observe algo con detenimiento. A veces algún usuario de la librería necesita emplear únicamente un pequeño valor en sus temporizadores, y decide usar sólo un byte para representar sus retardos... Cuando van a cambiar el valor del WORD que representa el contador, lo almacenan simplemente en la variable TimerN (digamos, Timer0). Y ESTÁ MAL. Tienen que recordar que esta máquina es BIG ENDIAN, y que el valor Terminal de un WORD está en "Timer0+1" (en el segundo byte del WORD). Una aproximación mejor sería cargar el valor en el registro índice H:X (su valor de 8 bits quedaría en la parte inferior de índice) y mover sus 16 bits a Timer0; el procesador lo almacena como corresponde, dejando el byte que representa su información, en la parte apropiada de la variable. 059 ; Defines Bits for RTC Programming 105 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 Lo mejor es que verifique TODOS los bits empleados aquí, con el Reference Manual del MC9S08QE128, en donde están claramente definidos todos y cada uno de los bits aquí utilizados. Al leer las Macros y funciones definidas en esta librería, esté pendiente de caer en cuenta de la implementación Down‐Up, de lo más elemental hasta lo más sofisticado. Las Macros TimeOut, NTimeOut, TimeOutS y NTimeOutS son de confección más bien elemental. La Macro SetimerMS requiere alguna aclaración explicativa; recibe dos parámetros, el timer que ha de programarse, y el valor de 'time' en Milisegundos. 091 SetimerMS: MACRO timer, time ; time is either CONSTANT or VARIABLE Lo primero que hace, antes de comenzar a jugar con las variables asociadas, es marcar ese timer como READY (tiempo expirado). Lo hace colocando un uno (1) en el correspondiente bit de TimerFlags: 092 bset \1, TimerFlags ; Make timer X Ready... Mutex !!! Luego resguarda el registro índice, que va a usar para cargar el valor del tiempo (time) y almacenarlo en la variable asociada (TimerX). Observe la sangría en el texto, que resalta varias instrucciones para una sola operación. Finalmente, recupera el valor del registro índice: 093 pshh ; save H:X 094 pshx ; .. 095 ldhx \2 ; time in milliseconds (50) <<< 096 sthx Timer\1 ; .. <<< 097 pulx ; restore H:X 098 pulh ; .. 099 bclr \1, TimerFlags ; Make timer X Not Ready... Para la Macro WaitMS_on resulta también innecesario algún otro comentario. En cambio, se ha incluido una funcionalidad (WaitISR_on) que permite utilizar la librería de timers8, que están basados en las interrupciones del reloj, DENTRO DE OTRAS rutinas de interrupción. Esto se hizo por comodidad, y como ejemplo de que los manuales de Motorola/Freescale están equivocados al decir que, habilitar interrupciones DENTRO de interrupciones, es más conveniente NO HACERLO, que porque dizque es complicado, y no trae ningún provecho ni mejora en el comportamiento de los programas. Complicado, y de cuidado, sí es. Pero si le hicieran caso al manual, los manufacturantes de sistemas operativos no tendrían trabajo. WaitISR_on, en términos generales, equivale a Wait_ON, pero está especialmente programada para funcionar DENTRO de las rutinas de interrupción. NUNCA USE Wait_ON dentro de una rutina de interrupciones. TAMPOCO USE EL MISMO NÚMERO DE TIMER DENTRO DE LAS RUTINAS DE INTERRUPCIONES Y EN EL PROGRAMA PRINCIPAL. 106 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 107 WaitISR_on: MACRO timer, time ; time is Constant or Variable Resguarda en el Stack el valor del acumulador; salva también el valor del CCR en el Stack, para guardar el estado actual del CPU y FUNDAMENTALMENTE SI ESTÁ HABILITADO PARA INTERRUPCIONES, O DESHABILITADO (IFlag) Esto es importante porque, al momento de usar esta Macro WaitISR_on, la rutina debe deshabilitar las interrupciones para poder trabajar “toqueteando” las variables del timer involucrado. Luego de eso habría la tendencia a REHABILITAR las interrupciones... pero lo que corresponde es DEJARLAS COMO ESTABAN cuando WaitISR_on comenzó a trabajar... De lo contrario, se generará un DESASTRE. 108 psha ; save ACC 109 tpa ; push CCR Saves IMask status 110 psha ; .. Ahora se deshabilitan las interrupciones (SEI) pues, como acabo de decir, no se puede manipular la estructura de datos de un timer, si hay la posibilidad de que otra rutina, fuera de ésta interrupción, también trate de hacerle cambios. Lo apropiado es DESHABILITARLAS... 111 sei ; DISABLE INTERRUPTS (IMask=1): Let Luego, se modifica la estructura de datos del temporizador (SetimerMS) y se REHABILITAN LAS INTERRUPCIONES (CLI), pues de lo contrario el timer no funcionaría, ya que opera por interrupciones. Se hace un bucle de espera (que no necesariamente es BLOQUEANTE, ya que ¡las interrupciones están ACTIVAS!). 112 SetimerMS \1, \2 ; ..Setimer used inside Int. Routines 113 cli ; REENABLE INTERRUPTS: Let RTC Count 114 brclr \1, TimerFlags, * ; Wait 'time' mseconds on 'timer' Al terminar el 'brclr', habrá expirado el timer; y es hora de recomponer todo. 115 pula ; Restore Interrupt Flag Mask 116 tap ; ..to its Saved value 117 pula ; Restore ACC Es importante notar en UdateTmr, que NO TOCA NADA (dado que entra por interrupciones) si el bit asociado al timer está en uno: 122 brset Timer\1.Rdy, TimerFlags, \@Next ; Mutex !!! (y, note que Setimer lo primero que hace, antes de manipular nada más, es colocar ese bit en uno. Así, si Setimer ha comenzado a actualizar la estructura de datos de un temporizador dado, la rutina de interrupciones NO LO ACTUALIZARÁ, gracias a ese bit que, además de servir de indicador de READY, sirve como Semáforo para la apropiación Mutuamente Exclusiva de ese recurso: MUTEX) 120 ; Ancillary MACRO DEFINITIONS 121 UpdateTmr: MACRO timer 122 brset Timer\1.Rdy, TimerFlags, \@Next ; Mutex !!! 123 ldhx Timer\1 ; load H:X 124 aix #‐1 ; decrement TimerXX 107 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 125 sthx Timer\1 ; .. 126 bne \@Next 127 bset Timer\1.Rdy, TimerFlags ; 0?: Mark TimerXX READY 128 \@Next: La rutina de inicialización de las variables, Init8Timers, no necesita mayores aclaratorias. Solo que en las líneas 144, 145 se escoge un reloj para el RTC, que es de 1KHz (1 milisegundo por tick), muy conveniente para nuestros propósitos. Hay toda una gama diferente para escoger; a usted le toca estudiarla, en caso de que sus temporizadores requieran ser mucho más rápidos, o más lentos. 132 Init8Timers: MACRO ... 144 lda #RTC_FLAGS ; Use 1KHz internal clock/1: 1mS tick 145 sta RTCSC ; ..Clear RTCIF; IntEnable RTC La rutina de interrupción del RTC (Real Time Clock: TIMERS8ISR), que atiende las actualizaciones de los diversos temporizadores, se basa en la Macro UpdateTmr que, como ya vimos, respeta a un temporizador si fuera de esta rutina de interrupciones, alguien lo está manipulando, vía Setimer: 148 ; RTC Interrupt Service Routine (ISR). 90 cycles... 149 TIMERS8ISR: MACRO 150 pshh ; H Not saved by Interrupt process 151 UpdateTmr 0 ; Protected from Setimers by Mutex 152 UpdateTmr 1 ; ... 153 UpdateTmr 2 154 UpdateTmr 3 155 UpdateTmr 4 156 UpdateTmr 5 157 UpdateTmr 6 158 UpdateTmr 7 160 RTCISRexit: 161 lda #RTC_FLAGS ; Use 1KHz internal clock/1: 1mS tick 162 sta RTCSC ; ..Clear RTCIF; IntEnable RTC 163 pulh ; H not restored by RTI process 164 rti ; ..Return from Interrupt 21) Ejemplo del uso de la Librería de TIMERS (timer8HS.asm): Las cosas que hay que recordar para emplear correctamente la librería de timers, están todas marcadas en los ejemplos con el símbolo: <<< No deje de mirar esas marcas en los ejemplos, y atender a las recomendaciones... ["Laboratorios\Lab2\Timers8HS\timer8HS.asm"] 01 ;******************************************************************** 02 ; Programa Timer8HS.asm: TST Timer Support 03 ; Luis G. Uribe C., L24N2003 L08N4 M27F7 D08L7 (timeOut) J16M2009 04 ; V31Y9 V08J2012 C04D2013 L09D2013 108 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 05 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 06 ; Include files 07 ; 08 ; CHECK‐LIST: Conserve el orden Y la posición relativa de: 09 ; .. derivative.inc, timers8HS.inc e Init8Timers 10 ; 11 ; Si usa Init8Timers, *TIENE* que inicializar el vector: INT_RTC 12 ; ..con DC.W RTC_INTERRUPT 13 NOLIST 14 INCLUDE 'derivative.inc' 15 LIST 16 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 17 ; 2) DEFINES 18 ram: SET Z_RAMStart ; <<<TIMERS8: <<< 19 ;==================================================================== 20 ; 3) Global Variables 21 ORG ram ; <<<TIMERS8: *BEFORE* 'timers8HS.inc'<<< 22 NOLIST 23 include 'timers8HS.inc' ; <<<TIMERS8: Code, Vars & Macros 24 LIST 25 var: DS.W 1 26 var2: DS.W 1 27 ;******************************************************************** 28 ; Begin of Code Section 29 ABSENTRY Main ; Export symbol (ABSOLUTE Assembly select) 30 ORG rom 31 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 32 ; Always include the following 4 instructions 33 Main: lda #COP_Disable 34 sta SOPT1 ; System Options 1 35 ldhx #initStack ; Init SP 36 txs 37 Init8Timers 38 ;==================================================================== 39 ; Main Loop (Your Code) 40 ldhx #300 ; Begin with 300 mS delay, via 'Var' 41 sthx var ; .. 42 ldhx #2 ; 2 mS delay, via Var2 43 sthx var2 ; .. 44 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 45 cli ; Enable interrupts now 46 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 109 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 47 again: 48 Setimer 2, #200 ; Begin 200 mSeconds via Constant 49 Setimer 5, var ; 300 mSeconds via VARIABLE 50 loop: TimeOut 2, Two_on ; If TimeOut 2 goto Two_on 51 TimeOutS 5, Five_on ; else if TimeOut 5, brs to Five_on 52 bra loop ; else 'loop' 53 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 54 Five_on: ; From BRS (TimeOutS); return via RTS 55 lda #5 ; ACC=5: identify THIS routine 56 Setimer 5, var ; Reinstall timer5 w/300 mS 57 WaitMS_on 7, var2 ; 2 mSeconds from Variable (var2) 58 ldhx var 59 bne Five_cont 60 rts 61 Five_cont: 62 aix #‐1 63 sthx var 64 rts 65 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 66 Two_on: ; From BRA (TimeOut); return via BRA 67 lda #2 ; ACC=2: identify THIS routine 68 Setimer 2, #200 ; Reinstall timer w/200 mS 69 WaitMS_on 0, #150 ; Test WaitMS_on constant 70 bra loop 71 ;==================================================================== 72 ; PROGRAM TRAILER: 73 ; 74 ; 1) TIM8INT Macro 75 ; 2) Interrupt Vectors 76 ; 77 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 78 ; RTC Interrupt Service Routine, si usa Init8Timers... 79 RTC_INTERRUPT: 80 TIMERS8ISR 81 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 82 ; Interrupt Vectors 83 dummy_isr: ; <<< Must be placed in ROM Space 84 rti 85 ORG Vrtc ; Increasing priority from bottom up 86 DC.W RTC_INTERRUPT ; (Si usa Init8Timers...) 87 ORG Virq ; Virq, Vswi and Vreset 88 DC.W dummy_isr ; IRQ 89 DC.W dummy_isr ; SWI 90 DC.W Main ; RESET. Maximum priority. Asynch. 91 END 110 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 COMENTARIOS a ["Laboratorios\Lab2\Timers8HS\timer8HS.asm"]: Aspectos novedosos: 18 ram: SET Z_RAMStart ; <<<TIMERS8: <<< 20 ; 3) Global Variables 21 ORG ram ; <<<TIMERS8: *BEFORE* 'timers8HS.inc'<<< 22 NOLIST 23 include 'timers8HS.inc' ; <<<TIMERS8: Code, Vars & Macros 24 LIST 33 Main: lda #COP_Disable ... 37 Init8Timers 40 ldhx #300 ; Begin with 300 mS delay, via 'Var' 41 sthx var ; .. 42 ldhx #2 ; 2 mS delay, via Var2 43 sthx var2 ; .. 44 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 45 cli ; Enable interrupts now Suele olvidarse este último paso, y nada funciona. Está todo activo, pero el procesador ignora todas las interrupciones. Lo mismo suele ocurrir con la rutina de interrupciones del RTC. Hay que incluirla de acuerdo al siguiente código: 79 RTC_INTERRUPT: 80 TIMERS8ISR 82 ; Interrupt Vectors 85 ORG Vrtc ; Increasing priority from bottom up 86 DC.W RTC_INTERRUPT ; (Si usa Init8Timers...) ... 22) "WaitISR_on" Interactuando con la "IRQISR" ["Laboratorios\Lab2\Timers8HS\tim8_IRQ‐HS.asm"] 001 ;******************************************************************** 002 ; Programa Tim8_IRQ‐HS.asm: Timer calls inside IRQISR J01M2007 D12A09 003 ; Luis G. Uribe C., J16A09 V29Y09 V08J2012 C20F2013 S21D2013 004 ; C20F2013: Use 'WaitISR_on 7,delay' on IRQISR, instead of WaitMS_on 005 ; 006 ; Program showS interactions between RTC 'timers8' interrupt routines 007 ; ..and 'IRQ' interrupts, when it is needed to request timer delays 008 ; ..from IRQISR. This is a typical case when you need to rearm global 009 ; ..interrupts DESPITE THE ADVISE IN CONTRARY FROM MOTOROLA TECHNICAL 111 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 010 ; ..DOCUMENTS... (cfr. 01CPU08RM!ReferenceManual‐U.pdf, page 30) 011 ; 012 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 013 ; Include files 014 NOLIST 015 INCLUDE 'derivative.inc' 016 LIST 017 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 018 ; Parameter definitions 019 IRQACK: EQU IRQSC_IRQACK 020 IRQIE: EQU IRQSC_IRQIE 021 IRQPE: EQU IRQSC_IRQPE 022 ram: SET Z_RAMStart ; $80 023 ;==================================================================== 024 ; Global Variables 025 ORG ram ; Put this *BEFORE* 'timers8HS.inc' 026 NOLIST 027 include 'timers8HS.inc' ; <<<TIMERS8: Code, Vars & Macros 028 LIST 029 delay: DS.W 1 030 counter: DS.B 1 031 ;******************************************************************** 032 ; MAIN PROGRAM HEADER: 033 ABSENTRY Main ; Export symbol (ABSOLUTE Assembly select) 034 ORG rom 035 Main: lda #COP_Disable ; RSTE=0: PTA5 is NOT for ~RESET 036 sta SOPT1 ; ..System Options 1 037 ldhx #initStack ; Init SP 038 txs 039 ;==================================================================== 040 ; External Interrupt Initialization (>>>IRQ<<<) 041 ; 1. Mask interrupts by clearing IRQIE in IRQSC. 042 ; (IRQIE=0 on Power on Reset, by Default) 043 ; 2. Select the pin polarity by setting the appropriate IRQEDG bits 044 ; in IRQSC (IRQEDG=0 is falling edge triggering) 045 ; (IRQEDG=0 on Power on Reset, by Default) 046 ; 3. If using internal pull‐up/pull‐down device, clear the IRQPDD bit 047 ; in IRQSC (IRQPDD=0 on Power on Reset, by Default) 048 ; 4. Enable the IRQ pin by setting the appropriate IRQPE bit in IRQSC 049 ; (IRQPE=0 by Default on Power on Reset >>>WE MUST set IRQPE=1<<<) 050 ; NOTE: IRQSC: equ $000F? Zero Page Register. Use Direct addressing!! 051 ; 5. Write to IRQACK in IRQSC to clear any false interrupts. 052 ; 6. Set IRQIE in IRQSC to enable interrupts 112 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 053 bset IRQPE, IRQSC ; 4. above 054 bset IRQACK, IRQSC ; 5. above. IRQACK=1 to clear (!) IRQF 055 bset IRQIE, IRQSC ; 6. above 056 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 057 mov #$FF, PTCDD ; Set Ports C & D all bits for output 058 mov #$FF, PTEDD ; ..(they drive the LEDs on DEMOQE128) 059 ldhx #20 ; time in milliseconds (20) 060 sthx delay ; .. 061 mov #$FF, counter ; Decrement!!! instead of increment... 062 mov #$FF, PTCD ; Leds have negative logic: 0 to light 063 mov #$FF, PTED ; .. 064 Init8Timers 065 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 066 ; Main Loop 067 cli ; Global Enable interrupts NOW 068 again: bra again 069 ;==================================================================== 070 ; PROGRAM TRAILER: 071 ; 072 ; 1) TIM8INT Macro 073 ; 2) Interrupt Vectors 074 ; 075 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 076 ; RTC Interrupt Service Routine, si usa Init8Timers... 077 RTC_INTERRUPT: 078 TIMERS8ISR 079 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 080 ; IRQ INTERRUPT SERVICE ROUTINE 081 ; IRQ has one of the highest priorities in the HCS08, and the higher 082 ; ..hardware priority, bellow Reset and SWI. That is ok for 083 ; ..responsiveness, but care must be taken to assure that no 084 ; ..>>>PRIORITY INVERSION<<< ocurrs through long ISR times, for 085 ; ...example by using debouncing delays. If this is the case, you 086 ; ..MUST auto‐disable the IRQ interrupts and re‐enable global 087 ; ..interrupts, to permit other lower peripherals access... 088 IRQISR: 089 bclr IRQIE, IRQSC; **SELF‐DISABLE IRQ Interrupts** 090 pshh ; Not saved by interrupt proc 091 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 092 ; 20mS delay processing for IRQISR: Help to avoid multiple 093 ; ..interrupts due to contact bouncing from IRQ pin... 094 WaitISR_on 7, delay ; 20 MSeconds for debouncing. This only 095 ; ..works if RTC is enabled to interrupt.. 096 ; ..You could have included NOP loops to 097 ; ..delay: but you CANNOT block all other 098 ; ..peripheral interrupts! So, you must 099 ; ..follow this same strategy... 113 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 100 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 101 ; *** NOTE *** Timer 7 >>>MUST NOT<<< BE USED ANYWHERE ELSE! 102 ; ..It belongs EXCLUSIVELY to this 'IRQISR' 103 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 104 dec counter ; Drive LEDs using negative logic, is 105 mov counter, PTCD ; 6 bits to drive LEDs 106 mov counter, PTED ; 2 bits more to drive higher LEDs 107 IRQISRexit: 108 bset IRQACK, IRQSC ; IRQACK=1 to clear (!) IRQF 109 bset IRQIE, IRQSC ; Re‐enable IRQ interrupts 110 pulh ; Not restored by interrupt process 111 rti ; Return from Interrupt 112 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 113 ; Interrupt Vectors 114 ORG Vrtc ; Increasing priority from bottom up 115 DC.W RTC_INTERRUPT ; (Si usa Init8Timers...) 116 ORG Virq 117 DC.W IRQISR ; IRQ 118 ORG Vreset 119 DC.W Main ; RESET. Maximum priority. Asynch. 120 END COMENTARIOS a ["Laboratorios\Lab2\Timers8HS\tim8_IRQ-HS.asm"]: Aspectos novedosos: Protocolo completo de Habilitación del IRQ: External Interrupt Initialization (>>>IRQ<<<) 041 ; 1. Mask interrupts by clearing IRQIE in IRQSC. 042 ; (IRQIE=0 on Power on Reset, by Default) 043 ; 2. Select the pin POLARITY by setting the appropriate IRQEDG bits 044 ; in IRQSC (IRQEDG=0 is falling edge triggering) 045 ; (IRQEDG=0 on Power on Reset, by Default) 046 ; 3. If using internal pull‐up/pull‐down device, clear the IRQPDD bit 047 ; in IRQSC (IRQPDD=0 on Power on Reset, by Default) 048 ; 4. Enable the IRQ pin by setting the appropriate IRQPE bit in IRQSC 049 ; (IRQPE=0 by Default on Power on Reset >>>WE MUST set IRQPE=1<<<) 050 ; NOTE: IRQSC: equ $000F? Zero Page Register. Use Direct addressing!! 051 ; 5. Write to IRQACK in IRQSC to clear any false interrupts. 052 ; 6. Set IRQIE in IRQSC to enable interrupts 114 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 053 bset IRQPE, IRQSC ; 4. above 054 bset IRQACK, IRQSC ; 5. above. IRQACK=1 to clear (!) IRQF 055 bset IRQIE, IRQSC ; 6. above Programación de los LEDs: 057 mov #$FF, PTCDD ; Set Ports C & D all bits for output 058 mov #$FF, PTEDD ; ..(they drive the LEDs on DEMOQE128) 059 ldhx #20 ; time in milliseconds (20) 060 sthx delay ; .. Los LEDs están alambrados para ENCENDER con un CERO: 061 mov #$FF, counter ; Decrement!!! instead of increment... 062 mov #$FF, PTCD ; Leds have negative logic: 0 to light 063 mov #$FF, PTED ; .. 080 ; IRQ INTERRUPT SERVICE ROUTINE 081 ; IRQ has one of the highest priorities in the HCS08, and the higher 082 ; ..hardware priority, bellow Reset and SWI. That is ok for 083 ; ..responsiveness, but care must be taken to assure that no 084 ; ..>>>PRIORITY INVERSION<<< ocurrs through long ISR times, for 085 ; ...example by using debouncing delays. If this is the case, you 086 ; ..MUST auto‐disable the IRQ interrupts and re‐enable global 087 ; ..interrupts, to permit other lower peripherals access... La ISR del periférico que vaya a emplear WaitISR_on, tiene particularidades necesarias para funcionar. En primer lugar, dado que va a rehabilitarse la atención de interrupciones, ANTES de terminar esa IRQ (en el ejemplo, ISRIRQ), es INDISPENSABLE AUTO DESHABILITAR las interrupciones: 088 IRQISR: 089 bclr IRQIE, IRQSC; **SELF‐DISABLE IRQ Interrupts** En este ejemplo sencillo, se llama a continuación a WaitISR_on (que si ya leyeron su código, hace varias cosas, entre ellas, HABILITAR LAS INTERRUPCIONES DEL CPU. (De otra forma, no operarían las interrupciones del timer, RTC) 094 WaitISR_on 7, delay ; 20 MSeconds for debouncing. 101 ; *** NOTE *** Timer 7 >>>MUST NOT<<< BE USED ANYWHERE ELSE! 102 ; ..It belongs EXCLUSIVELY to this 'IRQISR' 103 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ En el ejemplo, cada vez que se activa el IRQ, se incremental los LEDs. Como encienden al revés, en lugar de incrementar, decrementamos... (truco) 104 dec counter ; Drive LEDs using negative logic, is 105 mov counter, PTCD ; 6 bits to drive LEDs 106 mov counter, PTED ; 2 bits more to drive higher LEDs Para finalizar IRQISR, se da el Acknowledge correspondiente al hardware, y se retorna al programa principal: 115 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 107 IRQISRexit: 108 bset IRQACK, IRQSC ; IRQACK=1 to clear (!) IRQF 109 bset IRQIE, IRQSC ; Re‐enable IRQ interrupts ... 111 rti ; Return from Interrupt 23) Enciende los 8 LEDs con Diferentes Intervalos ["Laboratorios\Lab2\Timers8HS\TimedFlash8.asm"] 001 ;******************************************************************** 002 ; Program TimedFlash8.asm: Luis G Uribe C. D10J2012 C20F2013 C04D2013 003 ; Flashes all 8 LEDs at different intervals each one. Push IRQ to 004 ; ..toggle program OFF/ON 005 ; C20F2013: Do NOT use timer 7 inside IRQISR AND in Main program... 006 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 007 ; Include files 008 NOLIST 009 INCLUDE 'derivative.inc' 010 LIST 011 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 012 ; Parameter definitions 013 IRQACK: EQU IRQSC_IRQACK 014 IRQIE: EQU IRQSC_IRQIE 015 IRQPE: EQU IRQSC_IRQPE 016 ram: SET Z_RAMStart ; $80 017 ;==================================================================== 018 ; Global Variables 019 ORG ram ; <<<Put this *BEFORE* 'timers8HS.inc'<<< 020 NOLIST 021 include 'timers8HS.inc' ; <<<TIMERS8: Code, Vars & Macros 022 LIST 023 LEDsMask: DS.B 1 ; Ram bank #0 (direct addressing) 024 Toggle: DS.B 1 025 ;******************************************************************** 026 ; MAIN PROGRAM HEADER: 027 ABSENTRY Main ; Export symbol (ABSOLUTE Assembly select) 028 ORG rom 029 Main: lda #COP_Disable ; RSTE=0: PTA5 is NOT for ~RESET 030 sta SOPT1 ; ..System Options 1 031 ldhx #initStack ; Init SP 032 txs 033 ;==================================================================== 034 ; External Interrupt Initialization (>>>IRQ<<<) (see Tim8_IRQ‐HS.asm) 116 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 035 bset IRQPE, IRQSC ; 4. above 036 bset IRQACK, IRQSC ; 5. above. IRQACK=1 to clear (!) IRQF 037 bset IRQIE, IRQSC ; 6. above 038 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 039 ; Init LEDs PORTs PTCD (bits 0‐5) and PTED (bits 6‐7) 040 mov #$FF, PTCDD ; Set Ports C & D all bits for output 041 mov #$FF, PTEDD ; ..(they drive the LEDs on DEMOQE128) 042 Init8Timers 043 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 044 ; Main Loop 045 cli ; Global Enable interrupts NOW 046 Setimer 0, #673 >> 1 047 Setimer 1, #150 >> 1 048 Setimer 2, #2755 >> 1 049 Setimer 3, #1398 >> 1 050 Setimer 4, #511 >> 1 051 Setimer 5, #1002 >> 1 052 Setimer 6, #3000 >> 1 053 Setimer 7, #355 >> 1 054 mov #$01, Toggle ; Begin in RUN state 055 loop: 056 brclr 0, Toggle, * ; Wait for Bit0 (RUN/~STOP) 057 clr LEDsMask 058 NTimeOut 0, ET0 059 bset 0, LEDsMask 060 Setimer 0, #673 >> 1 061 ET0: 062 NTimeOut 1, ET1 063 bset 1, LEDsMask 064 Setimer 1, #150 >> 1 065 ET1: 066 NTimeOut 2, ET2 067 bset 2, LEDsMask 068 Setimer 2, #2755 >> 1 069 ET2: 070 NTimeOut 3, ET3 071 bset 3, LEDsMask 072 Setimer 3, #1398 >> 1 073 ET3: 074 NTimeOut 4, ET4 075 bset 4, LEDsMask 076 Setimer 4, #511 >> 1 077 ET4: 078 NTimeOut 5, ET5 079 bset 5, LEDsMask 080 Setimer 5, #1002 >> 1 081 ET5: 082 NTimeOut 6, ET6 083 bset 6, LEDsMask 117 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 084 Setimer 6, #3000 >> 1 085 ET6: 086 NTimeOut 7, ET7 087 bset 7, LEDsMask 088 Setimer 7, #355 >> 1 089 ET7: 090 lda PTCD 091 eor LEDsMask 092 sta PTCD 093 sta PTED 094 jmp loop 095 ;==================================================================== 096 ; PROGRAM TRAILER: 097 ; 1) TIM8INT Macro 098 ; 2) Interrupt Vectors 099 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 100 ; RTC Interrupt Service Routine, si usa Init8Timers... 101 RTC_INTERRUPT: 102 TIMERS8ISR 103 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 104 ; IRQ INTERRUPT SERVICE ROUTINE 105 IRQISR: 106 bclr IRQIE, IRQSC; **SELF‐DISABLE IRQ Interrupts** 107 pshh ; Not saved by interrupt proc 108 lda Toggle 109 eor #%00000001 110 sta Toggle 111 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 112 IRQISRexit: 113 sei 114 bset IRQACK, IRQSC ; IRQACK=1 to clear (!) IRQF 115 bset IRQIE, IRQSC ; Re‐enable IRQ interrupts 116 pulh ; Not restored by interrupt process 117 rti ; Return from Interrupt 118 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 119 ; Interrupt Vectors 120 dummy_isr: ; <<<< Must be placed in ROM Space 121 rti 122 ORG Vrtc ; Increasing priority from bottom up 123 DC.W RTC_INTERRUPT ; (Si usa Init8Timers...) 124 ORG Virq ; Virq, Vswi and Vreset 125 DC.W IRQISR ; IRQ 126 DC.W dummy_isr ; SWI 127 DC.W Main ; RESET. Maximum priority. Asynch. 128 END 118 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 COMENTARIOS a ["Laboratorios\Lab2\Timers8HS\TimedFlash8.asm"]: Este ejercicio resulta simpático de observar en la tarjeta DEMOQE128: 044 ; Main Loop 045 cli ; Global Enable interrupts NOW Activa los 8 timers, cada uno con un valor que obtuve de un programa que genera números al azar. El número es dividido por dos (>> 1) para que dure la mitad encendido y la mitad apagado (50% Dutty Cycle) 046 Setimer 0, #673 >> 1 047 Setimer 1, #150 >> 1 048 Setimer 2, #2755 >> 1 049 Setimer 3, #1398 >> 1 050 Setimer 4, #511 >> 1 051 Setimer 5, #1002 >> 1 052 Setimer 6, #3000 >> 1 053 Setimer 7, #355 >> 1 054 mov #$01, Toggle ; Begin in RUN state Toggle es una variable que sirve para detener o continuar el parpadeo, al oprimir el botón de IRQ. El proceso es muy sencillo. Se trata de un lazo infinito, en el que se pregunta por cada temporizador para ver si ya expiró. Cuando alguno ya cumplió, se debe cambiar el estado del correspondiente LED. La rutina no afecta directamente los LEDs, sino que, a partir de una máscara (LEDsMask), que comienza en ceros al principio del ciclo, se coloca en 1 el bit correspondiente a cada LED que tenga que CAMBIAR. Al finalizar el ciclo, se hace un EXOR del LEDsMask con el valor actual de los LEDs (línea 091 eor). Así, se cambia el valor, de encendido a apagado, o viceversa, para aquellos a los cuales se les venció el tiempo. Desde luego, cada vez que para alguno expira el intervalo, se lo vuelve a inicializar, con Setimer... 091 eor LEDsMask 055 loop: 056 brclr 0, Toggle, * ; Wait for Bit0 (RUN/~STOP) 057 clr LEDsMask 058 NTimeOut 0, ET0 059 bset 0, LEDsMask 060 Setimer 0, #673 >> 1 061 ET0: 062 NTimeOut 1, ET1 063 bset 1, LEDsMask 064 Setimer 1, #150 >> 1 ... 089 ET7: 090 lda PTCD 091 eor LEDsMask 119 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 092 sta PTCD 093 sta PTED 094 jmp loop La 105 IRQISR básicamente alterna la variable Toggle (su bit #0), de esta manera, el programa principal puede detener todo el proceso, o reanudarlo, de acuerdo al valor de la variable Toggle: 056 brclr 0, Toggle, * ; Wait for Bit0 (RUN/~STOP) ... 108 lda Toggle 109 eor #%00000001 110 sta Toggle 24) Enciende ORDENADAMENTE los 8 LEDs con Diferentes Intervalos Los LEDs más lentos están a un lado de la tarjeta; los más rápidos al otro extremo. Es una variación sencilla del ejercicio anterior, y no necesita mayores comentarios: ["Laboratorios\Lab2\Timers8HS\TimedFlash8Ordered.asm"] 001 ;******************************************************************** 002 ; TimedFlash8Ordered.asm: Luis G. Uribe C. D10J2012 C20F2013 S22G13 003 ; C04D2013, J19D2013 cosmetics 004 ; Flashes all 8 LEDs at different intervals each one. Push IRQ to 005 ; ..toggle program OFF/ON 006 ; C20F2013: Do NOT use timer 7 inside IRQISR AND in Main program... 007 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 008 ; Include files 009 NOLIST 010 INCLUDE 'derivative.inc' 011 LIST 012 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 013 ; Parameter definitions 014 IRQACK: EQU IRQSC_IRQACK 015 IRQIE: EQU IRQSC_IRQIE 016 IRQPE: EQU IRQSC_IRQPE 017 ram: SET Z_RAMStart ; $80 018 ;==================================================================== 019 ; Global Variables 020 ORG ram ; <<<Put this *BEFORE* 'timers8HS.inc'<<< 021 NOLIST 022 include 'timers8HS.inc' ; <<<TIMERS8: Code, Vars & Macros 023 LIST 024 LEDsMask: DS.B 1 ; Ram bank #0 (direct addressing) 025 Toggle: DS.B 1 026 ;******************************************************************** 120 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 027 ; MAIN PROGRAM HEADER: 028 ABSENTRY Main ; Export symbol (ABSOLUTE Assembly select) 029 ORG rom 030 Main: lda #COP_Disable ; RSTE=0: PTA5 is NOT for ~RESET 031 sta SOPT1 ; ..System Options 1 032 ldhx #initStack ; Init SP 033 txs 034 ;==================================================================== 035 ; External Interrupt Initialization (>>>IRQ<<<) (see Tim8_IRQ‐HS.asm) 036 bset IRQPE, IRQSC ; 4. above 037 bset IRQACK, IRQSC ; 5. above. IRQACK=1 to clear (!) IRQF 038 bset IRQIE, IRQSC ; 6. above 039 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 040 ; Init LEDs PORTs PTCD (bits 0‐5) and PTED (bits 6‐7) 041 mov #$FF, PTCDD ; Set Ports C & D all bits for output 042 mov #$FF, PTEDD ; ..(they drive the LEDs on DEMOQE128) 043 Init8Timers 044 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 045 ; Main Loop 046 cli ; Global Enable interrupts NOW 047 SetimerMS 0, # 150 >> 1 048 SetimerMS 1, # 355 >> 1 049 SetimerMS 2, # 511 >> 1 050 SetimerMS 3, # 673 >> 1 051 SetimerMS 4, #1002 >> 1 052 SetimerMS 5, #1398 >> 1 053 SetimerMS 6, #2755 >> 1 054 SetimerMS 7, #3000 >> 1 055 mov #$01, Toggle ; Begin in RUN state 056 loop: 057 brclr 0, Toggle, * ; Wait for Bit0 (RUN/~STOP) 058 clr LEDsMask 059 NTimeOut 0, ET0 060 bset 0, LEDsMask 061 SetimerMS 0, #150 >> 1 062 ET0: 063 NTimeOut 1, ET1 064 bset 1, LEDsMask 065 SetimerMS 1, #355 >> 1 066 ET1: 067 NTimeOut 2, ET2 068 bset 2, LEDsMask 069 SetimerMS 2, #511 >> 1 070 ET2: 071 NTimeOut 3, ET3 072 bset 3, LEDsMask 073 SetimerMS 3, #673 >> 1 121 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 074 ET3: 075 NTimeOut 4, ET4 076 bset 4, LEDsMask 077 SetimerMS 4, #1002 >> 1 078 ET4: 079 NTimeOut 5, ET5 080 bset 5, LEDsMask 081 SetimerMS 5, #1398 >> 1 082 ET5: 083 NTimeOut 6, ET6 084 bset 6, LEDsMask 085 SetimerMS 6, #2755 >> 1 086 ET6: 087 NTimeOut 7, ET7 088 bset 7, LEDsMask 089 SetimerMS 7, #3000 >> 1 090 ET7: 091 lda PTCD 092 eor LEDsMask 093 sta PTCD 094 sta PTED 095 jmp loop 096 ;==================================================================== 097 ; PROGRAM TRAILER: 098 ; 1) TIM8INT Macro 099 ; 2) Interrupt Vectors 100 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 101 ; RTC Interrupt Service Routine, si usa Init8Timers... 102 RTC_INTERRUPT: 103 TIMERS8ISR 104 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 105 ; IRQ INTERRUPT SERVICE ROUTINE 106 IRQISR: 107 bclr IRQIE, IRQSC; **SELF‐DISABLE IRQ Interrupts** 108 pshh ; Not saved by interrupt proc 109 lda Toggle 110 eor #%00000001 111 sta Toggle 112 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 113 IRQISRexit: 114 sei 115 bset IRQACK, IRQSC ; IRQACK=1 to clear (!) IRQF 116 bset IRQIE, IRQSC ; Re‐enable IRQ interrupts 117 pulh ; Not restored by interrupt process 118 rti ; Return from Interrupt 119 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 120 ; Interrupt Vectors 121 dummy_isr: ; <<< Must be placed in ROM Space 122 rti 123 ORG Vrtc ; Increasing priority from bottom up 122 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 124 DC.W RTC_INTERRUPT ; (Si usa Init8Timers...) 125 ORG Virq ; Virq, Vswi and Vreset 126 DC.W IRQISR ; IRQ 127 DC.W dummy_isr ; SWI 128 DC.W Main ; RESET. Maximum priority. Asynch. 129 END Finalización Tema TIMERS Antes de finalizar el tema de los Timers, y después de haber leído la documentación hasta aquí, haberla estudiado y entenderla... haga un examen de conciencia que le permita decir con certeza, si no hay uno o más detalles que usted hubiera pasado por alto, o que ni siquiera entendió. Este es el nivel de detalle al que hay que acostumbrarse cuando trabaja en ésta área de los sistemas Embebidos, donde todo depende de todo, y cuando no pensar en un bit que nos sirva como Mutex (tal como lo manejan la rutina de interrupciones del timer, y otras como el Setimer...) puede dar al traste con todo un proyecto. METICULOSIDAD, MUCHO ANÁLISIS Y REFLEXIÓN EN CADA PASO, MUCHAS PRUEBAS (Test‐Driven Development, TDD) son menester en un proyecto. No crea que esta dificultad es inherente sólo a nuestro campo. La parte electrónica sólo es más fácil porque hay una gran cantidad de funcionalidades de muy alto nivel y gran complejidad incluida en circuitos integrados, tanto analógicos como digitales; de frecuencia bajas y muy altas. Por eso sostengo que hay que aceptar que nuestra forma de diseño ha cambiado: ahora hay que saber INTEGRAR componentes, que pueden ser tarjetas completas, como la DEMOQE128, o los Credit Card Computers, que por $100 nos colocan en las manos todo el poder de un PC IBM/AT compatible, como los que compro en CompuLab http://compulab.co.il/ “The 486CORE you sent to us has arrived at this very moment to our office. I must congratulate you and CompuLab: This is truly a beautiful piece of hardware.” Luis G. Uribe, Vessing 2000 PROGRAMAS AVANZADOS EN ASSEMBLY LANGUAGE, PARTE III Hemos cubierto hasta ahora una sección introductoria, donde aprendimos a usar el conjunto de instrucciones del HCS08, sus modos de direccionamiento, y allí hicimos nuestras primeras aproximaciones a la programación de periféricos, encendiendo algunos LEDs y leyendo los interruptores de la tarjeta DEMOQE128. Aprendimos a usar el ambiente de desarrollo CodeWarrior y su debbuger, que nos permite seguir paso a paso el programa directamente DENTRO del MCU, lo cual se fundamenta en la 123 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 nueva versión de esta familia de microcontroladores, que incluye un acceso directo a los registros internos del MCU, y una infraestructura de Debugging que permite a un programa externo (CodeWarrior) interactuar con los recursos internos del micro, SIN detener su funcionamiento ni casi interferir con él. Para hacer un trabajo similar en otras familias de microcontroladores, se necesita adquirir una interfaz conocida como ICE (In Circuit Emulator) que, por un lado reemplaza al micro en la tarjeta del cliente, mediante un enchufe que tiene la misma forma del MCU y que por el otro extremo se conecta al PC; allí, un programa SIMULA el comportamiento del micro, genera todos los valores de voltaje en los pines que produciría el MCU original, lee las variables externas, digitales o analógicas como si fuera el MCU y, desde luego que interactúa con el usuario, puesto que no hay tal MCU, sino una versión simulada del mismo. Un dispositivo como éste, dependiendo del manufacturante, puede costar entre $600 y $2000 que, como se verá, no necesariamente resulta barato. Pues ésta familia del HCS08 incluye dentro del mismo chip todos los recursos que permiten hacer exactamente lo mismo que con el ICE, y a un costo nulo. En los ejercicios un poco más avanzados aprendimos a crear variables dinámicas en el stack, al estilo del C; probamos a descomponer los programas en subrutinas, y conocimos el protocolo de llamado de las mismas: CREAR EL STACK FRAME PARA SUBRUTINAS: ‐ Guardar los parámetros en el Stack; ‐ Reservar el espacio para las variables locales de la subrutina; ‐ Hacer la llamada; ‐ Inicializar las variables locales, si es el caso; ‐ Ejecutar el código; ‐ Almacenar el valor resultante (casi siempre un Byte) en el Acumulador, o en una variable: externa, o Global, o definida mediante un apuntador. REMOVER EL STACK FRAME ACTUAL Y RECUPERAR EL ANTERIOR ‐ Eliminar tanto variables locales como parámetros RETORNAR. Empleando variables en el stack pudimos hacer con facilidad programas recursivos, al estilo de las torres de Hanoi, cálculo de factorial y similares. Y... comenzamos la parte avanzada, donde aprendimos a programar una batería de Timers, que además de resultarnos muy beneficiosos en nuestros laboratorios y en el proyecto, nos sirvió para ver con detenimiento la forma como se diseña y construye SOFTWARE: Una definición del problema, descomponiéndolo en funciones desde el nivel más alto, bajando hasta llegar a definir las operaciones más elementales (Top Down Design); y la codificación, que hicimos desde éstas funciones elementales hacia arriba, hasta llegar a implementar el código de más alto nivel (Buttom Up Coding). Hicimos mucho esfuerzo en mostrar el desarrollo de esa librería, descomponiéndola en Políticas y Mecanismos, para definir e implementar por una lado, código que sólo tiene que ver con LO QUE SE VA A 124 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 HACER, sin tener en cuenta cómo se lo va a hacer (si van a hacerse los timers con retrasos, con temporizadores, empleando o no interrupciones...). Y por último, la codificación de los Mecanismos, que sí dependen de cómo se va a hacer cada operación. Pero aún allí, puede obligarse al programador a establecer una separación entre las rutinas que TOCAN el hardware, y aquellas que no. Windows, por ejemplo, tiene una capa de software que separa el hardware de otro software, y se la denomina HAL: Hardware Abstraction Layer. Es muy importante lograr al máximo esa separación, de manera que si se cambia el hardware no haya que tirar a la basura todo el software. Recuerdan haber oído cada rato que el micro era un 486, un Pentium, un PRO, un MMX, un Quadracore? Y eso ocurre con una velocidad pasmosa. Y también, que sus discos eran de 560 Mb, de 1Gb, de 100 Gb, de 1 Terabyte, de estado sólido... Así que: NO separar el hardware específico, del resto del código, es GARANTÍA DE SUICIDIO EN UNA EMPRESA. Llegamos ahora al último aspecto que puede cubrirse en las horas reservadas para este curso de Arquitectura del Computador, que ha hecho énfasis primordial en la UTILIZACIÓN de los microcontroladores, más que en su diseño. El periférico de comunicación serial (SCI en el MC9S08QE128) tiene varias ventajas para el aprendizaje, por las cuales lo hemos incluido aquí. En PRIMER lugar, una de las principales funciones que realizan los MCUs en sistemas embebidos consiste en intercambiar información con unos con otros, y con sistemas de mayor jerarquía, como servidores, etc. Estudiar este periférico se enmarca dentro de la importancia de la función. SEGUNDO, cubre la gran mayoría de aspectos básicos que se deben considerar siempre al utilizar una función de éstas: manejo de los periféricos propiamente dichos (encenderlos, apagarlos), control de sus líneas de habilitación para interrupción, y protocolo de intercambio de información: Data Ready, Done, donde el periférico produce una señal de que hay datos listos para leerse, y el MCU le da un reconocimiento (Acknowledge) al periférico para que él pueda proceder correctamente con otro dato. Lo mismo ocurre con las interrupciones: su habilitación, procesamiento, reconocimiento. Se tocan otros aspectos como la habilitación de interrupciones DENTRO de rutinas de interrupciones, a pesar de todo lo que los manuales de Freescale hablan en contra... Hay que aprender todo el protocolo de interrupción: qué guarda el micro en el Stack, qué no (y que por lo tanto tiene que guardar el programador a mano), en qué orden se guardan y se recuperan los valores almacenados, cómo se desactivan automáticamente las interrupciones a nivel general del CPU, etc. TERCERO: Como parte del protocolo de atención se aprende a manejar el vector de interrupciones, y se hace mucho énfasis en NO hacer la mayor parte del proceso dentro de la rutina de interrupciones, sino en ‐‐por ejemplo‐‐ recibir un dato, almacenarlo en algún lugar reservado e idóneo para ello, informar al programa principal de ese EVENTO, quizás activando una bandera (flag), y retornar rápidamente al código al que se interrumpió. En este sentido es conveniente incluir el manejo de Colas de Datos, lo cual se hace precisamente aquí y ahora, para sincronizar los dos eventos, de por sí asíncronos entre 125 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 ellos, que son los datos entrantes de un equipo o usuario externo, independiente, y el MCU. Se indican variantes del método, mediante TABLAS, que pueden funcionar como Double Buffers cuando se procesan datos que llegan en ráfagas, como es el caso de la manipulación de discos, etc. Todo lo anterior se integra, pues, en la siguiente sección. COLAS DE DATOS Cuando dos sistemas de diferentes velocidades se comunican entre sí, de manera asíncrona, es decir, cuando no existe relación temporal definida, clara y precisa entre ellos, es indispensable sincronizarlos o de lo contrario se perderán datos durante la transferencia de información. Un ejemplo cotidiano es una persona empleando el teclado para enviar información a su PC; no podrían ser más diferentes sus velocidades, y no existe sincronía ni por asomo entre el usuario y el PC. La forma de sincronizarlos varía de acuerdo a la naturaleza de la información transmitida. Si el sistema más rápido puede darse el lujo de emplear buena parte de su tiempo esperando que el usuario le transmita una letra, quizás basta con un elemento de memoria en el que reposará la letra mientras se la procesa, antes de venir a leer el siguiente símbolo. El dispositivo elemental, unitario, de memoria, suele ser un registro (Data Buffer) interpuesto entre el dispositivo Productor de la información (canal de comunicación con el usuario), y el Consumidor de la misma, en este caso, el CPU del PC (Esta unidad de acople recibe el nombre de INTERFAZ; en inglés Interface. NO SE TRADUCE AL CASTELLANO COMO INTERFACE! Para facilitar la vida del PC, se agrega a la Interfaz una bandera, el bit de Data Ready, que se activa cuando ha llegado una letra por el canal de información, producida desde afuera del PC por el usuario al oprimir una tecla. Al leer la tecla, se repone a cero el valor de la bandera, de tal manera que la unidad de comunicación sepa que ya leyeron el dato que ella aportó. A este protocolo, realizado manipulando ordenadamente la bandera de Data Ready, se lo conoce como Hand‐Shaking (en inglés: apretón de manos). El saludo o Hand‐Shaking verbalizado sería algo así como: "Hola, tengo un dato". "Ah, un momento, ya lo recibo". "Listo, gracias y ciao". "Ciao". Las dificultades comienzan cuando el CPU no puede bloquearse esperando a que llegue cada símbolo, porque eso lo pondría a trabajar más o menos a la velocidad del subsistema lento, en este caso, el usuario. Una alternativa es lograr que el programa que el CPU va a estar corriendo entre letra y letra, se comporte como una especie de CICLO de longitud indefinida que, entre las cosas que tiene que realizar, está la de pasar a revisar si llegó o no una letra. ¿Cuánto tiempo tiene el CPU para dar la vuelta y preguntar por una letra a la entrada, antes de que comience a llegar OTRA LETRA, porque sobrescribiría parcial o totalmente la letra anterior? Pues si el registro de entrada es un "Shift Register" que va recibiendo uno a uno los N bits de cada letra, el CPU tiene que atender al periférico ANTES de que transcurra UN TIEMPO DE BIT de comunicaciones. Así, llega el último bit de 126 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 un símbolo producido por el usuario en el teclado y se activa el bit de STATUS, Data Ready. El CPU tiene que leerlo y actuar ANTES de que comience a llegar el primer bit del segundo símbolo. Si se está en una transferencia continua de datos, se calcula el tiempo necesario de reacción del CPU, como se lo conoce, infiriendo, de la velocidad de comunicación, el tiempo de cada bit. Ese es la cota superior o límite en tiempo que puede pasar entre atención y atención al periférico, para garantizar que no se pierden ningún símbolo recibido. Una modificación que hace más llevadera la vida de los sistemas consiste en agregarle un Registro Adicional al Serializador de Entrada (el serializador que es el que va recibiendo uno a uno los bits de la letra, y cuando tiene N ‐‐normalmente 8 bits para nuestros ejemplos, siendo 8 el número más empleado en la actualidad‐‐ el controlador de la interfaz lo transfiere a ese Data Buffer. Ahora el CPU tiene TODO UN TIEMPO DE BYTE TRANSMITIDO, entre atención y atención al periférico. Como cada byte (8 bits) puede durar 10 tiempos de bit en transmitirse, se ha disminuido en un orden de magnitud (dividido por 10) el tiempo permitido al CPU para atender los símbolos. A esta combinación de serializador alimentando a otro registro, que es el registro que en definitiva OPERA el CPU, se lo conoce como Double Buffer (porque hay 2 registros involucrados, o buffers). Cuando la velocidad con la que la unidad de comunicaciones le entrega los datos al CPU en algunas oportunidades, es mayor del tiempo libre, o de holgura, del procesador, éste se ve en la necesidad de atender los datos con mayor agilidad. Y para facilitar este intercambio, se inventó el mecanismo conocido como INTERRUPCIONES, que consiste en lograr que el CPU responda rápidamente a cada letra pero, si por algún motivo no tiene tiempo para procesar la información de entrada, tiene que moverla del double buffer a la memoria. Cuando la memoria de trabajo tiene un principio y un fin, como una tabla, se la conoce como BUFFER de Entrada. Desde luego puede haber BUFFERS de Salida también (tablas). Si la velocidad de transferencia de información es muy elevada, por ejemplo en la entrada, se emplean DOS BUFFERS, y se los conmuta de tal manera de estar adquiriendo información sobre un BUFFER, mientras se está procesando el otro. A esta técnica se la conoce también como DOUBLE BUFFERING, y se distingue de la anterior por la cantidad de elementos de información que conforma cada buffer. En el caso mencionado antes, eran solo dos elementos o registros: el serializador, y el registro buffer, propiamente dicho. En este segundo caso (que desafortunadamente recibe el mismo nombre, Double Buffer, o Buffering) el tamaño del buffer suele ser grande, entre 64 bytes para canales de comunicación muy rápidos, hasta 512 bytes para unidades adaptadoras de discos, que cuando dicen transferir información lo hacen de manera muy veloz. Pero hay otra alternativa, frente al Double Buffering, que consiste en el empleo de COLAS o QUEUEs; algunas de ellas realizadas a nivel de circuitos integrados (FIFOs). Una cola puede verse como una tabla de tamaño fijo, pero CIRCULAR, lo cual quiere decir que si se van colocando datos en ella y se llega al final, el próximo dato SE COLOCARÁ AL PRINCIPIO de la tabla otra vez. Esto produce la ilusión de que esa área de memoria es CIRCULAR, pues el final de la tabla está contiguo al principio, como si fuera un anillo (o círculo) que no tiene fin. El mayor uso de las Colas se consigue cuando el proceso Productor de datos puede generar a veces, ráfagas de información, como picos de transmisión, pero que normalmente está por debajo de lo que podríamos llamar Comunicación Ininterrumpida, o 127 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 continua. Es así que la comunicación va a un paso dado, pero de pronto se acelera en una ráfaga de una duración acotada, inferior a X caracteres. Si además el Consumidor va extrayendo con cierta periodicidad los datos de la cola, y cuando lo hace lo hace muy rápidamente (de la misma cola), la Cola presenta la imagen virtual de que es de mayor capacidad operativa de su real capacidad física. Uno de los aspectos oscuros del empleo de Colas es el establecimiento del tamaño de las mismas, ya que su capacidad depende de la diferencia de velocidades entre Productor y Consumidor. Una alternativa consiste en simular la cola en el PC, alimentándola y extrayendo información de ella al azar, e ir incrementando un contador que determine cuándo, al tratar de agregarse un elemento a la cola, ésta estaba llena, lo cual conforma un error, y también, dependiendo del caso, si se quiso retirar un elemento de información de la cola, y no se pudo debido a que ésta se encontraba vacía. Una Cola, pues, es una Estructura de Datos que está materializada en la memoria como una tabla, y emplea varios elementos accesorios para su correcta operación. Se requiere una variable que determine el tamaño definido para la cola, 'SIZE'(1) en nuestra descripción. (Nota: los números entre paréntesis corresponden a cada elemento identificado de la misma manera en el próximo dibujo) Es cómodo tener un pointer que señale siempre a dónde comienza la tabla: BASE, que apunta a "BUF"(2). Se necesita un apuntador que indique en cuál posición de la tabla se colocará la próxima letra que llegue en secuencia, y haya que almacenarla. Este es el PUT pointer, o simplemente "PUT"(3). Se necesita otro apuntador, al que le corresponde señalar cuál es la letra que debe salir en secuencia de la cola, cuando se haga una lectura de allí. Este es el GET pointer, o sencillamente "GET"(4). Es cómodo tener otro pointer que identifique el final de la tabla: LIMIT, y una variable "N"(5), que indique la cantidad de bytes que almacena la Cola en un instante dado. Para llevar la pista de cuántos bytes están almacenados en la cola, cada vez que se incluya uno debe incrementarse el contador, y al momento de extraer un dato, debe decrementarse dicho valor. Así, siempre "N"(5) contiene la cantidad de Símbolos almacenados en un momento determinado. En 'C', un elemento de información como el que acabamos de describir se representa de manera natural como una STRUCT; la nuestra sería (tomada de mis rutinas de Colas para el PC): typedef struct { byte *put; /* n (5) */ byte *get; /* (2) buf‐‐‐‐> +‐‐‐+ */ word n; /* |(1)| ‐‐‐> (4) get */ word c; /* (3) put ‐‐‐> | s | */ byte *base; /* | i | */ byte *limit; /* | z | */ word size; /* | e | */ } QUE; /* +‐‐‐+ */ 128 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 En esta definición de QUE están los dos apuntadores a byte: *put y *get; el contador de elementos que, para esta implementación es un word n (16 bits, con lo que la capacidad máxima de las colas es de 64Kbytes); los apuntadores a byte, que indican el comienzo y al final de la tabla: byte *base y byte *limit; una constante para cada cola, que es su tamaño: word size; y finalmente una variable auxiliar para almacenar un símbolo de la tabla temporalmente, word c (es word, no byte, porque igual que el símbolo EOF en la librería estándar de C, se necesita un word para almacenarlo). Una ilustración de los rangos de valores que pueden alcanzar las diferentes variables, y la definición de cola llena y vacía es la siguiente: Sea 'size'(1) de 'buf'(2) igual a 5, como ejemplo. Los pointers 'put'(3) y 'get'(4) sólo pueden tener los valores: buf, buf + 1, buf + 2, buf + 3, buf + 4; es decir, los pointers deben permanecer en el rango: buf <= pointer < (buf+size), o, lo que es lo mismo: buf <= pointer < limit 'n'(5) puede valer: 0 (vacío), 1, 2, 3, 4 and 5 (lleno). Note que 'n' es UN SEMÁFORO que impone las siguientes reglas de tráfico: NO 'deQue' cuando n sea <= 0 NO 'enQue' cuando n sea >= size Con estos antecedentes en mente, analicemos mi librería de colas para el HC9S08, escritas en Assembly Language: 25) BIBLIOTECA de COLAS: Que.inc ["Laboratorios\Lab3\SciComm\Que.inc"] 001 ;******************************************************************** 002 ; Que.inc: Queue Support (for SCI) 003 ; Luis G. Uribe C., C14M2007 C15A09 L08J09 J14J2012 (HCS08) M05M2013 004 ; S30N2013 (defineQue & initQue now begin with Lower Case) C18D2013 005 ;==================================================================== 006 ; THE FOLLOWING MACROS ARE DEFINED IN THIS LIBRARY 007 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 008 ;defineQue: MACRO size1; ===>> Use 'defineQue' in **RAMstart ORG* 009 ;initQue: MACRO name1, size2; ==>>Use 'initQue' in *ROMstart ORG* 010 ;enQue: MACRO name1 ; enQ Accumulator into Que: 'name1' 011 ;deQue: MACRO name1 ; deQ Accumulator from Que: 'name1' 012 ;==================================================================== 013 ; EXAMPLES OF USE 014 ;... 015 ; Global Variables 016 ; ORG ram 129 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 017 ;inpQ: defineQue 6 ; Use macro 'defineQue' in 'ORG ram' 018 ;outQ: defineQue 5 019 ;... 020 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 021 ; ORG rom 022 ;... 023 ; initQue inpQ, 6 ; Use macro 'initQue' in 'ORG rom' 024 ; initQue outQ, 5 025 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 026 ; enQue inpQ 027 ; bcs full_inpQ 028 ;... 029 ; deQue inpQ 030 ; bcs empty_inpQ 031 ; enQue outQ 032 ; bcs full_outQ 033 ;... 034 ;==================================================================== 035 ; DATA DEFINITION. 036 ; 037 ; NOTE: This is ONE way to work with >>>STRUCTURES<<< in Assembler... 038 ; References to internal RAM struct positions are done via symbols: 039 ; The following EQUs are defined to help us in locating the different 040 ; ..variables used in Queue's struct; i.e: 041 ; _QN: BYTE: unsign number of chars stored any time in one Queue 042 ; _QPUT: WORD: pointer used to store the next char, vía enQue; 043 ; _QGET: WORD: pointer to deQue one char. 044 ; _QSIZE: BYTE: static space reserved for Queue; 045 ; _QBASE: WORD: address of the first char to be stored in the 046 ; ..reserved memory of each Queue; 047 ; _QLIMIT: WORD: last element's address in a Queue; used in wrap 048 ; ..around process, when a pointer crosses the border 049 ; _QBUF: BYTES: here begins RAM space for characters to be stored 050 _QN: EQU 0 ; BYTE: data bytes counter N 051 _QPUT: EQU 1 ; WORD: pointer for storing data into Queue 052 _QGET: EQU 3 ; WORD: pointer for getting data from Queue 053 _QSIZE: EQU 5 ; BYTE: sizeof 054 _QBASE: EQU 6 ; WORD: base address of Queue struc 055 _QLIMIT: EQU 8 ; WORD: last address of buffer 056 _QBUF: EQU 10 ; BYTES: reserve 'size' data bytes 057 ;==================================================================== 058 ; "defineQue" is used with 1 parameter: SIZE of the Queue. 059 ; ..defineQue RESERVES RAM space, and *MUST* be used in RAMspace ORG. 060 ; ===>> ^^^^ <<<=== 061 ; To use "defineQue", you *MUST* begin it with a LABEL: This *IS* 062 ; ..the NAME of your QUEUE; i.e: 063 ; 064 ;********************** 065 ;inpQ: defineQue 6 * 066 ;********************** 067 ; NOTE: It is clever to define data bytes ram space, the last... 130 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 068 defineQue: MACRO size1; ===>> Use 'defineQue' in **RAMstart ORG** 069 ; ===>>===>===>===>===>===>> ^^^^^^^^ <<<=== 070 DS _QPUT ‐ _QN ; n: data bytes counter N 071 DS _QGET ‐ _QPUT ; put: ptr for STORE data into Queue 072 DS _QSIZE ‐ _QGET ; get: ptr for GET data from Queue 073 DS _QBASE ‐ _QSIZE ; size: sizeof 074 DS _QLIMIT ‐ _QBASE ; base: base address of Queue struc 075 DS _QBUF ‐ _QLIMIT ; limit: last address of buffer 076 DS \1 ; buf: reserve 'size' data bytes 077 ENDM 078 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 079 ; "initQue" is used with 2 parameters: NAME of the Queue, AND SIZE. 080 ; initQue STORES information in the Queue's RAM space, and 081 ; .. *MUST* be used in ** ROMspace ORG ** 082 ;==>> ^^^^ <<<=== 083 ; For initQue you must write SAME parameters used on defineQue; i.e: 084 ; 085 ;****************** 086 ; initQue inpQ, 6 * 'inpQ' was Queue's NAME in defineQue; size was 6 087 ;****************** 088 ; 089 ; ALL references to internal RAM positions are done via the symbols 090 ; .. _QN, _QPUT, _QGET, _QSIZE, _QBASE, _QLIMIT and _QBUF 091 ; ..Note that this is ONE way to work with STRUCTURES in assembler... 092 initQue: MACRO name1, size2; ==>> Use 'initQue' in *ROMstart ORG* 093 ; ==>>====>====>======>> ^^^^^^^^ <<== 094 clra ; mark Queue as empty (n = 0) 095 sta (\1 + _QN) ; 'sta', not 'clr OP' cause OP could 096 ; ..reside in 16 bit address space 097 ldhx #(\1 + _QBUF) 098 sthx (\1 + _QBASE) 099 sthx (\1 + _QPUT) 100 sthx (\1 + _QGET) 101 lda #\2 102 sta (\1 + _QSIZE) 103 aix #\2 104 sthx (\1 + _QLIMIT) 105 ENDM 106 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 107 ; "enQue" stores the content of ACCumulator in the designated QUEUE 108 ; 109 ;******************************************************************** 110 ;* You may program other usefull macros such as: enQueK for dealing * 111 ;* ..with constants, enQueV to enQue variables, etc It's up to you * 112 ;******************************************************************** 113 ; 114 ; If there is NO space for storing some character, the macro signals 115 ; ..it by setting the C bit on CCR, so the invoking program may 116 ; ..know that the Queue is full, and take actions... 117 ; 131 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 118 ; NOTE: enQue is NOT a blocking action... 119 ; 120 ;****************** 121 ; enQue outQ; * enQ Accumulator into Queue named outQ 122 ;****************** 123 enQue: MACRO name1 ; enQ Accumulator INTO Queue: 'name1' 124 psha 125 lda (\1 + _QN) ; Verify that there is room (n < SIZE) 126 cmp (\1 + _QSIZE) 127 pula 128 blo \@ok 129 sec ; c = 1 signal error: Queue full 130 bra \@exitQ 131 \@ok: ldhx (\1 + _QPUT ) ; *put++ = accumulator 132 sta ,x ; .. 133 aix #1 ; .. 134 sthx (\1 + _QPUT ) ; .. 135 cphx (\1 + _QLIMIT) ; .. 136 blo \@incn ; put pointer inside que? ok 137 ldhx (\1 + _QBASE) ; else: rollover: reasign put to BASE 138 sthx (\1 + _QPUT ) ; .. 139 \@incn: 140 clc ; c = 0: enQue was succesfull 141 ldhx #(\1 + _QN) ; n++. This is the LAST thing to be 142 inc ,x ; <<< <<< ..done: 'n' IS the semaphore 143 \@exitQ: 144 ENDM 145 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 146 ; "deQue" retreives data from the designated Queue and stores it in 147 ; ..the ACCumulator. 148 ; 149 ;******************************************************************** 150 ;* You may program other macros, such as: deQueV for retreive * 151 ;* ..data and store it on variables, etc. It is up to you * 152 ;******************************************************************** 153 ; 154 ; If there is NO data for retrieving, the macro signals it by SETting 155 ; ..the C bit on CCR, so the invoking program may know that the Queue 156 ; ..is empty, and take actions... 157 ; 158 ; NOTE: deQue is NOT a blocking action... 159 ; 160 ;****************** 161 ; deQue inpQ; * deQue from Queue named inpQ into Accumulator 162 ;****************** 163 deQue: MACRO name1 ; deQ: Accumulator FROM Queue: 'name1' 164 lda (\1 + _QN) ; Verify that que is not empty (n > 0) 165 bne \@ok 166 sec ; c = 1 signal error: Queue full 167 bra \@exitQ 168 \@ok: ldhx (\1 + _QGET ) ; accumulator = *get++ 132 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 169 lda ,x ; .. 170 aix #1 ; .. 171 sthx (\1 + _QGET ) ; .. 172 cphx (\1 + _QLIMIT) ; .. 173 blo \@decn ; get pointer inside que? ok 174 ldhx (\1 + _QBASE) ; else: rollover: reasign get to BASE 175 sthx (\1 + _QGET ) ; .. 176 \@decn: 177 clc ; c = 0: enQue was succesfull 178 ldhx #(\1 + _QN) ; n‐‐. This is the LAST thing to be 179 dec ,x ; <<< <<< ..done: 'n' IS the semaphore 180 \@exitQ: 181 ENDM COMENTARIOS a ["Laboratorios\Lab3\SciComm\Que.inc"]: Se le ofrecen al usuario 4 funciones principales: Para cada Cola se precisa hacer una definición ANTES de invocar cualquier otra función relacionada: 008 ;defineQue: MACRO size1; ===>> Use 'defineQue' in **RAMstart ORG* Ejemplo: inpQ: defineQue 6 Se define una Cola de 6 elementos (en esta implementación todos los elementos son BYTES pero, desde luego, usted puede emplear otros valores, como enteros, Longs, y otros Tipos de Datos. Pero esto es preferible (como casi todo) programarlo en C. NOTA IMPORTANTE: 'defineQue' debe usarse habiendo previamente abierto un DATA SECTION: 'ORG ram', porque 'defineQue' reserva espacio en RAM para los apuntadores y demás variables relacionadas, así como también para la tabla circular propiamente dicha. Para cada cola se precisa, además, inicializar apropiadamente sus apuntadoras y variables relacionadas; esto se hace con: Ejemplo: initQue inpQ, 6 NOTA IMPORTANTE: 'InitQue' sólo debe emplearse estando en CODE SECTION, es decir, habiendo hecho previamente un 'ORG rom', ya que es la parte que genera código ajustado a cada cola en particular. 133 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 Finalmente están las funciones de uso rutinario, para almacenar y extraer datos de una cola, las Macros enQue y deQue: 010 ;enQue: MACRO name1 ; enQ Accumulator into Que: 'name1' 011 ;deQue: MACRO name1 ; deQ Accumulator from Que: 'name1' Cada Macro hace uso del dato que está previamente guardado en el Acumulador. A continuación, un ejemplo que cubre todo lo anterior: Para definir las Colas, hay que abrir un DATA SECTION (org RAM) y luego llamar la Macro defineQue, tantas veces como Colas quieran definirse: 015 ; Global Variables 016 ; ORG ram 017 ;inpQ: defineQue 6 ; Use macro 'defineQue' in 'ORG ram' 018 ;outQ: defineQue 5 Note que el NOMBRE de las colas, inpQ y outQ, son ETIQUETAS de la Macro defineQue. Cuando van a inicializarse las colas, hay que definir un CODE SECTION (org ROM): 019 ;... 021 ; ORG rom 022 ;... 023 ; initQue inpQ, 6 ; Use macro 'initQue' in 'ORG rom' 024 ; initQue outQ, 5 Note que el nombre de las Colas NO es un nuevo "Label", sino que las etiquetas que se emplearon en la Definición de las Colas, se usan ahora como PARÁMETROS de las macros de Inicialización: inpQ y outQ. El ejemplo encola un char, almacenado en el Acumulador, en la cola de entrada, inpQ; más adelante, otra sección del código (seguramente una rutina que transmite los datos que se han encolado), saca elementos almacenados, posiblemente los preprocesa y, finalmente, los encola en outQ para que, probablemente, la rutina de interrupciones que se encarga de transmitir información, la envíe al PC, por ejemplo. 025 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 026 ; enQue inpQ 027 ; bcs full_inpQ 028 ;... 029 ; deQue inpQ 030 ; bcs empty_inpQ 031 ; enQue outQ 032 ; bcs full_outQ 033 ;... "STRUCT" in ASSEMBLY LANGUAGE Primero hay que volver a mirar los elementos que componen este elemento de información, porque no todos sus componentes son exactamente como en el ejemplo en C. Los tamaños en nuestra implementación actual son: 134 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 041 ; _QN: BYTE: unsign number of chars stored any time in one Queue Fíjense que en esta implementación, una Cola puede identificar máximo 256 caracteres, pues 'N' mide solo un byte. 042 ; _QPUT: WORD: pointer used to store the next char, via enQue; 043 ; _QGET: WORD: pointer to deQue one char. 044 ; _QSIZE: BYTE: static space reserved for Queue; Aquí, de nuevo, el tamaño no corresponde al que puede definirse en un entero, sino en un byte. 045 ; _QBASE: WORD: address of the first char to be stored in the 046 ; ..reserved memory of each Queue; 047 ; _QLIMIT: WORD: last element's address in a Queue; used in wrap 048 ; ..around process, when a pointer crosses the border 049 ; _QBUF: BYTES: here begins RAM space for characters to be stored Con estas definiciones concretas, definimos ahora el valor de cada símbolo, utilizando la pseudo instrucción EQU: 050 _QN: EQU 0 ; BYTE: data bytes counter N _QN representa un Offset, o desplazamiento de 0, en nuestra 'STRUCT'. Como _QN mide un BYTE (es 'N', el contador de bytes que hay en un momento determinado en la Cola), y todo se mide en Bytes, el siguiente elemento está UN (1) BYTE después de _QN; por eso el EQU 1: 051 _QPUT: EQU 1 ; WORD: pointer for storing data into Queue _QPUT, como es un apuntador, tiene que medir 16 bits (DOS [2] bytes), pues en el HC9S08 todas las direcciones deben ser capaces de discriminar entre 65536 posiciones de memoria. Por eso, el siguiente elemento está separado de _QPUT por DOS (2) unidades. Como ya llevábamos UNO (1), el siguiente es 1+2=3: 052 _QGET: EQU 3 ; WORD: pointer for getting data from Queue Igual ocurre con el siguiente, que se separa por DOS (2) del anterior, y 3+2=5: 053 _QSIZE: EQU 5 ; BYTE: sizeof _QSIZE mide UN (1) byte, así que el próximo, _QBASE, se separa de éste por UNA (1) posición, lo que da: 5+1=6: 054 _QBASE: EQU 6 ; WORD: base address of Queue struc El próximo se separa por DOS (2), que es lo que mide _QBASE, dando 6+2=8: 055 _QLIMIT: EQU 8 ; WORD: last address of buffer 135 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 y, finalmente, el buffer COMIENZA separado por DOS (2), que es lo que mide _QLIMIT, dando 8+2=10: 056 _QBUF: EQU 10 ; BYTES: reserve 'size' data bytes OBSERVACIÓN NOTABLE: La posición en que coloqué el comienzo de la tabla, _QBUF, ES LA MEJOR POSICIÓN PARA ALOJAR LA TABLA en esta definición. ¿Qué pasaría si la hubiéramos incluido entre los símbolos _QGET y _QSIZE, o entre otros cualesquiera? ¡PIÉNSELO! "It is clever to define data bytes ram space, the last..." Hechas todas estas parametrizaciones se las emplea para finalmente DEFINIR LA COLA EN RAM, mediante la: MACRO DEFINEQUE: 068 defineQue: MACRO size1; ===>> Use 'defineQue' in **RAMstart ORG** 069 ; ===>>===>===>===>===>===>> ^^^^^^^^ <<<=== 070 DS _QPUT ‐ _QN ; n: data bytes counter N 071 DS _QGET ‐ _QPUT ; put: ptr for STORE data into Queue 072 DS _QSIZE ‐ _QGET ; get: ptr for GET data from Queue 073 DS _QBASE ‐ _QSIZE ; size: sizeof 074 DS _QLIMIT ‐ _QBASE ; base: base address of Queue struc 075 DS _QBUF ‐ _QLIMIT ; limit: last address of buffer 076 DS \1 ; buf: reserve 'size' data bytes Fíjense cómo cada variable usa el número de bytes apropiado, empleando DS (Data Storage) y la cantidad que resulta de calcular el número de bytes que resulta haciendo: siguiente símbolo menos actual, como en: 070 DS _QPUT ‐ _QN ; n: data bytes counter N Se habrían podido colocar estas cantidades a mano, pero me tomé la molestia de hacerlo mediante parámetros que me permiten, si quiero cambiar algo como el tamaño de la cola (pero NO queremos) para que fuera de hasta 64 Kbytes, con sólo modificar un símbolo (o dos...) el resto se reacomoda automáticamente. Es EVIDENTE que en una máquina tan pequeña, NO vamos a usar colas de tamaño mayor a 256, que además son suficientes en MUCHOS casos, incluso para máquinas más grandes. Así que este ejemplo sólo está codificado así, para que ustedes vean la manera profesional de hacerlo: PARAMETRIZADO. Así, espero que hayan aprendido LA MANERA de trabajar estructuras en Assembly Language. MACRO INITQUE: 092 initQue: MACRO name1, size2; ==>> Use 'initQue' in *ROMstart ORG* 094 clra ; mark Queue as empty (n = 0) 095 sta (\1 + _QN) ; 'sta', not 'clr OP' cause OP could 096 ; ..reside in 16 bit address space 136 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 Cuando se comienza a trabajar con una Cola, los apuntadores a la BASE y también los de PUT y GET deben señalar todos al PRINCIPIO DE LA TABLA Circular. Note que la expresión #(\1 + _QBUF) representa: una CONSTANTE (#), igual a la base de la Estructura de la Cola ("\1", el primer parámetro) MÁS el desplazamiento a donde se comienza la tabla: _QBUF) 097 ldhx #(\1 + _QBUF) 098 sthx (\1 + _QBASE) 099 sthx (\1 + _QPUT) 100 sthx (\1 + _QGET) La inicialización de los otros dos parámetros es trivial (¿o no? Si no está claro, escriba TODAS las definiciones, y verá lo que hace el AIX #\2; verifique lo que se cargó antes en el registro índice H:X): 101 lda #\2 102 sta (\1 + _QSIZE) ... ... ... 103 aix #\2 104 sthx (\1 + _QLIMIT) Finalmente, revisemos cómo se codificaron las Macros para el manejo normal de la Colas: enQue y deQue (encolar y decolar...). "enQue": Almacena el contenido del Acumulador en la Cola requerida: El problema cuando se va a encolar algo, es que NO haya espacio para recibir un nuevo valor. Si fuera así, la Macro lo SEÑALA colocando en UNO el bit de carry, C, en el CCR; así, el programa que invoca la Macro puede saber si la cola estaba llena, y tomar acciones. Nótese que la manera como codifiqué la Macro enQue NO ES DE MANERA BLOQUEADORA, lo cual resulta muy conveniente... Usted simplemente encola algo y luego pregunta si se hizo bien, mirando el bit de Carry, C. Ahí usted toma la decisión que corresponda. 123 enQue: MACRO name1 ; enQ Accumulator INTO Queue: 'name1' Guarda el valor del Acumulador, en el Stack: 124 psha Verifica que haya espacio (que n sea < SIZE): 125 lda (\1 + _QN) ; Verify that there is room (n < SIZE) 126 cmp (\1 + _QSIZE) 127 pula 128 blo \@ok Si SÍ hay espacio en la Cola, continua en la etiqueta marcada como: \@ok; de NO haber espacio, se activa la bandera C (Carry), y termina: 129 sec ; c = 1 signal error: Queue full 130 bra \@exitQ 137 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 Habiendo verificado que SÍ hay espacio, carga la dirección para ENCOLAR (según el pointer PUT), en el registro índice H:X, y usando el direccionamiento indexado almacena el nuevo dato, tomándolo del Acumulador: 131 \@ok: ldhx (\1 + _QPUT ) ; *put++ = accumulator 132 sta ,x ; .. Una vez guardado el dato, se incrementa el apuntador (aix #1), para que señale a la próxima posición que recibirá un dato, y se guarda ese nuevo valor, incrementado, en la posición de memoria que reservada para almacenar ese apuntador: 133 aix #1 ; .. 134 sthx (\1 + _QPUT ) ; .. Compara ese NUEVO valor, para ver si se apunta fuera del área asignada: 135 cphx (\1 + _QLIMIT) ; .. 136 blo \@incn ; put pointer inside que? OK Si el apuntador contiene una dirección que queda FUERA del área asignada, hay que reiniciarlo, para que apunte a la BASE (ROLLOVER; este mecanismo es el que la cola sea CIRCULAR): 137 ldhx (\1 + _QBASE) ; else: rollover: reasign put to BASE 138 sthx (\1 + _QPUT ) ; .. Si el apuntador señala correctamente dentro del área asignada, se borra la bandera C, Carry, para indicar así que TODO ESTÁ BIEN: 139 \@incn: 140 clc ; c = 0: enQue was succesfull Luego viene la parte quizás MÁS DIFÍCIL DE COMPRENDER, CONCEPTUALMENTE: Como "N" es el MUTEX (semáforo binario), === lo >>>ÚLTIMO<<< que hay que hacer al almacenar algo en la Cola es >>>INCREMENTAR "N"<<<; hacerlo ANTES puede deshacer la magia del semáforo, que probablemente ya no nos resguarde contra inconsistencias. Supongamos, por ejemplo, que la Cola está VACÍA y "Main" estuviera en el proceso del "enQue", tratando de ENCOLAR un símbolo. Si alguna rutina irrumpe tratando de DECOLAR algo para, por ejemplo, transmitirlo, y "N" le indica que SÍ hay información en la Cola (porque se incrementó "N", por ejemplo, al principio de la Macro, ANTES de que en realidad se hubiera almacenado nada), pues el "deQue" de la ISR LEERÍA esa información, ¡QUE NO EXISTE! 138 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 CAOS garantizado... Esto es sólo un DETALLE, pero es... ¡FUNDAMENTAL! No es SENCILLO de analizar, y es MUCHO MÁS DIFÍCIL DE... CODIFICAR. 141 ldhx #(\1 + _QN) ; n++. This is the LAST thing to be 142 inc ,x ; <<< <<< ..done: 'n' IS the semaphore La explicación sobre "deQue" resulta algo así como el DUAL de la que acabo de hacer sobre "enQue", y por eso no voy a presentar aquí más comentarios. Se sigue la misma norma con relación al bit C: Carry, que si está EN 0 representa que todo fue BIEN, y EN 1, indica que hubo un ERROR (fue a extraerse algo de la cola, y ésta estaba VACÍA...). El problema en esta parte es similar al anterior: Supongamos, por ejemplo, que la Cola está LLENA y "Main" estuviera en el proceso del "deQue", tratando de DECOLAR un símbolo. Si alguna rutina irrumpe tratando de ENCOLAR algo, por ejemplo lo que acaba de leer del receptor, y "N" le indica que SÍ hay espacio en la Cola (porque Main incrementó "N", por ejemplo, al principio de la Macro deQue, ANTES de en realidad haberlo leído (cambió el orden que en realidad hay que seguir), pues el "enQue" de la ISR PERMITIRÍA ALMACENAR SU información, EN UNA POSICIÓN QUE AÚN NO HA SIDO LEÍDA POR MAIN! CAOS garantizado de nuevo... Este es otro DETALLE pequeño pero igualmente... ¡FUNDAMENTAL! Complicado de analizar y... MUCHO MÁS DIFÍCIL DE CODIFICAR. Verifique con MUCHO detenimiento en el código, que tampoco aquí existe JAMÁS un comportamiento indebido, como que la rutina de interrupciones almacene algo sin que haya espacio dónde colocarlo... NOTA: La codificación del bit de Carry, C, se usa en el lenguaje "C" desde el comienzo de los tiempos, para retornar CERO si una función SÍ operó bien, y UNO si NÓ operó normalmente. Así se comportan, por ejemplo, las funciones de bajo nivel de la sección correspondiente a <io.h>. Esas cosas usted ha debido aprenderlas en sus cursos de Programación. Si no fue así, lo estafaron. Cuando termine de ponderar la nitidez del código, trate de hacer nuevamente un Examen de Conciencia, y diga si usted hubiera podido pensar en toda la complejidad subyacente en las interacciones que pueden ocurrir asincrónicamente, entre un Main tratando de Encolar, y una rutina de interrupciones tratando de Decolar... o viceversa, SIN QUE SE PUEDA JAMÁS EXTRAER DE LA COLA ALGO QUE NO HAY, O ENCOLAR ALGO EN UNA POSICIÓN DE LA QUE AÚN NO SE HA DESOCUPADO! 26) Programa Elemental para Probar la Librería de COLAS Programa muy sencillo, que encola en "inpQ" una secuencia de caracteres consecutivos, comenzando con la letra 'A', y finaliza cuando "inpQ" se llena. A continuación procede a extraerlos de "inpQ" y los va trasladando a "outQ", hasta que "inpQ" se vacíe o 139 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 "outQ" se llene, lo primero que ocurra. El comportamiento debe verificarse con el Debugger, siguiendo paso a paso el programa. ["Laboratorios\Lab3\SciComm\4quetst.asm"] 01 ;******************************************************************** 02 ; Programa 4QueTst.asm: Test Que Support (for SCI) 03 ; Luis G. Uribe C., M13M2007 C15A09 L08J09 S16J2012 (HCS08) 04 ; 05 ; See in this program: The use of Que.inc, defineQue, initQue, enQue 06 ; ..and deQue. Tray and code the program using RCV interrupts, using 07 ; ..a 'Queue' for temporally storing the incoming data. 08 ; You must be carefully and not exceed the free RAM on the chip... 09 ; 10 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 11 ; Include files that do not define ram 12 ; 13 ; CHECK‐LIST: Conserve this order, and the relative position of: 14 ; ..'derivative.inc' and 'Que.inc' 15 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 16 ; Include files that does not define ram 17 NOLIST 18 INCLUDE 'derivative.inc' 19 INCLUDE 'Que.inc' ; <<<=== Que.inc file does NOT use RAM 20 LIST ; ..storage, and THIS is it's place 21 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 22 ; 2) DEFINES 23 rom: SET ROMStart ; $2080 24 ram: SET Z_RAMStart ; $80 25 initStack: EQU RAMEnd + 1 ; $1800=$17FF+1. Stack begins on $17FF 26 COP_Disable: EQU $42 27 ;==================================================================== 28 ; 3) Global Variables 29 ORG ram 30 inpQ: defineQue 6 ; Use macro 'defineQue' in 'ORG ram' 31 outQ: defineQue 5 32 ;******************************************************************** 33 ; 4) MAIN PROGRAM HEADER: 34 ABSENTRY Main ; Export symbol (ABSOLUTE Assembly select) 35 ORG rom 36 Main: lda #COP_Disable ; RSTE=0: PTA5 is NOT for ~RESET 37 sta SOPT1 ; ..System Options 1 38 ldhx #initStack ; Set up SP 39 txs ; ... 40 ;==================================================================== 41 initQue inpQ, 6 ; Use macro 'InitQue' in 'ORG rom' 42 initQue outQ, 5 43 lda #'A' 44 cont: enQue inpQ 140 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 45 bcs full 46 inca 47 bra cont 48 full: 49 cont2: deQue inpQ 50 bcs empty 51 enQue outQ 52 bcs full2 53 bra cont2 54 empty: 55 full2: bra * 56 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 57 nop ; <<<NEEDED by CodeWarrior 10.1&2 (not 6.3). INCREDIBLE<<< 58 ; This 'nop' MAY be removed for CW 6.3 ... 59 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 60 ; Interrupt Vectors 61 ORG Vreset 62 DC.W Main ; RESET. Maximum priority. Asynch. 63 END COMENTARIOS a ["Laboratorios\Lab3\SciComm\4quetst.asm"] El que estudia este texto hará bien en la recomendación de codificar el programa usando interrupciones RCV y una 'Queue' para almacenar temporalmente la información que llega. Como siempre, vienen: 11 ; Include files that do not define ram Esto es IMPORTANTE: 13 ; CHECK‐LIST: Conserve this ORDER, and the relative position of: 14 ; ..'derivative.inc' and 'Que.inc' Este ejercicio sólo tiene las siguientes (Global) Variables en RAM (defineQue). Usted DEBE jugar y cambiar los tamaños de cada cola para ver cómo se modifica el comportamiento del programa. 29 ORG ram 30 inpQ: defineQue 6 ; Use macro 'defineQue' in 'ORG ram' 31 outQ: defineQue 5 Después de la DEFINICIÓN, al comienzo se hace la inicialización: 41 initQue inpQ, 6 ; Use macro 'InitQue' in 'ORG rom' 42 initQue outQ, 5 El programa enviará letras consecutivas, comenzando en la 'A', hasta que la cola "inpQ" se llene. Inicializa el acumulador: 141 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 43 lda #'A' El ciclo que encola hasta que inpQ se llene: 44 cont: enQue inpQ 45 bcs full Incrementar una letra produce la siguiente: La 'A' + 1, produce 'B': 46 inca 47 bra cont Cuando la cola "inpQ" se llene se extraen otra vez de "inpQ" y se los traslada a la cola "outQ", hasta que "inpQ" se vacíe o hasta que "outQ" se llene. Verificar el comportamiento del programa con el Debugger, siguiéndolo paso a paso. 48 full: 49 cont2: deQue inpQ 50 bcs empty 51 enQue outQ 52 bcs full2 53 bra cont2 Se finaliza con la consabida imitación del HALT: 55 full2: bra * La modificación que se sugiere hacer al programa consiste en colocar una Cola para recibir por interrupciones lo que llega del PC. La rutina de interrupciones (a la cual se llega luego de las habilitaciones respectivas, y porque un símbolo llegó por la línea de comunicaciones, y así generó una interrupción de RCV), toma la letra, la ENCOLA y se devuelve (RTI). El programa principal, Main, se la pasa leyendo de la cola mientras ésta se encuentra vacía; y, cuando llega a encontrarla NO vacía, DECOLA una letra y la devuelve al PC (INCREMENTADA como ya vimos), usando la Macro PUTCHAR; luego regresa al ciclo infinito. AGREGO: Cuando eso funcione, trate de colocar dos (2) colas, una para recibir lo que llega del PC, como acabamos de ver, y otra para almacenar lo que se va a transmitir; es decir, la parte de código que antes hacía PUTCHAR, ahora hace "enQ outQ" y, LUEGO, HABILITA LAS INTERRUPCIONES de salida. La rutina de interrupciones de salida, a la cual se llega cuando se pueda, si es que el periférico transmisor está disponible (previas TODAS las habilitaciones necesarias)... DESENCOLA ("deQ outQ") si ve que NO ESTABA VACÍA transmite vía PUTCHAR. Si la cola estaba vacía, la rutina de transmisión se AUTO‐DESHABILITA para interrupciones. ESTO ES ¡IMPORTANTE! Y en esas se la pasan. La idea es que usted aprenda a usar los mecanismos de recepción y transmisión, tanto de manera programada como por interrupciones, usando como vehículo para aprender, las comunicaciones seriales con el PC. Así, estará preparado para sus proyectos... hoy, y luego como graduado. 142 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 COMUNICACIONES SERIALES ESTE ES EL CULMEN DE LA PRESENTACIÓN; AQUÍ CONFLUYEN TODAS LAS TÉCNICAS APRENDIDAS; el periférico de comunicaciones no es TAN simple de estudiar pero, desde luego, es mucho más sencillo que los stacks TCP/IP y Bluethoot, o algunos otros periféricos de comunicaciones sincrónicas, o el I2C y el SPI (Serial Peripheral Interface, with full‐ duplex or single‐wire bidirectional; double‐buffered transmit and receive; master or slave mode; MSB‐first or LSB‐first shifting...) Este NO ES UN TRATADO DE COMUNICACIONES SERIALES. El lector está avisado de que debe revisar documentos publicados en relación al tema. Yo mismo incluí el PDF: "SerialComm16F84A(UribeChap14RS232)" en la Página de mi curso en Asignaturas de la USB. Y en el Internet hay multitud de documentos relacionados. Usted tiene que aprender por su cuenta, en otra parte, cuáles son los voltajes empleados en las transacciones RS‐232F; las velocidades; la distancia de los cables y el tipo, los conectores; el nombre y significado de las señales como DTR, DSR, RTS, CLS, Carrier Detect, TX, RX, Modem, Null Modem, dónde van los conectores hembra, y donde los conectores macho; Gender Exchanger; lo que es comunicación Full y Half Duplex; Simplex. Start bit, Stop bit, técnicas de lectura sobre la señal de entrada para garantizar 5% de holgura en el oscilador local de recepción, etc. Tiene que leer algún texto para esto, como ya dije. 27) LIBRERÍA de COMUNICACIONES SERIALES: SciComm.inc Repito mi objetivo primordial: Presentar la Metodología de Programación que define lo que va a codificarse, de forma "Top‐Down Design", y la materialización del software de manera "Buttom‐Up". Observen y evalúen primordialmente, en esta biblioteca que les obsequio, el uso de esas técnicas de diseño y programación. ["Laboratorios\Lab3\SciComm\SciComm.inc"] 001 ;******************************************************************** 002 ; SciComm.inc: SCI Comm. Support M28E2014 003 ; Luis G. Uribe C., D11M2007 L12M07 J16A09 L08J09 004 ; ..S16J2012 (HCS08) D24J2012 M26F2013 J12D2013 (putcX) C18D2013 005 ; J12D2013: Modified 'putcX' to enable any possible following 'BEQ' 006 ; M10D2013: Changed 'XmtRcvEnable' to 'XmtRcvActivate' 007 ; M26F2013: Fixed: Include Commas in List of ALL Macros (comments!) 008 ; J03E2013: List ALL Macros, for reference, in this header... AddresS 009 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 010 ; ** REFER to >>> >> 02MC9S08QE128RM(ReferenceManual)‐U.pdf << <<< ** 012 ;==================================================================== 013 ; THE FOLLOWING MACROS ARE DEFINED IN THIS LIBRARY 014 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 015 ;SCI9600N8: MACRO ; Program SCI @9600, 8, N, 1 016 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 017 ;getchar: MACRO ; MAY rearm Rcv interrupts, inside ISR 018 ; getcX: MACRO Ptr1 ;..MAY rearm interrupts, inside ISR. *Ptr1++ 019 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 143 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 020 ;putchar: MACRO ; MAY rearm Xmt interrupts, inside ISR 021 ; putc: MACRO #K or V ; MAY rearm Xmt interrupts, inside ISR 022 ; putcX: MACRO Ptr1 ;..MAY rearm interrupts, inside ISR; *Ptr1++ 023 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 024 ;RcvChar: MACRO 025 ;XmtChar: MACRO 026 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 027 ;XmtRcvActivate: MACRO 028 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 029 ; Ancillary Macros 030 ; 031 ;IfRcvRdy: MACRO brAddressIfReady1 032 ;IfXmtRdy: MACRO brAddressIfReady1 033 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 034 ;XmtIntEn: MACRO 035 ;XmtIntDsb: MACRO 036 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 037 ;RcvIntEn: MACRO 038 ;RcvIntDsb: MACRO 039 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 040 ;ComIntEn: MACRO ; Interrupt Enable for Communications 041 ; 042 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 043 ;*TODO*: Define IfNotRcvRdy, IfNotXmtRdy, IfNotRcvRdyS, IfNotXmtRdyS 044 ; ..(cfr. timers8) if you ever need them. 045 ; CREATE kbhit, Nkbhit & equivalents for output. getc var 046 ;==================================================================== 047 ; DEFINE REGISTERS: (in 'MC9S08QE128.inc'), AND REQUIRED BITS 048 ; ..(here), for SCI Serial Communications Interface 049 ;******************************************************************** 050 ; SCI1BD: EQU $0020 ; Baude Rate register 051 ; LOOPMODE!!! 052 Bauds9600Value: EQU $001A ; 16 bit: 4000000/(16*9600)=26.04=0x1A 053 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 054 ; SCI1D: EQU $27 055 ; >>> >>> ALIAS <<< <<< (Other names for same address. BE CAREFULL !) 056 XMTBUF: EQU SCI1D ; Transmiter Buffer 057 RCVBUF: EQU SCI1D ; Receiver Buffer 058 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 059 ; SCI1C2: EQU $23 060 ; >>> >>> ALIAS <<< <<< (Other names for same address. BE CAREFULL !) 061 XMTCTRLREG: EQU SCI1C2 062 RCVCTRLREG: EQU SCI1C2 063 XMTEN: EQU mSCI1C2_TE ; TE: Transmiter Enable %00001000 064 XMTIEN: EQU mSCI1C2_TIE ; TIE: Xmt INT Enable %10000000 065 XMTIEN.bit: EQU SCI1C2_TIE ; 7 144 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 066 RCVEN: EQU mSCI1C2_RE ; RE: Receiver Enable %00000100 067 RCVIEN: EQU mSCI1C2_RIE ; RIE: Rcv INT Enable %00100000 068 RCVIEN.bit: EQU SCI1C2_RIE ; 5 069 XmtRcvEnab: EQU (XMTEN | RCVEN) ;Enable BOTH devices %00001100 070 ;==================================================================== 071 ; SCI1S1: EQU $24 072 ; >>>>>>ALIAS<<<<<< (Other names for the SAME ADDRESS. BE CAREFULL!) 073 XMTSTATREG: EQU SCI1S1 074 RCVSTATREG: EQU SCI1S1 075 ; Relevant bits: 076 XMTRDY: EQU mSCI1S1_TDRE ; Transmiter Data Register Empty 077 XMTRDY.bit: EQU SCI1S1_TDRE ; 7 (mSCI1S1_TDRE: %10000000) 078 RCVRDY: EQU mSCI1S1_RDRF ; Receive Data Register Full 079 RCVRDY.bit: EQU SCI1S1_RDRF ; 5 (mSCI1S1_RDRF: %00100000) 080 XMTEMPTY: EQU mSCI1S1_TC ; Transmission Complete Flag 081 XMTEMPTY.bit: EQU SCI1S1_TC ; 6 (mSCI1S1_TC: %01000000): last! 082 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 083 ; Receiver: 084 OVERRUNERR: EQU mSCI1S1_OR ; OR: Overrun %00001000 085 NOISERR: EQU mSCI1S1_NF ; NF: Noise Flag %00000100 086 FRAMERR: EQU mSCI1S1_FE ; FE: Framing Error %00000010 087 PARERR: EQU mSCI1S1_PF ; PE: Parity Error %00000001 088 RCVErrs: EQU (OVERRUNERR | NOISERR | FRAMERR | PARERR) 089 ;******************************************************************** 090 ; MACRO DEFINITIONS 091 ; NOTE: Accumulator, H:X, CCR are NOT preserved through Comm. Macros 092 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 093 ; No need for previous enabled SCI peripherals 094 SCI9600N8: MACRO ; Program SCI @9600, 8, N 095 ldhx #Bauds9600Value ; program 9600bps value 096 sthx SCI1BD ; .. 097 ENDM 098 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 099 XmtRcvActivate: MACRO 100 mov #XmtRcvEnab, SCI1C2 ; ACTIVATE BOTH units: xmt & rcv 101 ENDM 102 ;==================================================================== 103 IfRcvRdy: MACRO brAddressIfReady1 104 brset RCVRDY.bit, RCVSTATREG, \1 105 ENDM 106 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 107 getchar: MACRO ; MAY rearm Rcv interrupts, inside ISR 108 brclr RCVRDY.bit, RCVSTATREG, * ; rearm.. 109 lda RCVBUF ; ..rearm 110 ENDM 145 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 111 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 112 getcX: MACRO Ptr1 ;..MAY rearm interrupts, inside ISR. *Ptr1++ 113 ldhx \1 114 brclr RCVRDY.bit, RCVSTATREG, * ; rearm.. 115 mov RCVBUF, x+ ; ..rearm 116 tpa ; save CCR to enable any following BEQ 117 sthx \1 118 tap ; ..restore CCR: enable following BEQ 119 ENDM 120 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 121 RcvChar: MACRO 122 lda RCVBUF 123 ENDM 124 ;==================================================================== 125 IfXmtRdy: MACRO brAddressIfReady1 126 brset XMTRDY.bit, XMTSTATREG, \1 127 ENDM 128 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 129 putchar: MACRO ; MAY rearm Xmt interrupts, inside ISR 130 brclr XMTRDY.bit, XMTSTATREG, * ; rearm.. 131 sta XMTBUF ; ..rearm 132 ENDM 133 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 134 putc: MACRO #K or V ; MAY rearm Xmt interrupts, inside ISR 135 lda \1 136 brclr XMTRDY.bit, XMTSTATREG, * ; rearm.. 137 sta XMTBUF ; ..rearm 138 ENDM 139 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 140 putcX: MACRO Ptr1 ;..MAY rearm interrupts, inside ISR; *Ptr1++ 141 ldhx \1 142 brclr XMTRDY.bit, XMTSTATREG, * ; rearm.. 143 mov x+, XMTBUF ; ..rearm 144 tpa ; save CCR to enable any following BEQ 145 sthx \1 146 tap ; ..restore CCR: enable following BEQ 147 ENDM 148 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 149 XmtChar: MACRO 150 sta XMTBUF 151 ENDM 152 ;==================================================================== 153 XmtIntEn: MACRO 154 bset XMTIEN.bit, XMTCTRLREG 155 ENDM 156 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 157 XmtIntDsb: MACRO 158 bclr XMTIEN.bit, XMTCTRLREG 159 ENDM 160 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 161 RcvIntEn: MACRO 162 bset RCVIEN.bit, RCVCTRLREG 146 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 163 ENDM 164 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 165 RcvIntDsb: MACRO 166 bclr RCVIEN.bit, RCVCTRLREG 167 ENDM 168 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 169 ComIntEn: MACRO ; Interrupt Enable for Communications 170 bset RCVIEN.bit, RCVCTRLREG ; Just only RCV... 171 ; Must Enable XMT *ONLY* when needed 172 lda RCVSTATREG ; Clear any possibly pending 173 lda RCVBUF ; ..RCV Ready Flag 174 ENDM COMENTARIOS a ["Laboratorios\Lab3\SciComm\SciComm.inc"]: Comencemos, pues, identificando las funcionalidades que requiere el usuario cuando necesita manipular un periférico de comunicaciones seriales, RS232. No olviden revisar el Manual de Referencia que publiqué: 02MC9S08QE128RM(ReferenceManual)‐U.pdf. Primero se presenta la funcionalidad que empleamos para definir los parámetros de comunicación. En principio, en un canal de comunicación serial, del tipo definido como RS‐232, hay variables que se definen FUERA del protocolo. Eso quiere decir que si usted tiene que comunicarse por ejemplo con CANTV, empleando este método, tiene que LLAMARLOS por teléfono, preguntarles qué valores tienen para: Número de bits de cada símbolo (8 es el más común, pero NO el único); la velocidad en bits por segundo (9600 bps, en nuestra librería, pero hay MUCHOS otros valores que se emplean, llegando hasta 115,200 bps, y más, sobre todo en comunicación LOCAL, la que no se transmite vía Modem o Radio... Luego, está un bit llamado "Parity", o paridad, que servía, al principio más que ahora, para ayudar a identificar errores de recepción. Hay uno más: el número de STOP bits, que hace mucho tiempo se usa como UN (1) Stop bit. Nuestra Macro para inicializar el canal de comunicaciones seriales es: 015 ;SCI9600N8: MACRO ; Program SCI @9600, 8, N, 1 Es decir: 9600 bps, NO parity, 8 bits per char, ONE (1) stop bit. Para leer y transmitir caracteres, el usuario AMARÍA poder tener funcionalidades equivalentes a las de "getchar" y "putchar" del C... Por eso aquí están, a continuación: 017 ;getchar: MACRO ; MAY rearm Rcv interrupts, inside ISR 018 ; getcX: MACRO Ptr1 ;..MAY rearm interrupts, inside ISR. *Ptr1++ "getchar" funciona igual que en C: toma un símbolo de entrada (En C vienen del teclado; aquí, del puerto serial) y nos lo retorna en el Acumulador. "getcX" lee datos y los almacena, no en el Acumulador, sino empleando el registro índice H:X; muy útil para almacenar en una tabla los caracteres recibidos. 020 ;putchar: MACRO ; MAY rearm Xmt interrupts, inside ISR 021 ; putc: MACRO #K or V ; MAY rearm Xmt interrupts, inside ISR 022 ; putcX: MACRO Ptr1 ;..MAY rearm interrupts, inside ISR; *Ptr1++ 147 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 "putchar", transmite lo que está en el Acumulador, empleando la línea de comunicaciones seriales. "putc" se usa cuando desean transmitirse CONSTANTES o VARIABLES; y "putcX" para enviar información que está almacenada en TABLAS, "apuntadas" empleando el registro índice H:X. Las próximas 2 Macros son de más bajo nivel: 024 ;RcvChar: MACRO 025 ;XmtChar: MACRO Dije con anterioridad que los periféricos del MC9S08QE128 tienen, en primer lugar, que ENERGIZARSE: muchos de esos periféricos NO están conectados permanentemente a la fuente de alimentación, para disminuir el consumo de energía. Los dos periféricos que tienen que ver con la comunicación serial, RS‐232 (SCI), que son el de Transmisión (XMT) y el de Recepción (RCV), se energiza cada uno por aparte. Yo activo los dos de una sola vez, porque en mis ejemplos estamos empleando AMBOS: XMT and RCV "Activate": 027 ;XmtRcvActivate: MACRO Las funcionalidades de más bajo nivel aún, son: 029 ; Ancillary Macros 031 ;IfRcvRdy: MACRO brAddressIfReady1 032 ;IfXmtRdy: MACRO brAddressIfReady1 que sirven para ver si el dispositivo de recepción (RCV) o el de transmisión (XMT) están disponibles (READY), a fin de saber si se los puede emplear. Para habilitar o deshabilitar cada dispositivo, independientemente, para que interrumpan: ‐ Para el Transmisor (XMT), activar y desactivar las interrupciones: 034 ;XmtIntEn: MACRO 035 ;XmtIntDsb: MACRO ‐ Para el Receptor (RCV), activar y desactivar las interrupciones: 037 ;RcvIntEn: MACRO 038 ;RcvIntDsb: MACRO Y, finalmente, una forma conveniente de activar AMBOS periféricos simultáneamente para trabajar con ellos: 040 ;ComIntEn: MACRO ; Interrupt Enable for Communications La definición de registros usted tiene que analizarla JUNTO a los manuales de referencia para el SCI, Serial Communications Interface: 050 ; SCI1BD: EQU $0020 ; Baude Rate register 052 Bauds9600Value: EQU $001A ; 16 bit: 4000000/(16*9600)=26.04=0x1A 148 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 NOTA: Para aumentar la compresión del código, he utilizado varios ALIAS, que son NOMBRES ALTERNATIVOS para unas mismas direcciones o variables. Hay que ser MUY CUIDADOSOS cuando se emplean ALIAS, para no caer en olvidos que nos lleven a creer que los Alias SON ¡DIFERENTES VARIABLES, O REGISTROS, O POSICIONES DE MEMORIA! (Recuerde: Son DISTINTOS nombres para LAS MISMAS COSAS ENTIDADES: registros, posiciones de memoria, ETC.) 056 XMTBUF: EQU SCI1D ; Transmiter Buffer 057 RCVBUF: EQU SCI1D ; Receiver Buffer INTERESANTE notar que AMBOS de registros, el buffer de transmisión y el de recepción, corresponden a la MISMA ENTIDAD FÍSICA: SCI1D. Esto es así porque el campo de direcciones es un recurso MUY LIMITADO; y para optimizar, se definen AMBOS registros con la misma dirección. ¿Cómo sabe el CPU cuál de los dos periféricos, el de entrada o el de salida, estamos direccionando? Pues, si estamos LEYENDO de esa única dirección, seguro estamos intentando RECIBIR desde el periférico RECEPTOR. Y si estamos ESCRIBIENDO sobre ese MISMO recurso, con seguridad estamos tratando de TRANSMITIR por el periférico TRANSMISOR. Con seguridad coincidirá conmigo en que es MUCHO más inteligible un símbolo como RCVBUF (Receiver Buffer, o registro de datos del receptor), que SCI1D. En el código hay otros ejemplos en los que la inteligibilidad de los símbolos por mí definidos resulta con mucho superior a la de las definiciones de Freescale! Registros de Control del Transmisor: 061 XMTCTRLREG: EQU SCI1C2 062 RCVCTRLREG: EQU SCI1C2 RECUERDE: hay que HABILITAR el transmisor (ENABLE: XMTEN), para trabajar con él; además, si debe interrumpir, hay que habilitarlo también para interrupción (XMTIEN): Bits de uso frecuente: 063 XMTEN: EQU mSCI1C2_TE ; TE: Transmiter Enable %00001000 064 XMTIEN: EQU mSCI1C2_TIE ; TIE: Xmt INT Enable %10000000 065 XMTIEN.bit: EQU SCI1C2_TIE ; 7 Vuelvo a recalcar: Una cosa es el XMTEN (Transmiter ENABLE), que ACTIVA al periférico transmisor (lo ENERGIZA), ..y otra muy distinta XMTIEN, que es el Interrupt ENable para el XMT (Transmiter). Y el XMTIEN que yo introduzco, es mucho mejor nombre que mSCI1C2_TIE. El número binario a la derecha de mis definiciones, identifica CUÁL bit es el referido: TIE: Xmt INT Enable %10000000 (bit 7) del registro de control asociado, XMTCTRLREG. Es importante saber cuáles son los bits, para poderlos ver con el Debugger. Este número (7) identifica en decimal, el correspondiente bit: 065 XMTIEN.bit: EQU SCI1C2_TIE ; 7 149 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 Definiciones equivalentes para el Receptor: 066 RCVEN: EQU mSCI1C2_RE ; RE: Receiver Enable %00000100 067 RCVIEN: EQU mSCI1C2_RIE ; RIE: Rcv INT Enable %00100000 El bit para habilitar las interrupciones de transmisión se define por aparte: 068 RCVIEN.bit: EQU SCI1C2_RIE ; 5 Finalmente, tenemos una forma de activar AMBOS dispositivos (para que reciban alimentación eléctrica); un símbolo que cubre los dos periféricos: 069 XmtRcvEnab: EQU (XMTEN | RCVEN) ;Enable BOTH devices %00001100 Antes se definieron los bits de los registros de Control; ahora les corresponde el turno a los Status Registers: 073 XMTSTATREG: EQU SCI1S1 074 RCVSTATREG: EQU SCI1S1 Los bits más usados en ellos, que el usuario nunca verá si está empleando mis Macros: 076 XMTRDY: EQU mSCI1S1_TDRE ; Transmiter Data Register Empty 077 XMTRDY.bit: EQU SCI1S1_TDRE ; 7 (mSCI1S1_TDRE: %10000000) 078 RCVRDY: EQU mSCI1S1_RDRF ; Receive Data Register Full 079 RCVRDY.bit: EQU SCI1S1_RDRF ; 5 (mSCI1S1_RDRF: %00100000) 080 XMTEMPTY: EQU mSCI1S1_TC ; Transmission Complete Flag 081 XMTEMPTY.bit: EQU SCI1S1_TC ; 6 (mSCI1S1_TC: %01000000): last! ¿Por qué para recepción hay un solo bit de READY, y para transmisión hay dos? Bueno, los dos periféricos son CASI DUALES el uno del otro, pero no exactamente. Al comienzo, cuando hablamos de Colas, indicamos una modificación para el dispositivo de entrada, que TAMBIÉN se le hizo al periférico DE SALIDA: Se le agregó un segundo Registro Adicional al Serializador de Salida (el serializador que es el que va transmitiendo uno a uno los bits de la letra) y cuando éste ha transmitido 8 bits, el controlador de la interfaz toma otro símbolo del Data Buffer y lo lleva al serializador, si es que hay algún dato ya en el Data Buffer. A esta combinación de Serializador alimentado por otro registro, que es aquel de donde en definitiva el CPU ESCRIBE los datos, se lo conoce, al igual que en el caso del receptor, como Double Buffer (porque hay 2 registros involucrados, o buffers). El protocolo es similar al del caso de entrada: cada vez que hay un Bit de Ready activo en la transmisión, el CPU puede mover el siguiente dato al Transmiter Buffer. En algún momento ese dato pasa al serializador, y como el Buffer de Transmisión ha quedado libre, se vuelve a activar el Bit de Ready de Transmisión, lo que permite al programa principal colocar un nuevo valor en el Transmiter Buffer, mientras se está transmitiendo el dato anterior. 150 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 Ahora, cuando ya no hay nada más que transmitir, la última vez que el subsistema de salida activa un Transmiter Ready, el programa principal podría CORTAR la comunicación en ese momento, pero PERDERÍA la transmisión del último byte porque, aunque el buffer de salida está disponible, el periférico de salida NO ha terminado de serializar el último dato que tiene que transmitir por el canal de comunicaciones. Esta situación se trató en los primeros UARTS, mediante un retardo que el programa principal activaba cuando transfería el último dato al Transmiter Buffer. Y NO cerraba el canal de comunicaciones (¿Modem? ¿Radio?) hasta que no hubiera transcurrido ese tiempo, calculado como mayor al necesario para enviar un byte. En los UARTs modernos se colocó un SEGUNDO bit de Ready para el transmisor; que sólo usamos antes de cerrar el canal de comunicaciones después de haber transmitido nuestro último byte. En nuestros ejercicios, éste no es el caso, pero en la vida real, sí. Un último grupo de bits dentro del SCI, que también pueden INTERRUMPIR y tienen su propio vector de interrupciones, es el de los ERRORES. Note que NO hay errores de TRANSMISIÓN, porque no tenemos control ni conocimiento de lo que pasa, una vez que hemos enviado un símbolo al canal de comunicaciones. Sólo hay errores de RECEPCIÓN: 084 OVERRUNERR: EQU mSCI1S1_OR ; OR: Overrun %00001000 El OVERRUNERR lo detecta la interfaz de entrada, cuando va a almacenar en el Data Buffer un valor que acaba de serializar, y ese registro de entrada NO HA SIDO LEÍDO por el procesador. La unidad controladora de entrada lo sabe, porque el protocolo de recepción ordena que cuando el CPU lee un dato, debe borrar la bandera de Ready de entrada. Si esta bandera está en uno, para el momento en que se debe colocar un nuevo dato en el registro de entrada, se sabe que el nuevo dato BORRARÁ el valor viejo, y como el CPU no ha indicado que lo leyó, se ESTÁN PERDIENDO DATOS en la recepción. El canal de comunicaciones está alimentando información a una velocidad superior a la que el CPU la está procesando... 085 NOISERR: EQU mSCI1S1_NF ; NF: Noise Flag %00000100 El Error de Ruido lo infiere el receptor si, cuando está realizando el proceso de muestreo de los bits de entrada, encuentra que hay cambios en la información, más allá de los previsibles en los bordes de la señal de muestreo. El manual de referencia tiene la descripción exacta. 086 FRAMERR: EQU mSCI1S1_FE ; FE: Framing Error %00000010 Un Framing Error se produce cuando el receptor comienza a muestrear nuevos bits, porque ha detectado un bit de ARRANQUE (START) y, al llegar al fin NO ENCUENTRA UN STOP BIT. Esto puede deberse a excesivo ruido en la línea, que hizo creer a la interfaz que había un START bit, o a una pérdida de sincronismo por alguna otra causa, y que en todo caso produce que la información de entrada no tenga el Marco o FRAME correcto. Note que si, habiéndose salido de sincronismo, por mala suerte el último bit, que se lee, erróneo, es un UNO, la interfaz NO puede detectar ningún Framing Error... 087 PARERR: EQU mSCI1S1_PF ; PE: Parity Error %00000001 El bit de paridad (PAR o IMPAR) se inventó al comienzo cuando los dispositivos de comunicaciones seriales eran electromecánicos (TÉLEX), y había MUCHOS errores en la 151 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 transmisión. La idea era añadir un bit a la información, codificándola de tal manera que hubiera una mitad de símbolos POSIBLES, y una mitad de valores IMPOSIBLES. Así, si al receptor le llega un símbolo del grupo equivocado, sabe que ha habido UN error de comunicaciones. Si el dato es del grupo válido, se supone que ha llegado bien (aunque si hay DOS errores, o un número PAR de ellos, el receptor no es capaz de identificarlo). Una manera fácil de añadir el bit extra, consiste en hacerlo de tal manera que TODOS los bits de la variable que se está transmitiendo, sean PARES (EVEN PARITY) o IMPARES (ODD PARITY). Un dispositivo muy sencillo suma el número de bits de la información (sumador; cadena de EXORs), y si el número unos ya coincide con la paridad, no agrega nada, pero si el número de unos no coincide, se agrega un uno, a fin de obligar al símbolo para que siempre su cantidad coincida con la paridad, número par o impar de unos. Finalmente, se hace una combinación para identificar CUALQUIER condición detectable de error: 088 RCVErrs: EQU (OVERRUNERR | NOISERR | FRAMERR | PARERR) Unidades de comunicación serial más avanzadas, como las que se emplean en el PC IBM/AT compatible, tienen un CUARTO elemento que puede también generar interrupciones, en un vector independiente. Se trata del manejador de Línea (LINE CONTROLER) encargado de ver si el Modem se activó o se desactivó, si se perdió la portadora o regresó, si hay un inconveniente con el Modem (DSR inválido), o si en algún momento no puede aceptar un símbolo nuevo (CTS inválido). Escribir un paquete completo de comunicaciones, que considere los cuatro elementos disponibles en las interfaces industriales de comunicaciones, no resulta nada sencillo. Pero se compran por menos de $60, con garantía, soporte, y documentación impecable para su uso. El resto de la biblioteca casi no necesita mayor comentario, si se analizan con detenimiento las instrucciones de cada Macro. Bueno; la primera Macro le coloca al dispositivo SCI (tanto al transmisor como al receptor) los valores apropiados de BPS (bits por segundo: 9600 para nuestros ejemplos [¡SIEMPRE FUNCIONAN!]), 8 bits para cada elemento transferido, NO parity y un (1) Stop bit. El registro en el que se programan esos parámetros es el SCI1BD: Baud Rate. Véase el Reference Manual. 094 SCI9600N8: MACRO ; Program SCI @9600, 8, N 095 ldhx #Bauds9600Value ; program 9600bps value 096 sthx SCI1BD ; .. La ACTIVACIÓN de ambos dispositivos, transmisor y receptor (energizarlos): 099 XmtRcvActivate: MACRO 100 mov #XmtRcvEnab, SCI1C2 ; ACTIVATE BOTH units: xmt & rcv Macro auxiliar para determinar si el receptor está listo: 103 IfRcvRdy: MACRO brAddressIfReady1 104 brset RCVRDY.bit, RCVSTATREG, \1 152 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 Si llega a necesitar la macro opuesta (IfRcvNotRdy), es así: If_NOT_RcvRdy: MACRO brAddressIfNotReady1 brclr RCVRDY.bit, RCVSTATREG, \1 >>>LA<<< Macro principal, que lee como si se estuviera haciendo en C: 107 getchar: MACRO ; MAY rearm Rcv interrupts, inside ISR 108 brclr RCVRDY.bit, RCVSTATREG, * ; rearm.. 109 lda RCVBUF ; ..rearm ¿Sencillo, no? Observe que esta Macro es BLOQUEANTE, igual que en el C... Además, si está leyendo Strings, que son elementos de información terminados en un byte ZERO, a continuación de un "getchar" usted puede simplemente hacer un BEQ, o BNE, y estas instrucciones hacen referencia al ÚLTIMO byte transferido por getchar (LDA hace una comparación tácita del valor que transfiere, contra Cero). Ojo que ambas Macros de RECEPCIÓN, Y LAS SIMILARES PARA TRANSMISIÓN (putchar, putc, putcX), HAN SIDO PROGRAMADAS de tal manera que ‐‐además‐‐, "REARMEN" las interrupciones de cada periférico y, así, ¡PUEDEN USARSE TAMBIÉN dentro de las rutinas de interrupción! Eso me quedó MUUUY BIEN. Eso es así porque el protocolo de Acknowledge de Interrupción, que le dice al periférico de Recepción, o al de Transmisión, que ya fueron atendidos, es la misma cosa que llevarles la bandera de Ready a Cero a dichos dispositivos, y consiste en lo siguiente (tomado directamente del Manual de Referencia): "To clear RCVRDY, read RCVSTATREG with RCVRDY = 1 and THEN read the SCI data register (RCVBUF)". Esto es exactamente lo que hace getchar: 107 getchar: MACRO ; MAY rearm Rcv interrupts, inside ISR 108 brclr RCVRDY.bit, RCVSTATREG, *; Espera a que RCVRDY = 1 109 lda RCVBUF ; Luego LEE del RCVBUF Una observación para el lector avispado: En las rutinas de interrupción puede suponerse que NO HABRÍA necesidad de incluir: 108 brclr RCVRDY.bit, RCVSTATREG, *; Espera a que RCVRDY = 1 pues únicamente se llega a la rutina de interrupción porque el periférico ESTÁ READY! Parecería superfluo. Pero, esos dos pasos son los que se requieren, según el texto en inglés que acabo de copiar directamente del Reference Manual. NOTA: El manual es INEXACTO e IMPRECISO, porque cuando dice: "read RCVSTATREG with RCVRDY = 1 and THEN read RCVBUF", NO DICE SI ENTRE ESAS DOS INSTRUCCIONES PUEDEN, O NO, HABER OTRAS INSTRUCCIONES (que sería el caso en que una interrupción se colara en medio de ellas, y la cosa quedaría como: a) "read RCVSTATREG with RCVRDY=1", b) a continuación un número indeterminado de instrucciones (las de la rutina de interrupción) y finalmente, c) el "read RCVBUF". 153 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 La única manera de esclarecer esto es mediante investigación, incluyendo instrucciones a mano y viendo que, en realidad, sí funciona con instrucciones extras incluidas entre las otras dos.. Pero es poco profesional que para saber lo que hace un dispositivo, no sea suficiente con leer la documentación. "To clear XMTRDY, read XMTSTATREG with XMTRDY = 1 and then write to the SCI data register (XMTBUF)". Esto es exactamente lo que hace putchar: 129 putchar: MACRO ; MAY rearm Xmt interrupts, inside ISR 130 brclr XMTRDY.bit, XMTSTATREG, *; Espera a que XMTRDY = 1 131 sta XMTBUF ; Luego ESCRIBE en el XMTBUF NOTA: En dispositivos más avanzados, como la familia Intel que se usa en los PC IBM/AT compatibles, el protocolo de Acnowledge de entrada y salida lo realiza el hardware: la misma instrucción que LEE el dato del buffer de entrada, BORRA LA BANDERA de Ready; la misma instrucción que escribe sobre el periférico de salida BORRA LA BANDERA de Ready. No era algo tan complicado, como para obligar al programador a hacer esta operación por software. Mais c'est la vie... Note que al igual de su equivalente en C, getchar es BLOQUEANTE: una vez invocada, esperará a que llegue una letra, ANTES de continuar adelante con el programa. Si en algún momento decide que usted no debe bloquearse (un programa CASI NUNCA debe bloquearse, porque se pierde casi todo el control sobre el sistema), usted puede preguntar si hay letras en el receptor ANTES de llamar a getchar para leerlas. Este es el equivalente a usar kbhit() en C pero, muy probablemente ustedes jamás oyeron hablar de kbhit() antes. NotKbhit (más útil aquí que kbhit) puede codificarse así (es igual a If_NOT_RcvRdy): NotKbhit EQU If_NOT_RcvRdy El código quedaría así: NotKbhit cont ; Si no hay datos, no los lee getchar ; ..getchar no se bloquea, porque sí hay datos cont: Una última funcionalidad muy útil en el receptor consiste en no leer los datos sobre el acumulador, sino sobre una tabla, que se supone que vamos a llenar con los diferentes símbolos que lleguen. También es BLOQUEANTE: 112 getcX: MACRO Ptr1 ;..MAY rearm interrupts, inside ISR. *Ptr1++ Se carga el registro índice con el Apuntador hacia el próximo elemento disponible en la tabla: 113 ldhx \1 154 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 Se espera a que llegue una letra, al igual que en getchar: 114 brclr RCVRDY.bit, RCVSTATREG, * ; rearm.. Al llegar un nuevo dato se lo lleva a la tabla con el MOV, SE INCREMENTA el apuntador en un solo golpe, gracias al lujo de instrucciones y modos de direccionamiento que tiene este excepcional microcontrolador: 115 mov RCVBUF, x+ ; ..rearm Ahora, una funcionalidad prometida en estas macros es que, una vez transferido el elemento de información principal (el DATO que llegó), el programador pueda hacer un salto condicional, aprovechando el valor de la bandera Z, activado por el conjunto de instrucciones ENRIQUECIDO del MCU. Pero, a pesar de que YA transferimos el dato a su sitio, y que el MOV ya colocó el bit Z en su estado apropiado, todavía no hemos llevado el nuevo valor del pointer incrementado a su sitio. Al hacer la transferencia del apuntador, se van a alterar las banderas del CCR, en particular la Z. Así que hay que preservar el valor actual del CCR (tpa) ANTES de guardar el apuntador (sthx), y recobrarlo después (tap): 116 tpa ; save CCR to enable any following BEQ 117 sthx \1 118 tap ; ..restore CCR: enable following BEQ Observe que "tpa" y "tap" fueron pensadas de tal manera que NO modifican las banderas del CCR! NOTE: INCREÍBLEMENTE, este es el comentario que figura en el Manual de Referencia, en relación a la instrucción TAP: * NOTE: The TAP instruction was added to improve testability of * the CPU08, and so few practical applications of the * instruction exist. The author of this infamous paragraph HAS NO IDEA OF WHAT HE IS TALKING ABOUT. This instruction was NOT added to improve testability; it is one of the most important Op‐ Codes in the entire HC08 instruction set, and one that deserves a full example on using it! For example, to write some generic routine, one that could be used inside an ISR, if you need to disable interrupts, later you can not simply enable them: You must restore interrupts to the state they where at the beginning, something like this: ... ; TPA ; Transfer CCR to Acc (TPA is TAP’s twin opcode) PSHA ; ..Save Acc (CCR) into stack ;...Later you may recover the interrupt state, as follows: PULA ; Pop (er... Pull) Acc TAP ; ..Transfer Acc to CCR ; ..(saved flags, including “I” Flag) ;... 155 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 By the way, TAP stands for: “Transfer Accumulator to PROCESSOR STATUS WORD”. You know, they begun calling the flags: PSW and, then, change their mind and renamed the PSW like Condition Code Register (CCR) but, the instruction codes remain using the old nomenclature (TAP, TPA). Usted tiene que pensar en todo; la programación es una actividad MUY MINUCIOSA. Por eso, hay que dividir en problema en zonas muy muy pequeñas, que nos permitan VER todas las interacciones. Si usted hace un código de mil líneas, y luego comienza a ver por qué no funciona, está perdido. Las Macros para el transmisor son las DUALES del receptor; por eso los comentarios serán mínimos: 129 putchar: MACRO ; MAY rearm Xmt interrupts, inside ISR 130 brclr XMTRDY.bit, XMTSTATREG, * ; rearm.. 131 sta XMTBUF ; ..rearm "putchar" es bloqueante. putc no tiene equivalente en el dispositivo receptor: Se usa putc cuando se van a transmitir CONSTANTES O VARIABLES; nos permite no tener que cargar explícitamente esas constantes o variables en el acumulador: 134 putc: MACRO #K or V ; MAY rearm Xmt interrupts, inside ISR 135 lda \1 136 brclr XMTRDY.bit, XMTSTATREG, * ; rearm.. 137 sta XMTBUF ; ..rearm putcX es muy similar a getcX. Para poder hacer uso de las instrucciones enriquecidas, tal como en getcX, antes de almacenar el apuntador incrementado, hay que guardar el valor de Z (CCR), cargado por el dato que se ha transmitido, y recuperarlo después de mover el apuntador: 140 putcX: MACRO Ptr1 ;..MAY rearm interrupts, inside ISR; *Ptr1++ 141 ldhx \1 142 brclr XMTRDY.bit, XMTSTATREG, * ; rearm.. 143 mov x+, XMTBUF ; ..rearm 144 tpa ; save CCR to enable any following BEQ 145 sthx \1 146 tap ; ..restore CCR: enable following BEQ Las macros XmtIntEn, XmtIntDsb, RcvIntEn, RcvIntDsb son simplemente encender o apagar el bit correspondiente. ComIntEn hace "Interrupt Enable for Communications". Es decir, una vez colocados los valores: SCI9600N8, y energizados los dos periféricos: XmtRcvActivate, se desea Activar el Subsistema Completo para trabajar por interrupciones: 169 ComIntEn: MACRO ; Interrupt Enable for Communications 170 bset RCVIEN.bit, RCVCTRLREG ; Just only RCV... 171 ; Must Enable XMT *ONLY* when needed 172 lda RCVSTATREG ; Clear any possibly pending 173 lda RCVBUF ; ..RCV Ready Flag 156 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 Nótese que SÓLO SE ACTIVA LA RECEPCIÓN para que interrumpa. Esto es así porque el comportamiento de los periféricos de entrada es tal que NUNCA ellos están READY (nunca interrumpen) hasta que llega una letra o algún otro elemento por la correspondiente entrada. Los periféricos de salida (impresoras, transmisor RSR‐232, y cualquier otro), cuando se los Activa es más que seguro que no están haciendo nada (nadie los ha mandado A transmitir nada, o no les han dado nada para imprimir). Así que, para comenzar, ESTÁN READY. Si se los activa para que interrumpan, como están Ready, van a interrumpir INMEDIATAMENTE. Por eso hay que posponer la habilitación para interrupciones de los periféricos de salida, hasta cuando haya salidas disponibles, como una tabla para transmitir por interrupciones, o una cola, o incluso una letra, si es que no se la desea enviar de manera programada, sino por interrupciones. NÓTESE que, además de la activación del bit de RCVIEN.bit, leyendo el RCVSTATREG y el RCVBUF: 172 lda RCVSTATREG ; Clear any possibly pending 173 lda RCVBUF ; ..RCV Ready Flag ..se BORRA cualquier posible "RCV Ready Flag" pendiente, si es que hubiera llegado algún símbolo ANTES de que activáramos el ComIntEn. (Y, esto... puede NO ser suficiente para activar confiablemente un dispositivo de Recepción, cuando los equipos externos NOS ESTÁN ENVIANDO INFORMACIÓN, de manera Asincrónica... Sobre los periféricos externos ¡solemos NO tener control!). Piense bien sobre las implicaciones que existen si queremos habilitar nuestra recepción, y no lo podemos hacer sin estar seguros de que ¡NO NOS HAN COMENZADO A TRANSMITIR TODAVÍA! El comportamiento de los periféricos de salida es así: la rutina, probablemente Main, llena una tabla o usa una, pregrabada en ROM (Flash), y habilita las interrupciones de salida. La ISR de transmisión: XMTISR, se activa cuando está disponible, si se encuentra habilitada para interrumpir. Así, saca un elemento de la tabla, lo transmite y retorna con RTI (Return from Interrupt). Cuando vuelva a quedar en estado de Ready, vuelve a interrumpir. Dos mecanismos suelen emplearse para identificar que ya no hay nada que procesar: o se acaba de enviar EL señalizador de final, que suele ser un byte NULL, lleno de ceros (éste es el mismo método que usa el C para terminar sus Strings), o llega a cero un contador que identifica cuántos caracteres faltan para terminar, y que se lo ha ido decrementando cada vez que se transmite una letra. Cualquiera que sea el método, cuando XMTISR determina que ya no hay nada más que enviar, SE AUTO DESHABILITA PARA INTERRUMPIR, y retorna con RTI. 157 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 NOTA: Si ha "encolado" una tabla para su transmisión, y quiere enviar otra, o reutilizar la misma área para enviar otros datos, tiene que estar seguro de MANTENER LA COHERENCIA. Mucho cuidado con comenzar una acción antes de terminar la otra. Puede hacerse, pero garantizando que jamás se trancará el juego. Por ejemplo, supongamos que se está empleando una COLA: Si Main hace "enQue" de algo y habilita las interrupciones, y la XMTISR hace "deQue" y lo transmite, y si en un momento Main está "enQueing" algo y quiere volver a habilitar las interrupciones, tiene que asegurarse que no ocurra una situación de DEAD‐LOCK, en la que Main acaba de habilitar las interrupciones y XMTISR las desactiva y se llegue a un estado en que a Main le aparece la cola llena, y no puede hacer un ulterior envío, y XMTISR NO interrumpe, porque está desactivada, y por tanto no saca caracteres de la cola. Eso es: SU PROGRAMA ESTÁ MUERTO: DEAD‐LOCK. Nada hace ningún proceso; cada entidad espera por la otra, y ninguna de las dos trabaja... Yo les dije que las interrupciones son una gran adición al modelo del computador, sin las cuales serían impensables las computadoras modernas, y que a pesar de ello, son el PEOR DOLOR DE CABEZA de un programador de sistemas Embebidos... El análisis se complica muchísimo, porque se tiende a creer que 2 instrucciones que se escribieron una a continuación de la otra, se ejecutarán una inmediatamente después de la otra. Cuando hay interrupciones activas, esto no necesariamente es así. Por eso, cuando esté haciendo un análisis de coherencia, que le permita garantizar que su programa jamás quedará en DEAD‐LOCK, para cada instrucción tiene que suponer que todas las ISR lo interrumpen, hacen su trabajo, y que todo sigue bien cuando su programa continúe a ejecutar la siguiente instrucción de su la listado. Es muy laborioso. NOTA FINAL: Usted debe haber observado que se ha empleado la instrucción MOV en varias partes; incluso aprovechando su modo auto incrementado para el registro Índice H:X. Pero la instrucción MOV requiere que al menos una de sus direcciones se encuentren en la página 0 (ZPage), es decir, dentro de las 256 primeras posiciones. Por tanto, estas rutinas PRECISAN la definición de sus tablas EN ZRam. Si ese no va a ser su caso, tiene que reescribirlas para usar parejas de LDA y STA que reemplacen los MOV. Espero que les haya gustado mi implementación de la librería de comunicaciones seriales, RS‐232, empleando el dispositivo SCI del MC9S08QU128. Llevo al menos 30 años implementando las mismas rutinas para el PC, para mis propios equipos de Supervisión y Control, a nivel de Maestras, y de RTUs (Remote Terminal Units), trabajando en sistemas tan diversos como QNX y DOS. No es que me senté esta semana y se las codifiqué.... 28) Transmisión. Se envían permanentemente al PC las letras de la 'A' mayúscula a la 'z' minúscula. En el PC se ejecuta un programa de comunicaciones tal como "Hyperterminal" (antigua utilidad nativa en Windows, y ya periclitada...), el "TeraTerm" (que es el que yo siempre uso), el "Putty" ("poty", como se pronuncia en Internet), el "RealTerm", que es el programa instalado en los PCs del Laboratorio C, y cualquier otro que ustedes conozcan para realizar esta actividad de interactuar en Windows con un dispositivo vía canal de comunicaciones RS‐232, en lo que se conoce una CONSOLA REMOTA. 158 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 ["Laboratorios\Lab3\SciComm\1tstXmt.asm"] 01 ;******************************************************************** 02 ; Programa 1TstXmt.asm: Test SCI Comm. Support 03 ; Luis G. Uribe C., D11M2007 J16A09 L08J09 S16J2012 (HCS08) M10D2013 04 ; 05 ; ..Send 'A' to 'z' letters for ever, to Hyperterminal 06 ; 07 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 08 ; Include files that no define ram 09 NOLIST 10 INCLUDE 'derivative.inc' 11 INCLUDE 'SciComm.inc' ; SciComm INC file does NOT use RAM 12 LIST ; ..storage and THIS is it's place 13 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 14 ; 2) DEFINES 15 ram: SET Z_RAMStart ; $80 16 rom: SET ROMStart ; $2080 17 initStack: EQU RAMEnd + 1 ; $1800=$17FF+1. Stack begins on $17FF 18 COP_Disable: EQU $42 19 ;==================================================================== 20 ; MAIN PROGRAM HEADER: 21 ABSENTRY Main 22 ORG rom 23 Main: lda #COP_Disable ; RSTE=0: PTA5 is NOT for ~RESET 24 sta SOPT1 ; ..System Options 1 25 ldhx #initStack ; Set up SP 26 txs ; ... 27 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 28 SCI9600N8 ; Setup Serial Communications Inter‐ 29 XmtRcvActivate ; ..face: 9600 bps, No parity, 8 bits 30 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 31 Forever: 32 lda #'A' ; Ascii 'A' 33 XmitLoop: 34 putchar 35 inca ; 'A' + 1 is 'B'; 'Z' + 1 is 'a' 36 cmp #'z' 37 bls XmitLoop 38 putc #$0D ; Carriage Return (CR: '\r' in C) 39 putc #$0A ; Line Feed (LF: '\n') 40 bra Forever ; To come to this end you'l need 41 ; ..217,191 cycles @4,194,304Hz 42 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 43 nop ; <<<NEEDED by CodeWarrior 10.1&2 (not 6.3). INCREDIBLE<<< 44 ; This 'nop' MAY be removed for CW 6.3 ... 45 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 46 ; Interrupt Vectors 159 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 47 ORG Vreset 48 DC.W Main ; RESET. Maximum priority. Asynch. 49 END COMENTARIOS a ["Laboratorios\Lab3\SciComm\1tstXmt.asm"] Así, nuestro primer programa "1TstXmt.asm" incluye la librería 'SciComm.inc' en un área que NO emplea RAM, a continuación de 'derivative.inc': 10 INCLUDE 'derivative.inc' 11 INCLUDE 'SciComm.inc' ; SciComm INC file does NOT use RAM No olvide que ésta es la posición donde DEBE incluir mi librería de comunicaciones. Luego, se definen los parámetros de comunicación (SCI9600N8) y se activan AMBOS periféricos: XmtRcvActivate (Activate Xmt & Rcv): 28 SCI9600N8 ; Setup Serial Communications Inter‐ 29 XmtRcvActivate ; ..face: 9600 bps, No parity, 8 bits El programa en sí es muy sencillo; consiste de un ciclo indefinido que transmite todas las letras entre la 'A' mayúscula y la 'z' minúscula: 31 Forever: 32 lda #'A' ; Ascii 'A' 33 XmitLoop: 34 putchar 35 inca ; 'A' + 1 is 'B'; 'Z' + 1 is 'a' 36 cmp #'z' 37 bls XmitLoop Cada vez que se termina una serie, se imprime el símbolo que representa, para la pantalla, el devolverse hacia el lado izquierdo de la misma: Carriage Return ('\r' in C), y luego el que obliga al cursor de la pantalla a bajar una línea: Line Feed (LF: '\n'): 38 putc #$0D ; Carriage Return (CR: '\r' in C) 39 putc #$0A ; Line Feed (LF: '\n') 29) ECHO. Este programa implementa una funcionalidad muy empleada entre los profesionales que tienen que interactuar con comunicaciones seriales, del tipo RS‐232. Consiste en hacer un ECO (ECHO) a los caracteres que le llegan a nuestro equipo, probablemente desde una estación Maestra, remota. Ustedes simularán la estación remota con el PC; su dispositivo local será nuestra tarjeta DEMOQE128. Ahora, resulta que en ocasiones, los programas de comunicación que se ejecutan en la estación maestra (o en el PC), tienen una función que a veces el operador olvida, y que se llama LOCAL ECHO. Esto significa que todo lo que se envía, por ejemplo desde el teclado del PC, se ve en la pantalla del mismo. Es claro que, si 160 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 está activa esa característica, es difícil saber si nos estamos comunicando con el periférico remoto, o no. Por eso el siguiente programa contesta con la letra que recibe, INCREMENTADA en uno. Se sabe que, en la codificación de los caracteres (clásicamente, ASCII: American Standard Code for Information Interchange), una letra del alfabeto es igual a la anterior, MAS UNO. Es decir, que la letra 'B' es igual a la letra 'A' MÁS 1. Así que nuestro programa de ECHO, toma cada símbolo que recibe, y devuelve el siguiente: Símbolo Recibido MÁS UNO. Ahora sí es fácil en el PC verificar que la comunicación se ha establecido. Si escribo un 'A', recibo una 'B', y así para todas las letras. Si usted escribe HAL, como se llamaba la supercomputadora de la película "2001 Odisea del Espacio", aparece en la pantalla... IBM. ¿Qué tal? ["Laboratorios\Lab3\SciComm\2echo1.asm"] 01 ;******************************************************************** 02 ; Programa 2Echo1.asm: Test SCI Comm. Support 03 ; Luis G. Uribe C., D11M2007 J16A09 L08J09 S16J2012 (HCS08) 04 ; M10D2013 cosmetics (XmtRcvActivate) 05 ; To be sure that there is "ECHO", return back 'letter+1', 06 ; ..i.e: if Hyperterminal sends 'A', HC908 will return 'B'... 07 ; ..for the range 'A' <= c < 'z'. 08 ; Any other chars are returned back without modification. 09 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 10 ; Include files that no define ram 11 NOLIST 12 INCLUDE 'derivative.inc' 13 INCLUDE 'SciComm.inc' ; SciComm INC file does NOT use RAM 14 LIST ; ..storage and THIS is it's place 15 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 16 ; 2) DEFINES 17 ram: SET Z_RAMStart ; $80 18 rom: SET ROMStart ; $2080 19 initStack: EQU RAMEnd + 1 ; $1800=$17FF+1. SP = $17FF 20 COP_Disable: EQU $42 21 ;==================================================================== 22 ; MAIN PROGRAM HEADER: 23 ABSENTRY Main 24 ORG rom 25 Main: lda #COP_Disable ; RSTE=0: PTA5 is NOT for ~RESET 26 sta SOPT1 ; ..System Options 1 27 ldhx #initStack ; Set up SP 28 txs ; ... 29 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 30 ; SCI9600N8: No need for previous enabled SCI peripherals 31 SCI9600N8 ; Setup Serial Communications Inter‐ 32 XmtRcvActivate ; ..face: 9600 bps, No parity, 8 bits 33 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 34 Prompt: 161 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 35 lda #'A' ; Ascii 'A' 36 XmitLoop: 37 putchar 38 inca 39 cmp #'z' 40 bls XmitLoop 41 putc #$0D ; Carriage Return (CR: '\r' in C) 42 putc #$0A ; Line Feed (LF: '\n') 43 ; BE PATIENT! To come here you'l need 44 ; ..217,191 cycles @4,194,304Hz 45 ;************ NOTE: *************** 46 ; To SIMULATE SCI *inputs* use PEMicro SCI1 debugger command. 48 ; It will be instructive to display SCI1S1 (status register), and see 49 ; ..most important flags, bits 7, 6 & 5: XMTRDY, XMTEMPTY & RCVRDY 50 ; While debugging, see how reading Status and writing BUF, clears 51 ; ..the RCV Ready flag; see also how XMT Status work (RDY & EMPTY) 52 inpLoop: 53 getchar ; Letters in range 'A' .. 'z' will 54 cmp #'A' ; ..be incremented before returning, 55 bhs inRange ; ..to be sure that what we see in 56 ; ..the PC isn't a LOCAL ECHO! 57 Xmit: putchar 58 bra inpLoop ; Stay in this loop, for ever 59 inRange: 60 cmp #'z' 61 bhs Xmit ; Don't increment 'z' either 62 incLetter: 63 inca ; Increment this received char 64 bra Xmit ; ..and return back it to PC 65 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 66 nop ; <<<NEEDED by CodeWarrior 10.1&2 (not 6.3). INCREDIBLE<<< 67 ; This 'nop' MAY be removed for CW 6.3 ... 68 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 69 ; Interrupt Vectors 70 ORG Vreset 71 DC.W Main ; RESET. Maximum priority. Asynch. 72 END COMENTARIOS a ["Laboratorios\Lab3\SciComm\2echo1.asm"]: No olvide la posición donde DEBE incluir mi librería de comunicaciones: 12 INCLUDE 'derivative.inc' 13 INCLUDE 'SciComm.inc' ; SciComm INC file does NOT use RAM A continuación, como en el ejemplo anterior, las Macros: 31 SCI9600N8 ; Setup Serial Communications Inter‐ 32 XmtRcvActivate ; ..face: 9600 bps, No parity, 8 bits 162 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 Primero el programa envía LA MISMA TRAMA del ejercicio anterior. Se llama "PROMPT" a un texto de advertencia que el computador le envía al usuario para recordarle que está en posición de recibir los datos que se le envíen. Por eso esta trama la llamamos aquí Prompt: 34 Prompt: 35 lda #'A' ; Ascii 'A' 36 XmitLoop: 37 putchar 38 inca 39 cmp #'z' 40 bls XmitLoop 41 putc #$0D ; Carriage Return (CR: '\r' in C) 42 putc #$0A ; Line Feed (LF: '\n') Luego recibe lo que le envían desde el PC, verifica que los símbolos se encuentren en el rango 'A' .. 'z'; si no, retransmite lo mismo que recibió... 52 inpLoop: 53 getchar ; Letters in range 'A' .. 'z' will 54 cmp #'A' ; ..be incremented before returning, 55 bhs inRange ; ..to be sure that what we see in 56 ; ..the PC isn't a LOCAL ECHO! 57 Xmit: putchar 58 bra inpLoop ; Stay in this loop, for ever 59 inRange: 60 cmp #'z' 61 bhs Xmit ; Don't increment 'z' either Y en caso afirmativo, de que el rango sea el apropiado, retransmite el símbolo recibido, INCREMENTADO: 62 incLetter: 63 inca ; Increment this received char 64 bra Xmit ; ..and return back it to PC 30) Transmisión por INTERRUPCIONES. Este programa es igual al anterior, pero usa las interrupciones de transmisión para enviar el 'Message': ["Laboratorios\Lab3\SciComm\3echoInt‐1.asm"] 01 ;******************************************************************** 02 ; Programa 3EchoInt‐1.asm: Test SCI Comm. Support 03 ; Luis G. Uribe C., M13M2007 J16A09 L08J09 L18J2012 (HCS08) D24J2012 04 ; M10D2013 cosmetics (XmtRcvActivate) 05 ; ..Same 2Echo1.asm program but using INTERRUPTS for XMT 'Message' 06 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 07 ; Include files that no define ram 08 NOLIST 09 INCLUDE 'derivative.inc' 10 INCLUDE 'SciComm.inc' ; SciComm INC file does NOT use RAM 163 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 11 LIST ; ..storage and THIS is it's place 12 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 13 ; 2) DEFINES 14 ram: SET Z_RAMStart ; $80 15 rom: SET ROMStart ; $2080 16 initStack: EQU RAMEnd + 1 ; $1800=$17FF+1. SP = $17FF 17 COP_Disable: EQU $42 18 ;==================================================================== 19 ; GLOBAL VARIABLES 20 ORG ram 21 XmtNchars: DS 1 ; Soft Flag for Main: 0 Nothing to Xmt 22 XmtPtr: DS 2 ; char pointer of next data to Xmt 23 ;******************************************************************** 24 ; MAIN PROGRAM HEADER: 25 ABSENTRY Main 26 ORG rom 27 Main: lda #COP_Disable ; RSTE=0: PTA5 is NOT for ~RESET 28 sta SOPT1 ; ..System Options 1 29 ldhx #initStack ; Set up SP 30 txs ; ... 31 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 32 ; SCI9600N8: No need for previous enabled SCI peripherals 33 SCI9600N8 ; Setup Serial Communications Inter‐ 34 XmtRcvActivate ; ..face: 9600 bps, No parity, 8 bits 35 ;==================================================================== 36 ; Transmit your message: Enque at once full 'Message' Table for Xmt: 37 Prompt: 38 ldhx #Message ; Init XmtPtr with Message address; 39 sthx XmtPtr ; ..init counter with # of chars 40 mov #LOW(MessageSize), XmtNchars ; ... 41 XmtIntEn ; XmtISR will send the message and 42 ; ..clear 'XmtNchars' when finished 43 cli ; <<<DON'T FORGET TO GLOBAL ENABLE INTs 44 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 45 XmtLoop: 46 tst XmtNchars ; Wait until done (tst: cmp with 0) 47 bne XmtLoop ; (Not needed here. Just: Go to BRA *) 48 bra * 49 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 50 Message: DC '12345', $0D, $0A ; Short message. DC? Define Constant 51 ;;Message: DC "Este es el mensaje de prueba, " 52 ;; DC "primero en transmitirse al PC por interrupciones" 53 ;; DC $0D, $0A ; Carriage Return, Line Feed (Newline) 55 MessageSize: EQU * ‐ Message 56 ;==================================================================== 57 XmtISR: 58 pshh ; Not saved by interrupt proc 59 tst XmtNchars ; See if done 60 beq XmtDone ; 0 chars? Goto Dsb Xmt Int 164 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 61 Xmt: putcX XmtPtr ; NOTE: putcX clears XMTRDY.bit 62 dec XmtNchars ; Adjust char counter 63 bra XmtIsrExit 64 XmtDone: 65 XmtIntDsb 66 XmtIsrExit: 67 pulh ; Not restored by interrupt proc 68 rti 69 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 73 ; Interrupt Vectors (ELIMINADAS LÍNEAS 70,71,72 por espacio. Ver SRC ORG) 74 ORG Vsci1tx 75 DC.W XmtISR ; SCI Transmit 76 ORG Vreset 77 DC.W Main ; RESET. Maximum priority. Asynch. 78 END COMENTARIOS a ["Laboratorios\Lab3\SciComm\3echoInt-1.asm"]: Como de costumbre, aquí van los Include Files que no definen ram: 09 INCLUDE 'derivative.inc' 10 INCLUDE 'SciComm.inc' ; SciComm INC file does NOT use RAM Se incluyen dos variables GLOBALES: un contador de los caracteres que faltan por enviar, XmtNchars, y el apuntador a la próxima letra del mensaje que se transmitirá: 20 ORG ram 21 XmtNchars: DS 1 ; Soft Flag for Main: 0 Nothing to Xmt 22 XmtPtr: DS 2 ; char pointer of next data to Xmt La consabida definición de los parámetros de comunicación, y la Activación de ambos dispositivos, el de transmisión y el de recepción: 33 SCI9600N8 ; Setup Serial Communications Inter‐ 34 XmtRcvActivate ; ..face: 9600 bps, No parity, 8 bits El mensaje se encuentra completo en la tabla 'Message'. Se origina cargando la dirección del mensaje en la variable que usamos como apuntador: 38 ldhx #Message ; Init XmtPtr with Message address; 39 sthx XmtPtr ; ..init counter with # of chars Obsérvese que la dirección de la tabla 'Message' es: #Message. Si usted mueve simplemente 'Message', estará copiando la PRIMERA LETRA del mismo, no su dirección. A continuación se inicializa la variable XmtNchars con el número que representa el tamaño del mensaje: #LOW(MessageSize). Nótense aquí dos cosas; en PRIMER lugar, el tamaño es el símbolo MessageSize, que cuando analicemos en seguida la constitución de la tabla veremos en detalle cómo logramos que el Assembler calcule ese valor por nosotros, para que no seamos nosotros los que tengamos que hacer JAMÁS el trabajo de la computadora. Luego, ese valor MessageSize puede engañar al Assembler, de tal manera que 165 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 éste no sepa con seguridad si ese símbolo representa 8 bits o 16. Para evitar toda ambigüedad se usa el operador LOW, que escoge los 8 bits inferiores del símbolo en cuestión. Y el numeral (#) se le agrega porque estamos usando la parte baja de (MessageSize) como una constante (el valor del tamaño reservado para esa tabla): 40 mov #LOW(MessageSize), XmtNchars ; ... De los dos procedimientos que suelen emplearse para verificar que la transmisión se terminó (ZStrings, terminados en NULL, o un Contador indicando la cantidad de símbolos que falta por transmitir), hemos escogido este último, un contador de letras. Después de esto estamos listos para transmitir; para esto habilitamos las interrupciones del transmisor y, POR ÚLTIMO, TAMBIÉN habilitamos la bandera GLOBAL del CPU, para que éste acepte y procese interrupts: 41 XmtIntEn ; XmtISR will send the message and 42 ; ..clear 'XmtNchars' when finished 43 cli ; <<<DON'T FORGET TO GLOBAL ENABLE INTs El ciclo final consiste en esperar a que se termine la transmisión, analizando la variable compartida (global) XmtNchars hasta que se haga cero, en cuyo caso llegamos al bra * que simula un HALT: 45 XmtLoop: 46 tst XmtNchars ; Wait until done (tst: cmp with 0) 47 bne XmtLoop ; (Not needed here. Just: Go to BRA *) 48 bra * Se ve con claridad que bastaría reemplazar esas 3 instrucciones con el "bra *", porque después de transmitir los datos, no hay ningún proceso ulterior en este ejercicio. También se ve que no se necesitan las interrupciones, porque el CPU no está haciendo nada mientras la ISR transmite el texto... porque éste es un ejemplo muy sencillo. Cuando usted haga su proyecto tendrá oportunidad de introducir en Main, código que trabaje CONCOMITANTEMENTE con la ISR. Viene la definición de la Tabla: 50 Message: DC '12345', $0D, $0A ; Short message. DC? Define Constant 55 MessageSize: EQU * ‐ Message El texto, muy corto en este ejercicio, corresponde a los números 12345 y luego, el Carriage Return (\r: 0x0D: $0D) y el New Line (\n: 0x0A: $0A). El DC es por "Define Constant"; debe utilizarse en el CODE SECTION, pues define espacio en RAM (Flash). Y para que sea el Assembler el que calcule el tamaño del texto, a fin de que si lo cambiamos NO TENGAMOS QUE VOLVER A CONTARLO, el truco es generar un símbolo, definido ahora por la pseudo instrucción EQU, así: 55 MessageSize: EQU * ‐ Message Recuerde que el LOCATION COUNTER (que usted debió aprender al hacer una de las monografías de la parte teórica del curso) se identifica en el CodeWarrior con el símbolo '*'. Y de esa manera es claro que el número de bytes es igual a la posición 166 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 siguiente al texto, identificada por el '*', menos la etiqueta que señala el comienzo de la tabla, Message: Si quiere ensayar con textos más largos, aquí hay un ejemplo: 51 Message: DC "Este es el mensaje de prueba, " 52 DC "primero en transmitirse al PC por interrupciones" 53 DC $0D, $0A ; Carriage Return, Line Feed (Newline) 55 MessageSize: EQU * ‐ Message La rutina de interrupciones para el transmisor, XmtISR, comienza guardando en el Stack el valor de la parte H del registro índice: H:X, porque el hardware NO salva ese byte automáticamente. Y como putcX emplea el registro índice, pues hay que resguardar su valor al inicio de la ISR, y restaurarlo para finalizar. 57 XmtISR: 58 pshh ; Not saved by interrupt proc Lo primero que se hace es verificar si aún hay algo por transmitir. Si no, terminamos saltando a la etiqueta XmtDone: 59 tst XmtNchars ; See if done 60 beq XmtDone ; 0 chars? Goto Dsb Xmt Int Si aún no se ha terminado la transmisión, ENVIAMOS la próxima letra empleando 'putcX XmtPtr' que, como ya dijimos, de paso BORRA el XMTRDY.bit. 61 Xmt: putcX XmtPtr ; NOTE: putcX clears XMTRDY.bit Se le resta uno al contador de letras, XmtNchars, y se retorna de la interrupción. 62 dec XmtNchars ; Adjust char counter 63 bra XmtIsrExit Cuando el contador determina que ya se terminó toda la transmisión, se AUTODESHABILITA la rutina de interrupción: 64 XmtDone: 65 XmtIntDsb y se ejecuta la parte final, recuperando el valor de H que se había guardado al principio, y retornando de interrupción: 66 XmtIsrExit: 67 pulh ; Not restored by interrupt proc 68 rti Los Interrupt Vectors son sólo el Vsci1tx y el Vreset: 74 ORG Vsci1tx 75 DC.W XmtISR ; SCI Transmit 76 ORG Vreset 167 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 77 DC.W Main ; RESET. Maximum priority. Asynch. 31) Cuarto Programa de COMUNICACIONES: Flash LEDs. Este programa es similar al anterior, pero hace parpadear los LEDs con cada byte enviado. Además de la librería 'SciComm.inc', en este ejercicio se incluye la librería 'timers8HS.inc': ["Laboratorios\Lab3\SciComm\3echoInt‐1Leds.asm"] 001 ;******************************************************************** 002 ; Programa 3EchoInt‐1Leds.asm: Test SCI Comm. Support 003 ; Luis G. Uribe C., M13M2007 J16A09 L08J09 L18J2012 (HCS08) C27J2012 004 ; ..J03E2013 M10D2013 cosmetics (XmtRcvActivate) 005 ; ..Same 3echoInt‐1.asm, but Flash LEDs with each byte sent. 006 ; In DEMOQUE128, DO ***NOT*** USE PTC5's ASSOCIATED LED, as it is 007 ; ..also the pin used to DISABLE COMM1 !!!, via Jumper J8, THANKS! 008 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 009 ; Include files that no define ram 010 NOLIST 011 INCLUDE 'derivative.inc' 012 INCLUDE 'SciComm.inc' ; SciComm INC file does NOT use RAM 013 LIST ; ..storage and THIS is it's place 014 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 015 ; 2) DEFINES 016 ram: SET Z_RAMStart ; $80 017 rom: SET ROMStart ; $2080 018 ;;;initStack: EQU RAMEnd + 1 ; $1800=$17FF+1. SP = $17FF 019 ;;;COP_Disable: EQU $42 020 ;==================================================================== 021 ; GLOBAL VARIABLES 022 ORG ram 023 NOLIST 024 include 'timers8HS.inc' ; <<<TIMERS8: Code, Vars & Macros 025 LIST 026 XmtNchars: DS 1 ; Soft Flag for Main: 0 Nothing to Xmt 027 XmtPtr: DS 2 ; char pointer of next data to Xmt 028 ;******************************************************************** 029 ; MAIN PROGRAM HEADER: 030 ABSENTRY Main 031 ORG rom 032 Main: lda #COP_Disable ; RSTE=0: PTA5 is NOT for ~RESET 033 sta SOPT1 ; ..System Options 1 034 ldhx #initStack ; Set up SP 035 txs ; ... 036 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 037 ; SCI9600N8: No need for previous enabled SCI peripherals 168 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 038 SCI9600N8 ; Setup Serial Communications Inter‐ 039 XmtRcvActivate ; ..face: 9600 bps, No parity, 8 bits 040 ;==================================================================== 041 ; Init LEDs PORTs PTCD (bits 0‐5) and PTED (bits 6‐7) 042 mov #%11110000, PTCD ; Turn on 4 leds, to show program 043 mov #%11110000, PTED ; ..start (0? Led ON. 1? Led OFF) 044 mov #%00011111, PTCDD ; Set Ports bits for output: C=5, D=2 045 mov #%11000000, PTEDD ; ..(they drive the LEDs on DEMOQE128 046 ; NOTE: PTC5 is programmed for INPUT so writting to PTC5 does NOTHING 047 ;******************************************************************** 048 ; NOTE: Genius working at PEMicro use same PTC5 pin to both, drive a 049 ; LED, AND FOR DISABLING COMMUNICATIONS, via Jumper J8, THANKS! 050 ; So, if you use BOTH COMM1 and Leds, do NOT use PTC5 associated LED. 051 ;******************************************************************** 052 Init8Timers 053 cli ; <<<DON'T FORGET TO GLOBAL ENABLE INTs 054 Loop: WaitMS_on 7, #750 ; Show the lights... 055 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 056 ; Transmit your message: Enque at once full 'Message' Table for Xmt: 057 Prompt: 058 ldhx #Message ; Init XmtPtr with Message address; 059 sthx XmtPtr ; ..init counter with # of chars 060 mov #LOW(MessageSize), XmtNchars ; ... 061 XmtIntEn ; XmtISR will send the message and 062 ; ..clear 'XmtNchars' when finished 063 XmtLoop: 064 tst XmtNchars ; Wait until done (tst: cmp with 0) 065 bne XmtLoop ; .. 066 WaitMS_on 7, #750 ; Show the lights... 067 ldhx #Message2 ; Init XmtPtr with Message address; 068 sthx XmtPtr ; ..init counter with # of chars 069 mov #LOW(MessageSize), XmtNchars ; ... 070 XmtIntEn ; XmtISR will send the message and 071 XmtLoop2: 072 tst XmtNchars ; Wait until done (tst: cmp with 0) 073 bne XmtLoop2 ; .. 074 bra Loop 075 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 076 Message: DC '1234567890', $0D ; Short message. DC? Define Constant 077 MessageSize: EQU * ‐ Message 078 Message2: DC '6789012345', $0D ; Short message. DC? Define Constant 079 ;;Message: DC "Este es el mensaje de prueba, " 080 ;; DC "primero en transmitirse al PC por interrupciones" 081 ;; DC $0D, $0A ; Carriage Return, Line Feed (Newline) 082 ;;MessageEnd: 083 ;==================================================================== 169 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 084 XmtISR: 085 pshh ; Not saved by interrupt proc 086 tst XmtNchars ; See if done 087 beq XmtDone ; 0 chars? Goto Dsb Xmt Int 088 lda #$FF ; Flash Leds to show program progress 089 eor PTCD ; .. 090 eor PTED ; .. 091 sta PTCD ; .. 092 sta PTED ; .. 093 Xmt: putcX XmtPtr ; NOTE: putcX clears XMTRDY.bit 094 dec XmtNchars ; Adjust char counter 095 bra XmtIsrExit 096 XmtDone: 097 XmtIntDsb 098 XmtIsrExit: 099 pulh ; Not restored by interrupt proc 100 rti 101 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 102 nop ; <<<NEEDED by CodeWarrior 10.1&2 (not 6.3). INCREDIBLE<<< 103 ; This 'nop' MAY be removed for CW 6.3 ... 104 ;==================================================================== 105 ; RTC Interrupt Service Routine, si usa Init8Timers... 106 RTC_INTERRUPT: 107 TIMERS8ISR 108 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 109 ; Interrupt Vectors 110 ORG Vrtc ; Increasing priority from bottom up 111 DC.W RTC_INTERRUPT ; (Si usa Init8Timers...) 112 ORG Vsci1tx 113 DC.W XmtISR ; SCI Transmit 114 ORG Vreset 115 DC.W Main ; RESET. Maximum priority. Asynch. 116 END COMENTARIOS a ["Laboratorios\Lab3\SciComm\3echoInt-1Leds.asm"]: Lo primero es comentar que en la tarjeta DEMOQUE128, NO se puede usar el LED asociado con PTC5's ASSOCIATED LED, porque ese mismo pin se emplea en esa tarjeta para DESHABILITAR LAS COMUNICACIONES !!!, vía Jumper J8. Eso... era INNECESARIO. ¡Gracias a PEMICRO! 009 ; Include files that no define ram 012 INCLUDE 'SciComm.inc' ; SciComm INC file does NOT use RAM Incluir la librería timers8HS.inc, que SÍ define variables en RAM: 022 ORG ram 024 include 'timers8HS.inc' ; <<<TIMERS8: Code, Vars & Macros 170 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 El contador de caracteres y el apuntador a la próxima letra que será transmitida: 026 XmtNchars: DS 1 ; Soft Flag for Main: 0 Nothing to Xmt 027 XmtPtr: DS 2 ; char pointer of next data to Xmt Parámetros de comunicación, y Activación (Activate) de XMT y RCV: 038 SCI9600N8 ; Setup Serial Communications Inter‐ 039 XmtRcvActivate ; ..face: 9600 bps, No parity, 8 bits INICIALIZACIÓN DE LOS LEDs. Los LEDs en la tarjeta DEMOQE128 son 8, pero NO provienen de un MISMO registro de 8 bits (lo que no le hubiera costado nada a la gente de PEMicro), sino que vienen 6 y dos, así: de PTCD: bits 0‐5, y de PTED; bits 6‐7. Eso... era INNECESARIO. Gracias, PEMICRO! Lo primero que se hace cuando se van a programar uno o más bits para SALIDA como es el caso de los LEDs en este ejercicio, es inicializarlos con el valor que para el ejercicio sea el más conveniente, ANTES DE DEFINIRLOS COMO SALIDA (Recuerde que, al encender el MCU, TODOS los puertos son de ENTRADA). Los puertos de entrada y salida digital tienen varios registros asociados. El más importante es el de datos: PTCD para el puerto "C", PTED para el puerto "E", y así sucesivamente (cfr. el respectivo Reference Manual) 041 ; Init LEDs PORTs PTCD (bits 0‐5) and PTED (bits 6‐7) 042 mov #%11110000, PTCD ; Turn on 4 leds, to show program 043 mov #%11110000, PTED ; ..start (0? Led ON. 1? Led OFF) Después de que ya tienen un valor de salida asignado, pueden ahora sí programarse para SALIDA: 044 mov #%00011111, PTCDD ; Set Ports bits for output: C=5, D=2 045 mov #%11000000, PTEDD ; ..(they drive the LEDs on DEMOQE128 046 ; NOTE: PTC5 is ‐‐^‐‐‐‐‐ programmed for INPUT: Writting to it does NOTHING ADVERTENCIA: A algunos GENIOS que trabajan en PEMicro, se les ocurrió usar el MISMO pin PTC5 para manejar simultáneamente un LED Y TAMBIÉN PARA DESHABILITAR LAS COMUNICACIONES, vía el Jumper J8, Gracias, PEMicro! Así que si su proyecto usa simultáneamente el puerto de comunicaciones SCI1 y LEDs, NO use el LED5, asociado con PTC5. Ahora, inicializa los timers, como siempre se ha hecho, y habilita las interrupciones globales del CPU: 052 Init8Timers 053 cli ; <<<DON'T FORGET TO GLOBAL ENABLE INTs 171 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 Primero espera un poco menos de un segundo para que se vean los LEDs: 054 Loop: WaitMS_on 7, #750 ; Show the lights... Transmita el mensaje; esta parte es idéntica a la del ejercicio anterior: 057 Prompt: 058 ldhx #Message ; Init XmtPtr with Message address; 059 sthx XmtPtr ; ..init counter with # of chars 060 mov #LOW(MessageSize), XmtNchars ; ... Después de cargar todos los parámetros para el mensaje #1, habilita las interrupciones de transmisión: 061 XmtIntEn ; XmtISR will send the message and Y luego viene el ciclo de espera hasta que se termine de transmitir: 063 XmtLoop: 064 tst XmtNchars ; Wait until done (tst: cmp with 0) 065 bne XmtLoop ; .. Cuando haya terminado (XmtNchars vale cero), vuelva a esperar, a fin de que se vean los LEDs: 066 WaitMS_on 7, #750 ; Show the lights... Ahora carga la dirección del nuevo mensaje, Message2. Note que como ambos mensajes tienen la misma longitud, solo se ha empleado UNA longitud para ellos dos. Eso no tiene por qué ser así. Si sus mensajes tienen diferente longitud, repita el mismo cálculo que se está haciendo para el mensaje uno, en el mensaje 2, y el resto sigue igual: 067 ldhx #Message2 ; Init XmtPtr with Message address; 068 sthx XmtPtr ; ..init counter with # of chars 069 mov #LOW(MessageSize), XmtNchars ; ... Vuelta a habilitar las interrupciones de salida, que se AUTODESHABILITARON al terminar de transmitir el mensaje anterior: 070 XmtIntEn ; XmtISR will send the message and 071 XmtLoop2: 072 tst XmtNchars ; Wait until done (tst: cmp with 0) 073 bne XmtLoop2 ; .. 074 bra Loop Y repite... Los mensajes: 076 Message: DC '1234567890', $0D ; Short message. DC? Define Constant 077 MessageSize: EQU * ‐ Message 078 Message2: DC '6789012345', $0D ; Short message. DC? Define Constant 172 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 La rutina de interrupciones para el transmisor. La primera parte es idéntica al ejercicio anterior: 084 XmtISR: 085 pshh ; Not saved by interrupt proc 086 tst XmtNchars ; See if done 087 beq XmtDone ; 0 chars? Goto Dsb Xmt Int Las próximas líneas niegan los bits de los registros C y E, que alimentan a los LEDs: 088 lda #$FF ; Flash Leds to show program progress 089 eor PTCD ; .. 090 eor PTED ; .. 091 sta PTCD ; .. 092 sta PTED ; .. Luego se transmite la siguiente letra; se decrementa el contador y se termina la ISR: 093 Xmt: putcX XmtPtr ; NOTE: putcX clears XMTRDY.bit 094 dec XmtNchars ; Adjust char counter 095 bra XmtIsrExit Cuando se transmitido todo el mensaje, se AUTODESHABILITA la rutina para interrupciones, y termina: 096 XmtDone: 097 XmtIntDsb 098 XmtIsrExit: 099 pulh ; Not restored by interrupt proc 100 rti La RTC Interrupt Service Routine: 106 RTC_INTERRUPT: 107 TIMERS8ISR Y los Interrupt Vectors, uno para mi librería de Timers, Vrtc, otro para el transmisor de información serial, Vsci1tx, y el siempre necesario, Vreset. 32) Programas de COMUNICACIONES que USTED Debe Hacer: Haga un programa que reciba información por interrupciones, que la RCVISR la encole, habilite las interrupciones de salida, que la XMTISR la saque de la cola y retransmita lo que 'recibe + 1', tal como hizo el programa de ECHO que ya describimos SIN interrupciones. Debe poder hacer un programa que simule un CHAT: En usuario escribe desde el PC, el MCU le hace ECHO normal (si recibe una A retransmite una A), y para simular que el otro usuario escribe desde el MCU, donde no hay muchos recursos... podemos pegar varios mensajes, uno a cada push button de entrada; el MCU lee el botón que se oprimió, encola el correspondiente mensaje, y realiza la transmisión por interrupciones. 173 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 Tiene que tomar previsiones para que no haya conflicto entre el mensaje que usted está transmitiendo desde el MCU, con el ECO que se le está haciendo a cada letra que reciba del PC. (Por ejemplo, si está transmitiendo su mensaje, puede ignorar por completo hacerle Eco a la información que de pronto le puedan enviar simultáneamente desde el PC). De los demás periféricos, el PWM se usará en los proyectos en los que haya sonidos (tipo Walkman, o piano). El ADC (Analog to Digital Converter) se empleará en proyectos donde, por ejemplo, quiera cambiarse el volumen del sonido, o la frecuencia del piano, o si se desea hacer un Voltímetro Digital (el potenciómetro alimenta directamente uno de los canales del ADC en la tarjeta DEMOQE128) Una aplicación del PWM es la de hacer que la tarjeta HABLE: Se graba la voz y se la reproduce vía PWM, de tal manera que la cornetica de la tarjeta suene, lo cual hará con muy mala calidad, porque la respuesta del piezoeléctrico, que hace de transductor de sonido, es muy pobre. Pero si usted puede reproducir una palabra, será un buen logro. Y los otros dispositivos no pueden usarse en el DEMOQE128 sin tocar los conectores, lo que está prohibido en los cursos de Arquitectura‐I en nuestros laboratorios. Ya tendrán oportunidad de emplearlos a fondo en los cursos más avanzados de Arquitectura, y en los de Laboratorios de Proyectos. PROGRAMAS VARIOS EN ASSEMBLY LANGUAGE 33) BITCOPY, MACRO y Ejemplo. En “Ingeniería Digital”, Cap. 3, se hace una introducción a uno de los más apasionantes y antiguos métodos de detección y corrección de error: El Código Hamming. No voy a repetir lo que allí escribí para su referencia; bástenos con explorar un ejemplo. Si se tiene una trama de información de 4 bits, llamados I7I6I5I3, y se quiere detectar la presencia o ausencia de UN error, se le agrega UN BIT DE PARIDAD, del cual ya hablamos con antelación al explicar los aspectos más relevantes de la LIBRERÍA DE COMUNICACIONES SERIALES: SciComm.inc. Richard Wesley Hamming hizo en 1950 una codificación simple, pura, clara, eficiente y eficaz, que permite DETECTAR la aparición de UN ERROR y CORREGIRLO, y detectar (pero NO corregir) la existencia de DOS errores. Para nuestro caso de ejemplo, Hamming dispuso los bits en la siguiente TRAMA (Trama Hamming): I7I6I5P4I3P2P1, en donde, además de los bits de Información, I7I6I5I3, se han entrelazado 3 bits adicionales, de paridad: P4P2P1 (note que su identificación corresponde a potencias de 2) En esa trama, Hamming hizo el siguiente planteamiento: Los 3 bits extras, de paridad, pueden establecer 8 combinaciones entre ellos. La combinación 000 indicará que NO hay error. Y las restantes 7 combinaciones, del 001 al 111, indicarán la posición en donde aparece el error, de la posición 1 a la 7, en decimal. Su codificación para cumplir con esta tan extraordinaria aproximación consistió en establecer, para la transmisión, el siguiente cálculo binario: 174 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 P1 = I3 XOR I5 XOR I7, el XOR entre los bits situados en las posiciones correspondientes a los números impares de la tabla de abajo: P2 = I3 XOR I6 XOR I7 (observe el por qué en la tabla) P4 = I5 XOR I6 XOR I7 (observe el por qué en la tabla) TABLA HAMMING: Error_Column# P P P | I I I I 4 2 1 | 7 6 5 3 ‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐ 0 0 0 0 (NO ERROR) 0 0 1 1 . . . . 0 1 0 2 . . . . 0 1 1 3 . . . 1 P1 = I3 XOR I5 XOR I7 1 0 0 4 . . . . P2 = I3 XOR I6 XOR I7 1 0 1 5 . . 1 . P4 = I5 XOR I6 XOR I7 1 1 0 6 . 1 . . 1 1 1 7 1 . . . Donde haya un 1, para cada Pn, se toma la Im que tenga 1 (si es que hay) y se la incluye en la ecuación. Ver la explicación para un caso de N bits de información esté en Ingeniería Digital. Supongamos que tenemos la Información de 4 bits del ejemplo y queremos calcular las 3 Paridades. Cómo haría usted los 6 XORs de las 3 ecuaciones booleanas? Una forma inmediata de hacer los XORs sería colocando los bits en dos variables del CPU, H1 y H2, en las siguientes posiciones, y emplear la instrucción EOR (XOR para el S08): H1|I5 I3 I3 H2|I6 I6 I5 XOR ‐‐+‐‐‐‐‐‐‐‐ H2|p4 p2 p1 (Respuesta Parcial) H1|I7 I7 I7 XOR ‐‐+‐‐‐‐‐‐‐‐ H2|P4 P2 P1 (RESPUESTA DEFINITIVA) A partir de los bits dispuestos en Trama Hamming, I7I6I5P4I3P2P1, resulta un tanto laborioso reasignar los bits hasta las posiciones requeridas en la tabla para hacer los XORs, y del resultado de vuelta a la variable que aloja la Trama Hamming: b7 b6 b5 b4 b3 b2 b1 b0 .. I7 I6 I5 P4 I3 P2 P1 (Variable de 7 bits; aloja la Trama Hamming) b7 b6 b5 b4 b3 b2 b1 b0 H1| I5 I3 I3 H2| I6 I6 I5 175 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 H1| I7 I7 I7 y de aquí otra vez a la trama: H2| P4 P2 P1 (RESPUESTA) Y si los bits de Información comienzan juntos: I7I6I5I3, hay que moverlos, al comienzo, para formar la Trama Hamming. Se transmite la trama así elaborada y en el destino se aplica un cálculo similar, que usted tiene que revisar en Ingeniería Digital. Al transmitir, se comienza con 4 bits de información y se forma una trama de 7 bits; al receptor le llegan los 7 bits, se los procesa y se producen los 4 bits de información, libres de error, mientras no ocurra más de un error en cada trama de 7. Finalmente, se pueden detectar y corregir cualquier cantidad necesaria de errores, calculando las tramas como acabo de indicar, y transmitiéndolas VERTICALMENTE. El receptor las reacomoda HORIZONTALMENTE, y corrige todos los errores anticipados, según explico en la referencia mencionada. El objetivo de este ejercicio es mostrar un procedimiento, la MACRO BITCOPY, y un ejemplo. A usted le vendría bien aplicarla al cálculo de Hamming, tanto para Generación del Código Hamming como para la Detección y Corrección del error. Para un examen o evaluación, los ejercicios suelen tener más de 3 bits de información, lo que produce tramas que no caben en un BYTE. Sin embargo el procedimiento es similar al anterior. En Ingeniería Digital se encuentra la generalización a N bits de Información, y el agregado de un bit de paridad extra, que permite DETECTAR dos errores, aunque el algoritmo no permita corregirlos. ["Laboratorios\Proy\Buzzer\BitCopy.asm"] 01 ;******************************************************************** 02 ; BitCopy.asm; Luis G. Uribe C., D01L2012 03 ; MACRO BITCOPY and test program. 04 ; To implement "C" statments like this: 05 ; PTCD_PTCD0 = PTBD_PTBD5; 06 ; // Display PWM output, pin PTB5, into LED wired to PTC0 07 ;******************************************************************** 08 NOLIST 09 INCLUDE 'derivative.inc' 10 LIST 11 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 12 ; 2) DEFINES 13 14 ram: SET Z_RAMStart ; $80 15 rom: SET ROMStart ; $2080 16 initStack: EQU RAMEnd + 1 ; $1800=$17FF+1. SP = $17FF 17 COP_Disable: EQU $42 18 19 ;==================================================================== 20 BITCOPY MACRO SrcAdd, SrcBit, DstAdd, DstBit 176 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 21 ; Example: BITCOPY Src, 3, Dst, 5 ; Use any variables: DIR or EXT 22 lda \1 23 bit #1<<(\2&$07) ; If SrcAdd[SrcBit] == 0 24 beq \@Clr_DstAdd ; ..goto Clr_DstAdd[DstBit] 25 ;Set_DstAdd[DstBit]: ; Else, Set_DstAdd[DstBit] 26 lda \3 27 ora #1<<(\4&$07) 28 bra \@cont 29 ;Clr_DstAdd[DstBit]: 30 \@Clr_DstAdd: 31 lda \3 32 and #~(1<<(\4&$07)) 33 \@cont: 34 sta \3 35 ENDM 36 37 ;******************************************************************** 38 ; MAIN PROGRAM HEADER: 39 ORG ram 40 Src: DS 1 41 Dst: DS 1 42 43 ABSENTRY Main 44 ORG rom 46 Main: lda #COP_Disable ; RSTE=0: PTA5 is NOT for ~RESET 47 sta SOPT1 ; ..System Options 1 48 ldhx #initStack ; Set up SP 49 txs ; ... 50 51 lda #0 52 sta Src 53 lda #$FF 54 sta Dst 55 BITCOPY Src, 3, Dst, 5 ; copy a 0 56 57 lda #$FF 58 sta Src 59 lda #$0 60 sta Dst 61 BITCOPY Src, 2, Dst, 6 ; copy a 1 62 63 bra * 64 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 65 nop ; <<<NEEDED by CodeWarrior 10.1&2 (not 6.3). INCREDIBLE<<< 66 ; This 'nop' MAY be removed for CW 6.3 ... 67 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 68 ; Interrupt Vectors 70 ORG Vreset 71 DC.W Main ; RESET. Maximum priority. Asynch. 73 END 177 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 COMENTARIOS a ["Laboratorios\Proy\Buzzer\BitCopy.asm"]: En C es relativamente fácil expresar estos movimientos de bits, de un lugar a otro, tales como: 05 ; PTCD_PTCD0 = PTBD_PTBD5; 06 ; // Display PWM output, pin PTB5, into LED wired to PTC0 En realidad están asignados a una Struct de BITS, en Union con el Byte (en este caso, de los registros C y B). 20 BITCOPY MACRO SrcAdd, SrcBit, DstAdd, DstBit 21 ; Example: BITCOPY Src, 3, Dst, 5 ; Use any variables: DIR or EXT La Macro tiene 4 parámetros: la dirección (1) de la variable FUENTE (SRC), de donde se va a tomar el bit (2) para transportarlo a la variable (3) DESTINO, DST, en la posición de bit (4) OBSERVACIONES: A) Las variables pueden estar colocadas en cualquier posición de la RAM, porque se usan LDA y STA en lugar de MOV. B) Los parámetros 2 y 4, correspondientes a las posiciones de los bits, NO SON VARIABLES: SON CONSTANTES, porque constantes del 0 al 7 son las que hay que darle a la BIT del S08 que aquí se usa. Además, ese número NO lleva el símbolo '#': son números decimales SIMPLES, del 0 al 7, sin ningún otro modificador agregado. >>> IMPORTANTE <<< 22 lda \1 Se carga en el acumulador el primer parámetro: La variable SRC. 23 bit #1<<(\2&$07) ; If SrcAdd[SrcBit] == 0 Se hace una comparación del bit SRC deseado, indicado por el parámetro \2, para ver si está en cero o en uno. Para hacer la comparación se usa la instrucción BIT (Bit Test), que hace un AND entre el Acumulador y el Operando, y compara el resultado contra cero. Obsérvese que el Operando es un uno colocado en la posición de bit indicada por el número \2; esto que acabo de decir se escribe así: #1<<(\2&$07) ...un uno (#1) colocado en la posición (<<) de bit indicada por el número \2 (<<(\2&$07). Al parámetro \2 se le ha hecho un AND (&) con el número $07 (binario %111), a fin de que si por error el usuario llama la Macro pasándole un número MAYOR a 7, éste queda automáticamente confinado al rango permitido de 0 a 7. Claro? 24 beq \@Clr_DstAdd ; ..goto Clr_DstAdd[DstBit] Si el bit es Cero se salta (BEQ) a BORRAR el bit en la dirección destino: Clr_DstAdd. Como ésta es una dirección que se repetiría cada vez que se empleara la Macro (lo cual daría error, porque no puede haber más de una definición para una dirección, ni para 178 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 una variable), el truco consiste en anteceder la dirección que se indica dentro de la Macro, con un \@. Esto le indica al Assembler que genere una dirección diferente, cada vez que vea ese símbolo \@Clr_DstAdd. A continuación viene el código que se ejecuta si el bit era UNO, y por tanto hay que colocar en uno (SET) el bit en el destino. Primero se carga el acumulador con la variable DST: 26 lda \3 y, para colocar un uno en el bit deseado de DST, se le hace un OR (ora) así: 27 ora #1<<(\4&$07) Aquí, de nuevo, la posición es: un 1 (#1), desplazado a la izquierda (<<) tantos pasos cuantos indique el último parámetro (\4), al que se lo ha “confinado” automáticamente al rango de 0 a 7 mediante el AND (\4&$07). Finalmente sale de la Macro, no sin antes guardar el valor del Acumulador en la variable DST, indicada por el parámetro \3. 28 bra \@cont Si el bit estuvo en cero, hay que colocar en cero el bit correspondiente de DST, haciendo un AND del valor (del Acumulador) con un número formado por TODOS UNOS, Excepto un CERO en la posición que se quiere borrar: 29 ;Clr_DstAdd[DstBit]: 30 \@Clr_DstAdd: 31 lda \3 32 and #~(1<<(\4&$07)) La expresión "and #~(1<<(\4&$07))" hace exactamente eso: un AND (and) con una máscara de puros UNOS que se consigue haciendo el NEGADO booleano de UN uno en la posición que se quiere borrar: ~: Negado booleano; la posición que se quiere borrar: (1<<(\4&$07)). Claro? Ejemplo: Se carga un cero en SRC, y se llena de puros unos el DST: 51 lda #0 52 sta Src 53 lda #$FF 54 sta Dst Se copia el bit 3 de SRC, al bit 5 de DST: 55 BITCOPY Src, 3, Dst, 5 ; copy a 0 Con el Debugger puede verse el valor resultante. 179 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 Se carga ahora al revés, puros UNOS en SRC, y puros CEROS en DST: 57 lda #$FF 58 sta Src 59 lda #$0 60 sta Dst Y se copia el bit 2 de SRC, al bit 6 de DST. Véase con el Debugger. 61 BITCOPY Src, 2, Dst, 6 ; copy a 1 34) USO Básico del ADC, y Display en LEDs. El siguiente programa habilita el Analog to Digital Converter para realizar conversiones continuamente (free wheeling) y presentar los resultados en los LEDs. La entrada se toma del potenciómetro del DEMOQE128. Sirve como ejemplo para cuando necesite hacer conversiones de análogo a digital, programar y manejar los LEDs. 01 ["Laboratorios\Proy\Buzzer\ADC.asm"] 02 ;******************************************************************** 03 ; ADC.asm; Luis G. Uribe C., D01L2012 L11M2013 04 ; L11M2013: Clarification about ADC pins ENABLE (APCTLx) 05 ; DESCRIPTION: The ADC module is configured in continuous conversion 06 ; mode, every obtained value is display in port C&E (8‐LEDs). 07 ; PTA0 is the blue POTENCIOMETER in DEMOQE128 !!! 08 ;******************************************************************** 09 NOLIST 10 INCLUDE 'derivative.inc' 11 LIST ; ..storage and THIS is it's place 12 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 13 ; 2) DEFINES 14 ram: SET Z_RAMStart ; $80 15 rom: SET ROMStart ; $2080 16 initStack: EQU RAMEnd + 1 ; $1800=$17FF+1. SP = $17FF 17 COP_Disable: EQU $42 18 ;******************************************************************** 19 ; MAIN PROGRAM HEADER: 20 ABSENTRY Main 21 ORG rom 22 Main: lda #COP_Disable ; RSTE=0: PTA5 is NOT for ~RESET 23 sta SOPT1 ; ..System Options 1 24 ldhx #initStack ; Set up SP 25 txs ; ... 26 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 27 ; MCU_Init 28 ;; lda #$23 ; Enable Bus Clock to the ADC module 29 ;; sta SCGC1 30 ;; clra ; Disable unused peripherals Bus clock 31 ;; sta SCGC2 32 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 180 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 33 ; GPIO_Init 34 ; Init LEDs PORTs PTCD (bits 0‐5) and PTED (bits 6‐7) 35 mov #%11110000, PTCD ; Turn on 4 leds, to show program 36 mov #%11110000, PTED ; ..start (0? Led ON. 1? Led OFF) 37 mov #%00011111, PTCDD ; Set Ports bits for output: C=5, D=2 38 mov #%11000000, PTEDD ; ..(they drive the LEDs on DEMOQE128 39 ; NOTE: PTC5 is programmed for INPUT so writting to PTC5 does NOTHING 40 ;******************************************************************** 41 ; NOTE: Genius working at PEMicro use same PTC5 pin to both, drive a 42 ; LED, AND FOR DISABLING COMMUNICATIONS, via Jumper J8, THANKS! 43 ; So, if you use BOTH COMM1 and Leds, do NOT use PTC5 associated LED. 44 ;******************************************************************** 45 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 46 ; ADC_configuration 47 ; Int. disable. Continuous conversion 48 mov #$20, ADCSC1 ; ..mode and channel 0 active 49 clr ADCSC2 ; Software trigger selected 50 mov #$30, ADCCFG ; Input clock/2. Long Sample time 51 ; ..config. 8‐bit conversion 52 ;==================================================================== 53 ; See "02MC9S08QE128RM(ReferenceManual)‐U.pdf", 10.5.1 ADC Module 54 ; ..Initialization Example, 10.5.1.2 Pseudo‐Code Example: 55 ; APCTL1 = 0x02 means: AD1 pin I/O control disabled. 56 ; ..All other AD pins remain general purpose I/O pins 57 ; Manual says: A "1" in APCTLx: Pin I/O control DISABLED. 58 ; ..It means: A "1" in APCTLx: ADC pin ENABLED !!! <<< <<< <<< <<< 59 ; 60 ; These people can not even write a decent manual... 61 clr APCTL1 ; ALL ADC pins DISABLE; they work as GPIO 62 bset APCTL1_ADPC0, APCTL1 ; ENABLE channel0 for ADC input 63 bset ADCSC1_AIEN, ADCSC1 ; Enable ADC interrupt 64 cli 65 bra * 66 ;******************************************************************** 67 ; interrupt VectorNumber_Vadc ADC_ISR 68 ADC_ISR: 69 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 70 ; NOTE: Reading ADCRL clear COCO Flag, AS IT SHOULD BE DONE ON ALL 71 ; ..PERIPHERALS THAT HAVE DATA BUFFERS TO READ OR WRITE!!! 72 lda ADCRL ; Negate before display 73 coma ; ..(LEDs turn on with 0's) 74 sta PTCD ; Move ADC value to port C 75 sta PTED ; ..and to port E 76 rti 77 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 78 nop ; <<<NEEDED by CodeWarrior 10.1&2 (not 6.3). INCREDIBLE<<< 79 ; This 'nop' MAY be removed for CW 6.3 ... 80 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 181 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 81 ; Interrupt Vectors 82 ORG Vadc 83 DC.W ADC_ISR 84 ORG Vreset 85 DC.W Main ; RESET. Maximum priority. Asynch. 86 END COMENTARIOS a ["Laboratorios\Proy\Buzzer\ADC.asm"] Importante notar que el Potenciómetro Azul del DEMOQE128 se conecta al ADC por la entrada PTA0: 07 ; PTA0 is the blue POTENCIOMETER in DEMOQE128 !!! El siguiente código no se ejecuta, pero se lo ha incluido de todas maneras para ilustrar la configuración del MCU cuando hay que habilitar el módulo de ADC. Está comentado porque las acciones que aquí se tomarían, se han ejecutado durante el Power On Reset, y como no las hemos modificado, no necesitamos repetirlas. PERO, si se cambiara alguno de estos "settings", la forma de VOLVER a configurarlos es ejecutando estas 4 líneas de código. Para mayor información hay que revisar, como de costumbre, el Reference Manual. 26 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 27 ; MCU_Init 28 ;; lda #$23 ; Enable Bus Clock to the ADC module 29 ;; sta SCGC1 30 ;; clra ; Disable unused peripherals Bus clock 31 ;; sta SCGC2 La explicación de la inicialización de los 8 LEDs es exactamente igual a la que se hizo en: 30) CUARTO PROGRAMA DE COMUNICACIONES, y no la vamos a repetir aquí. Como la salida del potenciómetro alimenta el terminal PTA0, en donde se encuentra el canal 0 del ADC, su configuración consiste en activar el canal 0 de entrada Analógica, y poner el ADC en modo de Conversión Continua, activado por Software (cuando podría activarse por Hardware). También se selecciona conversión de 8 bits, porque el ADC del MC9S08QE128 puede trabajar con otras longitudes para el resultado de la conversión (así se obtiene mayor resolución). El código: 46 ; ADC_configuration 47 ; Int. disable. Continuous conversion 48 mov #$20, ADCSC1 ; ..mode and channel 0 active 49 clr ADCSC2 ; Software trigger selected 50 mov #$30, ADCCFG ; Input clock/2. Long Sample time 51 ; ..config. 8‐bit conversion La siguiente es una aclaratoria sobre uno de los tópicos peor descritos en el "02MC9S08QE128RM(ReferenceManual)‐U.pdf", 10.5.1. Hay un ejemplo de inicialización, 10.5.1.2, escrito en Pseudo‐Código y comentado: 182 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 Primero se deshabilita el control de I/O para el pin o terminal AD1; los demás terminales permanecen como pines de I/O de propósitos generales: 55 ; APCTL1 = 0x02 means: AD1 pin I/O control disabled. 56 ; ..All other AD pins remain general purpose I/O pins El manual, bien oscurecido, dice que un UNO en un pin x del APCTL (bit APCTLx) le DESHABILITA a ese pin el CONTROL DE I/O. POR TANTO, ESE pin x del APCTL, por haberlo programado a UNO, hace que el correspondiente pin del ADC SE HABILITE (para funciones de ADC) Mucho más sencillo hubiera sido expresar esta funcionalidad de la siguiente manera (redactado lo más en POSITIVO que es posible): "Para HABILITAR un pin x del conversor ADC, para que funcione como ENTRADA ANALÓGICA, se coloca un UNO en el correspondiente bit x del registro Analógico para el Control de Pines: APCTL (bit APCTLx). "Para HABILITAR como I/O digital un pin x, de los que pueden actuar como entrada analógica del ADC (24 canales analógicos en el MC9S08QE128), se coloca un CERO en el correspondiente bit x del APCTL, APCTLx. "Después de Power On Reset (POR), todos los pines que potencialmente pueden servir como entradas analógicas del ADC, son I/O DIGITALES GENÉRICOS y así, pueden programarse para entrada o salida, colocándoseles, o no, resistencias de Pull‐up, y todas las demás funcionalidades definidas para las entradas y salidas digitales genéricas. Por eso nuestros ejemplos nunca habilitaron esos pines mediante un CERO en los bits APCTLx: es el valor estándar. 57 ; Manual says: A "1" in APCTLx: Pin I/O control DISABLED. 58 ; ..It means: A "1" in APCTLx: ADC pin ENABLED !!! <<< <<< <<< <<< Se programa el canal cero del ADC para que manipule entradas analógicas; los otro 7 bits de ESE específico grupo de 24, en el MC9S08QE128, quedan en CERO, y por lo tanto siguen siendo Entradas y Salidas Digitales Genéricas, GPIO. 61 clr APCTL1 ; ALL ADC pins DISABLE; they work as GPIO 62 bset APCTL1_ADPC0, APCTL1 ; ENABLE channel0 for ADC input En nuestro caso, podríamos haber hecho solamente el BSET ya que, como hemos explicado, los otros 7 bits del APCTL1 están en CERO (no necesitan un CLR). Desde luego, este código se deja acá, por si usted tiene necesidad de revertir en algún otro ejercicio, las entradas de Analógicas, a GPIO. FINALMENTE se habilita el ADC para que Interrumpa, lo mismo que al CPU, y se simula un HALT mediante el BRA *: 63 bset ADCSC1_AIEN, ADCSC1 ; Enable ADC interrupt 64 cli 65 bra * 183 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 NOTA: Como no fue mi intención establecer en este documento un subsistema para el ADC, programado con todas las reglas de Top‐Down Design y Bottom‐Up Coding que se emplearon en las librerías que desarrollé para TEMPORIZADORES y COMUNICACIONES SERIALES, no usé nombres de un valor semántico más elevado, establecidos mediante ALIAS a las, para nuestros propósitos, oscuras identificaciones que definieron los diseñadores del MCU. Hubieran sido definiciones algo así como "AdcIntEn" (parangón del "XmtIntEn" empleado en anteriores ejercicios...) Cada vez que se produce una conversión, se coloca en UNO la bandera de READY, que en el conversor ADC se llama COCO: COnversion COmplete. Lo interesante del periférico ADC de este MCU es que BASTA CON LEER EL REGISTRO DE DATOS, ADCRL, para AUTOMÁTICAMENTE generar el Acknowledge de la Interrupción (reposicionando COCO en CERO). Esto, aunque opera como las minicomputadoras Digital (PDP, VAX) lo hacían, o como funciona el PC/AT Compatible, arquitectura desarrollada por la IBM y aún en uso actualmente, es decir, que OPERA COMO TODOS LOS PERIFÉRICO DEBÍAN HACERLO, introduce una nueva IRREGULARIDAD: ‐ Periféricos a los que el INTACK se les da por software, leyendo la bandera de READY en UNO y, a continuación, leyendo el DATA BUFFER, talo como está definido el periférico SCI de Comunicaciones Seriales. ‐ Periféricos que reconocen un INTACK con el solo hecho de leer el vector de interrupciones asociado (la dirección de la IRQISR. Este modo se usó en el ancestro directo del MC9S08, el MC908, sin la S). ‐ El ADC sólo requiere que se lea el DATABUF para aceptar un ACK (COCO retorna a CERO) En la rutina de interrupción, ADC_ISR, se lee el valor del BUFFER, ADCRL (Register LOW, que proporciona los 8 bits requeridos). Esto produce, como ya dijimos, un ACK que resetea el COCO. Invertimos el valor negándolo (con COMA, 1's Complement Accumulator), porque como ya sabemos, los LEDs en la tarjeta de desarrollo DEMOQE128 se encienden con CEROS. El valor lo llevamos a los dos registros en que los genios de PE Micro descompusieron los 8 LEDs (6 en PTC y 2 en PTE). Por último, la rutina hace un RTI. 68 ADC_ISR: 72 lda ADCRL ; Negate before display 73 coma ; ..(LEDs turn on with 0's) 74 sta PTCD ; Move ADC value to port C 75 sta PTED ; ..and to port E 76 rti NOTE: No se ha salvado por programa el registro H al comienzo, porque esta rutina no usa para nada el registro índice, H:X. 35) Uso Básico del ADC, Reversar Datos Antes de ir a los LEDs. Suponga que va a alojar dentro de una caja su tarjeta de desarrollo, y que quiere dejar una ventanilla por donde puedan verse desde el exterior. Si mira bien, notará que los bits quedan al revés, con el bit0 a la izquierda. Así que la secuencia 1, 2, 3, 4, 5, 6, 7 se verá como: 1000.., 0100.., 0110.., 0001.., 1001.., 0011.., 0111 184 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 Eso me da la base a decir que PE Micro ALAMBRÓ LOS LEDs AL REVÉS. Este ejercicio es parecido al anterior, pero a los bits del resultado de la conversión deL ADC se los reordena para que la información en binario que se presenta mediante los LEDs, quede AL DERECHO. ["Laboratorios\Proy\Buzzer\ADC_Reverse.asm"] 001 ;******************************************************************** 002 ; ADC_Reverse.asm; Luis G. Uribe C., D01L2012 003 ; DESCRIPTION: The ADC module is configured in Continuous Conversion 004 ; ..Mode, every obtained value is display in port C&E (8‐LEDs). 005 ; PTA0 is the blue POTENCIOMETER in DEMOQE128 !!! 006 ; 007 ;******************************************************************** 008 ; NOTE from Luis G. Uribe C. Desktop: 009 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 010 ; Genius working at PEMicro not only used same PTC5 pin to both drive 011 ; an LED, AND FOR DISABLING COMMUNICATIONS, via Jumper J8, !THANKS!, 012 ; but REVERSE WIRED the LEDs with Bit0 at the LEFT!! !THANKS!, 013 ; and REVERSE DRIVE the LEDs so they light with a '0' !THANKS!, 014 ; and REVERSE WIRED the POTENCIOMETER, to provide 0 Volts when fully 015 ; .. turned CLOCKWISE, and 3 Volts when turned BACKWARDS !THANKS!. 016 ; .. Did they ever use a KNOB to raise the music volume aloud ??? 017 ;******************************************************************** 018 NOLIST 019 INCLUDE 'derivative.inc' 020 LIST ; ..storage and THIS is it's place 021 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 022 ; 2) DEFINES 023 ram: SET Z_RAMStart ; $80 024 rom: SET ROMStart ; $2080 025 initStack: EQU RAMEnd + 1 ; $1800=$17FF+1. SP = $17FF 026 COP_Disable: EQU $42 027 ;==================================================================== 028 BITCOPY MACRO SrcAdd, SrcBit, DstAdd, DstBit 029 ; Example: BITCOPY Src, 3, Dst, 5 ; Use any variables: DIR or EXT 030 lda \1 031 bit #1<<(\2&$07) ; If SrcAdd[SrcBit] == 0 032 beq \@Clr_DstAdd ; ..goto Clr_DstAdd[DstBit] 033 ;Set_DstAdd[DstBit]: ; Else, Set_DstAdd[DstBit] 034 lda \3 035 ora #1<<(\4&$07) 036 bra \@cont 037 ;Clr_DstAdd[DstBit]: 038 \@Clr_DstAdd: 039 lda \3 040 and #~(1<<(\4&$07)) 041 \@cont: 042 sta \3 043 ENDM 185 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 044 ;==================================================================== 045 ; Global Variables 046 ORG ram 047 ADC_tmp1 DS.B 1 048 ADC_tmp2 DS.B 1 049 ;******************************************************************** 050 ; MAIN PROGRAM HEADER: 051 ABSENTRY Main 052 ORG rom 053 Main: lda #COP_Disable ; RSTE=0: PTA5 is NOT for ~RESET 054 sta SOPT1 ; ..System Options 1 055 ldhx #initStack ; Set up SP 056 txs ; ... 057 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 058 ; This is the 'MCU_Init' from the Original C program. However, I am 059 ; ..using POR (Power On Reset) defaults: Clock to EVERY peripheral. 060 ; 061 ; MCU_Init 062 ;; lda #$23 ; Enable Bus Clock to the ADC module 063 ;; sta SCGC1 064 ;; clra ; Disable unused peripherals Bus clock 065 ;; sta SCGC2 066 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 067 ; GPIO_Init 068 ; Init LEDs PORTs PTCD (bits 0‐5) and PTED (bits 6‐7) 069 ; 070 ; NOTE: For OUTPUT Pins, always Set‐up first the initial value you 071 ; desire for them, and THEN program the pin for Output. 072 mov #%11110000, PTCD ; Turn on 4 leds, to show program 073 mov #%11110000, PTED ; ..start (0? Led ON. 1? Led OFF) 074 mov #%00011111, PTCDD; Set Ports bits for output: C=5, D=2 075 mov #%11000000, PTEDD; ..(they drive the LEDs on DEMOQE128 076 ; NOTE: PTC5 is programmed for INPUT so writting to PTC5 does NOTHING 077 ;******************************************************************** 078 ; NOTE: Genius working at PEMicro use same PTC5 pin to both, drive a 079 ; LED, AND FOR DISABLING COMMUNICATIONS, via Jumper J8, THANKS! 080 ; So, if you use BOTH COMM1 and Leds, do NOT use PTC5 associated LED. 081 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 082 ; ADC_configuration 083 ; Int. disable. Continuous conversion 084 mov #$20, ADCSC1 ; ..mode and channel 0 active 085 clr ADCSC2 ; Software trigger selected 086 mov #$30, ADCCFG ; Input clock/2; Long Sample time 087 ; ..config; 8‐bit conversion 088 clr APCTL1 ; ADC0 pin disable; it works as GPIO... 089 ; =================================================================== 090 bset APCTL1_ADPC0, APCTL1; Select channel for ADC0 input 186 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 091 bset ADCSC1_AIEN, ADCSC1 ; Enable ADC interrupt 092 cli ; Global Enable CPU Interrupts. 093 bra * 094 ;******************************************************************** 095 ; interrupt VectorNumber_Vadc ADC_ISR 096 ADC_ISR: 097 ;******************************* 098 ; NOTE: Reading ADCRL clear COCO Flag, AS IT SHOULD BE DONE ON 099 ; ..ALL PERIPHERALS THAT HAVE DATA BUFFERS TO READ OR WRITE!!! 100 ;******************************* 101 lda ADCRL ; Negate before display, THANKS PE Micro! 102 coma ; ..(LEDs turn on with 0's) 103 sta ADC_tmp1 104 ; Exchange bit positions, THANKS PE Micro! 105 BITCOPY ADC_tmp1, 0, ADC_tmp2, 7 106 BITCOPY ADC_tmp1, 1, ADC_tmp2, 6 107 BITCOPY ADC_tmp1, 2, ADC_tmp2, 5 108 BITCOPY ADC_tmp1, 3, ADC_tmp2, 4 109 BITCOPY ADC_tmp1, 4, ADC_tmp2, 3 110 BITCOPY ADC_tmp1, 5, ADC_tmp2, 2 111 BITCOPY ADC_tmp1, 6, ADC_tmp2, 1 112 BITCOPY ADC_tmp1, 7, ADC_tmp2, 0 113 mov ADC_tmp2, PTCD ; Move ADC value to port C 114 mov ADC_tmp2, PTED ; ..and to port E 115 rti 116 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 117 nop ; <<<NEEDED by CodeWarrior 10.1&2 (not 6.3). INCREDIBLE<<< 118 ; This 'nop' MAY be removed for CW 6.3 ... 119 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 120 ; Interrupt Vectors 121 ORG Vadc 122 DC.W ADC_ISR 123 ORG Vreset 124 DC.W Main ; RESET. Maximum priority. Asynch. 125 END COMENTARIOS a ["Laboratorios\Proy\Buzzer\ADC_Reverse.asm"]: Se incluye la Macro BITCOPY, anteriormente explicada y usada: 028 BITCOPY MACRO SrcAdd, SrcBit, DstAdd, DstBit Dos variables Globales temporales (Bytes) para manipular el valor de la conversión: 046 ORG ram 047 ADC_tmp1 DS.B 1 048 ADC_tmp2 DS.B 1 187 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 Se programan los LEDs y el ADC como en el ejercicio anterior; y se culmina con una espera eterna, mediante el BRA *. La ISR del conversor comienza como antes, leyendo el BUFFER: ADCRL y negándolo (COMA: 1's COMPLEMENT ACCUMULATOR), y lo almacena en ADC_tmp1: 101 lda ADCRL ; Negate before display, THANKS PE Micro! 102 coma ; ..(LEDs turn on with 0's) 103 sta ADC_tmp1 Usa ADC_tmp1 como variable SRC para la Macro BITCOPY, y cruza los bits almacenándolos en la segunda variable temporal, ADC_tmp2. Note cómo copia el bit 0 al 7, el 1 al 6, 2 al 5, 3 al 4, 4 al 3, 5 al 2, 6 al 1 y finalmente, el bit 7 al bit 0. 104 ; Exchange bit positions, THANKS PE Micro! 105 BITCOPY ADC_tmp1, 0, ADC_tmp2, 7 106 BITCOPY ADC_tmp1, 1, ADC_tmp2, 6 107 BITCOPY ADC_tmp1, 2, ADC_tmp2, 5 108 BITCOPY ADC_tmp1, 3, ADC_tmp2, 4 109 BITCOPY ADC_tmp1, 4, ADC_tmp2, 3 110 BITCOPY ADC_tmp1, 5, ADC_tmp2, 2 111 BITCOPY ADC_tmp1, 6, ADC_tmp2, 1 112 BITCOPY ADC_tmp1, 7, ADC_tmp2, 0 Terminada la reasignación de bits, los presentamos en los 8 LEDs y terminamos con RTI: 113 mov ADC_tmp2, PTCD ; Move ADC value to port C 114 mov ADC_tmp2, PTED ; ..and to port E 115 rti Dado que esta IRQ no resguarda por programa el registro H, usted tiene que asegurarse de que la Maco BITCOPY TAMPOCO USE el registro Índice H:X. DETALLES, DETALLES... UN PUNTO MÁS: Como el POTENCIÓMETRO TAMBIÉN SE CABLEÓ AL REVÉS: significa que al estar completamente a la izquierda entrega el mayor voltaje (3V), y al rotarlo hacia la derecha baja el voltaje linealmente hasta llegar a CERO V, una truco para arreglar ese problema, por software, es convertir las medidas que en hexadecimal van de $FF a $00 (de izquierda a derecha), en medidas que vayan de $00 a $FF. Se ve que basta con NEGAR la medida del conversor para solucionar el problema del alambrado ilógico del potenciómetro. Pero, YA HAY UN 'COMA' para resolver el problema de que los LEDs encienden al revés, en CERO: 102 coma ; ..(LEDs turn on with 0's) Recuerde que negar dos veces equivale a NO NEGAR. Por tanto, NO HAY NECESIDAD del 'COMA de la línea 102; basta con eliminarla, o comentarla, y ahora todo parecerá funcionar bien. 188 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 36) Contador de UNOS. Contar los bits que están en uno, o en cero, en una variable, es un clásico en los cursos de programación. En C dos son las soluciones más publicitadas; la primera consiste en tener una variable auxiliar que vale ...001 y se va desplazando a la izquierda, N veces (8 para un byte): ...001 ...010 ...100. Cada vez se hace un AND con el operando y si el resultado es diferente de cero se incrementa el contador de unos. Una, más interesante aún, consiste en aplicar la expresión x &= (x ‐ 1); que ELIMINA de 'x' el UNO que está más a la izquierda: while( x ) { cnt ++; x &= ( x ‐ 1 ); } Una ventaja de esta última aproximación consiste en que sólo hay que repetir el ciclo mientras haya UNOS, no necesariamente 8 veces (8, para Bytes) El ejemplo que incluyo a continuación, hace uso de la instrucción de LEFT SHIFT, que desplaza la variable en cuestión, una posición a la izquierda y el bit que SALE queda almacenado en la bandera C (Carry). Se incrementa el contador cada vez que C == 1. Si se repite el código 8 veces (para Bytes), habremos contado el número de unos. Un atajo consiste en disponer de una segunda condición, para detener el procedimiento si la variable llega a cero (antes de terminar los 8 ciclos). Esto agrega la misma ventaja del último ejemplo en C, y también permite repetir el ciclo sólo mientras haya UNOS, no necesariamente 8 veces (8, para Bytes) Este ejercicio está pensado para ejecutarse con el DEBUGGER, paso a paso, a fin de poder introducir las entradas a mano (simuladas), vía PTAD. Usted puede emplear los interruptores de la tarjeta DEMOQE128 que usan, por ejemplo, el PTC, o usar otra variable puesta en el programa para ese propósito. ["Books\Interfacing‐HCS08\Examples\bitcount3.asm"] 01 ;******************************************************************** 02 ; BitCount3.asm, HCS08_CPU, Luis G. Uribe C., M05F2013 03 ; ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 04 ; Include files 05 NOLIST 06 INCLUDE 'derivative.inc' 07 LIST 08 ; ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 09 ; Parameter definitions 10 ram: SET Z_RAMStart ; $80. Cfr. 'MC9S08QE128‐U.inc' 11 rom: SET ROMStart ; $2080 12 initStack: EQU RAMEnd + 1 ; $17FF + 1 = $1800 13 COP_Disable: EQU $42 14 ; =================================================================== 189 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 15 ; Begin of Code Section 16 ABSENTRY Main ; Export symbol (DEBUGGER entry) 17 ORG rom ; $2080: HCS08 ROMStart (Flash) 18 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 19 ; *** ALWAYS *** include the following 4 instructions 20 Main: lda #COP_Disable 21 sta SOPT1 ; System Options 1 22 ldhx #initStack ; Init SP. H:X <‐ $1800 23 txs ; .. SP <= $17FF 24 ; ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 25 clra ; Initialize A to 0 26 ldx PTAD ; Load operand into X 27 Loop: lslx ; Shift out a bit of operand 28 beq Zero 29 adc #0 ; Accumulate it 30 bra Loop 31 Zero: adc #0 ; Accumulate it 32 bra * 33 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 34 nop ; <<<NEEDED by CodeWarrior 10.1&2 (not 6.3). INCREDIBLE<<< 35 ; This 'nop' MAY be removed for CW 6.3 ... 36 ;******************************************************************** 37 ; Interrupt Vectors 38 ORG Vreset 39 DC.W Main ; RESET: HCS08 Power On (PON) procedure. 40 END COMENTARIOS a ["Books\Interfacing-HCS08\Examples\bitcount3.asm"]: El contador será el Acumulador, y se lo inicializa en cero. Sólo para el ejercicio, se supone que la entrada que funciona como el operando a la que se le contarán los unos se toma del Puerto A (PTAD); como ya se dijo, usted puede colocar otra variable si así lo desea. El operando se lee desde PTAD y se almacena en la parte BAJA del registro Índice: X 25 clra ; Initialize A to 0 26 ldx PTAD ; Load operand into X El ciclo Desplaza el operando (X) un bit a la Izquierda; si encuentra que esta operación produjo Cero (Conjunto de Instrucciones Enriquecido, que compara el resultado de la mayoría de las operaciones contra CERO), se termina la acción. 27 Loop: lslx ; Shift out a bit of operand 28 beq Zero Si aún la variable no vale Cero, se suma el Carry al Acumulador; como cada bit que sale del operando al hacerse un desplazamiento, se almacena en el bit de Carry, C, éste quedará en Cero o en Uno según corresponda. Si se suma el Carry a Acumulador, se produce un Incremento si lo que se desplazó era un UNO; si era un CERO, la suma no 190 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 genera ningún incremento. De esta manera se van contabilizando cuántos unos había en el operando. Luego repite el ciclo. Note que no hay una instrucción simple para sumar el Carry al Acumulador; lo que hay es un compuesta, ADC, que suma un operando al Acumulador, y a la vez le suma el valor del Carry. Como lo único que queremos es sumar el Carry, y nada más, usamos ADC con un operando de CERO: ADC #0 29 adc #0 ; Accumulate it 30 bra Loop Si la variable sí llegó a Cero, se termina el procedimiento, sumando antes el Carry al Acumulador, lo que produce un Incremento si lo que se desplazó era un UNO. 31 Zero: adc #0 ; Accumulate it 32 bra * 37) Programación de un TONO en el BUZZER. Recuerde que estamos en una sección de "Misceláneos"; estos programas se incluyen como EJEMPLO para cuando necesiten emplear funcionalidades similares. No están construidos con todas las técnicas profesionales que se emplearon en las primeras secciones, que culminaron con las Colas y las Comunicaciones Seriales. Sin embargo, si llega a necesitar programar el Buzzer que trae la tarjeta de desarrollo DEMOQE128, esta es una GUÍA ELEMENTAL. NOTA: Este programa en Assembly Language lo adapté tomándolo de uno de Freescale: PWM.c. FUNCIONA BIEN y ha sido empleado durante varios trimestres como base para proyectos tales como Morse, Piano, Walkman, generación de sonidos diversos, etc. Sin embargo, ahora que fui a escribir la explicación línea por línea, encuentro que en PWM.c, y por tanto aquí también, se programa como salida la línea PTC0, sin que en este momento yo encuentre una justificación apropiada para esto. Ustedes pueden eliminar esa línea, y agradeceré me reporten si todo funciona bien, para removerlas del código en futuras ediciones. ["Laboratorios\Proy\Buzzer\Buzzer.asm"] 01 ;******************************************************************** 02 ; Buzzer.asm; Luis G. Uribe C., D01L2012 03 ; DESCRIPTION: This project uses the PWM functionality of the TPM1 04 ; module. A PWM signal is generated and when the MCU is interrupted. 05 ; PTB5 is connected to BUZZER in DEMOQE128 !! 06 ;******************************************************************** 07 NOLIST 08 INCLUDE 'derivative.inc' 09 LIST ; ..storage and THIS is it's place 10 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 11 ; 2) DEFINES 12 ram: SET Z_RAMStart ; $80 191 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 13 rom: SET ROMStart ; $2080 14 initStack: EQU RAMEnd + 1 ; $1800=$17FF+1. SP = $17FF 15 COP_Disable: EQU $42 16 ;******************************************************************** 17 ; MAIN PROGRAM HEADER: 18 ABSENTRY Main 19 ORG rom 20 Main: lda #COP_Disable ; RSTE=0: PTA5 is NOT for ~RESET 21 sta SOPT1 ; ..System Options 1 22 ldhx #initStack ; Set up SP 23 txs ; ... 24 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 25 ; MCU_Init 26 lda #$20 ; Enable Bus Clock to the TPWM1 module 27 sta SCGC1 28 clra ; Disable unused peripherals Bus clock 29 sta SCGC2 30 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 31 ; GPIO_Init 32 clr PTCD ; Put 0's in PTC0 port 33 mov #$01, PTCDD ; Configure PTC0 pin as output 34 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 35 ; TPM_configuration 36 mov #$68, TPM1C1SC ; Chan.1 IntEn; PWM Edge Align. Or $24 37 ;******************************* 38 ; PIANO FREQUENCY TABLE FROM DO (C)... SI (B), DO (C) 39 ; C 1 D 1 E 1 F 1 G 1 A 1 B 1 C 2 40 ; 2094 1976 1760 1661 1480 1319 1175 1047 41 FREQ EQU 2094 42 FREQ_4 EQU FREQ >> 2 ; 25% Dutty Cycle 43 ldhx #FREQ ; Really... it is PERIOD... 44 sthx TPM1MOD 45 ldhx #FREQ_4 ; 25% Dutty Cycle 46 sthx TPM1C1V 47 mov #%1000, TPM1SC ; Bus_rate_clock/1 = TPM Clock Source 48 ; More Dividers: Table 16‐4 in 02MC9S08QE128RM(ReferenceManual)‐U.pdf 49 ; mov #%1111, TPM1SC ; Bus_rate_clock/128= TPM Clock Source 50 ; mov #%1110, TPM1SC ; Bus_rate_clock/64 = TPM Clock Source 51 ; mov #%1000, TPM1SC ; Bus_rate_clock/1 = TPM Clock Source 52 ;==================================================================== 53 cli 54 bra * 55 ;******************************************************************** 56 ; interrupt VectorNumber_Vtpm1ch1 TPM_ISR 57 TPM_ISR: 58 lda TPM1C1SC ; Clear TPWM flags 192 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 59 bclr 7, TPM1C1SC ; Two‐step flag acknowledgement 60 rti 61 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 62 nop ; <<<NEEDED by CodeWarrior 10.1&2 (not 6.3). INCREDIBLE<<< 63 ; This 'nop' MAY be removed for CW 6.3 ... 64 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 65 ; Interrupt Vectors 66 ORG Vtpm1ch1 67 DC.W TPM_ISR 68 ORG Vreset 69 DC.W Main ; RESET. Maximum priority. Asynch. 70 END COMENTARIOS a ["Laboratorios\Proy\Buzzer\Buzzer.asm"]: Los generadores de frecuencia que empleamos en este MCU, son fundamentalmente los PWM, que generan señales "moduladas" por amplitud de pulso, Pulse Width Modulation. En particular, se usa la funcionalidad PWM del módulo TPM1. 04 ; A PWM signal is generated and when the MCU is interrupted. 05 ; PTB5 is connected to BUZZER in DEMOQE128 !! La inicialización del MCU incluye habilitar el Bus Clock para el módulo TPM1; a los periféricos no utilizados se les deshabilita el Bus Clock. Usted debe revisar con el Reference Manual, si los valores aquí programados para habilitar el TPMWM1 son o no los estándar cuando hay un POR. De ser así, puede eliminar (como ya se hizo en el caso del ADC) este código sin ningún problema 25 ; MCU_Init 26 lda #$20 ; Enable Bus Clock to the TPWM1 module 27 sta SCGC1 28 clra ; Disable unused peripherals Bus clock 29 sta SCGC2 Lo siguiente es programar el terminal PTC0 como salida. Como dije anteriormente, NO encuentro ahora una JUSTIFICACIÓN apropiada para esto. Luego de que usted logre que el programa funcione (¡TODOS funcionan!), eliminen esas dos (2) líneas y reporte si todo funciona bien, para proceder a removerlas del código en futuras ediciones. O si alguien encuentra algún motivo plausible para habilitar PTC0 como salida... Yo imagino que es una imprecisión del programa original PWM.c 31 ; GPIO_Init 32 clr PTCD ; Put 0's in PTC0 port 33 mov #$01, PTCDD ; Configure PTC0 pin as output 193 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 La configuración del TPM consiste en programar las interrupciones del Canal 1, y selecciona "alineación por Eje" para el PWM (Edge Align). Revise el Reference Manual para ver qué otras alternativas existen. 35 ; TPM_configuration 36 mov #$68, TPM1C1SC ; Chan.1 IntEn; PWM Edge Align. Or $24 Los valores que coloco en TPM1MOD (cfr. Reference Manual) y en TPM1C1V para generar UNA escala del piano (8 notas BLANCAS, no hay NEGRAS), SON EMPÍRICOS (la noche anterior a la primera vez que íbamos a usar la escala musical, me la pasé cambiando valores hasta lograr esta lista. En nuestro DEMOQE128, no es sólo cuestión de conocer las frecuencias de la escala; hay que ver la RESPUESTA del transductor de sonido, o BUZZER). Usted puede ajustar estas cantidades a sus necesidades pero... todos los proyectos, hasta ahora, los han usado con éxito relativo. Recuerden que estos valores son de Período, no de Frecuencia. 38 ; PIANO FREQUENCY TABLE FROM DO (C)... SI (B), DO (C) 39 ; C 1 D 1 E 1 F 1 G 1 A 1 B 1 C 2 40 ; 2094 1976 1760 1661 1480 1319 1175 1047 Esa es la escala completa. En este ejercicio se GENERARÁ UNA SÓLA NOTA. 41 freq equ 2094 Al generar PWM, un valor fundamental es la Frecuencia de la onda; el otro es el llamado Dutty Cycle, que es el porcentaje, de 0% a 100%, que la señal debe estar en UNO. Después de probar durante algún tiempo, encontré experimentalmente que el Dutty Cycle, para obtener la mayor intensidad sonora (volumen) sobre el Buzzer de la tarjeta de demostración, DEMOQE128, era del 25% (1/4). Por eso el cálculo de FREQ / 4. Esto es absolutamente arbitrario y, a lo mejor, lo que funciona bien en mi transductor piezoeléctrico, puede no ser lo más apropiado para el de su tarjeta de desarrollo. Hay que experimentar. 42 FREQ_4 EQU FREQ >> 2 ; 25% Dutty Cycle NOTA: Recuerde que aquellos los cálculos tales como el definido en la línea 42 (FREQ >> 2), se realizan en Assembly Time, no en Run Time; es decir, lo ejecuta el Assembler, y no el MCU. Se programa entonces el Período en el registro TPM1MOD (cfr. el Reference Manual para toda esta explicación) 43 ldhx #FREQ ; Really... it is PERIOD... 44 sthx TPM1MOD Y en el registro TPM1C1V, en donde se define el Dutty Cycle, colocamos 1/4 del valor del Período. 45 ldhx #FREQ_4 ; 25% Dutty Cycle 46 sthx TPM1C1V 194 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 Escogemos la Fuente de Reloj para el TMP: 47 mov #%1000, TPM1SC ; Bus_rate_clock/1 = TPM Clock Source Una lista adicional de posibles divisores es la siguiente, según la Tabla 16‐4 del 02MC9S08QE128RM(ReferenceManual)‐U.pdf 49 ; mov #%1111, TPM1SC ; Bus_rate_clock/128= TPM Clock Source 50 ; mov #%1110, TPM1SC ; Bus_rate_clock/64 = TPM Clock Source 51 ; mov #%1000, TPM1SC ; Bus_rate_clock/1 = TPM Clock Source Finalmente se activan las interrupciones en el CPU, y se simula un HALT con el BRA *: 53 cli 54 bra * La Rutina de Interrupciones (TPM_ISR) realiza el PROTOCOLO de Reconocimiento de Interrupciones (Interrupt Acknowledge) para este periférico, que tiene dos (2) pasos, siempre según el Reference Manual: Se lee el registro TPM1C1SC, y a continuación se borra (BCLR) en ese mismo registro, el bit 7 (CH1F: Ready Flag del Canal 1) 56 ; interrupt VectorNumber_Vtpm1ch1 TPM_ISR 57 TPM_ISR: 58 lda TPM1C1SC ; Clear TPWM flags 59 bclr 7, TPM1C1SC ; Two‐step flag acknowledgement 60 rti 38) Un "WALKMAN" Elemental. Hay una gran cantidad de aspectos que habría que programar para interpretar automáticamente una melodía en un dispositivo electrónico, tipo Walkman, pero los dos esenciales son altura (frecuencia) y duración. Por simplicidad, este ejercicio pretende interpretar una canción de Una Sola Voz, con un solo instrumento musical (a diferencia de las obras en general, que emplean múltiples voces e instrumentos). Se basa en el programa anterior, al que le han agregado varias cosas: una tabla que represente precisamente cada nota, o punto de sonido, mediante los dos aspectos referidos, frecuencia y duración. En realidad, como vimos en el ejercicio anterior, en lugar de la frecuencia se colocará el período, y se incluyen sólo algunas de las Duraciones estándar de la notación musical (Redonda, Blanca con Puntillo, Blanca, Negra), que se han definido en milisegundos, mediante una asignación aproximada y arbitraria, y que puede modificarse. Una melodía se interpretará de la siguiente manera: Se comienza al principio de la tabla, se lee el valor de altitud (que en nuestro caso, como ya dijimos, no es frecuencia, sino su inverso: período), se programa el oscilador que está conectado a la pequeña cornetica del DEMOQE128, o Buzzer, y se emplea el parámetro correspondiente a la duración para llamar una de las rutinas de Esperar Milisegundos, incluida en la librería de Timers que ya estudiamos. 195 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 Al expirar el lapso programado, se procede con la siguiente entrada (de dos posiciones) de la tabla y así hasta su terminación. ["Laboratorios\Proy\Buzzer\Walkman.asm"] 001 ;******************************************************************** 002 ; Walkman.asm; Luis G. Uribe C., D01L2012 L09D2013 003 ; DESCRIPTION: This project uses the PWM functionality of the TPM1 004 ; module. A PWM signal is generated and when the MCU is interrupted, 005 ; the Dutty cycle is incremented in 1. PTB5 is BUZZER in DEMOQE128 !! 006 ;******************************************************************** 007 NOLIST 008 INCLUDE 'derivative.inc' 009 LIST 010 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 011 ; 2) DEFINES 012 ram: SET Z_RAMStart ; $80 013 rom: SET ROMStart ; $2080 014 ;;initStack: EQU RAMEnd + 1 ; $1800=$17FF+1. SP = $17FF 015 ;;COP_Disable: EQU $42 016 ;==================================================================== 017 ; Global Variables 018 ORG ram ; <<<Put this *BEFORE* 'timers8HS.inc'<<< 019 NOLIST 020 include 'timers8HS.inc' ; <<<TIMERS8: Code, Vars & Macros 021 LIST 022 SongPtr: DS.W 1 023 NNotes: DS.W 1 024 NoteTime: DS.W 1 025 DCycle: DS.W 1 026 ;******************************************************************** 027 ; MAIN PROGRAM HEADER: 028 ABSENTRY Main 029 ORG rom 030 ;******************************* 031 ; PIANO FREQUENCY TABLE FROM DO (C)... SI (B), DO (C) 032 ; C 1 D 1 E 1 F 1 G 1 A 1 B 1 C 2 033 ; 2094 1976 1760 1661 1480 1319 1175 1047 034 C1: EQU 2094 035 D1: EQU 1976 036 E1: EQU 1760 037 F1: EQU 1661 038 G1: EQU 1480 039 A1: EQU 1319 040 B1: EQU 1175 041 C2: EQU 1047 042 Div: EQU 0 043 ; Standard Durations in Music: 044 ; 045 r: EQU 2000 >> Div ; Redonda 046 b_: EQU 1500 >> Div ; Blanca con Puntillo 196 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 047 b: EQU 1000 >> Div ; Blanca 048 n: EQU 500 >> Div ; Negra 049 ;;;Song: DC.W C1,b_, D1,n 050 ;;; DC.W E1,b_, G1,b 051 ;;; DC.W C2,n, B1,n, A1,n, G1,n 052 ;;; DC.W A1,b, G1,b 053 ;;; DC.W F1,n, A1,n, E1,n, G1,n 054 ;;; DC.W F1,n, D1,n, C1,b 055 ;;;SongSize EQU (* ‐ Song) / 4 056 ; Song table is formed by NOTE & DURATION pair of 16‐bit values 057 ; 058 Song: DC.W C1,b, D1,b 059 DC.W E1,b, F1,b 060 DC.W G1,b, A1,b, B1,b, C2,b 061 DC.W C2,b, B1,b, A1,b, G1,b 062 DC.W F1,b, E1,b 063 DC.W D1,b, C1,r 064 DC.W 0,1 065 SongSize EQU (* ‐ Song) / 4 066 ;******************************* 067 Main: lda #COP_Disable ; RSTE=0: PTA5 is NOT for ~RESET 068 sta SOPT1 ; ..System Options 1 069 ldhx #initStack ; Set up SP 070 txs ; ... 071 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 072 ; This is the 'MCU_Init' from the Original C program. However, I am 073 ; ..using POR (Power On Reset) defaults: Clock to EVERY peripheral. 074 ; 075 ; MCU_Init 076 ;; lda #$20 ; Enable Bus Clock to the TPWM1 module 077 ;; sta SCGC1 078 ;; clra ; Disable unused peripherals Bus clock 079 ;; sta SCGC2 080 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 081 ; GPIO_Init 082 clr PTCD ; Put 0's in PTC0 port 083 mov #$01, PTCDD ; Configure PTC0 pin as output 084 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 085 ; TPM_configuration 086 mov #$68, TPM1C1SC ; Chan.1 IntEn; PWM Edge Align. Or $24 087 mov #%1000, TPM1SC ; Bus_rate_clock/1 = TPM Clock Source 088 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 089 Init8Timers ; Init Timers & Gloabal Enable CPU inter‐ 090 cli ; ..rupts (TPM & RTC are already enabled) 091 ;==================================================================== 092 ldhx #SongSize ; Get SongSize, constant representing # of 093 sthx NNotes ; ..NOTES; store it into variable 'NNotes' 094 beq EndSong ; If NNotes is 0, finish the song... 095 ldhx #Song ; Get Address of the Song: '#Song' it is! 096 Loop: sthx SongPtr ; Store Song's Address into SongPointer & 197 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 097 ldhx , X ; ..using it, load the next NOTE into H:X 098 sthx TPM1MOD ; Store into TPM1MOD to fix TPM1's PERIOD 099 sthx DCycle ; Store into DCycle too 100 ldhx #DCycle ; Get DCycle ADDRESS into H:X to be used 101 lsr , X ; ..as a pointer to DCycle. Divide DCycle 102 lsr , X ; ..by 4 to get 25% Dutty Cycle 103 ldhx , X ; Using DCycle address in H:X, get DCycle 104 sthx TPM1C1V ; ..value into H:X, and move it to TPM1C1V 105 ; ..to set the Dutty Cycle... 106 ldhx SongPtr ; Get the Song Pointer; 107 aix #2 ; ..add #2 to point to next DURATION 108 sthx SongPtr ; ..and save new value. 109 ldhx , X ; Using this new address, get the next 110 sthx NoteTime ; ..Note TIME from Song Table 111 WaitMS_on 0, NoteTime ; Now TPM1 generates PWM pulses to drive 112 ; ..buzzer; Wait here for the desired time 113 ldhx NNotes ; At end get NNotes, decrease by 1 and see 114 aix #‐1 ; ..if the Song is done. 115 sthx NNotes ; .. 116 beq EndSong ; .. 117 ldhx SongPtr ; If not, load address of next Song table 118 aix #2 ; ..possition, point it to the next NOTE 119 bra Loop ; ..and Loop again 120 ;==================================================================== 121 EndSong: 122 clr TPM1C1SC ; Chan.1 IntDisable 123 bra * ; ..and Wait Forever 124 ;******************************************************************** 125 ; Interrupt VectorNumber_Vtpm1ch1 TPM_ISR 126 TPM_ISR: 127 lda TPM1C1SC ; Clear TPWM flags in this Two‐step flag 128 bclr 7, TPM1C1SC ; ..acknowledgement to reenable TPM1 INTs. 129 rti ; Return from Interrupt 130 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 131 ; RTC Interrupt Service Routine 132 RTC_INTERRUPT: 133 TIMERS8ISR 134 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 135 nop ; <<<NEEDED by CodeWarrior 10.1&2 (not 6.3). INCREDIBLE<<< 136 ; This 'nop' MAY be removed for CW 6.3 ... 137 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 138 ; Interrupt Vectors 139 ORG Vtpm1ch1 140 DC.W TPM_ISR 141 ORG Vrtc 142 DC.W RTC_INTERRUPT 143 ORG Vreset 144 DC.W Main ; RESET. Maximum priority. Asynch. 145 END 198 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 COMENTARIOS a ["Laboratorios\Proy\Buzzer\Walkman.asm"]: Recordar siempre el sitio apropiado para cada INCLUDE: 017 ; Global Variables 018 ORG ram ; <<<Put this *BEFORE* 'timers8HS.inc'<<< 019 NOLIST 020 include 'timers8HS.inc' ; <<<TIMERS8: Code, Vars & Macros 021 LIST Hay cuatro (4) variables de 16 bits (WORDS): ‐ SongPtr con el apuntador a la canción (a la tabla que la representa, y que está compuesta por PAREJAS de valores: Período y Duración) ‐ NNotes, que lleva la cantidad de notas (de DOS valores) faltantes para finalizar la canción ‐ NoteTime, donde se almacena temporalmente la duración de cada nota, para emplear con facilidad la función de la librería de Timers ‐ DCycle, en donde se calcula el Dutty Cycle para cada nota (empleando la misma aproximación del ejercicio anterior) 022 SongPtr: DS.W 1 023 NNotes: DS.W 1 024 NoteTime: DS.W 1 025 DCycle: DS.W 1 Las definiciones de las notas (sus ALTURAS, aquí: sus períodos) son las mismas introducidos en el ejercicio anterior: 031 ; PIANO FREQUENCY TABLE FROM DO (C)... SI (B), DO (C) 032 ; C 1 D 1 E 1 F 1 G 1 A 1 B 1 C 2 033 ; 2094 1976 1760 1661 1480 1319 1175 1047 034 C1: EQU 2094 035 D1: EQU 1976 036 E1: EQU 1760 037 F1: EQU 1661 038 G1: EQU 1480 039 A1: EQU 1319 040 B1: EQU 1175 041 C2: EQU 1047 A continuación se definen algunas de las Duraciones estándar de la notación musical (r: Redonda, b_: Blanca con Puntillo, b: Blanca, n: Negra), en milisegundos, como se dijo antes, mediante asignación aproximada y arbitraria. Para modificar con cierta facilidad estas duraciones, se ha incluido un parámetro, Div, mediante el cual se las puede dividir por 2, 4, etc. Así se logra que la canción vaya más rápido, si es lo deseado: 199 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 042 Div: EQU 0 043 ; Standard Durations in Music: 044 ; 045 r: EQU 2000 >> Div ; Redonda 046 b_: EQU 1500 >> Div ; Blanca con Puntillo 047 b: EQU 1000 >> Div ; Blanca 048 n: EQU 500 >> Div ; Negra Como ejemplo, no hemos incluido una verdadera canción sino una ESCALA que sube desde la nota más baja, C1, hasta la más alta C2, y luego se regresa hasta el principio. Para finalizar, hemos agregamos a la tabla, luego de la canción (o escala), una entrada con un período de CERO y una duración de un Milisegundos. Su propósito es que NO quede sonando indefinidamente el Buzzer (CERO Período lo silencia). 058 Song: DC.W C1,b, D1,b 059 DC.W E1,b, F1,b 060 DC.W G1,b, A1,b, B1,b, C2,b 061 DC.W C2,b, B1,b, A1,b, G1,b 062 DC.W F1,b, E1,b 063 DC.W D1,b, C1,r 064 DC.W 0,1 065 SongSize EQU (* ‐ Song) / 4 Como se ha sugerido, el NÚMERO DE NOTAS (en la tabla) lo calcula el Assembler empleando el símbolo SongSize que se coloca LUEGO de la tabla y que en este caso tiene el valor: SongSize = (* ‐ Song) / 4; es decir, SongSize es igual a la posición ACTUAL (*), que es justo la POSICIÓN posterior a la tabla, menos el PRINCIPIO de la tabla (Song), dividido por cuatro (4), pues cada NOTA tiene cuatro (4) BYTES. La inicialización: GPIO_Init y TPM_configuration, ya las hemos repasado varias veces. Se inicializan los Timers: 089 Init8Timers ; Init Timers & Global Enable CPU inter‐ 090 cli ; ..rupts (TPM & RTC are already enabled) Y ahora sí comienza el código nuevo, para interpretar una canción: Primero se inicializa la variable NNotes, que indica cuántas notas faltan para finalizar la canción: 092 ldhx #SongSize ; Get SongSize, constant representing # of 093 sthx NNotes ; ..NOTES; store it into variable 'NNotes' Si NNotes fuera CERO, se terminaría la interpretación de la canción: 094 beq EndSong ; If NNotes is 0, finish the song... 200 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 Luego se almacena el PRINCIPIO de la canción, es decir, la DIRECCIÓN de la tabla Song, en la variable APUNTADORA para ese efecto: SongPtr 095 ldhx #Song ; Get Address of the Song: '#Song' it is! 096 Loop: sthx SongPtr ; Store Song's Address into SongPointer & Para comenzar, ese será el PRINCIPIO de la canción (o de la Tabla); luego será la posición ACTUAL de la tabla; por eso, ahí se establece el "Loop". Empleando el APUNTADOR, se lee el "siguiente" valor de la NOTA, AL MISMO registro H:X. 097 ldhx , X ; ..using it, load the next NOTE into H:X NOTA: Es GENIAL poder tener en H:X la dirección de lo que se quiere cargar EN EL PROPIO H:X !!! Se almacena, después, el valor de H:X: en el registro TPM1MOD, que define el PERÍODO, y en la variable DCycle, para calcular el Dutty Cycle: 098 sthx TPM1MOD ; Store into TPM1MOD to fix TPM1's PERIOD 099 sthx DCycle ; Store into DCycle too Luego se copia LA DIRECCIÓN de DCycle (#DCycle: NOTE EL # !!) en H:X, y con dos LSR, Logical Shift Right, se divide el valor inicial por CUATRO (4) (que es el valor EXPERIMENTAL con el cual conseguí que sonara MEJOR el Buzzer en MI tarjeta DEMOQE128): 100 ldhx #DCycle ; Get DCycle ADDRESS into H:X to be used 101 lsr , X ; ..as a pointer to DCycle. Divide DCycle 102 lsr , X ; ..by 4 to get 25% Dutty Cycle Después de dividir el Período por 4, se lo almacena en el registro TPM1C1V, que es donde se determina, finalmente, el Dutty Cycle: 103 ldhx , X ; Using DCycle address in H:X, get DCycle 104 sthx TPM1C1V ; ..value into H:X, and move it to TPM1C1V 105 ; ..to set the Dutty Cycle... Luego cargamos en el registro Índice, la dirección de la SIGUIENTE nota de la canción (que, al comenzar, es la PRIMERA nota): 106 ldhx SongPtr ; Get the Song Pointer; Como cada nota está compuesta de dos valores: Altura (representada por su período: 2 BYTES) y por su Duración, 2 BYTES también, una vez que se Apunta a la primera nota (su Período), hay que apuntar a la Duración; por eso se incrementa el H:X en DOS (2), que es lo que mide el PERÍODO: 107 aix #2 ; ..add #2 to point to next DURATION 108 sthx SongPtr ; ..and save new value. 201 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 Se almacena temporalmente ese nuevo valor en SongPtr y, usando esa nueva dirección, que corresponde a la DURACIÓN (TIME), se carga ese valor en H:X (Genial modo de Direccionamiento) y se lo almacena donde corresponde: en NoteTime: 109 ldhx , X ; Using this new address, get the next 110 sthx NoteTime ; ..Note TIME from Song Table Y, usando ese valor NoteTime, se llama mi rutina WaitMS_on (timer 0); así hemos logrado activar el valor de Período, y activar el Timer 0 para que espere durante NoteTime: 111 WaitMS_on 0, NoteTime ; Now TPM1 generates PWM pulses to drive 112 ; ..buzzer; Wait here for the desired time Al finalizar, se decrementa el valor de NNotes; se lo almacena (en NNotes), y si ya se llegó a CERO se sale del "Loop" (BEQ) y se termina la canción: EndSong: 113 ldhx NNotes ; At end get NNotes, decrease by 1 and see 114 aix #‐1 ; ..if the Song is done. 115 sthx NNotes ; .. 116 beq EndSong ; .. Si NNotes NO llegó aún a Cero, y por tanto la canción NO se ha terminado, se vuelve a cargar en el registro Índice la dirección de la PRÓXIMA nota, H:X; se hacer apuntar esa dirección a la SIGUIENTE nota y se vuelve al "Loop": 117 ldhx SongPtr ; If not, load address of next Song table 118 aix #2 ; ..possition, point it to the next NOTE 119 bra Loop ; ..and Loop again Para terminar, se deshabilitan las interrupciones de TMP1, y se simula un HALT: 121 EndSong: 122 clr TPM1C1SC ; Chan.1 IntDisable 123 bra * ; ..and Wait Forever La rutina de interrupciones, TPM_ISR, opera igual que en el ejercicio anterior y no comentaremos aquí más sobre ella. La rutina de interrupciones de la librería de Timers también funciona como de costumbre. 39) "SWITCH", vía "COMPUTED GOTO". Una manera muy eficiente de ejecutar la funcionalidad equivalente al SWITCH del lenguaje C, consiste en tomar la variable de Control del Switch, y en base a ella saltar al código del correspondiente CASE. Hay ciertas variaciones al respecto; en el siguiente ejemplo, los CASEs tienen que ser CONTIGUOS, del 0 en adelante: case 0: case 1: case 2:, etc., lo cual sirve en una gran cantidad de ocasiones, en específico, cuando se están implementando Finite State Machines, FSM. 202 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 En este ejemplo, también, se ha supuesto que el número de posiciones o estados será siempre POTENCIA DE DOS. Si ese no es el caso, hay que reestructurar un poco el programa para acomodarlo. Otras veces los CASEs pueden tener valores que no comienzan en Cero (0), o que sean números cualesquiera, no necesariamente consecutivos como en el ejercicio más simple. Esto puede solucionarse de muchas maneras, por ejemplo, mediante una serie de CBEQ. Lo cual es menos elegante que la solución del presente ejercicio, pero más general... Este ejercicio está pensado para ejecutarse con el DEBUGGER, paso a paso, a fin de poder introducir las entradas a mano, vía el puerto SIMULADO PTAD. Usted puede emplear los interruptores de la tarjeta DEMOQE128 que usan, por ejemplo, el PTC, o usar otra variable puesta en el programa para ese propósito. ["Laboratorios\FSM‐FiniteStateMachines\ComputedGoTo.asm"] 01 ;************************* ComputedGoTo.asm ************************* 02 ; Luis G. Uribe C., Implement "SWITCH"; D17F2013 J05D2013 03 ; 1) Use COMPUTED GOTO (Cases ARE sequential: 0, 1, 2, ...) 04 ; 05 ; This example is only meant to be Debugged/Simulated. For real, you 06 ; ..will need to debounce inputs, perhaps include some 'InputReady' 07 ; ..signal (debounced), use Timers and produce some required outputs. 08 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 09 ; Include files 10 NOLIST 11 INCLUDE 'derivative.inc' 12 LIST 13 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 14 ; 2) DEFINES 15 ram: SET Z_RAMStart ; <<<TIMERS8: <<< 16 rom: SET ROMStart ; HCS08 ROMStart (Flash) 17 initStack: EQU $1800 18 COP_Disable: EQU %01000010 ; $42 19 ;==================================================================== 20 ; 3) Global Variables 21 ORG ram 22 STATE: DS.W 1 23 ;******************************************************************** 24 ; Begin of Code Section 25 ABSENTRY Main ; Export symbol (ABSOLUTE Assembly select) 26 ORG rom 27 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 28 ; *** ALWAYS *** include the following 4 instructions 29 Main: lda #COP_Disable 30 sta SOPT1 ; System Options 1 31 ldhx #initStack ; Init SP 32 txs 33 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 34 clr STATE ; HIGH end always will be 0 in the example 203 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 35 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 36 Forever: 37 lda PTAD 38 and #LOW(Table_Mask) ; Make input < 8 (0..7), no mater what 39 lsla ; Multiply by 2 (Table is 2 bytes/entry) 40 sta STATE + 1 ; >>> BIG ENDIAN... REMEMBER ??? <<< 41 ldhx STATE 42 ldhx Table, X ; o = Table[ ix ]; 43 jmp , X 44 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 45 ; THIS IS THE WAY TO DECLARE *TABLES OF ADDRESSES* (Constants): 46 ; 1: They will go in ROM‐Flash (constants...) 47 ; 2: They have a NAME ('Table' in this example) 48 ; 3: You populate tables with values, using DC.W assembler directive, 49 ; because addresses are 16 bits long. 50 ; 4: NORMALLY, you need to know the LENGHT of the table. So you 51 ; put a marker at the end: 'Table_End:' in this example, the 52 ; assembler calculates lenght as: ( Table_End ‐ Table_Begin ) / 2 53 ; (You do NOT shall calculate anything the assembler will !) 54 Table: 55 DC.W START, S1, S2, S3 ; Any sequence for this example 56 DC.W S2, S3, S2, START 57 Table_End: 58 TSIZE: EQU ( Table_End ‐ Table ) / 2 59 Table_Mask: EQU TSIZE ‐ 1 ; TSIZE *IS* a power of 2 60 START: nop ; 'nop': Replace it with YOUR code! 61 bra Forever ; 'bra': use JMP if too far away 62 S1: nop 63 bra Forever 64 S2: nop 65 bra Forever 66 S3: nop 67 bra Forever 68 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 69 ; Interrupt Vectors 70 dummy_isr: ; <<< Must be placed in ROM Space 71 rti 72 ORG Vswi ; Vswi and Vreset 73 DC.W dummy_isr ; SWI 74 DC.W Main ; RESET. Maximum priority. Asynch. 75 END COMENTARIOS a ["Laboratorios\FSM-FiniteStateMachines\ComputedGoTo.asm"]: Al principio se INICIA la variable de Control: STATE, de 16 bits (2 Bytes), de la cual, en este ejercicio que sólo tiene 8 estados o posiciones en la tabla, sólo se utilizarán los 3 bits MENOS significativos y, en general, sólo se empleará el BYTE MENOS significativo de STATE. Por eso, se borra (CLR) SU BYTE **MÁS** SIGNIFICATIVO: 34 clr STATE ; HIGH end always will be 0 in the example 204 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 Recuerden: Esta máquina es BIG ENDIAN. Para borrar EL BYTE MÁS SIGNIFICATIVO de una variable de 16 bits, como nuestra variable 'STATE, hay que borrar la posición STATE; por eso', el 'CLR STATE' de la línea 34. OJO: 34 clr STATE ; HIGH end always will be 0 in the example NO BORRA TODA LA VARIABLE 'STATE', solo su parte MÁS SIGNIFICATIVA. El ciclo infinito para este ejercicio comienza leyendo la variable de Control, PTAD, en el acumulador, y asegurándose que está dentro del rango válido, que para este ejercicio es entre 0 y 7, inclusive: 36 Forever: 37 lda PTAD 38 and #LOW(Table_Mask) ; Make input < 8 (0..7), no mater what Como es un requisito para este ejemplo, que la tabla sea siempre potencia de DOS, se ha calculado más abajo el valor 'Table_Mask', que es igual a la potencia de 2 que define el tamaño (OCHO en este ejercicio), MENOS 1: SIETE, que en binario es: 00..111. Así, el 38 AND #LOW(Table_Mask) toma (LOW) los 8 bits menos significativos de 'Table_Mask' (00000111) y hace un AND entre esa Constante (el NUMERAL: #) y el Acumulador, con lo que, no importa cuál sea el valor alimentado, siempre quedará confinado a números entre 0 y 7, ambos inclusive. Luego que tenemos la variable de entrada apropiadamente VALIDADA, la multiplicamos por DOS (LSLA) porque la tabla es de DIRECCIONES (a dónde Saltar) y en esta máquina las direcciones son de 16 bits (DOS bytes) 39 lsla ; Multiply by 2 (Table is 2 bytes/entry) Ese valor, que es la Entrada, confinada, multiplicada por 2, se la almacena en la Variable STATE. Como sólo estamos usando el Byte MENOS significativo de STATE, se la almacena en 'STATE+1', porque esta máquina es BIG ENDIAN, y la posición MENOS SIGNIFICATIVA está DE ÚLTIMO: 40 sta STATE + 1 ; >>> BIG ENDIAN... REMEMBER ??? <<< Ahora, se toma el valor de STATE (16 bits, de los cuales ya hemos garantizado que el MSB es Cero), se lo carga en el registro Índice, H:X: 41 ldhx STATE Y empleando ese índice cargamos, en el mismo registro Índice, H:X, el valor 'Table + STATE'. Ese valor que se carga (observe bien!), corresponde a la DIRECCIÓN, sacada de la Tabla, de la rutina a donde hay que Saltar para ejecutar lo que corresponde a ese Número de Case, identificado por la variable de Control! Finalmente, se salta a dicha rutina. Se emplea a fondo el direccionamiento Indexado. Pocos MCUs de 8 bits tienen tanta exuberancia de Arquitectura! 42 ldhx Table, X ; o = Table[ ix ]; 43 jmp , X 205 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 Veamos ahora cómo se estructura la Tabla de Direcciones. En primer lugar, las entradas en la Tabla son Direcciones de las diferentes Rutinas que han de materializar la funcionalidad de cada Case; así que la Tabla normalmente irá en ROM (Flash). Podría pensarse en tablas dinámicas, que recibieran sus valores en RUN TIME, pero no es el caso de este ejemplo. Esas Tablas irían en RAM, y habría que inicializarlas mediante código. Las Tablas en ROM (Flash) las inicializa el Assembler, y el QUEMADOR del código en el MPU las copia inicializadas a la Flash de Programa. Segundo, las Tablas tienen una identificación, o nombre, que en nuestro ejemplo es "Table". En caso de tablas de Direcciones, se inicializan con valores declarados mediante la directiva de Assembler DC.W (Define Constant Word: 16 bits). Casi siempre es útil conocer el tamaño o longitud de la tabla, lo que hemos calculado siempre de una manera simple y automática: se coloca una etiqueta en la posición siguiente a la última de la tabla, se le resta la dirección de la posición de arranque, lo cual nos da el tamaño en BYTES. Si se desea calcular el tamaño en Cantidad de ELEMENTOS (en nuestro ejemplo, el número de direcciones almacenadas en la tabla), se divide por el número de bytes que mide la entidad almacenada, en este caso, se divide por 2, pues dos bytes tiene cada dirección: (Table_End ‐ Table_Begin)/2 Usted NUNCA tiene que hacer cálculos; para eso está trabajando en una COMPUTADORA. La tabla para nuestro ejemplo, que contiene una secuencia inventada, es la siguiente: 54 Table: 55 DC.W START, S1, S2, S3 ; Any sequence for this example 56 DC.W S2, S3, S2, START 57 Table_End: 58 TSIZE: EQU ( Table_End ‐ Table ) / 2 59 Table_Mask: EQU TSIZE ‐ 1 ; TSIZE *IS* a power of 2 En otra localidad se colocan las rutinas que materializarán cada CASE. Pueden estar colocadas antes o después de esta posición, pueden estar separadas por todo el programa (lo cual, si bien es posible, NO parece conveniente) o ir en cualquier orden: 60 START: nop ; 'nop': Replace it with YOUR code! 61 bra Forever ; 'bra': use JMP if too far away 62 S1: nop 63 bra Forever 64 S2: nop 65 bra Forever 66 S3: nop 67 bra Forever Hemos reemplazado todo lo que sería el código necesario para implementar cada uno de los CASEs, por NOPs, que usted puede cambiar a voluntad por las rutinas necesarias para sus programas particulares, según sea el caso. NOTAS: En caso que la etiqueta 'Forever' (en el ejemplo) esté muy apartada, por la cantidad de código necesario para los CASEs, en lugar de BRA Forever usted puede colocar JMP. 206 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 Si el número de elementos de la tabla, NO es potencia de 2, habrá que cambiar la metodología que aquí se aplicó, de confinar las variables de Control a números entre 0 y 7, mediante un AND con el #7. Una forma puede ser Comparando la variable de control con la parte Baja y la Alta del RANGO permitido para su ejemplo. Es IMPORTANTE garantizar que su programa, aunque sea por error en la información de entrada, NO saltará a ejecutar código que no sea apropiado: Si la tabla tiene 5 entradas, saltar a la posición 6 o a la 7... ES un error GRAVE. Pero observe: aunque la Información de Entrada no debe sobrepasar cierto rango; si se EXTRALIMITA por error en su adquisición, procesarla mediante un AND, o empleando comparaciones, para obligarla a permanecer confinada dentro de ese rango, PUEDE NO SER LA SOLUCIÓN. Es decir, en la vida real, ambas cosas son errores: a) ejecutar una rutina que se sabe que NO se corresponde con la entrada, y que seguramente NI EXISTE, y b) ejecutar la rutina que resulta de confinar obligadamente a la variable a que permanezca dentro del rango acordado. Por ejemplo, si la tabla tiene 8 valores, de las posiciones 0 a la 7, y la variable de Control indica, por error de lectura o por equivocación del operador, etc., que se debe ejecutar la RUTINA "10" (%1010), sería un ERROR GRAVE tratar de ejecutar dicha RUTINA "10" de la tabla, PORQUE NO EXISTE! pero también puede estar MUY MAL ejecutar la rutina 'DOS', que es en nuestro ejemplo lo que resulta al hacer un AND entre %1010 y %0111: %0010. Así que en un ejercicio real, usted tiene que decidir qué hacer si se produce un error; por ejemplo, avisar al operador y que él tome la decisión, tomar un valor "estimado" por un algoritmo apropiado, tomar un valor mínimo si es que la variable está por debajo de él, o máximo si se ubica por encima del techo, etc. Cada caso será distinto. Pero NO puede dejar que su variable de Control obligue a su CPU a ejecutar una rutina que usted ni siquiera escribió. 40) La FÁBRICA de CHOCOLATES. Este ejercicio está definido en el libro "Ingeniería Digital", que ha sido publicado como parte del curso de Arquitectura‐I. Allí puede ver de qué se trata. A continuación se incluye la solución. Observe que MUCHOS ejercicios de Redes Combinatorias, como la solución al problema del EDP CENTER, que también se encuentra en el libro "Ingeniería Digital", se resuelven de una manera Similar o IGUAL al problema de La Fábrica de Chocolates. 01 ["Laboratorios\Tables\FabricaDeChocolates.asm"] 02 ;******************************************************************** 03 ; FabricaDeChocolates.asm, Luis G. Uribe C., V15F2013 04 ; See how to read a Table. Use HX register as index to travel 'Table' 05 ; ‐ input comes from PTAD. You need to MASK out all not used bits. 06 ; Use AND with 0x07 [%00000111; #LOW(Table_Mask)] to Mask 'input'. 07 ; Main loop reads PTAD values (you will fake inputs from debugger); 08 ; ..read RESPONSES from Table, and output them to 'output' variable 09 ; ..(in real life, you will output this values to the output PORT) 207 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 10 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 11 ; Include files 12 NOLIST 13 INCLUDE 'derivative.inc' 14 LIST 15 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 16 ; 2) DEFINES 17 ram: SET Z_RAMStart ; <<<TIMERS8: <<< 18 rom: SET ROMStart ; HCS08 ROMStart (Flash) 19 initStack: EQU $1800 20 COP_Disable: EQU %01000010 ; $42 21 ;==================================================================== 22 ; 3) Global Variables 23 ORG ram 24 input: DS.W 1 ; DS.W: HX register needs 16 bits values.. 25 output: DS.B 1 26 ;******************************************************************** 27 ; Begin of Code Section 28 ABSENTRY Main ; Export symbol (ABSOLUTE Assembly select) 29 ORG rom 30 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 31 ; *** ALWAYS *** include the following 4 instructions 32 Main: lda #COP_Disable 33 sta SOPT1 ; System Options 1 34 ldhx #initStack ; Init SP 35 txs 36 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 37 clr input ; HIGH end always will be 0 in the example 38 Forever: 39 lda PTAD ; Be sure: input < 8 (0..7) 40 and #LOW(Table_Mask) 41 sta input + 1 ; >>> BIG ENDIAN... REMEMBER ??? <<< 42 ldhx input 43 lda Table, X ; o = Table[ ix ]; 44 sta output ; .. 45 bra Forever 46 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 47 ; THE FOLLOWING IS THE WAY TO DECLARE *TABLES OF CONSTANTS*: 48 ; 1: They go in ROM‐Flash (constants...) 49 ; 2: They have a NAME ('Table' in this example) 50 ; 3: You populate tables with values, using DC assembler directive 51 ; 4: NORMALLY, you need to know the LENGHT of the table. So you 52 ; put a marker at the end: 'Table_End:' in this example, the 53 ; assembler calculates the lenght as: 'Table_End ‐ Table_Begin' 54 ; (You do NOT like to calculate anything the assembler can !) 55 Table: 56 DC.B %100, %100, %100, %100 ; Stop,Half,Full are bits 57 DC.B %100, %010, %010, %001 ; ..b2, b1, b0 on 'output' 58 Table_End: 208 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 59 TSIZE: EQU Table_End ‐ Table 60 Table_Mask: EQU TSIZE ‐ 1 ; TSIZE *IS* a power of 2 61 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 62 ; Interrupt Vectors 63 dummy_isr: ; <<< Must be placed in ROM Space 64 rti 65 ORG Vswi ; Vswi and Vreset 66 DC.W dummy_isr ; SWI 67 DC.W Main ; RESET. Maximum priority. Asynch. 68 END A estas alturas, ustedes deben poder comprender por completo la solución que aquí presento, sin comentarios adicionales. 41) TABLAS de DATOS; Implementación del "FOR". Se muestra como leer una tabla para efectuar consecutivamente la expresión en C: o = Table[ ix ]; dentro de un ciclo FOR. En muchas aplicaciones la variable de salida ("o", output) será un Puerto del MCU, o una interfaz de comunicaciones seriales. Usted sabrá cambiar el ejercicio para acomodar sus necesidades específicas. Aproveché para definir la variable "o" en el Stack, cosa que ya aprendimos a hacer. Además, la variable 'ix', índice dentro de "Tabla", se ha definido como UNSIGNED. Usted puede designar en sus programas el calificativo SIGNED. Es importante resaltar que usted debe saber aplicar las Ramificaciones (Branches) APROPIADOS PARA CADA OCASIÓN. De lo contrario usted habrá cometido la misma TORPEZA que Andrew S. Tanenbaum al diseñar su fallido MIC (1, 2 y 3) para su libro de arquitectura. 01 ["Laboratorios\Tables\Tables0.asm"] 02 ;******************************************************************** 03 ; Tables0.asm, Luis G. Uribe C., V15F2013 04 ; See how to read a Table. Use HX register as index to travel 'Table' 05 ; ‐ "o" var has space reserved in Stack 06 ; ‐ See how to implement a FOR LOOP 07 ; ‐ In this example, 'ix' var is UNSIGNED char 08 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 09 ; Include files 10 NOLIST 11 INCLUDE 'derivative.inc' 12 LIST 13 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 14 ; 2) DEFINES 15 ram: SET Z_RAMStart ; <<<TIMERS8: <<< 16 rom: SET ROMStart ; HCS08 ROMStart (Flash) 209 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 17 initStack: EQU $1800 18 COP_Disable: EQU %01000010 ; $42 19 ;==================================================================== 20 ; Begin of Code Section 21 ABSENTRY Main ; Export symbol (ABSOLUTE Assembly select) 22 ORG rom 23 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 24 ; *** ALWAYS *** include the following 4 instructions 25 Main: lda #COP_Disable 26 sta SOPT1 ; System Options 1 27 ldhx #initStack ; Init SP 28 txs 29 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 30 pshh ; Reserve space for "o" var stored in 1,SP 31 For01_Init: ; for( ix = 0; ix < TSIZE; ix ++ ) 32 For01: ; I PREFER LABEL 'For01:', not For01_Init 33 clrx ; INIT FOR: ix = 0; 34 clrh ; .. 35 For01_Tst: ; In the example, ix IS 'unsigned' char; 36 cphx #TSIZE ; ..if( ! (ix < TSIZE ) goto For01_End; 37 ; ..NOTE: HERE (ix<8) you may use 'cpx'... 38 bhs For01_Exit ; ..Dont use 'BGT'; use 'BHS': ix IS uchar 39 For01_Code: 40 lda Table, X ; o = Table[ ix ]; 41 sta 1, SP ; .. 42 For01_End: 43 aix #1 ; ix ++. HERE (ix<8) you may use 'incx'... 44 bra For01_Tst 45 For01_Exit: 46 pulh ; restore Stack } //end main 47 bra * 48 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 49 ; THE FOLLOWING IS THE WAY TO DECLARE *TABLES OF CONSTANTS*: 50 ; 1: They go in ROM‐Flash (constants...) 51 ; 2: They have a NAME ('Table' in this example) 52 ; 3: You populate tables with values, using DC assembler directive 53 ; 4: NORMALLY, you need to know the LENGHT of the table. So you 54 ; put a marker at the end: 'Table_End:' in this example, the 55 ; assembler calculates the lenght as: 'Table_End ‐ Table_Begin' 56 ; (You do NOT like to calculate anything the assembler can !) 57 Table: 58 DC.B 7, 6, 5, 4, 3, 2, 1, 0 59 Table_End: 60 TSIZE: EQU Table_End ‐ Table 61 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 62 ; Interrupt Vectors 63 dummy_isr: ; <<< Must be placed in ROM Space 64 rti 65 ORG Vswi ; Vswi and Vreset 210 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 66 DC.W dummy_isr ; SWI 67 DC.W Main ; RESET. Maximum priority. Asynch. 68 END COMENTARIOS a ["Laboratorios\Tables\Tables0.asm"] Después de la rutinaria inicialización, se pasa a reservar espacio en el Stack para la variable "o"; esto se hace decrementando el SP, mediante un push de un byte: 30 pshh ; Reserve space for "o" var stored in 1,SP A continuación el código genérico para los FOR, que como se recordará, tienen a) una parte de Inicialización de variables, b) un Cuerpo con las instrucciones que usted quiere repetir dentro del ciclo y c) una parte de Finalización, luego de la cual está la Salida del FOR. La Inicialización aquí [ for( ix = 0; ix < TSIZE; ix ++ ) ] consiste en colocar en CERO la variable índice, que está en el Stack: 31 For01_Init: ; for( ix = 0; ix < TSIZE; ix ++ ) 32 For01: ; I PREFER LABEL 'For01:', not For01_Init 33 clrx ; INIT FOR: ix = 0; 34 clrh ; .. A continuación se verifica (TST) que todavía hay trabajo que hacer, [ ix < TSIZE; ]; si ya se terminó se va a la sección de salida, Exit: 35 For01_Tst: ; In the example, ix IS 'unsigned' char; 36 cphx #TSIZE ; ..if( ! (ix < TSIZE ) goto For01_End; 37 ; ..NOTE: HERE (ix<8) you may use 'cpx'... 38 bhs For01_Exit ; ..Dont use 'BGT'; use 'BHS': ix IS uchar Observe dos cosas importantes: en este ejemplo usted puede emplear simplemente comparaciones con X, el LSB del registro índice H:X, porque 'ix' solo asumirá valores inferiores a 8. Lo segundo es que no se pueden usar Ramificaciones (BRANCHES) de las definidas para números SIGNED, como BGT; tiene que revisar y emplear solamente las definidas para UNSIGNED, como BHS (o, si fuera el caso, hay un grupo de ellas definidas para AMBOS: Signed y Unsigned, como BEQ, BNE, etc.) La parte del Cuerpo de su programa en este caso es simplemente cargar de Table el elemento correspondiente a 'ix' y almacenarlo (como ejemplo) en la variable de salida 'o': 39 For01_Code: 40 lda Table, X ; o = Table[ ix ]; 41 sta 1, SP ; .. En la parte de finalización del FOR se incrementa el índice, [ix ++] y se continúa en la parte de TST del FOR, y el ciclo continúa: 42 For01_End: 211 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 43 aix #1 ; ix ++. HERE (ix<8) you may use 'incx'... 44 bra For01_Tst Se suele usar AIX para incrementar (#1) el índice H:X directamente, pero en este ejemplo en particular, en que el valor del índice no sobrepasará de 8, también podría haberse empleado INCX, que incrementa sólo X, la parte LSB de H:X Para finalizar el FOR, se deja balanceado el Stack (COSA FUNDAMENTAL) mediante el PULH, y se simula un HALT: 45 For01_Exit: 46 pulh ; restore Stack } //end main 47 bra * En este sencillo ejemplo, la tabla está compuesta de 8 números. Para diferenciarlos perfectamente del 'ix', he escogido los 8 números en REVERSA. Usted puede emplear los que le resulten más convenientes: 57 Table: 58 DC.B 7, 6, 5, 4, 3, 2, 1, 0 59 Table_End: 60 TSIZE: EQU Table_End ‐ Table POR ÚLTIMO, si su variable no estuviera en Stack, usted tendría que definirla en RAM Space y el Código cambiaría sutilmente de STA 1, SP en la línea 41, a STA VAR: Esto corresponde a un extracto del programa: ["Laboratorios\Tables\Tables1.asm"] no listado: 39 For01_Code: 40 lda Table, X ; o = Table[ ix ]; 41 STA VAR ; .. Aplica igual para un puerto de salida. Si fuera a transmitir los valores de la tabla por un canal de comunicación serial, tendría que reemplazar la línea 41 por el llamado a una rutina de transmisión (lo que ya hicimos en los ejercicios dedicados a COMUNICACIONES) 42) Last, but not Least: 8 Bits Rotate Left. Como recordará, las instrucciones de ROTATE (izquierda y derecha) dirigen al Carry, C, el bit que sale de la variable, y alimentan el otro extremo con C; esto producen una rotación de NUEVE (9) bits. Este ejercicio muestra cómo realizar la misma instrucción pero dentro de OCHO (8) bits. Corresponde a un Rotate LEFT, pero basta con cambiar las instrucciones ROLA y ROL por las equivalentes hacia la derecha, RORA y ROR, para invertir el sentido del giro de rotación: 01 ["Evaluaciones\2013‐01Ene\Evaluaciones\Ex#1\Rotate8.asm"] 02 ;******************************************************************** 212 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 03 ; Rotate8.asm, Luis G. Uribe C., D17F2013 04 ; ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 05 ; Include files 06 NOLIST 07 INCLUDE 'derivative.inc' 08 LIST 09 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 10 ; 2) DEFINES 11 ram: SET Z_RAMStart ; <<<TIMERS8: <<< 12 rom: SET ROMStart ; HCS08 ROMStart (Flash) 13 initStack: EQU $1800 14 COP_Disable: EQU %01000010 ; $42 15 ;==================================================================== 16 ; 3) Global Variables 17 ORG ram 18 var: DS.B 1 19 ;******************************************************************** 20 ; Begin of Code Section 21 ABSENTRY Main ; Export symbol (ABSOLUTE Assembly select) 22 ORG rom 23 ; ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 24 ; *** ALWAYS *** include the following 4 instructions 25 Main: lda #COP_Disable 26 sta SOPT1 ; System Options 1 27 ldhx #initStack ; Init SP 28 txs 29 ; ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 30 mov #%10101010, var 31 ; ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 32 lda var 33 rola 34 rol var 35 bra * 36 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 37 ; Interrupt Vectors 38 dummy_isr: ; <<< Must be placed in ROM Space 39 rti 40 ORG Vswi ; Vswi and Vreset 41 DC.W dummy_isr ; SWI 42 DC.W Main ; RESET. Maximum priority. Asynch. 43 END COMENTARIOS a ["Evaluaciones\2013-01Ene\Evaluaciones\Ex#1\Rotate8.asm"]: Variable que estará sujeta a la rotación de 8 bits: 17 ORG ram 18 var: DS.B 1 Inicialización con un valor arbitrario: bits 76543210 30 mov #%10101010, var 213 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 Se carga 'var' en el Acumulador y se rota el Acumulador a la izquierda, lo cual almacena el bit 7 de 'var' en el carry, C: 32 lda var 33 rola Resultando después del ROLA en: Acc = %0101010x C = 1 (original bit7 de 'var') La 'x' en el bit0 del Acc es porque el valor del carry C, es indeterminado para comenzar. Ahora se rota 'var' a la izquierda, con lo cual el C, que era el bit7 de 'var', alimenta su bit0: 34 rol var 35 bra * Resultando en: var = %01010101 (ROTATE LEFT en OCHO BITS) C = 1 (pero ya no se usa más) 214 3 Capítulo I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 PROGRAMACIÓN EN "C" Unix, Linux, iOS, Windows, QNX... hacen del lenguaje “C” el más usado desde 1970 (44 años). Ningún otro ostenta esa marca. NOTAS INICIALES S ugiero encarecidamente que cada vez que encuentre algún aspecto del Lenguaje C en los ejercicios que a continuación presento, que usted NO ENTIENDA, lo ANOTE por aparte, Y ME HAGA LLEGAR LA LISTA COMPLETA AL FINALIZAR la lectura de esta sección correspondiente al Lenguaje C. Servirá para QUEJARME NUEVAMENTE a ver si logramos que CAMBIEN DE UNA VEZ LOS DOS CURSOS DE PROGRAMACIÓN. Y a usted le dará una idea DE MUCHAS COSAS QUE LE FALTAN POR APRENDER DEL LENGUAJE. En su profesión, ES MUY IMPORTANTE MANEJAR EL "C", y aquí YA NO LE ENSEÑARÁN MÁS PROGRAMACIÓN. Así que le corresponde a usted estar DESCONTENTO y... ¡ESTUDIAR POR SU CUENTA! DÓNDE ESTAMOS U na vez que hemos aprendido los aspectos más importantes de la Arquitectura, que resumo a continuación, para estudiar las cuales nos hemos auxiliado con ejercicios hechos en el Lenguaje Ensamblador, se justifica poco trabajar en Assembler, y menos como profesionales. Sólo utilizamos el ASM como vehículo para comprender y aplicar los siguientes conceptos importantes: Configuraciones de las máquinas de Von Neumman vs. las Harvard Clases y relevancia de conjuntos de instrucciones CISC y RISC Poderío y abundancia de los modos de direccionamiento Importancia de los métodos de Entrada y Salida Elementos de información como el Stack Definición de variables locales, en el Stack Uso de métodos recursivos Direccionamientos indexados, vía SP y vía registro índice H:X Interrupciones Interrupciones anidadas Inversión de prioridades 215 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 Habilitación de interrupciones a cierta altura de las ISR Aplicación de instrucciones tan importantes como el SWI Uso de TPA y TAP para guardar el Processor Status Word En el área de la aplicación de Microcontroladores a los sistemas EMBEBIDOS, el uso del lenguaje "C" está muy extendido. Otros que se emplean en menor escala son Java, ciertos BASICs, FORTH... Por eso introduje la programación de MC9S08QE128 empleando el "C", que es el lenguaje usado en los cursos más avanzados de Arquitectura y Laboratorio de Proyectos. GENERALIDADES D ividiremos los ejercicios de C en tres partes: Introducción, con los programas más simples, comparados con los similares en Assembler; el manejo de temporizadores, en la que presento mi librería de Timers, con funcionalidades casi exactas a las de el capítulo anterior; la librería de de Comunicaciones Seriales, iguales a su contraparte en ASM, y la librería para el manejo de Colas, imprescindible para operar apropiadamente las Comunicaciones con flexibilidad. Las carpetas en donde se encuentran los ejercicios introductorios son: Labs‐C\Fibonacci y Labs‐C\Lab1 43) Programa INTRODUCTORIO en C, para HCS08, Comparativo con ASM. Consulte el programa Laboratorios\Lab1\02Fibonacci.asm como referencia para nuestro programa introductorio en C. Al principio, las diferencias entre los dos programas son mínimas, según se dará cuenta, pero a partir de 'mainLoop', los dos son (casi) IDÉNTICOS. ["Labs‐C\Fibonacci\060Fibonacci.c"] 01 // Fibonacci.c (02Fibonacci.asm), Luis G. Uribe C., M10D2013 02 // ****************************************************************** 03 // 02Fibonacci.asm, Luis G. Uribe C., V10A2009 V08J2012 04 // ADAPTED from: HCS08‐RS08_Assembler_MCU_Eclipse.pdf, Listing 4.1 05 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 06 // Include files 07 #include "derivative.h" // Include peripheral declarations 08 #include "Fibonacci_.h" 09 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 10 // Definición de variables (ds: byte por defecto) 11 byte Counter; 12 byte FiboRes; // Aquí va la respuesta 13 void main ( void ) /*()*/ 14 { 15 mainLoop: 16 clra; 216 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 17 cntLoop: 18 inca; 19 cbeqa(14, mainLoop); // Larger values cause overflow 20 sta(Counter); // Update global variable 21 bsr(CalcFibo); 22 sta(FiboRes); // Store result 23 lda(Counter); // ..Activate BREAKPOINT here to see.. 24 // .. 1 2 3 5 8 13 21 34 55 89 144 233 25 bra(cntLoop); // Next round 26 for( ;; ) { /* EMPTY FOR */ } 27 } //end main 28 // ================================================================== 29 // Function to compute Fibonacci numbers. Argument is in A 30 void CalcFibo () 31 { 32 dbnza(fiboDo); // Fibonacci Do 33 inca; 34 rts; 35 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 36 fiboDo: 37 psha; // The counter 38 clrx; // Second last = 0 39 lda(0x01); // Last = 1 40 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 41 FiboLoop: 42 psha; // Push last 43 txa; 44 add(1,sp); 45 pulx; 46 dbnz(1, sp, FiboLoop); 47 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 48 FiboDone: 49 pulh; // Release counter 50 rts; // Result in A 51 } // end CalcFibo() COMENTARIOS a ["Labs-C\Fibonacci\060Fibonacci.c"]: La magia que nos permite hacer un programa en C, que se VÉ casi IGUAL al equivalente en Assembler, y que CORRE "IGUAL" (puede simularlo...) se encuentra en el include file: Fibonacci_.h, que tiene las definiciones que mimetizan el ASM en C. Lo analizaremos posteriormente. Adelante, cuando comencemos en serio, hablaremos en detalle acerca de los Include files; por el momento omito comentarlos. La definición de variables, que en ASM eran: "ds", byte por defecto: 217 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 11 byte Counter; 12 byte FiboRes; // Aquí va la respuesta ¿Sabe qué es "byte"? LAS SUPOSICIONES NUNCA PUEDEN CONVERTIRSE EN INFORMACIÓN. SI USTED AVANZA SIN ESCLARECER LAS COSAS QUE NO SABE, NO LE VA A IR BIEN EN LA VIDA La definición de (casi) todo lo que tiene que ver con este MCU se encuentra en "mc9s08qe128.h". Yo tengo una versión REDUCIDA: "mc9s08qe128‐U‐.h", fácil de consultar. En particular, allí figuran: ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ typedef unsigned char byte; typedef unsigned int word; ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ El comienzo en ASM era en 'Main'; aquí: 13 void main ( void ) /*()*/ El ciclo principal se llama igual en ASM, y las instrucciones aquí lo MIMETIZAN de una manera casi IGUAL. Inclusive, como los punto y comas se usan en ASM para comenzar COMENTARIOS, y en C para terminar 'SENTENCES', podemos rearreglarlos (;) para que se vean como el comienzo de los comentarios en ASM: 15 mainLoop: 16 clra ; // comienzo de comentario 17 cntLoop: 18 inca ; // .. tanto en ASM como en ... ¡C! Aquí viene una DIFERENCIA: aunque no es imposible evitar los paréntesis que se necesitan en C, pero no en ASM, para el llamado de las subrutinas, no es fácil, por lo que los hemos dejado...: 19 cbeqa(14, mainLoop) ; // Larger values cause overflow 20 sta(Counter) ; // Update global variable 21 bsr(CalcFibo) ; 22 sta(FiboRes) ; // Store result 23 lda(Counter) ; // ..Activate BREAKPOINT here to see.. 25 bra(cntLoop) ; // Next round Por eso decimos que el resultado es CASI idéntico. La función también está mimetizada de manera CASI igual, así: 30 void CalcFibo () { 32 dbnza(fiboDo); // Fibonacci Do 33 inca; 34 rts; 36 fiboDo: 218 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 37 psha; // The counter 38 clrx; // Second last = 0 39 lda(0x01); // Last = 1 41 FiboLoop: 42 psha; // Push last 43 txa; 44 add(1,sp); 45 pulx; 46 dbnz(1, sp, FiboLoop); 48 FiboDone: 49 pulh; // Release counter 50 rts; // Result in A ["Labs-C\Fibonacci\Fibonacci_.h"]: Analicemos ahora el include file: ["Labs‐C\Fibonacci\Fibonacci_.h"] 01 // ================================================================== 02 // Fibonacci_.h (02Fibonacci.asm), Luis G Uribe C, S09N2013 M10D2013 03 // Mimic CPU Registers 04 byte A; // Use A as a counter 05 byte X; 06 byte H; 07 #define clra A = 0 08 #define inca A++ 09 #define cbeqa(v,l) if(A == v) goto l 10 #define sta(v) v = A 11 #define bsr(l) l() 12 #define lda(v) A = v 13 #define bra(l) goto l 14 #define dbnza(l) if(‐‐A) goto l 15 #define dbnz(c,v,l) if( stack[v+c] ‐= 1 ) goto l 16 #define rts return 17 #define MAX_SP 16 18 #define clrx X = 0 19 #define txa A = X 20 #define add(n,v) A = A + stack[v+n] 21 #define psha stack[ sp‐‐ ] = A 22 #define pulx X = stack[ ++sp ] 23 #define pulh H = stack[ ++sp ] 24 word stack[ MAX_SP ]; 25 word sp = MAX_SP ‐ 1; 26 void CalcFibo(); 219 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 COMENTARIOS a ["Labs-C\Fibonacci\Fibonacci_.h"]: ["Labs‐C\Fibonacci\Fibonacci_.h"]: Como en C no tenemos acceso a los registros internos del CPU, los definimos como variables: 04 byte A; // Use A as a counter 05 byte X; 06 byte H; Una serie de MACROS (en C), definen las instrucciones del HCS08, mediante operaciones en C que simulen el comportamiento original. Por ejemplo, 'clra' en C sería: A = 0 07 #define clra A = 0 El BSR indica como parámetro, cuál es el nombre de la subrutina a la que se quiere saltar, bsr(l); la codificación es simplemente el "Label", o nombre de la rutina, seguido por un grupo de paréntesis: l() 11 #define bsr(l) l() Además escogí un ejercicio que emplea recursión, así que verán que simulé el Stack con un arreglo de Words. Para decrementar la variable que está en el Techo del Stack, y saltar a un "Label": 15 #define dbnz(c,v,l) if( stack[v+c] ‐= 1 ) goto l El tamaño del Stack lo defino arbitrariamente en 16: 17 #define MAX_SP 16 Sumarle al acumulador una variable dinámicamente definida en el Stack: 20 #define add(n,v) A = A + stack[v+n] Manipular el Stack (push y pop) para el Acumulador y el registro índice H:X 21 #define psha stack[ sp‐‐ ] = A 22 #define pulx X = stack[ ++sp ] 23 #define pulh H = stack[ ++sp ] La definición del Stack, y la definición e inicialización del Stack Pointer SP: 24 byte stack[ MAX_SP ]; 25 byte sp = MAX_SP ‐ 1; El prototipo de la función recursiva: 26 void CalcFibo(); ¿NO ENTIENDE ALGUNO DE LOS #define? ¿OTRAS DECLARACIONES? TIENE QUE ESTUDIAR "C" 220 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 44) FIBONACCI PROGRAMADO EN C, NORMALMENTE, PARA COMPARAR ["Labs‐C\Fibonacci\090FibonacciOK.c"] 01 // ****************************************************************** 02 // FibonacciOK.c (090FibonacciOK.c) Luis G Uribe C, D10N2013 M10D2013 03 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 04 // Include files 05 #include "derivative.h" // Include peripheral declarations 06 #include "FibonacciOK_.h" 07 08 // ****************************************************************** 09 void main ( void ) /*()*/ 10 { word i, first = 0, last = 14; 11 volatile word fibonacci; 12 volatile byte tmp; 13 14 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 15 // >>> ALWAYS include the following 2 lines 16 // Cfr. 02MC9S08QE128RM(ReferenceManual)‐U.pdf, pag 101 17 18 #define COP_Disable 0x42 19 SOPT1 = COP_Disable; // System Options 1 20 21 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 22 // Main loop 23 24 for( i = first; i < last; i ++ ) { 25 fibonacci = fib( i ); 26 tmp ^= tmp; 27 } // BREAKPOINT here to see: 1 2 3 5 8 13 21 34 55 89 144 233 28 29 for( ;; ) { /* EMPTY FOR */ } 30 31 } //end main() 32 33 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 34 word fib ( word n ) /*()*/ 35 { 36 // return n < 2 ? n : fib( n ‐ 2 ) + fib( n ‐ 1 ); // Extended 37 return n < 2 ? 1 : fib( n ‐ 2 ) + fib( n ‐ 1 ); // Conventional 38 } COMENTARIOS a ["Labs-C\Fibonacci\090FibonacciOK.c"]: Este programa es C estándar y no amerita más comentarios. ¿NO ENTIENDE EL OPERADOR TERNARIO? ¿NI SABE SEÑALAR CUÁL ES? TIENE QUE ESTUDIAR "C" 221 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 45) EXPONENCIACIÓN POR MULTIPLICACIONES Y SUMAS SUCESIVAS: WHILE ["Labs‐C\Lab1\010Lab0e‐1.c"] 01 // ****************************************************************** 02 // 010Lab0e‐1.c: EXPONENCIACIÓN. Luis G. Uribe C. M10D2013 03 // ** Usa EL STACK; >>>SOLO<<< VARIABLES DINÁMICAS ** 04 // ****************************************************************** 05 // Include Files 06 #include <hidef.h> // For EnableInterrupts macro 07 #include "derivative.h" // Include peripheral declarations 08 #include "several_U.h" // Def: CpuIntEn, CpuIntDsb, EOF, Wait 09 // ****************************************************************** 10 void main ( void ) /*()*/ 11 { byte base = 2, exponent=5; // EXAMPLE 2^5=32. See 3^5=243 12 byte resultExp = 1; 13 byte multiplicand, multiplier, resultMpy; 14 while( exponent‐‐ ) { // ...by repeated multiplications 15 multiplier = base; 16 multiplicand = resultExp; 17 resultMpy = 0; 18 while( multiplier‐‐ ) { // ...by repeated additions 19 resultMpy += multiplicand; 20 } 21 resultExp = resultMpy; 22 } 23 Wait( 0 ); // Wait for Ever 24 } COMENTARIOS a ["Labs-C\Lab1\010Lab0e-1.c"]: Este programa es C estándar, por tanto no amerita más comentarios. 46) EXPONENCIACIÓN, MULTIPLICACIONES Y SUMAS SUCESIVAS: FUNCIONES ["Labs‐C\Lab1\020Lab0e‐2Sub.c"] 01 // ****************************************************************** 02 // 020Lab0e‐2Sub.c: EXPONENCIACIÓN. Luis G. Uribe C. J21N2013 03 // ** Usa EL STACK >>>SOLO<<< VARIABLES DINÁMICAS ** 04 // ****************************************************************** 05 // Include Files 06 #include <hidef.h> // For EnableInterrupts macro 07 #include "derivative.h" // Include peripheral declarations 08 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 09 // Functions Prototypes 10 byte expo( byte base, byte exponent ); 11 byte mply( byte multiplicand, byte multiplier ); 222 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 12 // ****************************************************************** 13 void main ( void ) /*()*/ 14 { byte base = 2, exponent=5; // EXAMPLE 2^5=32. See 3^5=243 15 volatile byte resultExp; 16 resultExp = expo( base, exponent ); 17 Wait( 0 ); // Wait for Ever 18 } 19 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 20 byte expo ( byte base, byte exponent ) /*()*/ 21 { byte resultExp = 1; 22 while( exponent‐‐ ) { // ...by repeated multiplications 23 resultExp = mply( resultExp, base ); 24 } 25 return resultExp; 26 } 27 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 28 byte mply ( byte multiplicand, byte multiplier ) /*()*/ 29 { byte resultMpy = 0; 30 while( multiplier‐‐ ) { // ...by repeated additions 31 resultMpy += multiplicand; 32 } 33 return resultMpy; 34 } COMENTARIOS a ["Labs-C\Lab1\020Lab0e-2Sub.c"]: Este programa es C estándar y no amerita más comentarios. 47) EXPONENCIACIÓN, FUNCIONES: PARA VISUAL STUDIO ["Labs‐C\Lab1\030Lab0e‐2SubVisualStudio.c"] 01 // ****************************************************************** 02 // 030Lab0e‐2SubVisualStudio.c EXPONENCIACIÓN LG. Uribe C. J21N2013 03 // ** Usa EL STACK; >>>SOLO<<< VARIABLES DINÁMICAS ** 04 // ****************************************************************** 05 #define Wait( e ) while(!(e)) 06 typedef unsigned char byte; 07 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 08 // Functions Prototypes 09 byte expo( byte base, byte exponent ); 10 byte mply( byte multiplicand, byte multiplier ); 11 // ****************************************************************** 12 void main ( void ) /*()*/ 13 { byte base = 3, exponent=5; // EXAMPLE 2^5=32. See 3^5=243 223 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 14 volatile byte resultExp; 15 resultExp = expo( base, exponent ); 16 Wait( 0 ); // Wait for Ever 17 } 18 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 19 byte expo ( byte base, byte exponent ) /*()*/ 20 { byte resultExp = 1; 21 while( exponent‐‐ ) { // ...by repeated multiplications 22 resultExp = mply( resultExp, base ); 23 } 24 return resultExp; 25 } 26 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 27 byte mply ( byte multiplicand, byte multiplier ) /*()*/ 28 { byte resultMpy = 0; 29 while( multiplier‐‐ ) { // ...by repeated additions 30 resultMpy += multiplicand; 31 } 32 return resultMpy; 33 } COMENTARIOS a ["Labs-C\Lab1\030Lab0e-2SubVisualStudio.c"]: Este programa es C estándar para ser procesado por Visual Studio, y no amerita más comentarios. 48) PRIMER PROGRAMA PARA HCS08: MINIMUM C PROGRAM ["Labs‐C\lab1\00a_l1.c"] 01 // ****************************************************************** 02 // 00a_L1.c, MINIMUM 'C' Program for HCS08, J21N2013 03 // Luis G. Uribe C., For CodeWarrior 10.3 04 // ================================================================== 05 06 void main ( void ) /*()*/ 07 { 08 for( ;; ) { 09 /* EMPTY FOR */ 10 } 11 } COMENTARIOS a ["Labs-C\lab1\00a_l1.c"]: Como ya se indicó, FORMA y PRESENTACIÓN de Código en nuestro trabajo, es FUNDAMENTAL para la vida de los proyectos. Los programas deben poder entenderse, por quien los hace, cuando vuelva a mirarlos, y por las personas que vengan después a modificarlos o repararlos. 224 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 Para eso se requieren, entre otras cosas, comentarios útiles, resaltantes y claros. Igual que en programas escritos para otros Lenguajes, en las primeras líneas se coloca el Nombre del programa, su propósito, para qué plataforma y qué revisión (identificación que permite llevar la pista de las modificaciones que se le han hecho); hay que incluir siempre fechas que indiquen el comienzo del programa, cuándo se lo modificó y la datación de la revisión actual. Cuando es importante, hay que agregar una lista de fechas e indicar explícitamente qué cosas se añadieron, se quitaron o se modificaron. También, las iniciales de la persona que hizo cambios en el código, que permita identificarlas para saber a quién preguntar si fuera necesario. 02 // 00a_L1.c, MINIMUM 'C' Program for HCS08, J21N2013 03 // Luis G. Uribe C., For CodeWarrior 10.3 Programa desarrollado el Jueves 21 de Noviembre de 2013, para el CodeWarrior 10.2 (ahora se sabe que funciona también en CW 10.5). SIEMPRE hay que indicarle al CPU dónde comienza su código pero, a diferencia del trabajo que ya hicimos anteriormente en Assembler, aquí es el compilador de C, y las demás herramientas asociadas a él y a su entorno de desarrollo, como el Linker, el Absolute Loader, etc.) los que determinan la dirección de arranque (manejo automático de los Vectores de Interrupción). Gracias a Dios. No hay mucho más que se pueda agregar para explicar este ejercicio tan sencillo. 06 void main ( void ) /*()*/ 07 { 08 for( ;; ) { 09 /* EMPTY FOR */ 10 } 11 } ¿NO COMPRENDE ALGUNO DE LOS ELEMENTOS DE ESTE PROGRAMA? ¡DESISTA! ¿NO ENTIENDE EL FOR? TIENE QUE ESTUDIAR "C" 49) PROGRAMA EN C PARA HCS08, UN POCO MÁS ÚTIL El programa anterior compila bien (aun cuando no hace nada más; es una forma equivalente al "BRA *" que hemos usado ya antes), hay un par de COSAS QUE SIEMPRE TENEMOS QUE AGREGAR EN NUESTROS PROGRAMAS, y las revisaremos a continuación: ["Labs‐C\Lab1\01c_L1.c"] 01 // ****************************************************************** 02 // 01c_L1.c, Small C program M10D2013 03 // Luis G. Uribe C., For CodeWarrior 10.3 04 05 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 06 // Include Files 07 #include <hidef.h> // For EnableInterrupts macro 08 #include "derivative.h" // Include peripheral declarations 09 225 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 10 // ****************************************************************** 11 void main ( void ) /*()*/ 12 { 13 byte var; // Depending on optimization options, 14 // ..C compiler may learn that nothing 15 var = 0; // ..is realy being done, and replace 16 while( 1 ) { // ..var, or all your code for empty/ 17 var ++; // ..null, block. If var is not seen 18 } // ..in Debug remove some optimization 19 } // ..declaring: volatile byte var; COMENTARIOS a ["Labs-C\Lab1\01c_L1.c"]: Los dos "include files" listados a continuación, casi siempre se incluyen. 07 #include <hidef.h> // For EnableInterrupts macro 08 #include "derivative.h" // Include peripheral declarations En "derivative.h" CodeWarrior incluye la particularización del MCU SUYO. Así, los programas NO cambian si son para uno u otro MCU; y CW parametriza en este Include File todo lo que tiene que ver con cada MCU en particular. Por ejemplo, para nuestro micro, la sección más importante en "derivative.h" es: #include <mc9s08qe128.h> <hidef.h> (Hardware Interface Definitions) se usa menos, ya que fundamentalmente lo que incluye son definición de Macros para la habilitación de Interrupciones. Más adelante veremos cómo manejamos nosotros estas funcionalidades. El ejercicio lo que trata de hacer, en su simplicidad, es incrementar permanentemente una variable 'var', de tipo 'unsigned char' (byte). La aclaratorio que hay que hacer, para que ustedes recuerden sus CONOCIMIENTO DE C, es que el Compilador puede darse cuenta, dependiendo de las opciones de optimización, que no se hace NADA con 'var': a pesar de incrementarla, nadie la usa. Así que, como buen "optimizador", puede quitar la variable por completo, y si yo hubiera diseñado el Optimizador, eliminaría TODO EL CÓDIGO. Como ustedes deben saber, de sus cursos de C, pueden deshabilitarse las opciones de OPTIMIZACIÓN para que el compilador NO elimine ni la variable inútil, ni el código INÚTIL. Otra forma que ustedes debieron aprender en sus cursos del lenguaje, es que si se declara esa variable como "volatile byte var;", el compilador asumirá que, sin que él entienda cómo, esa variable será usada en alguna otra parte que él desconoce (como, por ejemplo, si así se hace referencia a un Puerto Digital de Salida: El entorno externo USARÁ los valores calculados DENTRO del programa, aunque no se haga evidente para el compilador que esto sea así. 13 byte var; // Depending on optimization options, 14 // ..C compiler may learn that nothing 226 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 15 var = 0; // ..is realy being done, and replace 16 while( 1 ) { // ..var, or all your code for empty/ 17 var ++; // ..null, block. If var is not seen 18 } // ..in Debug remove some optimization 19 } // ..declaring: volatile byte var; Note en los programas en C, similarmente a lo que hicimos en los códigos en Assembler, la muy ajustada línea en donde se colocan los comentarios. En principio, // comienza en la columna 32. Y aquí también puede haber ocasiones en las que haya que mover los comentarios, pero mientras más estandarizado esté la forma de presentación del código, mejor para todos. 50) "INTERRUPTS" EN EL LENGUAJE C Las Interrupciones NO forman parte del lenguaje C, ni tampoco una gran cantidad de Extensiones, como la manipulación de apuntadores, identificándolos como 'far' o 'near', o los Vectores de Interrupción, o el manejo de memoria Segmentada mediante la aplicación de Modelos: Short, Tiny, Large, Medium, Data. (¿SABE USTED LO QUE SIGNIFICAN?) Muchos dispositivos dividen su memoria en Segmentos; por ejemplo, los modelos de la escala baja de la familia 0x86 de Intel. Ellos usan 16 bits de dirección, con lo cual pueden acceder a un máximo de 64 KBytes (65536). Mediante un registro que indica dónde comienza un Segmento (y cuyo valor puede alterarse de manera dinámica, para seleccionar en Run Time VARIOS Segmentos de memoria), se puede llegar hasta posiciones mucho más allá de los 64KB del modelo. El PC original tenía 640 KBytes de RAM (no llegaba ni al Megabyte). En Intel hay 4 registros de Segmento, con una función similar, llamados CS, Code Segment; DS, Data Segment, SS, Stack Segment y EX, Extra Segment. El CS confina el código del programa, que ha de ser capturado mediante el CS y el PC (Program Counter). En Assembler, si el programa es más grande que 64KB, se programa el CS y, en el momento en que se desea saltar a una posición que está fuera de ese Segmento, se reprograma el CS para utilizar otros 64KB. La manipulación suele no ser muy sencilla, ya que si se cambia el CS, DESAPARECEN las instrucciones que están haciendo la reprogramación, pero hay trucos que se acostumbran. Lo cierto es que para trabajar en C, si el programa es más grande que un Segmento, una manera de indicarle al compilador que él se haga cargo de ese trabajo de reprogramación del CS es, por ejemplo, definiendo apuntadores que puedan barrer mucho más de los 64KB (por ejemplo, TODA la memoria). La forma es mediante el indicativo 'far', así: far char *ptr; Así, el compilador se encarga de ir haciendo el trabajo que en Assembler correspondería al programador, de verificar si cada dirección está, o no, confinada a un segmento. En caso de no estarlo, el compilador incluye código para la reprogramación del CS y la transición, invisible para el programador, a otro Segmento. Algo parecido ocurre con el DS, o Segmento de Datos. El de Código verifica que algún salto (JMP) o llamado a subrutina (JSR), se comporte bien no importa en qué parte de la 227 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 memoria se encuentre el código. El de Dato, DS, opera de la misma manera, pero ya no para código, sino para manipular aquellas áreas de memoria que están en una posición más allá de las fronteras de 64KB impuestas por el Data Segment. Este ejercicio muestra cómo manejar las Interrupciones, con la Extensión para ese propósito definida por Motorola, Freescale, CodeWarrior, mediante un código sencillo que hace una manipulación cualquiera sobre una variable 'var', y cada vez que se recibe una interrupción del botón IRQ (external Interrupt Request), se niega una variable global, GlobalVar. ["Labs‐C\Lab1\03b_L1_M.c"] 01 // ****************************************************************** 02 // Programa 03b_L1_M.C, HCS08_CPU, Luis G. Uribe C, M10D2013 03 // 04 // Uso de Rutinas de Interrupción: 05 // A) Habilitación del periférico para Interrumpir 06 // B) Habilitación del CPU (cli) para que acepte Interrupciones 07 // (aparte del RESET, que **NO** es enmascarable) 08 // C) Inclusión del código de la rutina de Interrupción (Interrupt 09 // Service Routine, ISR). 10 // ****************************************************************** 11 // Include Files 12 #include <hidef.h> // For EnableInterrupts macro 13 #include "derivative.h" // Include peripheral declarations 14 #include "several_U.h" // Def: CpuIntEn, CpuIntDsb, EOF, Wait 15 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 16 // Functions Prototypes 17 byte rutina( byte var ); 18 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 19 // Global Variables 20 volatile byte GlobalVar; 21 // ****************************************************************** 22 void main ( void ) /*()*/ 23 { volatile byte var = 30; 24 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 25 // >>> ALWAYS include the following 2 lines 26 // Cfr. 02MC9S08QE128RM(ReferenceManual)‐U.pdf, pag 101 27 #define COP_Disable 0x42 28 SOPT1 = COP_Disable; // System Options 1 29 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 30 // Enable Interrupts for IRQ 31 // 32 // 1‐a) ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 33 IRQSC_IRQPE = 1; // BSET IRQSC_IRQPE, IRQSC: Enable IRQ PIN 228 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 34 // 1‐b) ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 35 // .. Clear any possible previous, false, interrupts by Setting 36 // .. IRQACK bit in IRQSC (IRQ Status and Control register) 37 IRQSC_IRQACK = 1; // BSET IRQSC_IRQACK, IRQSC 38 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 39 // >> >>> Try: IRQSC = IRQSC_IRQPE_MASK | IRQSC_IRQACK_MASK <<< <<< 40 // 2) ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 41 // 42 IRQSC_IRQIE = 1; // BSET IRQSC_IRQIE,IRQSC // IRQ pin IntEn 43 // =============================================================== 44 cli; // CPU Interrupt ENABLE 45 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 46 loop: 47 while( 1 ) { 48 var = rutina( var ‐ 10 ); 49 var = var; // COLOCAR BREAKPOINT AQUÍ (Debugger) 50 } 51 Wait( 0 ); // Wait for Ever 52 } 53 // ================================================================== 54 byte rutina ( byte var ) // Subrutina de ejemplo /*()*/ 55 { 56 while( var < 30 ) { 57 var++; 58 } 59 return var; 60 } 61 // ================================================================== 62 // EJEMPLO de Rutina de Interrupción (ISR for IRQ signal) 63 // NOTE: Es necesario realizar un ACKNOWLEDGE para que vuelva a suce‐ 64 // der la interrupción (Interrupt Handshaking... different for 65 // each peripheral: You need to read Manual for each one... :‐( 66 interrupt VectorNumber_Virq void IRQISR ( void ) /*()*/ 67 { 68 GlobalVar ^= (byte)‐1; 69 IRQSC_IRQACK = 1; // BSET IRQSC_IRQACK, IRQSC: ACK IRQ Interrupt 70 } // ..(Rearm IRQ Interrupts) COMENTARIOS a ["Labs-C\Lab1\03b_L1_M.c"]: Los Include Files normales y uno extra, "several_U.h" (LOS PRESENTO MÁS ADELANTE): 12 #include <hidef.h> // For EnableInterrupts macro 13 #include "derivative.h" // Include peripheral declarations 229 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 14 #include "several_U.h" // Def: CpuIntEn, CpuIntDsb, EOF, Wait El prototipo de la función 'rutina', que retorna un 'byte' y a la cual se le pasa un también byte, ( byte var ): 17 byte rutina( byte var ); Variable global. Nótese que se la ha definido como 'volatile', para que el OPTIMIZADOR del compilador NO LA OMITA, ni al código que también sería detectado como inútil (este programa es bastante inane): 20 volatile byte GlobalVar; Comienzo del programa y definición de 'var', también como 'volatile': 22 void main ( void ) /*()*/ 23 { volatile byte var = 30; En los programas que escribimos en lenguaje ensamblador, se incluían siempre al comienzo 4 instrucciones, dos para inicializar el SP, y dos para detener el COP, CPU Operating Properly. En C, el compilador incluye código invisible para el programador, mediante el cual inicializa apropiadamente el Stack, por lo que nos desentendemos de eso. Pero el COP sí hay que deshabilitarlo por programa. Un posible equivalente a: Main: lda #$42 ; $42(%0100_0010)? COPE(SOPT1b7)=0 ($1802) sta $1802 ; (SOPT1 Pup init to $C2 = %1100_0010) es el siguiente: 27 #define COP_Disable 0x42 28 SOPT1 = COP_Disable; // System Options 1 Ahora, para habilitar las interrupciones del IRQ, éste es el código en Assembler que siempre hemos usado, BSET IRQSC_IRQPE, IRQSC ; Enable IRQ PIN BSET IRQSC_IRQACK, IRQSC BSET IRQSC_IRQIE, IRQSC ; IRQ pin Interrupt Enable cli ; CPU Interrupt ENABLE en C el que corresponde es el siguiente: 33 IRQSC_IRQPE = 1; // BSET IRQSC_IRQPE, IRQSC: Enable IRQ PIN 37 IRQSC_IRQACK = 1; // BSET IRQSC_IRQACK, IRQSC 42 IRQSC_IRQIE = 1; // BSET IRQSC_IRQIE,IRQSC // IRQ pin IntEn 44 cli; // CPU Interrupt ENABLE Usted puede ensayar también (mucho MEJOR): 39 IRQSC = IRQSC_IRQPE_MASK | IRQSC_IRQACK_MASK 230 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 Funciona igual, pero es más simple. El ciclo principal del programa es muy sencillo, y en realidad corresponde a una espera artificial hasta que ocurra la interrupción. Este código debe ser seguido empleando el Debugger (colocando un Breakpoint donde se indica): 46 loop: 47 while( 1 ) { 48 var = rutina( var ‐ 10 ); 49 var = var; // COLOCAR BREAKPOINT AQUÍ (Debugger) 50 } 51 Wait( 0 ); // Wait for Ever La 'rutina' llamada, también es artificial: 54 byte rutina ( byte var ) // Subrutina de ejemplo /*()*/ 55 { 56 while( var < 30 ) { 57 var++; 58 } 59 return var; La rutina de Interrupciones tiene varios ASPECTOS NOVEDOSOS. Como dijimos que las funcionalidades no definidas por el estándar ANSI se manejan como Extensiones al lenguaje, y al compilador, las define cada proveedor, y si bien es cierto que se parecen bastante entre uno y otro, ¡no son iguales! Por tanto... NO SON PORTABLES. Y hay que aprenderlas para cada suplidor. La definición introduce el modificador no estándar 'interrupt'; lo tienen casi TODOS los que hacen compiladores. A continuación se agrega una constante, 'VectorNumber_Virq', definida de manera simbólica mediante la inclusión de <mc9s08qe128.h> (que a su vez, ha sido incluida por "derivative.h", junto con las declaraciones de los símbolos de los periféricos). Obviamente este 'VectorNumber_Virq' define la posición de la ISR en la tabla de Vectores de Interrupción (muy similar a lo que hicimos en ASM) Luego viene el TIPO de información que la rutina retorna; las rutinas de Interrupción, ISR, JAMÁS DEVUELVEN NADA y su tipo SIEMPRE es 'void'. Si una ISR, Interrupt Service Routine, debe comunicarse con el programa principal, tiene que hacerlo intercambiando valores mediante variables globales, como 'GlobalVar' en el presente ejemplo. Las ISR JAMÁS RECIBEN PARÁMETROS, como las subrutinas convencionales; por eso el '(void)'; si la ISR tiene que leer valores que 'main' o alguien más produce, tienen que intercambiarse TAMBIÉN (leerse, escribirse) en variables globales: 66 interrupt VectorNumber_Virq void IRQISR ( void ) /*()*/ 67 { 231 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 IRQISR (Rutina de Servicio de Interrupciones, ISR, para la Interrupción del botón de Interrupt Externo, IRQ), hace en este ejercicio algo muy simple: negar la variable GlobalVar (de manera booleana: cambiando ceros por unos y viceversa): 68 GlobalVar ^= (byte)‐1; ¿ENTIENDE LA EXPRESIÓN? SI NO, TIENE QUE ESTUDIAR "C" Luego, ejecuta el protocolo de reconocimiento (Acknowledge) de Interrupción del IRQ, que le indica al harware que el software YA atendió su solicitud de interrupción. Como hemos dicho a todo lo largo de este documento, en Freescale cada periférico tiene su propio protocolo de IntACK, que hay que estudiar en los manuales de referencia. En el caso del IRQ es muy simple (refiérase también a los ejercicios en Assembler): 69 IRQSC_IRQACK = 1; // BSET IRQSC_IRQACK, IRQSC: ACK IRQ Interrupt 70 } // ..(Rearm IRQ Interrupts) NOTA: En la familia anterior: HC08 (sin la 'S'), el IRQ tenía dos diferencias fundamentales con la familia actual, HCS08: 1) Las interrupciones de IRQ estaban ¡HABILITADAS SOLO CON ENCENDER EL MCU! Esto es **MUY POCO ORTODOXO**, y fue eliminado en el HCS08... 2) Bastaba con que el PC cargara el contenido del Vector de Interrupciones del IRQ, como parte del proceso de atender esa interrupción, para que automáticamente el Hardware tuviera un Acknowledge. Esto bien podrían haberlo dejado, pero lo cambiaron: ahora hay que darle explícitamente un ACK, según la línea 69 arriba. EL INCLUDE FILE "SEVERAL_U.H": 01 // ****************************************************************** 02 // several_U.h, Luis G. Uribe C, S30N2013 03 04 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 05 // Several Defines 06 07 #undef Wait 08 #define Wait(e) while( !(e)) 09 #undef CpuIntEn 10 #define CpuIntEn __asm CLI 11 #undef CpuIntDsb 12 #define CpuIntDsb __asm SEI 13 #undef EOF 14 #define EOF ((word)‐1) COMENTARIOS a ["Labs-C\Lab1\several_U.h"]: Primero se define un 'Wait', empleando un 'while'. Wait es muy conveniente expresado cosas como: Wait( kbhit() ): espere hasta que llegue una tecla. Empleando un while habría que escribir 232 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 while ( ! (kbhit() ); Como esta es una proposición en NEGATIVO, se hace más difícil su comprensión. En general nunca uso expresiones en negativo; las convierto en positivo, como es el caso del Wait, pero también, en Assembler, no uso BNE sino que introduzco un mnemónico nuevo: BDIFF. Es lo mismo, pero la comprensión es mucho mejor con la instrucción en positivo: SALTE SI SON DIFERENTES. Mucho mejor que SALTE SI *NO* SON IGUALES. Antes de definir el Wait, lo '#undef' ('undefine'), por si este compilador tiene definido su propio Wait (y como NO voy a usar ese Wait del compilador, si es que lo tuviera, aquí puedo cambiarlo por EL MÍO. El Visual Studio de Microsoft, ¡tiene UN WAIT!, y algunos otros compiladores también) 07 #undef Wait 08 #define Wait(e) while( !(e)) Para habilitar las interrupciones del CPU se emplea una instrucción de lenguaje ensamblador, que ¡NO puede expresarse en lenguaje C! Pero una de las extensiones de CASI todos los compiladores modernos, permite incluir instrucciones de Assembler, EN LÍNEA con el código en C. Esto es exactamente lo que se hace a continuación con el 'asm': 10 #define CpuIntEn __asm CLI Lo mismo se hace para la deshabilitación de interrupciones: 12 #define CpuIntDsb __asm SEI (Además, que mis nombres, CpuIntEn y CpuIntDsb, con seguridad que son ¡MUCHO más INTELIGENTES, DIGO: INTELIGIBLES, que los originales de Freescale!) Y, finalmente, hay un símbolo equivalente a su homónimo en el ANSI C que ya hemos empleado, y que yo lo defino a mi conveniencia aquí (en el presente ejercicio, NO se emplea): 14 #define EOF ((word)‐1) ¿NO ENTIENDE EL #define DE LA LÍNEA 14? TIENE QUE ESTUDIAR "C" OBSERVACIÓN: Ni hemos comenzado los ejercicios en C, y les pregunto: ‐ ¿Sabían algo de #undef? 233 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 ‐ ¿Qué valor inicial tiene GlobalVar (línea 20)? ‐ ¿Por qué en la línea 48 var = rutina( var ‐ 10 ); 'rutina' está PEGADA al paréntesis izquierdo, en tanto que en: 54 byte rutina ( byte var ) // Subrutina de ejemplo /*()*/ 'rutina' está SEPARADA del paréntesis izquierdo? ‐ ¿Para qué puede servir el 'moñito': /*()*/ ? Imagino, pero estoy desconfiado, que ¿sí habían usado #define? 51) "Interrupts" en el lenguaje C, variación El problema ["Labs‐C\Lab1\03b_L1_M2.c"] es similar al anterior y utiliza simplemente: 39 IRQSC = IRQSC_IRQPE_MASK | IRQSC_IRQACK_MASK Así: // Enable Interrupts for IRQ IRQSC = IRQSC_IRQPE_MASK | IRQSC_IRQACK_MASK; IRQSC_IRQIE = 1; // BSET IRQSC_IRQIE,IRQSC // IRQ pin IntEn cli; // CPU Interrupt ENABLE MANEJO DE TIMERS A quí les presento mi primera librería en C, el manejador de Temporizadores. Primero les incluyo una breve reseña acerca de las diversas versiones, en Lenguaje Ensamblador y ahora en Lenguaje C. Refiérase a los ejercicios que están en la carpeta: Carpetas Labs‐C\Lab2\TimersJ 52) LIBRERÍA DE TIMERS: timersJ.c 1) Esta segunda etapa de mi docencia en la USB comenzó en Abril de 2003 con el curso de Circuitos Digitales. La primera versión de la biblioteca de Timers en ASM, Timers8, la hice para mi primer curso de Arquitectura del Computador, el Lunes 24Nov2003, seguramente la terminé a las 3 am, antes de comenzar mi clase de ese día a las 9:30. El microcontrolador que se empleaba en aquella época era el Motorola 68HC908GP32, con memoria Flash (el "9" significa eso) y 32 KBytes de Memoria (toda Flash, menos 512 Bytes de RAM). El ensamblador tenía por nombre WinIDE y era gratuito. Tenía serias deficiencias en el tratamiento de Macros, lo que me obligó, por ejemplo, a definir DOS tipos de 'Setimer' y de 'Wait_on': uno para Constantes, y otro para Variables ('SetimerK', 'Wait_onK', 'SetimerV', 'Wait_onV'). 2) La siguiente revisión, Timers8B, la hice en mi siguiente curso de Arquitectura un año después: Lunes 08Nov2004, siendo la más novedosa mejora el haber hecho la ISR del 234 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 Timer (TBM: Timer Basic Module) interrumpible por otros dispositivos, y NO reentrante. Esto permitía estudiar el concepto, y a la vez hacer que mis timers, que realidad no requieren una exactitud ajustada al microsegundo, no molestaran a los demás periféricos, en caso de que aquellos necesitaran interrumpir. La no reentrancia se requería para evitar que el mismo TBM auto interrumpiera su propia ISR, en caso de haber terminado un conteo por Hardware antes de haber terminado su Interrupt Service Routine (por motivo de los demás periféricos). No es conciente aceptar una interrupción de un periférico sin haber terminado su ISR para una interrupción anterior. Esta funcionalidad se logra principalmente, haciendo que la ISR del TBM auto inhabilite su Interrupt Enable y rehabilite (CLI) las interrupciones del CPU. Recuerde que Motorola en sus Manuales de Referencia, advierte EQUIVOCADAMENTE en contrario de esta práctica. 3) La revisión Timers8C es del Jueves 01Dic2005 para mi siguiente curso; allí incluí la capacidad de que OTRAS ISRs pudieran emplear las Macros de la librería de Timers; así, por ejemplo, el IRQ o las comunicaciones podían, dentro de sus ISRs, temporizar ciertos eventos que el programador necesitara (timeouts y similares; control de "throttle" o regulador de velocidad). 4) Timers8D, 2007: esencialmente la diferencia fue modificar la programación del Oscilador maestro del CPU para que en vez de 8Mhz trabajara a 7,987,200 Hz para aproximar mejor la velocidad de 9600 bps en el SCI. 5) Timers8G: Cuando fui a dictar el curso de Arquitectura en Abril de 2009 me encontré que el Laboratorio había adoptado el compilador CodeWarrior por varias razones: a) manejaba C además de ASM; b) era el que Motorola promocionaba; c) WinIDE se estaba quebrando, imagino que no ganaban mucho regalando su compilador, y ahora quería cobrarlo, lo que obligó a compararlo con CW. El cambio no ofrecía en ese momento NINGÚN beneficio para mi clase de Arquitectura, pero desarraigaron definitivamente el WinIDE de todos los PCs del laboratorio, y ya no le dieron más soporte. Eso me obligó a generar el Jueves 16Abr2009 esta versión Timers8G específicamente para CodeWarrior. Las diferencias no eran tantas, pero de todas maneras fue un trabajón: Por ejemplo, en WinIDE y en CW un bit se identificaba como: Timer0Ready EQU 0 ; ..Bit #0; for brclr & brset Timer0.Rdy EQU 0 ; ..Bit #0; for brclr & brset En WinIDE y en CW la definición de las macros tenía el siguiente aspecto: $MACRO SetimerK timer,time ; time is a CONSTANT (K) Setimer: MACRO timer,time ; time is a CONSTANT (K) Desaparece el $ de $MACRO, y cambia de posición... El nombre de la Macro ahora es LA ETIQUETA. En WinIDE, por ejemplo, al primer parámetro de la Macro se le hacía referencia como %1, en tanto que en CW es \1; el % en CW identifica números binarios. Los números en base diez ahora son el estándar, no precisan identificador especial; en WinIDE se escribían como: !10 235 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 6) Mis clases hasta el 2012 no incluyeron las de Arquitectura; cuando regresé después de 3 años, acababan de cambiar ahora... EL CHIP. El sucesor fue el de la familia HS: HC9S08QE128 y se incluía la tarjeta de desarrollo DEMOQE128. TODO VOLVIÓ A CAMBIAR: había periféricos nuevos que reemplazaban funciones antiguas. Por ejemplo, el TMB ya no existía y lo reemplazó el RTC. Los ADC siguieron siendo los mismos, pero CAMBIARON los NOMBRES de los puertos y bits, de los originales de Motorola a los de nomenclatura Freescale. Algunos cambios sutiles, como los del IRQ que ya hemos mencionado; ya el oscilador maestro del CPU permite usarlo SIN programación, por lo que eliminó esa parte de la biblioteca de soporte. Para colmo, la versión actual del CodeWarrior es la 10.5, aunque en el Laboratorio sólo se le da soporte a la antigua 6.3, que difieren bastante las dos. Incluso, la nueva 10.x tiene nuevos bugs que hacen que programas que corrían bien en la 6.3, ahora no lo hagan. Así que correspondía la versión TimersH, y aprovechando la nueva denominación del chip, la rebauticé como TimersHS. La librería que aquí presento para el lenguaje C está basada en TimersHS, pero con diferencias que me obligaron a avanzar la versión; por eso en C la biblioteca de Timers que les ofrezco es la TimersJ.c (La siguiente a H es la I, pero hay ciertas letras que en los listados aún se prestan para confusión, dependiendo a veces del tipo de letra: La i con el 1, la O con el 0; la A con el 4... por eso no es timersI sino TimersJ) Si usted entendió a la perfección la versión en Assembler, no debería tener problemas con el estudio de TimesrJ.c ["Labs‐C\Lab2\TimersJ\timersJ.c"] 001 // ****************************************************************** 002 // TimersJ.c, Luis G. Uribe C. C19D2013 003 // 004 // ****************************************************************** 005 // Include Files 006 #include "timersJ_.h" 007 008 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 009 // Global Variables 010 volatile word timersMS[ NTIMERS ]; 011 012 // ================================================================== 013 void IniTimers ( byte ntimers ) /*()*/ 014 { // By default LINK init ALL timers ( timersMS[ NTIMERS ] ) to ZERO 015 // 'ntimers' NOT used here. Customize NTIMERS, in timersJ_.h 016 017 // Assure user defined NTIMERS (timersJ_.h) is 2's power! 018 Sassert( ! ( NTIMERS & ( NTIMERS ‐ 1 ) ) ); 019 020 RTCSC = RTC_FLAGS; // Use 1KHz internal clock/1: 1mS tick 021 // ..Clear RTCIF; IntEnable RTC 022 } /* IniTimers() */ 236 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 023 024 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 025 word SetimerMS ( byte timer, word timeMS ) /*()*/ 026 { word tmp = timersMS[ timer & ( NTIMERS ‐ 1 ) ]; // Reminding time 027 028 timersMS[ timer & ( NTIMERS ‐ 1 ) ] = timeMS; // ticks (mSeconds) 029 return tmp; 030 031 } /* SetimerMS() */ 032 033 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 034 byte Timeout ( byte timer ) /*()*/ 035 { 036 if( timersMS[ timer & ( NTIMERS ‐ 1 ) ] ) { 037 return 0; 038 } 039 return 1; // else... 040 041 } /* Timeout() */ 042 043 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 044 void WTimer ( byte timer ) /* >>>WARNING<<<: BLOCKS CPU */ /*()*/ 045 { 046 while( ! Timeout( timer ) ) { 047 /* Empty While */ 048 } 049 050 } /* WTimer() */ 051 052 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 053 void WaitMS_on ( byte timer, word timeMS ) /*()*/ 054 { /* >>>WARNING<<<: BLOCKS CPU */ 055 056 SetimerMS( timer, timeMS ); 057 WTimer( timer ); 058 059 } /* WaitMS_on() */ 060 061 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 062 void WaitISR_on ( byte timer, word timeMS ) /*()*/ 063 { //Save/Restore CCR is NOT possible from 'C'. We need the ASSEMBLER 064 065 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 066 // SAVE ACTUAL INTERRUPT MASK VALUE 067 asm { 068 tpa // Push CCR saves IMask status 069 psha // .. 070 } 071 072 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 073 // C Code 074 CpuIntDsb; // DISABLE INTERRUPTS (IMask=1): Let 237 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 075 SetimerMS( timer, timeMS ); // ..Setimer used inside Int. Routines 076 CpuIntEn; // REENABLE INTERRUPTS: Let RTC Count 077 WTimer( timer ); 078 079 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 080 // RESTORE SAVED INTERRUPT MASK VALUE. 081 asm { // Pop CCS restores saved IMask status 082 pula // .. >>>URGENT<<<: See page 183 of 083 tap // 01CPU08RM!ReferenceManual2013‐U.pdf 084 } // THIS WAS ***EXACTLY*** MY EXAMPLE IN THERE! 085 // (Stupid guy) 086 } /* WaitISR_on() */ 087 088 // ================================================================== 089 interrupt VectorNumber_Vrtc void TIMERS8ISR ( void ) /*()*/ 090 { byte i; 091 092 for( i = 0; i < NTIMERS; i ++ ) { 093 if( timersMS[ i ] ) { 094 timersMS[ i ] ‐‐; 095 } 096 } 097 RTCSC = RTC_FLAGS; // Use 1KHz internal clock/1: 1mS tick 098 // ..>> Clear RTCIF <<// IntEnable RTC 099 } /* TIMERS8ISR */ 100 101 // ****************************************************************** 102 // NOTE: The following code ***DOES NOT WORK*** (See above...); WHY ? 103 // if( ‐‐timersMS[ i ] < 0 ) { 104 // timersMS[ i ] = 0; 105 // } 106 107 // WHAT is 'Sassert'? HOW it works? Try using NTIMERS = 6 COMENTARIOS a ["Labs-C\Lab2\TimersJ\timersJ.c"]: Hecha el Miércoles 19Dic2013 Adelante se presenta el "timersJ_.h", pero sólo son los Prototipos de las 6 Funciones, definición de 3 parámetros del RTC, y la Macro Sassert(e) (Static Assert. Assert es de C. ¿SABÍA USTED? SI NO, TIENE QUE ESTUDIAR C) La representación de cada timer es un WORD, tal como en ASM. Hay un arreglo (vector), timersMS, compuesto por la cantidad NTIMERS (8 en esta presentación, pero usted puede ajustarla en "timersJ_.h" según sus preferencias) El arreglo es Global para facilitar su visibilidad en los programas que usan esta librería. Además, se ha declarado 'volatile' para evitar optimizaciones indeseadas en estas variables. 010 volatile word timersMS[ NTIMERS ]; 238 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 La primera funcionalidad permite la inicialización de la biblioteca, IniTimers. Se usa como IniTimers(8) (o la potencia de dos que usted defina), para mantener la SEMÁNTICA de la función, pero ese #8 NO se emplea en la biblioteca: la validación se hace contra la constante NTIMERS, definible por usted en "timersJ_.h" Como parte de la inicialización habría que colocar en CERO las variables que representan los timers; esto NO se hace explícitamente porque, ¿SABÍA USTED que el LINKER inicializa en CERO TODAS LAS VARIABLES "GLOBALES" QUE NO TIENEN ASIGNADO UN VALOR EXPLÍCITO? ¿NO? ¿SABE LO QUE ES LINKER? ¿NO? TIENE QUE ESTUDIAR C 013 void IniTimers ( byte ntimers ) /*()*/ Asegúrese de entender cómo Sassert valida en COMPILE TIME (no hace NADA en RUNTIME) que el número de timers por usted definido sea potencia de dos. 018 Sassert( ! ( NTIMERS & ( NTIMERS ‐ 1 ) ) ); La única operación que termina haciéndose explícitamente, es la selección de "1KHz" como reloj Interno (clock/1, lo que produce interrupciones cada milisegundo. Nuestra librería CUENTA TICKS de UN milisegundo. Si usted llegara a necesitar contar tiempos menores, el Reference Manual le indica cómo programar el RTCSC para lograrlo) 020 RTCSC = RTC_FLAGS; // Use 1KHz internal clock/1: 1mS tick La siguiente funcionalidad es la de activar uno de los timers: SetimerMS. Recibe dos parámetros: 'byte timer' (el número del timer, del 0 a 7 para esta implementación) y 'word timeMS', la cantidad de MILISEGUNDOS que ese temporizador debe contar antes de declararse READY. La función retorna la cantidad de MS que aún faltaban para que ese timer terminara (guardada al comienzo en 'tmp'); esto puede servir de verificación pues si no es cero, se está abortando un timer para volverlo a inicializar. Finalmente, se inicializa la variable correspondiente al 'timer' con el valor deseado en milisegundos: timeMS, tal como su contraparte en ASM. 025 word SetimerMS ( byte timer, word timeMS ) /*()*/ 026 { word tmp = timersMS[ timer & ( NTIMERS ‐ 1 ) ]; // Reminding time 028 timersMS[ timer & ( NTIMERS ‐ 1 ) ] = timeMS; // ticks (mSeconds) 029 return tmp; La funcionalidad que se usa para averiguar si expiró (llegó a Cero) o no, alguno de los timers es Timeout, que recibe como parámetro un 'byte timer' (de 0 a 7 en esta implementación) y devuelve sí o no, si hubo Timeout (retorna 0 si NO hay timeout; 1 si sí expiró ese timer) 034 byte Timeout ( byte timer ) /*()*/ 036 if( timersMS[ timer & ( NTIMERS ‐ 1 ) ] ) { 037 return 0; 038 } 039 return 1; // else... 239 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 Esperar bloqueando el flujo del programa, hasta que termine un cierto temporizador, que fue PREVIAMENTE inicializado por SetimerMS, es la función de WTimer, que recibe como parámetro el ID del timer (byte timer), y no retorna ningún valor; solo bloquea el programa. 044 void WTimer ( byte timer ) /* >>>WARNING<<<: BLOCKS CPU */ /*()*/ 046 while( ! Timeout( timer ) ) { 047 /* Empty While */ 048 } Esta es una de las oportunidades en las que es preferible expresar la condición de manera afirmativa: 046 Wait( Timeout( timer ) ); Lo cambiaré en alguna otra oportunidad. Para aquellas ocasiones que requieran programar un timer y esperar hasta que termine, todo en una sola operación, está WaitMS_on, que recibe dos parámetros, como SetimerMS, y no regresa ningún valor, como WTimer. 053 void WaitMS_on ( byte timer, word timeMS ) /*()*/ 056 SetimerMS( timer, timeMS ); 057 WTimer( timer ); Para realizar una espera estando en INTERRUPT STATE (es decir, ha ocurrido una interrupción y se está ejecutando la ISR del periférico en cuestión, y ésta requiere hacer una espera, se emplea WaitISR_on, equivalente de WaitMS_on, para ejecutarse en ISRs). WaitISR_on recibe 2 parámetros, igual que WaitMS_on: byte timer (número del temporizador que ha de usarse, del 0 al 7) y el número de Milisegundos, word timeMS. 062 void WaitISR_on ( byte timer, word timeMS ) /*()*/ Esta función salvaguarda el CCR en el Stack, y como el C no puede directamente realizar esta operación, se la implementa vía Inline ASM: El objeto de guardar el valor del CCR es para preservar el Interrupt State del CPU (I, Interrupt Mask), pues SetimerMS no se puede ejecutar si las interrupciones están activas (CLI), así que esta función las deshabilita, ejecuta el SetimerMS y... como ya explicamos en la sección de ASM, NO PUEDE SIMPLEMENTE HABILITAR LAS INTERRUPCIONES. Tiene que dejar el Interrupt State TAL COMO ESTABA ANTES de DESHABILITAR las interrupciones. Por eso, en vez de CLI, se recupera desde el Stack el valor previo de I, que estaba en el CCR. 066 // SAVE ACTUAL INTERRUPT MASK VALUE 067 asm { 068 tpa // Push CCR saves IMask status 069 psha // .. 070 } 073 // C Code 074 CpuIntDsb; // DISABLE INTERRUPTS (IMask=1): Let 075 SetimerMS( timer, timeMS ); // ..Setimer used inside Int. Routines 076 CpuIntEn; // REENABLE INTERRUPTS: Let RTC Count 077 WTimer( timer ); 240 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 080 // RESTORE SAVED INTERRUPT MASK VALUE. 081 asm { // Pop CCS restores saved IMask status 082 pula // .. >>>URGENT<<<: See page 183 of 083 tap // 01CPU08RM!ReferenceManual2013‐U.pdf 084 } // THIS WAS ***EXACTLY*** MY EXAMPLE IN THERE! Desde luego, se emplean las instrucciones TPA y TAP que el Reference Manual indica QUE NO SIRVEN PARA NADA! La rutina de Interrupciones, TIMERS8ISR, ni recibe parámetros ni devuelve valores (void) como ya se explicó. Es de tipo 'interrupt' e indica cuál es el Vector de interrupciones, VectorNumber_Vrtc, para que el Loader coloque la dirección de la ISR en la posición apropiada, en el Vector de Interrupciones. ¿SABE USTED LO QUE ES EL LOADER? NO? TIENE QUE ESTUDIAR C El código es muy sencillo: incrementar cada temporizador QUE NO HAYA EXPIRADO (lo cual se determina en el 'if' porque su valor ES diferente de CERO), y seguir el protocolo de Acknowledge de Interrupciones para el periférico: 089 interrupt VectorNumber_Vrtc void TIMERS8ISR ( void ) /*()*/ 090 { byte i; 092 for( i = 0; i < NTIMERS; i ++ ) { 093 if( timersMS[ i ] ) { 094 timersMS[ i ] ‐‐; 095 } 096 } 097 RTCSC = RTC_FLAGS; // Use 1KHz internal clock/1: 1mS tick 098 // ..>> Clear RTCIF <<// IntEnable RTC Se asigna RTCSC=RTC_FLAGS, como la rutina de inicialización, lo que Borra la Interrupt Flag, IF, del RTC, rearmando (permitiendo otras posteriores) sus interrupciones. También hubiéramos podido borrar exclusivamente ese bit RTCIF, pero el resultado es el mismo. OBSERVACIÓN: ¿Por qué reescribiendo el “IF” como sigue, el código NO TRABAJA? Código ORIGINAL: 093 if( timersMS[ i ] ) { 094 timersMS[ i ] ‐‐; Cambiado a: 103 // if( ‐‐timersMS[ i ] < 0 ) { 104 // timersMS[ i ] = 0; 105 // } ¿NO SABE POR QUÉ NO TRABAJA? TIENE QUE ESTUDIAR C URGENTEMENTE! 241 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 COMENTARIOS a ["Labs-C\Lab2\TimersJ\timersJ_.h"]: ["Labs‐C\Lab2\TimersJ\timersJ_.h"] 01 #ifndef TIMERSJ_H_INCLUDED 02 #define TIMERSJ_H_INCLUDED 03 // ****************************************************************** 04 // timersJ_.h, Luis G. Uribe C. C11D2013 05 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 06 #define NTIMERS 8 // 2's Power: 2, 4, 8, 16... 128 (max) 07 08 // ****************************************************************** 09 // Includes Files 10 // <<< INCREDIBLE: CW10.3 **NEEDS** to REInclude this files **AGAIN** 11 12 #include <hidef.h> // For EnableInterrupts macro 13 #include "derivative.h" // Include peripheral declarations 14 15 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 16 // Function Prototypes 17 18 void IniTimers( byte ntimers ); // Used NTIMERS, not ntimers here 19 word SetimerMS( byte timer, word timeMS ); 20 byte Timeout( byte timer ); 21 void WTimer( byte timer ); 22 void WaitMS_on( byte timer, word timeMS ); 23 void WaitISR_on(byte timer, word timeMS ); 24 25 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 26 // Parameter Definitions 27 28 #define RTCPS_BY_ONE 8U // Divided by 1, gives 1mS Tick 29 #define ONE_KHZ 0 // Uses internal 1Khz clock 30 31 #define RTC_FLAGS ( RTCSC_RTIF_MASK | ONE_KHZ | RTCSC_RTIE_MASK | \ 32 RTCPS_BY_ONE ) 33 // RTCSC_RTCPS0_MASK 1U <<< 2, ONE_KHZ (bits 2,1,0) 34 // RTCSC_RTCPS3_MASK 8U ... 35 // RTCPS_BY_ONE 8U <<< 4, Divide by 1; gives 1mS Tick 36 // RTCSC_RTIE_MASK 16U <<< 3 37 // RTCSC_RTIF_MASK 128U <<< 1 38 39 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 40 // Define Sassert, in case you need a Static assert 41 #define Sassert(e) do { enum { assert_static__ = 1/(e) }; } while (0) 42 43 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 44 // Several Defines 45 #undef Wait 46 #define Wait(e) while( !(e)) 47 #undef CpuIntEn 48 #define CpuIntEn __asm CLI 49 #undef CpuIntDsb 242 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 50 #define CpuIntDsb __asm SEI 51 #undef EOF 52 #define EOF ((word)‐1) 53 54 #endif // TIMERSJ_H_INCLUDED No hay mucho que comentar. Usted tiene que poder decir qué hace y cómo, el Sassert. 53) EJEMPLO #1 USANDO LIBRERÍA DE TIMERS Obsérvese cómo está de OCULTA la librería de Timers: No figura ni la definición de funciones, ni las variables que se usan, ni siquiera aparece aquí la ISR del RTC. OCULTAR (HIDE) la información y hacer que el acceso a ella sea ESTRUCTURADO, es el objetivo del OOPS (Object Oriented Programming System) Este ejercicio enciende los 8 leds a diferentes TIEMPOS, que han sido definidos mediante la tabla "const word Delays[]". Una activación del botón de IRQ detiene el programa; la siguiente activación hace que el programa continúe. Para experimentar, se usó TAMBIÉN la librería de Timers en la ISR del IRQ, a fin de separar las pulsaciones del botón, al menos 1 segundo (1000 MS) ["Labs‐C\Lab2\TimersJ\100TimedFlash8.c"] 01 // ****************************************************************** 02 // Program 100TimedFlash8.c: Luis G. Uribe C. D24N2013 C11D2013 03 // 8 LEDs flashing at different rates. IRQ toggle's program OFF/ON 04 // 05 // ****************************************************************** 06 // Include Files 07 #include <hidef.h> // For EnableInterrupts macro 08 #include "derivative.h" // Include peripheral declarations 09 10 #include "timersJ_.h" // Defines cli, sti 11 12 const word Delays[]= {150, 355, 511, 673, 1002, 1398, 2755, 3000}; 13 //const word Delays[]= {673, 150, 2755, 1398, 511, 1002, 3000, 355}; 14 15 byte Toggle = 1; // Begin in RUN state (1) 16 17 // ****************************************************************** 18 void main ( void ) /*()*/ 19 { byte i, LEDsMask; 20 21 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 22 // >>> ALWAYS include the following 2 lines 23 // Cfr. 02MC9S08QE128RM(ReferenceManual)‐U.pdf, pag 101 24 25 #define COP_Disable 0x42 26 SOPT1 = COP_Disable; // System Options 1 27 28 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 29 // Enable Interrupts for IRQ 30 243 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 31 IRQSC = IRQSC_IRQPE_MASK | IRQSC_IRQACK_MASK; 32 IRQSC_IRQIE = 1; // BSET IRQSC_IRQIE,IRQSC: IRQ pin IntEn 34 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 35 // Init LEDs PORTs PTCD (bits 0‐5) and PTED (bits 6‐7) for output 36 PTCDD = 0xFF; // ..(they drive LEDs on DEMOQE128) 37 PTEDD = 0xFF; 38 39 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 40 // Enable Timers 41 IniTimers( 8 ); 42 43 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 44 // Start 8 timers 45 for( i = 0; i < 8; i ++ ) { 46 SetimerMS( i, Delays[ i ] >> 1 ); 47 } 48 49 // =============================================================== 50 CpuIntEn; // CPU Interrupt ENABLE. DON'T FORGET! 51 52 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 53 // Loop for ever... IRQ toggles RUN/~STOP 54 55 while( 1 ) { 56 Wait( Toggle ); // IRQ: RUN/~STOP 57 for( LEDsMask = 0, i = 0; i < 8; i ++ ) { 58 if( Timeout( i ) ) { 59 LEDsMask |= 1 << i; 60 SetimerMS( i, Delays[ i ] >> 1 ); 61 } 62 } 63 PTED = ( PTCD ^= LEDsMask ); 64 } 65 } 66 67 // ================================================================== 68 // IRQ INTERRUPT SERVICE ROUTINE 69 // NOTE: Es necesario hacer UN ACKNOWLEDGE para que la interrupción 70 // vuelva a suceder (Interrupt Handshaking, different for each 71 // peripheral! Need to read Manual for each equipment... :‐( 72 73 interrupt VectorNumber_Virq void IRQISR ( void ) /*()*/ 74 { 75 IRQSC_IRQIE = 0; // Auto disable IRQ Interrupt 76 Toggle ^= (byte)1; 77 WaitISR_on( 7, 1000 ); 78 79 IRQSC_IRQACK = 1; // BSET IRQSC_IRQACK, IRQSC: ACK IRQ 80 // ..Interrupt (Re‐Arm IRQ Interrupts) 81 IRQSC_IRQIE = 1; // BSET IRQSC_IRQIE,IRQSC: ReEnable 82 // ..IRQ pin IntEn 83 } 244 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 COMENTARIOS a ["Labs-C\Lab2\TimersJ\100TimedFlash8.c"] Todo comienza como SIEMPRE. Luego viene la tabla de Delays, acompañada de una Alternativa, para que usted experimente. Ambas tablas han sido escogidos empleando una función random (en Perl) para números entre 150 y 3000: 12 const word Delays[]= {150, 355, 511, 673, 1002, 1398, 2755, 3000}; 13 //const word Delays[]= {673, 150, 2755, 1398, 511, 1002, 3000, 355}; La variable GLOBAL Toggle, que comienza en 1 (RUN state), será NEGADA (vía Exor) por la ISR del IRQ, cada vez que se oprima el botón de IRQ. 15 byte Toggle = 1; // Begin in RUN state (1) Dos variables locales: 18 void main ( void ) /*()*/ 19 { byte i, LEDsMask; Las 2 instrucciones que SIEMPRE hay que incluir: 25 #define COP_Disable 0x42 26 SOPT1 = COP_Disable; // System Options 1 Habilitación de Interrupciones para el IRQ: 31 IRQSC = IRQSC_IRQPE_MASK | IRQSC_IRQACK_MASK; 32 IRQSC_IRQIE = 1; // BSET IRQSC_IRQIE,IRQSC: IRQ pin IntEn Inicialización de los LEDs. Aquí hemos omitido el procedimiento RECOMENDADO, que ya ilustramos, y que indica colocar en un estado inicial adecuado el valor de las salidas, ANTES de programar los correspondientes puertos como Salida, ya que en este caso NO IMPORTA. 36 PTCDD = 0xFF; // ..(they drive LEDs on DEMOQE128) 37 PTEDD = 0xFF; Inicializamos los Timers: 41 IniTimers( 8 ); Arrancamos inicialmente todos los 8 timers leyendo de la tabla Delays: 45 for( i = 0; i < 8; i ++ ) { 46 SetimerMS( i, Delays[ i ] >> 1 ); 47 } Habilitamos las interrupciones del CPU: 50 CpuIntEn; // CPU Interrupt ENABLE. DON'T FORGET! 245 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 El ciclo infinito para este programa. Primero se bloquea el programa si la variable Toggle indica STOP (comienza en RUN State), así que si se oprime el botón de IRQ, se pausa o se continúa el programa: 55 while( 1 ) { 56 Wait( Toggle ); // IRQ: RUN/~STOP Si corresponde ejecutar el ciclo infinito, se inicializa la variable auxiliar LEDsMask en 0, y luego se van recorriendo todos los timers, buscando aquellos que ya Expiraron (Timeout), y se los señala con un 1 en LEDsMask, en la posición correspondiente a su ID, de 0 a 7; además, como ya les expiró el tiempo se los vuelve a activar con un SetimerMS: 57 for( LEDsMask = 0, i = 0; i < 8; i ++ ) { 58 if( Timeout( i ) ) { 59 LEDsMask |= 1 << i; 60 SetimerMS( i, Delays[ i ] >> 1 ); 61 } 62 } Al finalizar el análisis de todos los timers, se cambia (NIEGA) el estado de los LEDs correspondientes a aquellos que ya expiraron (usando un Exor: PTCD ^= LEDsMask). El resultado de ese nuevo valor se lo replica en PTED: 63 PTED = ( PTCD ^= LEDsMask ); La IRQ INTERRUPT SERVICE ROUTINE: Al comienzo se auto deshabilita para interrupciones, a fin de que las pulsaciones del botón IRQ no tengan repercusiones mientras no se termine la IRQISR: 73 interrupt VectorNumber_Virq void IRQISR ( void ) /*()*/ 74 { 75 IRQSC_IRQIE = 0; // Auto disable IRQ Interrupt Luego se invierte el estado de la variable Toggle empleando un Exor: 76 Toggle ^= (byte)1; Se habilita la ESPERA DENTRO DE INTERRUPCIONES usando el WaitISR_on para un Segundo, empleando el Timer #7: 77 WaitISR_on( 7, 1000 ); IMPORTANTÍSIMO: NO PUEDE USARSE EL MISMO TIMER (7 en el ejemplo) EN NINGUNA OTRA PARTE. Basta con que sean distintos para que no haya conflictos Terminada la espera, que bloquea cualquier entrada del usuario durante un segundo, se procede al Protocolo de Acknowledge de interrupciones para este periférico IRQ en 246 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 particular, que es diferente de los demás y que, como ya dijimos tantas veces, hace necesario estudiar cada periférico por separado: 79 IRQSC_IRQACK = 1; // BSET IRQSC_IRQACK, IRQSC: ACK IRQ 80 // ..Interrupt (Re‐Arm IRQ Interrupts) Se vuelve a rehabilitar las propias interrupciones, deshabilitadas al comienzo de la ISR, y se termina: 81 IRQSC_IRQIE = 1; // BSET IRQSC_IRQIE,IRQSC: ReEnable 82 // ..IRQ pin IntEn 54) EJEMPLO #2 USANDO LIBRERÍA DE TIMERS Este ejemplo es una extensión del anterior: Hay 4 tablas con valores seleccionados también al azar, y se usa el botón de IRQ para ir cambiando de tabla; se comienza por la tabla #0 (la primera) y la IRQISR hace pasar el índice con que se lee la tabla, por los números 0, 1, 2, 3, 0, 1, 2 ..: ["Labs‐C\Lab2\TimersJ\110TimedFlash8X4.c"]: 01 // ****************************************************************** 02 // Program TimedFlash8X4.c: Luis G. Uribe C. L25N2013 C11D2013 03 // 8 LEDs flashing at different rates. IRQ steps through Delay Tables 04 05 // ****************************************************************** 06 // Include Files 07 #include <hidef.h> // For EnableInterrupts macro 08 #include "derivative.h" // Include peripheral declarations 09 10 #include "timersJ_.h" // Defines cli, sti 11 12 const word Delays[4][8] = 13 { { 673, 150, 2755, 1398, 511, 1002, 3000, 355}, 14 { 355, 511, 2755, 1398, 1002, 673, 150, 3000}, 15 { 1002, 3000, 1398, 511, 673, 355, 2755, 150}, 16 { 355, 1002, 2755, 1398, 150, 511, 3000, 673}, 17 }; 18 byte Index = 0; 19 20 // ****************************************************************** 21 void main ( void ) /*()*/ 22 { byte i, LEDsMask; // Begin in RUN state (1) 23 24 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 25 // >>> ALWAYS include the following 2 lines 26 // Cfr. 02MC9S08QE128RM(ReferenceManual)‐U.pdf, pag 101 27 28 #define COP_Disable 0x42 29 SOPT1 = COP_Disable; // System Options 1 30 31 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 32 // Enable Interrupts for IRQ 247 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 34 IRQSC = IRQSC_IRQPE_MASK | IRQSC_IRQACK_MASK; 35 IRQSC_IRQIE = 1; // BSET IRQSC_IRQIE,IRQSC: IRQ pin IntEn 36 37 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 38 // Init LEDs PORTs PTCD (bits 0‐5) and PTED (bits 6‐7) for output 39 PTCDD = 0xFF; // ..(they drive LEDs on DEMOQE128) 40 PTEDD = 0xFF; 41 42 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 43 // Enable Timers 44 IniTimers( 8 ); 45 46 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 47 // Start 8 timers 48 for( i = 0; i < 8; i ++ ) { 49 SetimerMS( i, Delays[ Index ][ i ] >> 1 ); 50 } 52 // =============================================================== 53 CpuIntEn; // CPU Interrupt ENABLE. DON'T FORGET! 54 55 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 56 // Loop for ever.. Pressing IRQ steps through Delay Tables (Index) 57 58 while( 1 ) { 59 for( LEDsMask = 0, i = 0; i < 8; i ++ ) { 60 if( Timeout( i ) ) { 61 LEDsMask |= 1 << i; 62 SetimerMS( i, Delays[ Index ][ i ] >> 1 ); 63 } 64 } 65 PTED = ( PTCD ^= LEDsMask ); 66 } 67 } 68 69 // ================================================================== 70 // IRQ INTERRUPT SERVICE ROUTINE 71 // NOTE: It is neccesary to ACKNOWLEDGE the Interrupt, in order to 72 // re‐Arm it. 'Interrupt Handshaking'... is different for each 73 // peripheral! so you will need to... read the Manual & program 74 // a different routine... for EACH peripheral device. <;~( 75 76 interrupt VectorNumber_Virq void IRQISR ( void ) /*()*/ 77 { 78 IRQSC_IRQIE = 0; // AUTO Disable IRQ Interrupt 79 Index = ( Index + 1 ) & 0x03; // 0, 1, 2, 3, 0, 1, 2 .. 80 WaitISR_on( 7, 1000 ); 81 82 IRQSC_IRQACK = 1; // BSET IRQSC_IRQACK, IRQSC: ACK IRQ 83 // ..Interrupt (Rearm IRQ Interrupts) 84 IRQSC_IRQIE = 1; // BSET IRQSC_IRQIE,IRQSC: ReEnable 85 // ..IRQ pin IntEn 86 } 248 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 COMENTARIOS a ["Labs-C\Lab2\TimersJ\110TimedFlash8X4.c"]: Matriz de 4 vectores, c/u de 8 valores: 12 const word Delays[4][8] = 13 { { 673, 150, 2755, 1398, 511, 1002, 3000, 355}, 14 { 355, 511, 2755, 1398, 1002, 673, 150, 3000}, 15 { 1002, 3000, 1398, 511, 673, 355, 2755, 150}, 16 { 355, 1002, 2755, 1398, 150, 511, 3000, 673}, 17 }; Índice para seleccionar entre los 4 vectores, del 0 al 3: 18 byte Index = 0; 21 void main ( void ) /*()*/ 22 { byte i, LEDsMask; // Begin in RUN state (1) 23 La misma habilitación de IRQ para interrumpir, y la inicialización de los LEDs: 32 // Enable Interrupts for IRQ 38 // Init LEDs PORTs PTCD (bits 0‐5) and PTED (bits 6‐7) for output La inicialización de siempre para los Timers y habilitación de interrupciones del CPU: 44 IniTimers( 8 ); 48 for( i = 0; i < 8; i ++ ) { 49 SetimerMS( i, Delays[ Index ][ i ] >> 1 ); 50 } 53 CpuIntEn; // CPU Interrupt ENABLE. DON'T FORGET! Ciclo infinito, como en el ejercicio anterior, pero ahora el IRQ NO detiene y rearranca el código, sino que cambia el 'Index' entre 0 y 3: 58 while( 1 ) { 59 for( LEDsMask = 0, i = 0; i < 8; i ++ ) { 60 if( Timeout( i ) ) { 61 LEDsMask |= 1 << i; 62 SetimerMS( i, Delays[ Index ][ i ] >> 1 ); 63 } 64 } 65 PTED = ( PTCD ^= LEDsMask ); Rutina de interrupciones para el botón de IRQ; todas las líneas de programa son idénticas al ejercicio anterior, excepto por el ciclado de Index: 79 Index = ( Index + 1 ) & 0x03; // 0, 1, 2, 3, 0, 1, 2 .. ¿ENTIENDE CÓMO ES QUE INDEX CICLA ENTRE 0 Y 3? SI NO, TIENE QUE ESTUDIAR C. 249 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 SERIAL COMMUNICATIONS & DATA QUES IN C C omo en el caso de los ejercicios en Lenguaje ensamblador, la parte culminante por el momento, de los programas en C, son las rutinas de Comunicaciones Seriales y sus compañeros, los procesadores de Colas de Datos. Les presento mi segunda librería en C, el manejador de Comunicaciones Seriales y, a continuación, mi librería para el manejo de Colas de Datos, auxiliar imprescindible para el manejo de Comunicaciones con flexibilidad. Refiérase a los ejercicios que están en la carpeta: Carpetas Labs‐C\ Lab3\SciComm. 55) LIBRERÍA DE COMUNICACIONES SERIALES Primero analicemos SciComm.h, que define la biblioteca de Comunicaciones Seriales: ["Labs‐C\Lab3\SciComm\SciComm.h"] 001 #ifndef SCICOMM_H_INCLUDED 002 #define SCICOMM_H_INCLUDED 003 004 // ****************************************************************** 005 // SciComm.h: SCI Comm. Support 006 // Luis G. Uribe C., D11M2007 L12M07 J16A09 L08J09 007 // ..S16J2012 (HCS08) D24J2012 M26F2013 J28N2013 008 // S30N2013: Migrated back to 'C' S21D2013 cosmetics: XmtRcvActivate 009 010 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 011 // ** REFER to 02MC9S08QE128RM(ReferenceManual)‐U.pdf ** 012 #undef Wait 013 #define Wait(e) while( !(e)) 014 #undef CpuIntEn 015 #define CpuIntEn __asm CLI 016 #undef CpuIntDsb 017 #define CpuIntDsb __asm SEI 018 #undef EOF 019 #define EOF ((word)‐1) 020 021 // ================================================================== 022 // THE FOLLOWING MAIN **FUNCTIONALITIES** ARE DEFINED IN THIS LIBRARY 023 // >>MAIN<<: getchar(), putchar(), RcvIntEn, RcvIntDsb, XmtIntEn, 024 // XmtIntDsb, XmtRcvActivate 025 // 026 // >>ANCILLARY<<: Bauds9600Value, CommIntEn, RcvChar, RcvRdy, 027 // XmtChar, XmtRdy 028 029 // ================================================================== 030 // ***REDEFINE*** MC9S08QE128 REGISTERS AND REQUIRED BITS 031 // ..for SCI Serial Communications Interface 032 // >>> ALIAS <<< are other names for same addresses. BE CAREFUL! 033 034 // ****************************************************************** 035 // SCI1BD: EQU $0020 ; Baude Rate register 036 // LOOPMODE!!! 250 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 037 038 #define Bauds9600Value 0x001A // 16BITS: 4e6/(16*9600)=26.04=0x1A 039 040 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 041 // SCI1D: EQU $27 042 043 #define XMTBUF SCI1D // Transmiter Buffer 044 #define RCVBUF SCI1D // Receiver Buffer 045 046 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 047 // SCI1C2: EQU $23 048 049 #define XMTCTRLREG SCI1C2 050 #define RCVCTRLREG SCI1C2 051 052 #define XMTEN SCI1C2_TE_MASK // TE: Transmiter Enabl %00001000 053 #define XMTIEN SCI1C2_TIE_MASK // TIE: Xmt INT Enabl %10000000 054 #define XMTIEN_bit SCI1C2_TIE // 7 055 056 #define RCVEN SCI1C2_RE_MASK // RE: Receiver Enable %00000100 057 #define RCVIEN SCI1C2_RIE_MASK // RIE: Rcv INT Enable %00100000 058 #define RCVIEN_bit SCI1C2_RIE // 5 059 060 #define XmtRcvEnab (XMTEN | RCVEN) // Enable BOTH devices %00001100 061 062 // ================================================================== 063 // SCI1S1: EQU $24 064 065 #define XMTSTATREG SCI1S1 066 #define RCVSTATREG SCI1S1 067 068 // Relevant bits: 069 070 #define XMTRDY SCI1S1_TDRE_MASK // Transmiter Data Register Empty 071 #define XMTRDY_bit SCI1S1_TDRE // 7 (mSCI1S1_TDRE: %10000000) 072 073 #define RCVRDY SCI1S1_RDRF_MASK // Receive Data Register Full 074 #define RCVRDY_bit SCI1S1_RDRF // 5 (mSCI1S1_RDRF: %00100000) 075 076 #define XMTEMPTY SCI1S1_TC_MASK // Transmission Complete Flag 077 #define XMTEMPTY_bit SCI1S1_TC // 6 (mSCI1S1_TC: %01000000): LAST 078 079 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 080 // Receiver: 081 082 #define OVERRUNERR SCI1S1_OR // OR: Overrun %00001000 083 #define NOISERR SCI1S1_NF // NF: Noise Flag %00000100 084 #define FRAMERR SCI1S1_FE // FE: Framing Error %00000010 085 #define PARERR SCI1S1_PF // PE: Parity Error %00000001 086 087 #define RCVErrs (OVERRUNERR | NOISERR | FRAMERR | PARERR) 088 251 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 089 // ****************************************************************** 090 // MACRO DEFINITIONS 091 092 #define SCI9600N8 (SCI1BD = Bauds9600Value) // Prog. SCI @9600,8,N 093 094 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 095 #define XmtRcvActivate (SCI1C2 = XmtRcvEnab); /*ACTIVATE Xmt & Rcv*/\ 096 SCI9600N8 /* Serial Communications Interface*/ \ 097 /* .. 9600 bps, No parity, 8 bits */ 098 099 // ================================================================== 100 #define RcvRdy RCVRDY_bit 101 102 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 103 #define RcvChar RCVBUF 104 105 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 106 // getchar() MAY Rearm Rcv Interrupts if inside ISR 107 byte getchar() { 108 Wait( RcvRdy ); 109 return RcvChar; 110 } 111 112 // ================================================================== 113 #define XmtRdy XMTRDY_bit 114 115 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 116 #define XmtChar(c) (XMTBUF = (byte)(c)) 117 118 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 119 #define putchar(c) _putchar((byte)(c)) 120 // putchar(c) MAY rearm Xmt interrupts if inside ISR 121 byte _putchar( byte c ) 122 { 123 Wait( XmtRdy ); 124 XmtChar(c); 125 return c; 126 } 127 128 // ================================================================== 129 #define XmtIntEn (XMTIEN_bit = 1) 130 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 131 #define XmtIntDsb (XMTIEN_bit = 0) 132 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 133 #define RcvIntEn (RCVIEN_bit = 1) 134 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 135 #define RcvIntDsb (RCVIEN_bit = 0) 136 137 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 138 // Interrupt Enable for Communications (Remember: Output devices are 139 // ..enabled **ONLY** when they are ready for transmitting 140 // Reading RCVSTATREG and RCVBUF, clear any possibly RCV Ready Flag 252 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 141 // ..pending at the moment of interrupt enabling the device 142 143 #define CommIntEn { RCVSTATREG; RCVBUF; RcvIntEn; } 144 145 // ================================================================== 146 // Anciliary Stuff 147 148 #define I // NUL definition. From this point on, 'I' means NOTHING! 149 #define Forever for(;;){ 150 #define Endforever } 151 152 #endif // SCICOMM_H_INCLUDED COMENTARIOS a ["Labs-C\Lab3\SciComm\SciComm.h"]: 001 #ifndef SCICOMM_H_INCLUDED 002 #define SCICOMM_H_INCLUDED Las primeras dos líneas son muy comunes en todos los include files de C; su objetivo es que el código del archivo NO se agregue múltiples veces, aun cuando el programador, conciente o inconcientemente, lo haya incluido más de una vez. Si el símbolo, en este caso: SCICOMM_H_INCLUDED, que normalmente se extrae del mismo nombre del include file, no existe, entonces se lo DEFINE y se incluye el texto; si YA está definido, se salta toda la inclusión de código (hasta el #endif // SCICOMM_H_INCLUDED final) Refiérase al manual: 02MC9S08QE128RM(ReferenceManual)‐U.pdf Wait, CpuIntEn, CpuIntDsb, y EOF ya los vimos con anterioridad. Las funcionalidades PRINCIPALES definidas aquí son: XmtRcvActivate: Energiza y activa los dos periféricos: XMT Y RCV getchar(): Lee o recibe un símbolo del puerto de comunicaciones putchar(): Envía un símbolo por el puerto de comunicaciones RcvIntEn: Habilita las interrupciones del Receptor RcvIntDsb: Deshabilita las interrupciones del Receptor XmtIntEn: Habilita las interrupciones del Transmisor XmtIntDsb: Deshabilita las interrupciones del Transmisor Otras funcionalidades auxiliares son: Bauds9600Value: Programa el puerto SCI para trabajar a: 9600,8N1: 9600 bps,8 bits por símbolo, No parity, 1 stop bit Como de costumbre, se redefinen los símbolos definidos por el fabricante para cambiarlos por otros que tengan mayor significado, a fin de mejorar la inteligibilidad; son ALIAS o asignaciones numéricas: 035 // SCI1BD: EQU $0020 ; Baude Rate register 038 #define Bauds9600Value 0x001A // 16BITS: 4e6/(16*9600)=26.04=0x1A 253 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 Se observa aquí lo mismo que ya incluimos en la contraparte en ASM: que tanto el Registro de Transmisión (XMTBUF) como el de Recepción (RCVBUF), siendo DOS entidades DIFERENTES, tienen la MISMA dirección (0x27). La discriminación entre uno y otro se logra por el SENTIDO en que fluyen los datos. Si hay una asignación SOBRE el registro XMTBUF, es una transmisión; si la asignación es DESDE el registro, se lee el RCVBUF 041 // SCI1D: EQU $27 043 #define XMTBUF SCI1D // Transmiter Buffer 044 #define RCVBUF SCI1D // Receiver Buffer Hay un solo Registro de Control de interés en esta librería: SCI1C2 (SCI 1, Control‐2); de allí, algunos bits son para XMT y otros para RCV. Otros fabricantes diferencian mucho más abruptamente los dos periféricos, el de transmisión y el de recepción, e incluyen DOS registros de Control; por eso yo he incluido DOS nombres, XMTCTRLREG y RCVCTRLREG, pero son ALIAS del único registro físico: 047 // SCI1C2: EQU $23 049 #define XMTCTRLREG SCI1C2 050 #define RCVCTRLREG SCI1C2 Los bits de Control que nos interesan, tanto para XMT como para RCV, son: 052 #define XMTEN SCI1C2_TE_MASK // TE: Transmiter Enabl %00001000 053 #define XMTIEN SCI1C2_TIE_MASK // TIE: Xmt INT Enabl %10000000 054 #define XMTIEN_bit SCI1C2_TIE // 7 055 056 #define RCVEN SCI1C2_RE_MASK // RE: Receiver Enable %00000100 057 #define RCVIEN SCI1C2_RIE_MASK // RIE: Rcv INT Enable %00100000 058 #define RCVIEN_bit SCI1C2_RIE // 5 Hay que recordar que existe la Habilitación (ENABLE: XMTEN, RCVEN), que equivale a energizar el periférico, y que es diferente del IEN: Interrupt ENable: XMTIEN, RCVIEN. Nuestra librería siempre Habilita los dos periféricos simultáneamente, por lo que tenemos un símbolo para eso: XmtRcvEnab 060 #define XmtRcvEnab (XMTEN | RCVEN) // Enable BOTH devices %00001100 Con los elementos de Status ocurre lo mismo que con los de Control, que se encuentran reunidos tanto los de XMT como los de RCV, en el registro de Status1: SCI1S1 063 // SCI1S1: EQU $24 065 #define XMTSTATREG SCI1S1 066 #define RCVSTATREG SCI1S1 Los bits que nos interesan son: 070 #define XMTRDY SCI1S1_TDRE_MASK // Transmiter Data Register Empty 071 #define XMTRDY_bit SCI1S1_TDRE // 7 (mSCI1S1_TDRE: %10000000) 073 #define RCVRDY SCI1S1_RDRF_MASK // Receive Data Register Full 074 #define RCVRDY_bit SCI1S1_RDRF // 5 (mSCI1S1_RDRF: %00100000) 254 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 076 #define XMTEMPTY SCI1S1_TC_MASK // Transmission Complete Flag 077 #define XMTEMPTY_bit SCI1S1_TC // 6 (mSCI1S1_TC: %01000000): LAST Los ERRORES, como vimos al analizar las rutinas de Comunicaciones en ASM, sólo podemos detectarlos en el RECEPTOR: 082 #define OVERRUNERR SCI1S1_OR // OR: Overrun %00001000 083 #define NOISERR SCI1S1_NF // NF: Noise Flag %00000100 084 #define FRAMERR SCI1S1_FE // FE: Framing Error %00000010 085 #define PARERR SCI1S1_PF // PE: Parity Error %00000001 087 #define RCVErrs (OVERRUNERR | NOISERR | FRAMERR | PARERR) La definición es exactamente la misma que en ASM, pues es el MISMO periférico: OVERRUN ocurre cuando el serializador de entrada completa de leer los 10 bits del símbolo que llega, y le corresponde ahora trasladarlo al BUFFER de recepción, pero se encuentra con que el programa aún NO ha leído el carácter anterior; así que de todas maneras lo almacena en el registro de recepción, sobrescribiendo la información que allí se encontraba, y señala mediante esta bandera, que se está perdiendo información de entrada, por sobreescritura del RCVBUF. El programador tiene que tomar nota de esto, probablemente solicitando la retransmisión de la información al otro extremo de la línea. Normalmente hay que calibrar la cantidad de información y la velocidad de entrada, para evitar que esto suceda. Manejar la entrada por interrupciones, y hacer uso de FIFOs o COLAS es lo más indicado. NOISERR ocurre cuando la línea tiene ruido, que se refleja por variaciones en la señal que se está leyendo, y que ocurren entre el comienzo y el final de los bits. Se supone que la línea no cambia de valor en ese intervalo. FRAMERR se presenta cuando, habiendo el receptor detectado un bit de START, al llegar a la posición en donde debería estar el bit de STOP, NO hay un 1. Esto puede significar que el receptor se está descincronizando, y que a lo mejor el bit que detectó como Start no lo era. PARERR ocurre cuando, estando habilitado el cálculo de paridad, se encuentra que en la recepción ésta no coincide con el valor que tendría que haber. Nosotros NO estamos usando Paridad en nuestra biblioteca de Comunicaciones Seriales. 090 // MACRO DEFINITIONS 092 #define SCI9600N8 (SCI1BD = Bauds9600Value) // Prog. SCI @9600,8,N 095 #define XmtRcvActivate (SCI1C2 = XmtRcvEnab); /*ACTIVATE Xmt & Rcv*/\ 096 SCI9600N8 /* Serial Communications Interface*/ \ 097 /* .. 9600 bps, No parity, 8 bits */ Mire bien, para entender la Macro, los CONTINUADORES de línea: "\" Si algo allí no entiende, TIENE QUE ESTUDIAR C 255 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 100 #define RcvRdy RCVRDY_bit 103 #define RcvChar RCVBUF getchar() es una función BLOQUEANTE, que suspende el flujo del programa hasta que se haya recibido un dato, lo que se refleja al encender el bit de 'RcvRdy'. Devuelve el valor recibido: RcvChar (que tiene dos alias: RCVBUF, que a su vez es alias de SCI1D) La secuencia de: leer el bit RcvRdy y a continuación leer el RCVBUF (vía RcvChar), es también la necesaria para el protocolo de Acknowledge de interrupciones del RCV; por eso se usa 'getchar' también DENTRO de la ISR de Recepción. 106 // getchar() MAY Rearm Rcv Interrupts if inside ISR 107 byte getchar() { 108 Wait( RcvRdy ); 109 return RcvChar; 110 } En una próxima revisión, cambiaré RcvChar por RcvChar(), para mimetizar mejor el comportamiento de una función y asemejarla más a XmtChar(). Su uso cambiará así: 109 return RcvChar(); 113 #define XmtRdy XMTRDY_bit 116 #define XmtChar(c) (XMTBUF = (byte)(c)) putchar() también es una función BLOQUEANTE, que suspende el flujo del programa hasta que el Transmisor esté listo para enviar un dato, lo que se refleja al encender el bit de 'XmtRdy'. La secuencia de: leer el bit XmtRdy y a continuación escribir en el XMTBUF, vía XmtChar(), es también la necesaria para el protocolo de Acknowledge de interrupciones del XMT; por eso se usa 'putchar()' también DENTRO de la ISR de Transmisión. 119 #define putchar(c) _putchar((byte)(c)) Esta definición obliga mediante un CAST a que el símbolo que se transmite sea un BYTE, aunque el usuario haya empleado un WORD. ¿SABE LO QUE ES UN CAST? ¿SU USO E IMPORTANCIA? ¿NO? TIENE QUE ESTUDIAR C. 121 byte _putchar( byte c ) 122 { 123 Wait( XmtRdy ); 124 XmtChar(c); 125 return c; 126 } Símbolos importantes: 129 #define XmtIntEn (XMTIEN_bit = 1) 131 #define XmtIntDsb (XMTIEN_bit = 0) 133 #define RcvIntEn (RCVIEN_bit = 1) 135 #define RcvIntDsb (RCVIEN_bit = 0) 143 #define CommIntEn { RCVSTATREG; RCVBUF; RcvIntEn; } 256 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 El CommIntEn, o habilitación de interrupciones para Comunicaciones, hace lo mismo que mencionamos en el programa en ASM: sólo habilita las interrupciones de RCV; las de XMT debe habilitarse caso a caso por el programador, cuando haya algo que enviar por interrupciones. La funcionalidad, que usted debe CORROBORAR con el Reference Manual, consiste en leer el RCVSTATREG; luego leer el RCVBUF; y por último habilitar las Interrupciones de RCV, mediante: RcvIntEn. Código auxiliar: 148 #define I // NUL definition. From this point on, 'I' means NOTHING! 149 #define Forever for(;;){ 150 #define Endforever } 152 #endif // SCICOMM_H_INCLUDED ¿Para qué puede utilizarse el '#define I' que hace que la letra I no valga NADA? 56) Send 'A' to 'z' letters for ever, to PC ["Labs‐C\Lab3\SciComm\1tstXmt.c"] 01 // ****************************************************************** 02 // Program 1TstXmt.C: Test SCI Comm. Support 03 // Luis G. Uribe C., D11M2007 J16A09 L08J09 S16J2012(HCS08) 04 // C11D2013 (C) 05 // ..Send 'A' to 'z' letters for ever, to Hyperterminal 07 // ****************************************************************** 08 // Include Files 09 #include <hidef.h> // For EnableInterrupts macro 10 #include "derivative.h" // Include peripheral declarations 12 #include "SciComm.h" // SCI Comm. Support 14 #define CR 0x0D 15 #define LF 0x0A 17 // ****************************************************************** 18 void main ( void ) /*()*/ 19 { byte Letter; 21 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 22 // >>> ALWAYS include the following 2 lines 23 // Cfr. 02MC9S08QE128RM(ReferenceManual)‐U.pdf, pag 101 24 #define COP_Disable 0x42 25 SOPT1 = COP_Disable; // System Options 1 27 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 28 SCI9600N8; // Setup Serial Communications Inter‐ 29 XmtRcvActivate; // ..face: 9600 bps, No parity, 8 bits 30 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 32 while( 1 ) { 33 for( Letter = 'A'; Letter <= 'z'; Letter ++ ) { 34 putchar( Letter ); 35 } 36 putchar( CR ); // Carriage Return (CR: '\r' in C) 37 putchar( LF ); // Line Feed (LF: '\n') 38 } 39 } 257 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 COMENTARIOS a ["Labs-C\Lab3\SciComm\1tstXmt.c"]: El comienzo es ya conocido. Se definen luego dos símbolos, ambos definidos por el estándar ASCII (American Standard Code for Information Interchange) el CR, que es el símbolo que hace que el cursor se vaya a la izquierda (Carriage Return, cuando se trataba de máquinas de escribir, o Teletipos), y el NL (New Line, que salta a la línea siguiente) 14 #define CR 0x0D 15 #define LF 0x0A 18 void main ( void ) /*()*/ 19 { byte Letter; Luego de la parte más convencional, viene la inicialización del SCI: 28 SCI9600N8; // Setup Serial Communications Inter‐ 29 XmtRcvActivate; // ..face: 9600 bps, No parity, 8 bits Y QUÉ PASÓ CON EL CLI? Esta aplicación NO usa interrupciones. El ciclo infinito para este programa genera las letras desde la 'A' hasta la 'z' y las transmite con putchar( Letter ); al finalizar cada secuencia agrega un Retorno de Cursor, CR ('\r' en C) y un Salto de Línea, LF ('\n' en C), y recomienza con las letras en la siguiente línea, para siempre: 32 while( 1 ) { 33 for( Letter = 'A'; Letter <= 'z'; Letter ++ ) { 34 putchar( Letter ); 35 } 36 putchar( CR ); // Carriage Return (CR: '\r' in C) 37 putchar( LF ); // Line Feed (LF: '\n') 38 } 39 } 57) SEND STEP BY STEP (IRQ) 'A' TO 'Z' LETTERS TO PC & SHOW IN LEDS ["Labs‐C\Lab3\SciComm\1tstXmt‐Leds.c"] 01 // ****************************************************************** 02 // Programa 1TstXmtLeds.C: Test SCI Comm. Support 03 // Luis G. Uribe C., D11M2007 J16A09 L08J09 S16J2012(HCS08) 04 // C11D2013 (C) 05 // 06 // ..Send 'A' to 'z' letters for ever to Hyperterminal. LEDs display 07 // ..Step by step using IRQ 08 // ****************************************************************** 09 // Include Files 10 #include <hidef.h> /* for EnableInterrupts macro */ 11 #include "derivative.h" /* include peripheral declarations */ 12 13 #include "SciComm.h" // SCI Comm. Support 14 258 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 15 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 16 // Global Variables 17 18 volatile byte GlobalVar; 19 20 // ****************************************************************** 21 void main( void ) 22 { byte Letter; 23 24 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 25 // >>> ALWAYS include the following 2 lines 26 // Cfr. 02MC9S08QE128RM(ReferenceManual)‐U.pdf, pag 101 27 #define COP_Disable 0x42 28 SOPT1 = COP_Disable; // System Options 1 29 30 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 31 // Init LEDs PORTs PTCD (bits 0‐5) and PTED (bits 6‐7) for output 32 // In DEMOQUE128, DO ***NOT*** USE PTC5's ASSOCIATED LED, as it is 33 // ..also the pin used to DISABLE COMM1 !!! via Jumper J8, THANKS! 34 // 35 // NOTE1: For Output Pins/Ports, First load a SAFE value (you will 36 // ..decide it), then, program them for Outputs. This way, it will 37 // ..NOT appear FALSE output, values/glitches. 38 // 39 // NOTE2: Suppose your program has, for example, two outputs that 40 // ..will control the access for 2 devices to a common bus. The 41 // ..logic is: 00: No peripheral has access. 01: A has access. 42 // ..10: B has access. 11: Never happens! If it will, peripherals 43 // ..will burn out. 44 // Now, at Power Up, ALL digital pins default to INPUTS. Without 45 // ..Pull up resistors: They FLOAT!! For how long? It all depends 46 // .. on oscilator setup, and the time you expend before program‐ 47 // ..ming the pins for output. It means: may be BOTH A & B devices 48 // ..will reach the bus... and BURN! Short circuit their outputs!! 49 // Sleep on this, and come up with a solution... 50 51 PTCD = 0xFF; 52 PTED = 0xFF; 53 PTCDD = 0x1F; // ..(they drive LEDs on DEMOQE128) 54 PTEDD = 0xC0; 55 56 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 57 // Enable IRQ pin 58 IRQSC = IRQSC_IRQPE_MASK | IRQSC_IRQACK_MASK; 59 IRQSC_IRQIE = 1; // BSET IRQSC_IRQIE,IRQSC: IRQ pin IntEn 60 61 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 62 SCI9600N8; // Setup Serial Communications Inter‐ 63 XmtRcvActivate; // ..face: 9600 bps, No parity, 8 bits 64 65 CpuIntEn; 66 259 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 67 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 68 while( 1 ) { 69 for( Letter = 'A'; Letter <= 'z'; Letter ++ ) { 70 putchar( Letter ); 71 Wait( GlobalVar ); 72 GlobalVar = 0; 73 PTCD = ~Letter; 74 PTED = ~Letter; 75 } 76 putchar( '\r' ); // Carriage Return (CR) 77 putchar( '\n' ); // Line Feed (LF) 78 } 79 } 80 81 // ================================================================== 82 interrupt VectorNumber_Virq void IRQISR( void ) /*()*/ 83 { 84 GlobalVar = 1; 85 IRQSC_IRQACK = 1; // BSET IRQSC_IRQACK, IRQSC: ACK IRQ Interrupt 86 // ..(Rearm IRQ Interrupts) 87 } COMENTARIOS a ["Labs-C\Lab3\SciComm\1tstXmt-Leds.c"]: La parte inicial es la convencional. Lean otra vez (ya habíamos dicho lo mismo cuando hicimos los ejercicios en ASM) y con detenimiento los requisitos para programar los terminales digitales como SALIDAS: 31 // Init LEDs PORTs PTCD (bits 0‐5) and PTED (bits 6‐7) for output 32 // In DEMOQUE128, DO ***NOT*** USE PTC5's ASSOCIATED LED, as it is 33 // ..also the pin used to DISABLE COMM1 !!! via Jumper J8, THANKS! 34 // 35 // NOTE1: For Output Pins/Ports, First load a SAFE value (you will 36 // ..decide it), then, program them for Outputs. This way, it will 37 // ..NOT appear FALSE output, values/glitches. 38 // 39 // NOTE2: Suppose your program has, for example, two outputs that 40 // ..will control the access for 2 devices to a common bus. The 41 // ..logic is: 00: No peripheral has access. 01: A has access. 42 // ..10: B has access. 11: Never happens! If it will, peripherals 43 // ..will burn out. 44 // Now, at Power Up, ALL digital pins default to INPUTS. Without 45 // ..Pull up resistors: They FLOAT!! For how long? It all depends 46 // .. on oscilator setup, and the time you expend before program‐ 47 // ..ming the pins for output. IT MEANS: may be BOTH A & B devices 48 // ..will reach the bus... and BURN! Short circuit their outputs!! 49 // Sleep on this, AND COME UP WITH A SOLUTION... Se determina que queremos que al iniciar las salidas, todas tengan unos en sus terminales: 51 PTCD = 0xFF; 52 PTED = 0xFF; 260 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 Luego se programa los correspondientes bits en los Direction Registers, 5 van el PTC y 2 en PTE (total 7 LEDs). Se excluye el terminal PTC5, que queda como ENTRADA, porque como ya dijimos arriba, en el DEMOQUE128 el PTC5, si está como salida (para manejar el LED asociado), también INHABILITARÁ el puerto de Comunicaciones Seriales, vía el Jumper J8. GRACIAS PE Micro! 53 PTCDD = 0x1F; // ..(they drive LEDs on DEMOQE128) 54 PTEDD = 0xC0; La habilitación del IRQ y su autorización para Interrumpir, es la convencional: 58 IRQSC = IRQSC_IRQPE_MASK | IRQSC_IRQACK_MASK; 59 IRQSC_IRQIE = 1; // BSET IRQSC_IRQIE,IRQSC: IRQ pin IntEn Después se inicializa, como de costumbre, el puerto de comunicaciones SCI, se Activan los dos periféricos, el de recepción y el de transmisión, y AQUÍ SÍ, se habilitan las interrupciones del CPU: 62 SCI9600N8; // Setup Serial Communications Inter‐ 63 XmtRcvActivate; // ..face: 9600 bps, No parity, 8 bits 65 CpuIntEn; El ciclo indefinido es tan sencillo como el del anterior ejercicio, pero después de transmitir cada letra, incluye una espera a que 'GlobalVar' valga uno (¿por qué está inicializada en CERO? Si aún no lo sabe, DESISTA) Cuando la IRQISR coloca en 1 a GlobalVar, el programa continúa, reponiendo a cero la variable, para el próximo turno, y presentando la letra por los LEDs. Se mueve ~Letter (NEGADA booleanamente) porque, como ya sabemos, los genios de PE Micro encienden los LEDs con CEROS: 68 while( 1 ) { 69 for( Letter = 'A'; Letter <= 'z'; Letter ++ ) { 70 putchar( Letter ); 71 Wait( GlobalVar ); 72 GlobalVar = 0; 73 PTCD = ~Letter; 74 PTED = ~Letter; 75 } 76 putchar( '\r' ); // Carriage Return (CR) 77 putchar( '\n' ); // Line Feed (LF) 78 } 79 } La rutina IRQISR es la convencional, más GlobalVar = 1; 82 interrupt VectorNumber_Virq void IRQISR( void ) /*()*/ 83 { 84 GlobalVar = 1; 85 IRQSC_IRQACK = 1; // BSET IRQSC_IRQACK, IRQSC: ACK IRQ Interrupt 86 // ..(Rearm IRQ Interrupts) 87 } 261 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 58) ECHO Hace ECHO a los caracteres que le llegan al equipo desde otra estación. Para no engañarse con el ECO LOCAL el programa retransmite la letra recibida, INCREMENTADA en uno: si recibo un 'A', transmito una 'B' y así sucesivamente. Recuerde probarlo enviándole "HAL" (la supercomputadora de "2001 Odisea del Espacio"): aparece en la pantalla... IBM. ["Labs‐C\Lab3\SciComm\2echo1.c"] 01 // ****************************************************************** 02 // Program 2Echo1.c: Test SCI Comm. Support 03 // Luis G. Uribe C., D11M2007 J16A09 L08J09 S16J2012 (HCS08) 04 // C11DN2013 (C) 05 // Luis G. Uribe C., D11M2007 J16A09 L08J09 S16J2012 (HCS08) L25N2013 06 // To be sure that there is "ECHO", return back 'letter+1', 07 // ..i.e: if Hyperterminal sends 'A', HC9S08 will return 'B'... 08 // ..for the range: 'A' <= char < 'z'. 09 // Any other chars are returned back without modification. 10 // ****************************************************************** 11 // Include Files 12 #include <hidef.h> // for EnableInterrupts macro 13 #include "derivative.h" // include peripheral declarations 14 #include "SciComm.h" // SCI Comm. Support 15 // ****************************************************************** 16 void main( void ) 17 { byte Letter; 18 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 19 // >>> ALWAYS include the following 2 lines 20 // Cfr. 02MC9S08QE128RM(ReferenceManual)‐U.pdf, pag 101 21 #define COP_Disable 0x42 22 SOPT1 = COP_Disable; // System Options 1 23 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 24 SCI9600N8; // Setup Serial Communications Inter‐ 25 XmtRcvActivate; // ..face: 9600 bps, No parity, 8 bits 26 // Prompt: 27 for( Letter = 'A'; Letter <= 'z'; Letter ++ ) { 28 putchar( Letter ); 29 } 30 putchar( '\r' ); // Carriage Return (CR) 31 putchar( '\n' ); // Line Feed (LF) 32 // ************ NOTE: ************ 33 // To SIMULATE SCI *inputs* use PEMicro SCI1 debugger command. 34 // 262 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 35 // It will be instructive to display SCI1S1 (status register), and 36 // ..see most important flags, bits 7,6,5: XMTRDY,XMTEMPTY,RCVRDY. 37 // While debugging, see how reading Status AND writing BUF, clears 38 // ..the RCV Ready flag. See also how XMT Status work: RDY & EMPTY 39 while( 1 ) { // Only chars in range A..z will be 40 if( ( Letter = getchar() ) >= 'A' && Letter <= 'z' ) { 41 Letter ++; // ..returned as NEXT char: Letter ++ 42 } 43 putchar( Letter ); // ..Those not in range will echo 44 } // End while( 1 ) // ..WITHOUT any modification: Letter 45 } COMENTARIOS a ["Labs-C\Lab3\SciComm\2echo1.c"]: Este programa tampoco emplea las interrupciones: es todo entrada y salida programada; por eso no aparece un CpuIntEn. Se comienza enviando un aviso (Prompt) al otro terminal, para notificarle que el programa de este lado está listo para trabajar; aquí enviamos una línea compuesta por las letras de la 'A' a la 'z', pero usted puede usar un texto cualquiera como "Ready": 26 // Prompt: 27 for( Letter = 'A'; Letter <= 'z'; Letter ++ ) { 28 putchar( Letter ); 29 } 30 putchar( '\r' ); // Carriage Return (CR) 31 putchar( '\n' ); // Line Feed (LF) Si se quieren simular *inputs* del SCI, hay un comando apropiado en el Debugger. Resulta instructivo, además, mostrar en su pantalla el SCI1S1 (status register) y ver las banderas más importantes, bits 7, 6 y 5: XMTRDY, XMTEMPTY y RCVRDY. Observe también cómo, la acción de leer el Status Y escribir sobre BUF, BORRA el RCV Ready flag. Mire también cómo trabaja el XMT Status, ambos bits: RDY y EMPTY. 39 while( 1 ) { // Only chars in range A..z will be 40 if( ( Letter = getchar() ) >= 'A' && Letter <= 'z' ) { 41 Letter ++; // ..returned as NEXT char: Letter ++ 42 } 43 putchar( Letter ); // ..Those not in range will echo 44 } // End while( 1 ) // ..WITHOUT any modification: Letter 59) ECHO USANDO INTERRUPCIONES ["Labs‐C\Lab3\SciComm\3echoInt‐1.c"] 01 // ****************************************************************** 02 // Program 3EchoInt‐1.c: Test SCI Comm. Support 03 // Luis G. Uribe C., M13M2007 J16A09 L08J09 L18J2012 (HCS08) D24J2012 04 // S30N2013 (C) C11D2013 05 // ..Same 2Echo1.C program but using INTERRUPTS for XMT 263 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 06 // ****************************************************************** 07 // Include Files 08 #include <hidef.h> // for EnableInterrupts macro 09 #include "derivative.h" // include peripheral declarations 10 #include "SciComm.h" // SCI Comm. Support 11 // ================================================================== 12 // GLOBAL VARIABLES 13 byte XmtNchars; //Soft Flag for Main: 0 Nothing to Xmt 14 byte *XmtPtr; //char pointer of next data to Xmt 15 byte Message[] = "12345\n\r"; // Short message 16 // byte Message[] = "Este es el mensaje de prueba, " 17 // "primero en transmitirse al PC por interrupciones\n\r" 18 // ****************************************************************** 19 void main( void ) 20 { byte Letter; 21 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 22 // >>> ALWAYS include the following 2 lines 23 // Cfr. 02MC9S08QE128RM(ReferenceManual)‐U.pdf, pag 101 24 #define COP_Disable 0x42 25 SOPT1 = COP_Disable; // System Options 1 26 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 27 SCI9600N8; // Setup Serial Communications Inter‐ 28 XmtRcvActivate; // ..face: 9600 bps, No parity, 8 bits 29 // ================================================================== 30 // Transmit your message: Enque at once full 'Message' Table for Xmt: 31 // Prompt: 32 XmtPtr = Message; // Init XmtPtr with 'Message' address; 33 XmtNchars = sizeof( Message ); 34 XmtIntEn; // XmtISR will send the message and 35 // ..clear 'XmtNchars' when finished 36 CpuIntEn; // <<<DON'T FORGET: GLOBAL ENABLE INTs 37 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 38 // XmtLoop: 39 Wait( XmtNchars == 0 ); // Wait until done OR just WaitForEver 40 Wait( 0 ); // ..WaitForEver 41 } 42 // ================================================================== 43 interrupt VectorNumber_Vsci1tx void XmtISR( void ) /*()*/ 44 { 45 if( XmtNchars ) { // See if done; if not: 46 putchar( *XmtPtr++ ); // (NOTE: putchar clears XMTRDY.bit) 47 XmtNchars‐‐; // Adjust char counter 48 }else{ // If XmtDone: 49 XmtIntDsb; // .. 50 } 51 } 264 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 COMENTARIOS a ["Labs-C\Lab3\SciComm\3echoInt-1.c"]: Variables Globales, para ser visibles tanto en el programa principal: XmtNchars, que mantiene el número actual de caracteres que faltan por transmitirse; es de tipo byte, por lo cual no pueden manejarse mensajes de más de 255 letras; 0 significa vacío. *XmtPtr apuntador a caracteres, señala el siguiente dato que será transmitido. byte *XmtPtr 13 byte XmtNchars; //Soft Flag for Main: 0 Nothing to Xmt 14 byte *XmtPtr; //char pointer of next data to Xmt Siendo que los apuntadores tienen 16 bits, por qué no se hizo la definición como: word *XmtPtr SI DUDA TIENE QUE ESTUDIAR MUCHÍSIMO C (SIN DUDA) Luego viene el mensaje que se quiere transmitir; el segundo, comentado, es un poco más largo, para ensayar: 15 byte Message[] = "12345\n\r"; // Short message 16 // byte Message[] = "Este es el mensaje de prueba, " 17 // "primero en transmitirse al PC por interrupciones\n\r" La inicialización estándar, más la de las variables XmtPtr y XmtNchars. La tabla se 'encola' para ser transmitida de una sola vez: 27 SCI9600N8; // Setup Serial Communications Inter‐ 28 XmtRcvActivate; // ..face: 9600 bps, No parity, 8 bits 32 XmtPtr = Message; // Init XmtPtr with 'Message' address; 33 XmtNchars = sizeof( Message ); 34 XmtIntEn; // XmtISR will send the message and 35 // ..clear 'XmtNchars' when finished y NO OLVIDAR habilitar las interrupciones Globales, del CPU: 36 CpuIntEn; // <<<DON'T FORGET: GLOBAL ENABLE INTs El ciclo de transmisión es muy simple: el programa espera (Wait) hasta que el número de caracteres sea cero, lo que le indica la finalización de la transmisión del mensaje, y continúa; como este ejercicio llega hasta allí, hacemos una simulación de un HALT: 38 // XmtLoop: 39 Wait( XmtNchars == 0 ); // Wait until done OR just WaitForEver 40 Wait( 0 ); // ..WaitForEver La rutina de transmisión es muy simple; lo primero es la definición de tipo 'interrupt', la identificación de la posición de la rutina en el Vector de Interrupciones, 'VectorNumber_Vsci1tx' (todos SÍMBOLOS, no más NÚMEROS) definidos en el correspondiente include file. Por último, el nombre de la rutina de interrupciones, de tipo 'void': XmtISR 265 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 43 interrupt VectorNumber_Vsci1tx void XmtISR( void ) /*()*/ Si aún hay caracteres para transmitir, los envía con putchar (que realiza el ACK), incrementa el apuntador de caracteres, decrementa el número de símbolos que faltan por transmitirse, y RTI (AUNQUE XmtNchars‐‐ haya producido CERO) 45 if( XmtNchars ) { // See if done; if not: 46 putchar( *XmtPtr++ ); // (NOTE: putchar clears XMTRDY.bit) 47 XmtNchars‐‐; // Adjust char counter Si al momento de interrumpir para transmitir, se encuentra que XmtNchars vale cero, se AUTO deshabilitan las interrupciones de transmisión y se retorna con RTI: 48 }else{ // If XmtDone: 49 XmtIntDsb; // .. 50 } 51 } Este mecanismo en el cual el 'Cliente' (main) hace una petición a la rutina de Servicio (XmtISR), activándole las Interrupciones, y donde la ISR se AUTODESACTIVA al terminar lo que le solicitaron, ES LA FORMA en que usted debe trabajar: MECANISMO Cliente‐ Servidor. Ocurre que con frecuencia, por hacer un diseño no muy pulcro, aparecen varios Clientes (o lugares en su código en donde se habilitan las interrupciones), O SE MEZCLAN putchars desde MAIN y la ISR: ESO RARA VEZ FUNCIONA. Y el programador ¡NO VA A SABER POR QUÉ! NO LO HAGA 60) ECHO CON INTERRUPCIONES, ASCIZ STRINGS El mecanismo anterior define los mensajes mediante una posición de memoria en donde comienzan los símbolos almacenados (una tabla) y un Tamaño o cantidad de letras que han de manipularse. Como parte de la implementación se definen dos elementos auxiliares, el apuntador al próximo símbolo, y el Contador de Símbolos que faltan por procesarse. Así son los "strings" en C++. Una aproximación parecida cambia el Contador de Símbolos por un TERMINADOR, que es un símbolo especial que indica el final del mensaje. En C, los "strings" se definen así, y el terminador es un byte NULO, con todos sus bits en CERO. Un nombre, anterior a la existencia del lenguaje C, que designa esto mismo, es el de un texto de tipo ASCIZ (código ASCII terminado en ZERO) Este ejemplo es similar al anterior, pero muestra el manejo de mensajes del tipo ASCIZ: ["Labs‐C\Lab3\SciComm\3EchoInt‐2Z.c"] 01 // ****************************************************************** 02 // Program 3EchoInt‐2Z.c: Test SCI Comm. Support 03 // Luis G. Uribe C., M13M2007 J16A09 L08J09 L18J2012 (HCS08) D24J2012 04 // M10D2013 (C) 05 // ..Similar to 2Echoint‐1.C, but uses ASCIZ 266 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 06 // ****************************************************************** 07 // Include Files 08 #include <hidef.h> // For EnableInterrupts macro 09 #include "derivative.h" // Include peripheral declarations 10 #include "SciComm.h" // SCI Comm. Support 11 // ================================================================== 12 // GLOBAL VARIABLES 13 byte *XmtPtr; // char pointer of next data to Xmt 14 byte *Message = "12345\n\r"; // Short message 15 // byte *Message = "Este es el mensaje de prueba, " 16 // "primero en transmitirse al PC por interrupciones\n\r" 17 // ****************************************************************** 18 void main ( void ) /*()*/ 19 { 20 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 21 // >>> ALWAYS include the following 2 lines 22 // Cfr. 02MC9S08QE128RM(ReferenceManual)‐U.pdf, pag 101 23 #define COP_Disable 0x42 24 SOPT1 = COP_Disable; // System Options 1 25 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 26 SCI9600N8; // Setup Serial Communications Inter‐ 27 XmtRcvActivate; // ..face: 9600 bps, No parity, 8 bits 28 // ================================================================== 29 // Transmit your message: Enque at once full 'Message' Table for Xmt: 30 // Prompt: 31 XmtPtr = Message; // Init XmtPtr with 'Message' address; 32 XmtIntEn; // XmtISR will send the message and 33 // ..auto desable 34 CpuIntEn; // <<<DON'T FORGET: GLOBAL ENABLE INTs 35 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 36 // XmtLoop: 37 Wait( 0 ); // ..WaitForEver 38 } 39 // ================================================================== 40 interrupt VectorNumber_Vsci1tx void XmtISR ( void ) /*()*/ 41 { // NOTE: putchar clears XMTRDY.bit. Very clever... 42 if( ! putchar( *XmtPtr++ ) ) { // 'C' Strings end at '\0' 43 XmtIntDsb; 44 } 45 } 267 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 COMENTARIOS a ["Labs-C\Lab3\SciComm\3EchoInt-2Z.c"]: El Message es "12345\n\r", y como es un String de C (por las comillas), aunque no lo vemos sabemos que está terminado por un último símbolo, el '\0' o NUL byte. La diferencia con la definición anterior es sutil, pero fundamental. 14 byte *Message = "12345\n\r"; // Short message Aquí se inicializa el pointer de transmisión con la dirección donde comienza el mensaje, y simplemente se habilitan las interrupciones, del transmisor, y del CPU. El código no hace nada más, así que se simula un HALT al final: 31 XmtPtr = Message; // Init XmtPtr with 'Message' address; 32 XmtIntEn; // XmtISR will send the message and 33 // ..auto desable 34 CpuIntEn; // <<<DON'T FORGET: GLOBAL ENABLE INTs 37 Wait( 0 ); // ..WaitForEver La rutina XmtISR es BIEN SIMPLE: Cada vez que hay una interrupción, TRANSMITE la próxima letra y si ésta coincide con el NUL char, se auto deshabilita para interrumpir: 40 interrupt VectorNumber_Vsci1tx void XmtISR ( void ) /*()*/ 41 { // NOTE: putchar clears XMTRDY.bit. Very clever... 42 if( ! putchar( *XmtPtr++ ) ) { // 'C' Strings end at '\0' 43 XmtIntDsb; 44 } 45 } 61) COLAS DE DATOS El manejo de Colas es muy similar al que ya estudiamos en ASM: ¡aquél lo saqué de aquí! Ahora, USTED TIENE QUE SABER C PARA ENTENDER ESTE CÓDIGO. Hasta las COMAS son FUNDAMENTALES en el siguiente texto: ["Labs‐C\Lab3\SciComm\Que.h"] 01 #ifndef QUE_H_INCLUDED 02 #define QUE_H_INCLUDED 03 // ****************************************************************** 04 // QUE.H Luis G. Uribe C, V20J2014: .gt .pt 05 // 18S1990 D04S2011 V29N21013 06 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 07 // Several Definies 08 09 #undef Wait 10 #define Wait(e) while( !(e)) 11 #undef CpuIntEn 12 #define CpuIntEn __asm CLI 13 #undef CpuIntDsb 14 #define CpuIntDsb __asm SEI 15 #undef EOF 16 #define EOF ((word)‐1) 268 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 18 // REMEMBER!!!: 'byte' is UNSIGNED char; 'word' is UNSIGNED int 20 /* ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ */ 21 typedef struct { 22 byte *put; /* n (5) */ 23 byte *get; /* (2) buf‐‐‐‐> +‐‐‐+ */ 24 word n; /* |(1)| ‐‐‐> (4) get */ 25 word gt; /* (3) put ‐‐‐> | s | */ 26 word pt; /* | i | */ 27 byte *base; /* | z | */ 28 byte *limit; /* | e | */ 29 word size; /* +‐‐‐+ */ 30 } QUE; 31 32 /* Ejemplo: Sea 'size'(1) de 'buf'(2) igual a 5. * 33 * Los pointers 'put' (3) y 'get' (4) pueden tener los valores * 34 * buf, buf + 1, buf + 2, buf + 3, buf + 4; * 35 * es decir, los pointers deben permanecer en el rango * 36 * buf <= pointer < (buf+size), o, lo que es lo mismo * 37 * buf <= pointer < limit * 38 * * 39 * 'n' (5) puede valer: * 40 * 0 (vacío), 1, 2, 3, 4 y 5 (lleno). Note que 'n' ES * 41 * el >>>SEMAFORO<<<, e impone las siguientes reglas de tráfico: * 42 * * 43 * NO 'deQue' cuando n sea <= 0 * 44 * NO 'enQue' cuando n sea >= size * 45 * * 46 * Las siguientes operaciones están definidas: * 47 */ 49 #define defineQue( in, n ) byte _Q##in[ n ]; QUE in 50 51 #define initQue( in ) in.base = in.put = in.get = _Q##in; \ 52 in.n = 0; in.size = sizeof( _Q##in ); \ 53 in.limit = in.base + in.size 54 55 #define enQue( in, c ) ( in.n >= in.size ? EOF : \ 56 ( in.pt = c , *in.put++ = (byte)c , \ 57 in.put = in.put >= in.limit ? \ 58 in.base : in.put , \ 59 in.n ++ , in.pt \ 60 ) \ 61 ) 62 63 #define deQue( in ) ( in.n <= 0 ? EOF : \ 64 ( in.gt = *in.get ++ , \ 65 in.get = in.get >= in.limit ? \ 66 in.base : in.get , \ 67 in.n ‐‐ , in.gt \ 68 ) \ 69 ) 70 71 #endif // QUE_H_INCLUDED 269 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 COMENTARIOS a ["Labs-C\Lab3\SciComm\Que.h"]: Las primeras definiciones están duplicadas en varios otros sitios pero NO arrojan errores porque están todas antecedidas por #UNDEFs. En primer lugar la Estructura que tienen mis QUEs, plasmada en un 'typedef': QUE. En el esquema se indican los elementos que la componen, ordenados del 1 al 5. Una cola tiene: 1. Un tamaño físico, 'size' 2. Una dirección de comienzo, 'buf' 3. Un apuntador al sitio EN el que almacenará (enQue) el próximo símbolo, 'put'; se inicializa en 'buf'. 4. Un apuntador al sitio DESDE el que extraerá (deQue) la siguiente letra, 'get'; se inicializa en 'buf'. 5. Y 'n', MUY IMPORTANTE: indica cuántos caracteres hay en la cola en un momento dado. Este es el SEMÁFORO que rige y ordena las transacciones; así: 'n'puede valer 0 (vacío), 1, 2, ... 'size' (lleno) Las reglas son las siguientes: NO 'deQue' cuando n sea <= 0 NO 'enQue' cuando n sea >= size Hay dos variables temporales de tipo Word: 'gt' y 'pt'; luego veremos su uso, y por qué son WORD en vez de BYTE. Un apuntador al FINAL de la tabla, 'limit', no esencial pero para facilitar el código, y una posición que indica el tamaño de la cola (aquí la hemos definido como WORD, que permite colas hasta de 64K, lo que es mucho...) 20 /* ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ */ 21 typedef struct { 22 byte *put; /* n (5) */ 23 byte *get; /* (2) buf‐‐‐‐> +‐‐‐+ */ 24 word n; /* |(1)| ‐‐‐> (4) get */ 25 word gt; /* (3) put ‐‐‐> | s | */ 26 word pt; /* | i | */ 27 byte *base; /* | z | */ 28 byte *limit; /* | e | */ 29 word size; /* +‐‐‐+ */ 30 } QUE; Las siguientes operaciones están definidas, igual que en su contraparte en ASM: defineQue, initQue, enQue y deQue: El defineQue requiere un NOMBRE de la Cola y un tamaño. La definición... tienen que estudiarla. Si no la entienden... TIENEN QUE ESTUDIAR C 49 #define defineQue( in, n ) byte _Q##in[ n ]; QUE in Primero se define un arreglo (vector); byte _Q##in[ n ]; A que no vieron algo así... ESTUDIEN C. ## es el operador catenate: une dos símbolos. 270 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 El nombre del vector es: _Q seguido (CONCATENADO: ##) con el nombre que el usuario le da a su cola, representada por el parámetro 'in', y ocupando un espacio en BYTES de 'n', parámetro que reemplaza al tamaño que el usuario desea darle a la Cola. A continuación, separada por un punto y coma de la anterior, para que quede en la misma línea (¿habían visto varias instrucciones de C, en la misma línea?) viene: QUE in, que define una variable con el nombre que el usuario quiere, simbolizado en la Macro por el parámetro 'in', y de type QUE, según la definición 'typedef' anterior. Ejemplo: Si el usuario quiere definir una cola llamada 'ColaRead', de '16' bytes, lo expresará de la siguiente forma: defineQue( colaRead, 16 ) El resultado será, un arreglo: byte _QcolaRead[ 16 ]; y una estructura llamada 'colaRead', de tipo QUE. Mire el NOMBRE del arreglo: el símbolo '_Q' concatenado con el nombre que se le está dando a la cola: 'colaRead'. IMPORTANTE: 'defineQue' *TIENE* QUE EMPLEARSE COMO VARIABLE 'GLOBAL', FUERA DE CUALQUIER {}: Fuera de main(), y de cualquier función. Como en ASM. Después de definir la cola, hay que inicializarla. Esto sí se hace DENTRO DE MAIN. Para la cola 'colaRead', la invocación es: initQue( colaRead ); 51 #define initQue( in ) in.base = in.put = in.get = _Q##in; \ 52 in.n = 0; in.size = sizeof( _Q##in ); \ 53 in.limit = in.base + in.size Imagino que saben CÓMO continuar el código de una Macro en varias líneas: para eso se emplea el '\'. Note que la ÚLTIMA no lo lleva... Tal como se dijo antes, se inicializan los apuntadores base, put y get de la cola en consideración (en el ejemplo: colaRead.base, colaRead.put y colaRead.get). El valor que toma es el del comienzo de VECTOR que ya se DEFINIÓ: _Q##in, que en el ejemplo corresponde a: _QcolaRead. Las inicializaciones continúan: colaRead.n = 0; colaRead.size asume el TAMAÑO del Vector, sizeof( _QcolaRead ). Si no sabe cómo se usa 'sizeof', usted NO SABE "C" y TIENE QUE ESTUDIAR C, URGENTE. Finalmente se inicializa la variable in.limit para apuntar a la última dirección del Vector: 271 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 53 in.limit = in.base + in.size Es decir: colaRead.limit = colaRead.base + colaRead.size La operación para 'encolar' un byte... tomen ALIENTO. Menos mal que YO hago la librería y usted, fundamentalmente, LA USA (aunque si no la ENTIENDE, anda bastante MAL) 55 #define enQue( in, c ) ( in.n >= in.size ? EOF : \ 56 ( in.pt = c , *in.put++ = (byte)c , \ 57 in.put = in.put >= in.limit ? \ 58 in.base : in.put , \ 59 in.n ++ , in.pt \ 60 ) \ 61 ) Primero, ¿por qué tiene tantos paréntesis? Recuerden que el uso de esta Macro es: c = enQue( colaWrite, letra ); Entonces, QUÉ ES LO QUE DEVUELVE LA MACRO? Normalmente devuelve 'c', el MISMO parámetro que se le pasó para encolar ('letra' en el ejemplo), pero, si la cola está LLENA, y por tanto no se puede ejecutar la función de Encolar, se devuelve el símbolo EOF (espero que sí sepan lo que es EOF). Así que el valor de esa expresión, es 'c', si hay espacio en la cola, o EOF, si la cola está llena. ¿Cómo ocurre eso? Voy a reescribir la Macro para mejorar su legilibilidad, alineando los paréntesis que abren, con los que cierran, y voy a ELIMINAR los continuadores '\' de la Macro: ( in.n >= in.size ? EOF : ( in.pt = c , *in.put++ = (byte)c , in.put = in.put >= in.limit ? in.base : in.put , in.n ++ , in.pt ) ) Ahora voy a dejar sólo lo que tiene que ver con devolver 'c' o EOF. ELIMINO temporalmente 4 líneas Internas de la Macro), y lo coloco en una línea: ( in.n >= in.size ? EOF : (eliminado) , in.n ++ , in.pt ) Primero, el operador TERNARIO: expC ? expT : expF SI NO SABE QUÉ ES EL OPERADOR TERNARIO, USTED NO SABE C. ESTUDIE!!! 'expC' es CUALQUIER expresión; implica asignaciones, Comparaciones, funciones... El '?' PREGUNTA si 'expC' es Verdadera o Falsa. Si es verdadera, el operador ternario asume el valor 'expT', si es falsa el valor es 'expF', siendo expT y expF, a su vez, expresiones. Para separarlas está el símbolo ':' En nuestro caso: expC: in.n >= in.size expT: EOF expF: (eliminado) , in.n ++ , in.pt 272 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 Es decir, si el número de caracteres almacenados en la cola, es MAYOR O IGUAL al tamaño DEFINIDO, se devuelve EOF. Hasta ahí, ok. Pero, qué devuelve 'expF'? Qué es "in.n ++ , in.pt"? Es el COMMA OPERATOR. NO SABE QUE ES? TIENE QUE ESTUDIAR MUCHÍSIMO C. El Comma Operator evalúa, de IZQUIERDA a DERECHA, todas las expresiones que estén separadas por COMAS... Y RETORNA UN VALOR: EL DE LA *ÚLTIMA* EXPRESIÓN, LA DE LA *DERECHA*. En este caso, expF ejecuta (eliminado) primero, 'in.n++' luego y ' in.pt' después. Imagino que no lo saben, pero una variable ES una expresión. Así que: in.pt; ES una SENTENCIA. (SI NO SABE LA DIFERENCIA ENTRE EXPRESIÓN Y SENTENCIA, "EXPRESSION and STATEMENT", EN INGLÉS, DESISTA! NO TIENE LA MENOR IDEA DEL LENGUAJE C) Un “expression” y un “statement” TIENEN un valor. El valor de: in.pt; es lo que está almacenado en 'in.pt': es el VALOR del parámetro 'c', que se almacenó en in.pt en la línea 56: 56 ( in.pt = c , ... Entonces, 'expF': "(eliminado), in.n ++ , in.pt", DEVUELVE 'c'. Así funciona la primera parte: Si no hay espacio, devuelve EOF, de lo contrario, encola la letra y devuelve su valor. La parte interna de la Macro, que se ejecuta sólo si hay espacio, se encarga de Encolar el símbolo: ... *in.put++ = (byte)c , in.put = in.put >= in.limit ? in.base : in.put , Se almacena 'c', empleando el pointer 'put', que se auto incrementa. A 'c' se le hace un CAST a (byte), porque el usuario ha podido enviar algo de mayor longitud. A continuación (según lo ordena el operador COMMA), se ejecuta la expresión que está entre los paréntesis internos (que NO se necesitan: todo lo que está allí es UNA sola expresión; están sólo para mejorar la comprensión). Repito aquí la expresión interna, que es una asignación al apuntador in.put, proveniente de un OPERADOR TERNARIO: in.put = in.put >= in.limit ? in.base : in.put 273 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 Es decir, a in.put se le asigna un valor determinado así: si sobrepasó el límite, in.limit, o si está apuntando a él, el valor que se le asigna es LA BASE. Este es el punto que convierte un Vector, en una cola CIRCULAR. Lo que significa es que, luego de operar el último elemento del vector, el apuntador correspondiente APUNTA A LA BASE, operación conocida como WRAP AROUND. Si in.put no está en condición de Wrap Around, entonces NO SE LO MODIFICA; es decir, su valor futuro, in.put, es igual a su valor de entrada, in.put. Último punto, y lo más IMPORTANTE: El SEMÁFORO: in.n, NO PUEDE estar en NINGUNA OTRA posición del código. En el momento en que se lo modifica, ahí mismo puede ocurrir una interrupción para efectuar la operación contraria (si está haciendo enQue, se querrá hacer un deQue). El semáforo TIENE que garantizar que no importa DÓNDE ocurra la interrupción, TODO FUNCIONA A LA PERFECCIÓN. En el sitio en donde está, sólo falta por devolver el valor que está guardado en la variable de usuario 'in.pt', por lo que deQue NO altera para nada el estado de la cola, desde el punto de vista del enQue. Observe más adelante lo mismo, en relación a la Macro deQue. Para eso sirve la variable temporal in.gt que se usa allá. CLARO? C tiene una sintaxis MUY comprimida. Los siguientes símbolos simples: + ‐ * / % & | ^ ! ~ # ( ) _ = [ ] { } ; : ' " , . < > ? \ dobles: ++ ‐‐ ** && || ## == << >> poseen un significado. (Algunos tienen varios, dependiendo del contexto. Por ejemplo, las COMAS en las llamadas a Funciones, NO SON COMMA OPERATORS y, de hecho, su EVALUACIÓN es de DERECHA A IZQUIERDA, al revés del Operador Coma) SI NO CONOCE EL USO DE ALGUNO DE ESOS SÍMBOLOS, USTED NO SABE C, Y TIENE QUE ESTUDIAR MUCHÍSIMO. La macro deQue es para ser usada así: c = deQue( colaRead ); y debe ser ahora fácil de entender. 63 #define deQue( in ) ( in.n <= 0 ? EOF : \ 64 ( in.gt = *in.get ++ , \ 65 in.get = in.get >= in.limit ? \ 66 in.base : in.get , \ 67 in.n ‐‐ , in.gt \ 68 ) \ 69 ) Una librería GENÉRICA para el manejo de Colas de Datos, tendría que poder manipular cualquier tipo de variables, incluyendo las definidas vía Typedefs. Pero eso está más allá de lo que pretendo enseñar en este texto de ARQUITECTURA DEL COMPUTADOR. 62) COLAS DE DATOS: DEMOSTRACIÓN Se han mantenido las mismas etiquetas cont: y full:, para que se vea el comportamiento equivalente con su equivalente en ASM: ["Laboratorios\Lab3\SciComm\4quetst.asm"] 274 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 ["Labs‐C\Lab3\SciComm\4quetst.c"] 01 // ****************************************************************** 02 // Program 4QueTst.c: Test "Queue" Support (for SCI) 03 // Luis G Uribe C M13M2007 C15A09 L08J09 S16J2012(HCS08) S30N2013 "C" 04 // See: 4QueTst‐B.c (COMPACT) 05 // See in this program: The use of Que.H, defineQue, initQue, enQue 06 // ..and deQue. You must NOT exceed the free RAM on the MCU... 07 // 08 // ****************************************************************** 09 // Include Files 10 #include <hidef.h> // For EnableInterrupts macro 11 #include "derivative.h" // Include peripheral declarations 12 #include "Que.h" // QUEUE Support 13 // ================================================================== 14 // GLOBAL VARIABLES. See 'defQ' use... 15 // defQ >>>"ALWAYS" must be GLOBAL<<<: Out of any '{}' 16 defineQue( inQ, 6 ); /* DEFINE Queues 'inQ', 6 bytes & 'outQ', */ 17 defineQue( outQ, 5 ); /* ..5 bytes. Choose the names you want...*/ 18 // ****************************************************************** 19 void main( void ) 20 { byte c; 21 word cw; 22 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 23 // >>> ALWAYS include the following 2 lines 24 // Cfr. 02MC9S08QE128RM(ReferenceManual)‐U.pdf, pag 101 25 #define COP_Disable 0x42 26 SOPT1 = COP_Disable; // System Options 1 27 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 28 initQue( inQ ); 29 initQue( outQ ); 30 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 31 cont: 32 for( c = 'A'; ; c ++ ) { 33 if( enQue( inQ, c) == EOF ) // Put Debugger's breakpoint here 34 break; // ..to see chars going into 'inQ' 35 } 36 full: 37 while( 1 ) { 38 cw = deQue( inQ ); 39 if( cw == EOF ) 40 break; 41 if( enQue( outQ, cw ) == EOF ) // Will break here because... 42 break; // ..outQ is smaller than inQ 43 } 44 Wait( 0 ); 45 } 275 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 COMENTARIOS a ["Labs-C\Lab3\SciComm\4quetst.c"]: El #include "Que.h" como corresponde: 12 #include "Que.h" // QUEUE Support VARIABLES GLOBALES: EL defQue, SIEMPRE GLOBAL (como en la librería en ASM) Para el ejemplo se definan dos colas, inQ de 6 bytes, y outQ de 5: 16 defineQue( inQ, 6 ); /* DEFINE Queues 'inQ', 6 bytes & 'outQ', */ 17 defineQue( outQ, 5 ); /* ..5 bytes. Choose the names you want...*/ Al comienzo de main, la definición de 'c'. En realidad se usa un byte c y un word cw, como veremos adelante. 19 void main( void ) 20 { byte c; 21 word cw; La INICIALIZACIÓN de las colas: 28 initQue( inQ ); 29 initQue( outQ ); Se hace un ciclo indefinido (for sin límite: ; ;), que almacena letras comenzando desde la 'A'. Cuando la cola se llena, se termina el for (no se hace la comparación con el tamaño de la cola, para evidenciar el mecanismo que opera cuando la cola se llena). Para la simulación, esté pendiente de colocar BREAKPOINTS en donde se indica en el código, para visualizar lo que se está manipulando en las colas: 32 for( c = 'A'; ; c ++ ) { 33 if( enQue( inQ, c) == EOF ) // Put Debugger's breakpoint here 34 break; // ..to see chars going into 'inQ' 35 } Si no sabe que un break termina un FOR, está MUY MAL EN C: ¡ABANDONE! Ahora que se llenó inQ, se hace un ciclo indefinido, extrayendo uno a uno sus elementos y transfiriéndolos a la otra cola. Es exactamente lo que haría una Rutina de Comunicaciones: En la ISR del RCV, se lee el dato recibido y se lo almacena en la Cola de Entrada; main encuentra que hay datos en esa Cola, los saca, los procesa si es necesario y los almacena en la Cola de salida. A lo mejor habilita las interrupciones de salida EN ALGÚN MOMENTO APROPIADO. Cuando la ISR de RCV toma el control, saca el próximo valor de la cola de salida y lo transmite. En este ejemplo, el ciclo terminará en la línea 42, porque la cola de salida se llena antes de que se vacíe la de entrada, por los tamaños que decidí colocarles. 37 while( 1 ) { 38 cw = deQue( inQ ); 39 if( cw == EOF ) 40 break; 276 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 41 if( enQue( outQ, cw ) == EOF ) // Will break here because... 42 break; // ..outQ is smaller than inQ 43 } 44 Wait( 0 ); Para asignar el 'deQue' sobre una variable, SE NECESITA QUE LA VARIABLE SEA DE TIPO WORD. ¿Por qué? Si usted estudió el getchar del 'C', tiene que poder saberlo. Si no, USTED SABE MUY POCO DE C. ESTUDIE!!! Esta es la razón por la cual la Macro enQue hace un CAST a BYTE del valor que se le pasa para encolar... Revise si el cuerpo del while puede escribirse así: if( enQue( outQ, deQue( inQ ) ) == EOF ) break; ¿CÓMO FUNCIONARÍA? O ¿POR QUÉ NO? Tiene que analizar cuando deQue devuelve EOF, cuando lo hace enQue, cuando no lo hace ninguno y cuando lo hacen los dos. 63) CHAT POR INTERRUPCIONES, USANDO COLAS El último ejemplo es un CHAT por interrupciones, que muestra la interrelación entre todo: comunicaciones seriales, transmitiendo y recibiendo por interrupciones, con uso de colas. Además, se esquematiza la forma de programar separando lo más que se puedan, Políticas y Mecanismos, TAL COMO TIENE QUE HACERSE SIEMPRE. No oyó hablar nunca de Politics and Mechanisms? No le enseñaron PROGRAMACIÓN tampoco. En este ejercicio aún se pueden ocultar más algunas variables, como kbhit; pero no lo hice para resaltar los parecidos con el 'kbhit' que usted ha debido estudiar. ["Labs‐C\Lab3\SciComm\6ChatInt.c"] 01 // ****************************************************************** 02 // Program 6ChatInt.c: Send/Receive text to/from PC, Full Interrupt 03 // Luis G. Uribe C., D15D2013 04 #include "6ChatInt_.h" // include peripheral declarations 05 byte *Prompt = "\a\r\n\n0_Envíeme un texto, por favor <;‐) "; 06 defineQue( inQ, 48 ); // DEFINE Que 'inQ', 48 bytes 07 // ****************************************************************** 08 void main ( void ) /*()*/ 09 { /* ChatInt.c */ 277 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 10 SysInit; // COP_Disable 11 initQue( inQ ); 12 EnableIRQ; 13 XmtRcvActivate; // Enable Xmt & Rcv, on chip DEVICES; 9600bps 14 // ================================================================== 15 // Transmit your Prompt: Enque at once full 'Prompt' Table for Xmt: 16 SetupXmtBlk( Prompt ); // Txt Ptr; XmtControl=XMTMSG_S; XmtIntEn 17 CpuIntEn; // <<<REMEMBER: GLOBAL ENABLE INTs 18 RcvIntEn; // CommIntEn?{RCVSTATREG;RCVBUF;RcvIntEn;} 19 Forever 20 if( kbhit ) { // LOCAL (DEMOQE128) IRQ button depressed? 21 SetupXmtBlk( Prompt ); 22 kbhit = 0; 23 } 24 Endforever 25 Wait( 0 ); 26 } /* main() */ 27 // ================================================================== 28 interrupt VectorNumber_Virq void IRQISR ( void ) /*()*/ 29 { 30 kbhit = 1; 31 IRQSC_IRQACK = 1; // BSET IRQSC_IRQACK,IRQSC: ACK IRQ 32 } // .. Interrupt (Rearm IRQ Interrupts) 33 // ================================================================== 34 interrupt VectorNumber_Vsci1tx void XmtISR ( void ) /*()*/ 35 { // NOTE: putchar clears RCVRDY.bit. Very clever... 36 word c; // MUST be 'word' to hold both Chars And EOF (0xFFFF) 37 switch( XmtControl ) { 38 case XMTMSG_S: 39 if( ! putchar( *XmtPtr++ ) ) { // 'C' Strings end at '\0' 40 XmtControl = XMTDEFAULT_S; // ..(Zstrings: ASCIZ) 41 } 42 break; 43 case XMTDEFAULT_S: 44 if( ( c = deQue( inQ ) ) != EOF ) { // Did something arrive? 45 putchar( c ); // 'C' Strings end at '\0' 46 }else{ 47 XmtIntDsb; 48 } 49 break; 50 } 51 } 52 // ================================================================== 53 interrupt VectorNumber_Vsci1rx void RcvISR ( void ) /*()*/ 278 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 54 { // NOTE: getchar clears RCVRDY.bit. Very clever... 55 byte c; 56 c = getchar(); 57 if( enQue( inQ, c ) != EOF ) { 58 XmtIntEn; 59 } 60 } ["Labs‐C\Lab3\SciComm\6ChatInt_.h"] 01 #ifndef _6CHATINT_H_INCLUDED 02 #define _6CHATINT_H_INCLUDED 03 // ****************************************************************** 04 // 6ChatInt_.h: Send/Receive text to/from PC, Full Interrupt 05 // Luis G. Uribe C., D15D2013 06 // ****************************************************************** 07 // Include Files 08 #include <hidef.h> // for EnableInterrupts macro 09 #include "derivative.h" // include peripheral declarations 10 #include "SciComm.h" // SCI Comm. Support 11 #include "Que.h" // Que Support 12 #include "4InputPushButtons_.h"// Inputs4(0000b3b2b1b0)=PTA2,3;PTD2,3 13 // ================================================================== 14 // GLOBAL VARIABLES 15 typedef enum { XMTDEFAULT_S, XMTMSG_S } XmtControls; 16 XmtControls XmtControl = XMTDEFAULT_S; // May change this to bits. 17 byte *XmtPtr; // char pointer of next data to Xmt 18 byte kbhit; 19 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 20 // >>> ALWAYS include SysInit 21 // Cfr. 02MC9S08QE128RM(ReferenceManual)‐U.pdf, pag 101 22 #define COP_Disable 0x42 23 #define SysInit SOPT1 = COP_Disable /* System Options 1 */ 24 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 25 // Enable IRQ pin 26 #define EnableIRQ IRQSC = IRQSC_IRQPE_MASK | IRQSC_IRQACK_MASK;\ 27 IRQSC_IRQIE = 1 /* IRQ pin, IntEn */ 28 // ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 29 #define SetupXmtBlk(p) XmtPtr = (p); /* Point to Prompt */ \ 30 XmtControl = XMTMSG_S; \ 31 XmtIntEn /* XmtISR will send the Prompt */ 32 #endif // _6CHATINT_H_INCLUDED ["Labs‐C\Lab3\SciComm\4InputPushButtons_.h"] 279 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 01 #ifndef _4INPUTPUSHBUTTONS_H_INCLUDED 02 #define _4INPUTPUSHBUTTONS_H_INCLUDED 03 // ****************************************************************** 04 // 4InputPushButtons_.h: Inputs4(0000b3b2b1b0)=PTA2,3;PTD2,3 05 // Luis G. Uribe C., D15D2013 06 // ****************************************************************** 07 // Include Files 08 #include <hidef.h> // for EnableInterrupts macro 09 #include "derivative.h" // include peripheral declarations 10 // ================================================================== 11 // Defined Functionality 12 #define Setup4PushBtns PTAPE_PTAPE3 = PTAPE_PTAPE2 = PTDPE_PTDPE3 \ 13 = PTDPE_PTDPE2 = 1 /* Pull ups enable */ 14 #define get4Inputs() Inp3 = PTAD_PTAD2; Inp2 = PTAD_PTAD3; \ 15 Inp1 = PTDD_PTDD2; Inp0 = PTDD_PTDD3; \ 16 Inp4_7 = ~0; Inputs4 = ~Inputs4 17 // ================================================================== 18 // GLOBAL VARIABLES. IRQ pin Enabled in "6ChatInt_.h" 19 typedef union { 20 byte Byte; 21 struct { 22 byte INPUTS40 :1; // individual bits MUST be UNSIGNED 23 byte INPUTS41 :1; 24 byte INPUTS42 :1; 25 byte INPUTS43 :1; 26 byte spare :4; 27 } Bits; 28 } _INPUTS4; 29 extern volatile _INPUTS4 INPUTS4; 30 #define Inputs4 INPUTS4.Byte 31 #define Inp0 INPUTS4.Bits.INPUTS40 32 #define Inp1 INPUTS4.Bits.INPUTS41 33 #define Inp2 INPUTS4.Bits.INPUTS42 34 #define Inp3 INPUTS4.Bits.INPUTS43 35 #define Inp4_7 INPUTS4.Bits.spare 36 #define INP0 1U 37 #define INP1 2U 38 #define INP2 4U 39 #define INP3 8U 40 #endif // _4INPUTPUSHBUTTONS_H_INCLUDED COMENTARIOS a ["Labs-C\Lab3\SciComm\6ChatInt.c"]: En el archivo "6ChatInt_.h" se definen cosas de bajo nivel, relacionadas con periféricos, como veremos abajo: 280 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 04 #include "6ChatInt_.h" // include peripheral declarations El mensaje con el que queremos avisarle al usuario del PC que nos envíe un texto: 05 byte *Prompt = "\a\r\n\n0_Envíeme un texto, por favor <;‐) "; Se define una cola, inQ, de 48 posiciones: 06 defineQue( inQ, 48 ); // DEFINE Que 'inQ', 48 bytes 08 void main ( void ) /*()*/ 09 { /* ChatInt.c */ Las líneas 10 a la 13 son auto explicativas con solo leerlas; dicen qué se hará, NO cómo se hará (Politics; Mechanisms seen later) 10 SysInit; // COP_Disable 11 initQue( inQ ); 12 EnableIRQ; 13 XmtRcvActivate; // Enable Xmt & Rcv, on chip DEVICES; 9600bps Ahora se TRANSMITE EL PROMPT, que es del tipo ASCIZ, trasmitiéndolo todo de una sola vez, por interrupciones: 15 // Transmit your Prompt: Enque at once full 'Prompt' Table for Xmt: 16 SetupXmtBlk( Prompt ); // Txt Ptr; XmtControl=XMTMSG_S; XmtIntEn 17 CpuIntEn; // <<<REMEMBER: GLOBAL ENABLE INTs Se habilitan las interrupciones de recepción, para entrar al ciclo infinito. kbhit es una variable que se activa (1) por la ISR del IRQ; es decir, cada vez que se oprime el botón de IRQ, se transmite de nuevo el PROMPT. El ECHO se maneja directamente entre las ISR de RCV y XMT: 18 RcvIntEn; // CommIntEn?{RCVSTATREG;RCVBUF;RcvIntEn;} 19 Forever 20 if( kbhit ) { // LOCAL (DEMOQE128) IRQ button depressed? 21 SetupXmtBlk( Prompt ); 22 kbhit = 0; 23 } 24 Endforever 26 } /* main() */ La IRQISR es muy simple: cargar el valor de ACTIVO para la variable kbhit, y el ACK correspondiente al IRQ: 28 interrupt VectorNumber_Virq void IRQISR ( void ) /*()*/ 29 { 30 kbhit = 1; 31 IRQSC_IRQACK = 1; // BSET IRQSC_IRQACK,IRQSC: ACK IRQ 32 } // .. Interrupt (Rearm IRQ Interrupts) 281 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 La XmtISR: 34 interrupt VectorNumber_Vsci1tx void XmtISR ( void ) /*()*/ 35 { // NOTE: putchar clears RCVRDY.bit. Very clever... 36 word c; // MUST be 'word' to hold both Chars And EOF (0xFFFF) Hace dos actividades diferentes: XMTMSG_S (el echo de lo recibido) y XMTDEFAULT_S (transmisión del PROMPT: El case XMTMSG_S: es igual al último que vimos, cuando transmitimos un texto de tipo ASCIZ; al final se cambia la variable de Estado, XmtControl a su función de ECHO, que es la estándar: 37 switch( XmtControl ) { 38 case XMTMSG_S: 39 if( ! putchar( *XmtPtr++ ) ) { // 'C' Strings end at '\0' 40 XmtControl = XMTDEFAULT_S; // ..(Zstrings: ASCIZ) 41 } 42 break; El case XMTDEFAULT_S es también como ya hemos visto: si lo que se desencola, cuando hay una interrupción para Transmitir, no es EOF, quiere decir que sí hay caracteres para transmitirle al PC, y así se hace con putchar que, como ya lo sabemos, hace el debido ACK al XMT. Si en la cola no hay nada para transmitir, se auto deshabilitan las interrupciones del XMT: 43 case XMTDEFAULT_S: 44 if( ( c = deQue( inQ ) ) != EOF ) { // Did something arrive? 45 putchar( c ); // 'C' Strings end at '\0' 46 }else{ 47 XmtIntDsb; 48 } 49 break; 50 } 51 } La RcvISR es muy sencilla también, empleando esta librería de comunicaciones seriales: 52 // ================================================================== 53 interrupt VectorNumber_Vsci1rx void RcvISR ( void ) /*()*/ 54 { // NOTE: getchar clears RCVRDY.bit. Very clever... 55 byte c; 56 c = getchar(); 57 if( enQue( inQ, c ) != EOF ) { 58 XmtIntEn; 59 } 60 } COMENTARIOS a ["Labs-C\Lab3\SciComm\6ChatInt_.h"]: El 6ChatInt_.h consta de una parte de inicialización: 282 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 07 // Include Files 08 #include <hidef.h> // for EnableInterrupts macro 09 #include "derivative.h" // include peripheral declarations 10 #include "SciComm.h" // SCI Comm. Support 11 #include "Que.h" // Que Support 12 #include "4InputPushButtons_.h"// Inputs4(0000b3b2b1b0)=PTA2,3;PTD2,3 La definición de las VARIABLES GLOBALES: 15 typedef enum { XMTDEFAULT_S, XMTMSG_S } XmtControls; 16 XmtControls XmtControl = XMTDEFAULT_S; // May change this to bits. 17 byte *XmtPtr; // char pointer of next data to Xmt 18 byte kbhit; La definición de la Macro que realiza la inicialización del sistema Macro que usted debería UTILIZAR SIEMPRE, de ahora en adelante) 20 // >>> ALWAYS include SysInit 22 #define COP_Disable 0x42 23 #define SysInit SOPT1 = COP_Disable /* System Options 1 */ Se define una Macro para habilitar el IRQ; observe que necesita el '\' porque tiene más de una línea 26 #define EnableIRQ IRQSC = IRQSC_IRQPE_MASK | IRQSC_IRQACK_MASK;\ 27 IRQSC_IRQIE = 1 /* IRQ pin, IntEn */ Se define la Macro SetupXmtBlk, que inicializa y transmite el Prompt. Recuerde que hay una pequeña máquina de estados, controlada por la variable XmtControl; aquí se inicializa esa FSM con XMTMSG_S, que hace que la XmtISR transmita el mensaje ASCIZ: 29 #define SetupXmtBlk(p) XmtPtr = (p); /* Point to Prompt */ \ 30 XmtControl = XMTMSG_S; \ 31 XmtIntEn /* XmtISR will send the Prompt */ COMENTARIOS a ["Labs-C\Lab3\SciComm\4InputPushButtons_.h"]: Define las siguientes funcionalidades: Setup4PushBtns y get4Inputs(). Setup4PushBtns les activa todas las resistencias de Pull Up a los botones de entrada (un 1 las activas). No hay que activar esos terminales para entrada, porque después de POR, todos los pines digitales están activos como ENTRADAS. 12 #define Setup4PushBtns PTAPE_PTAPE3 = PTAPE_PTAPE2 = PTDPE_PTDPE3 \ 13 = PTDPE_PTDPE2 = 1 /* Pull ups enable */ get4Inputs() LEE los 4 botones, uno en cada bit de la variable definida como de tipo _INPUTS4. Después se colocan en valor inicial los 4 bits que no se usan (hay solo 4 botones) y por último SE NIEGAN LAS ENTRADA, tomándolas de la variable y almacenándolas 283 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 de nuevo allí. Se niegan porque, en la forma como la gente de PE Micro conectó los botones, cada vez que uno de ellos se ACTIVA, produce un CERO, lo cual podría resultar poco natural: 14 #define get4Inputs() Inp3 = PTAD_PTAD2; Inp2 = PTAD_PTAD3; \ 15 Inp1 = PTDD_PTDD2; Inp0 = PTDD_PTDD3; \ 16 Inp4_7 = ~0; Inputs4 = ~Inputs4 Se establece una estructura con los 4 bits para los botones, y el resto se define como 'spare'. Se colocan en UNION con un Byte, para facilitar, por ejemplo, el negar TODOS los bits de un golpe Inputs4=~Inputs4) 19 typedef union { 20 byte Byte; 21 struct { 22 byte INPUTS40 :1; // individual bits MUST be UNSIGNED 23 byte INPUTS41 :1; 24 byte INPUTS42 :1; 25 byte INPUTS43 :1; 26 byte spare :4; 27 } Bits; 28 } _INPUTS4; Se define la variable INPUTS4, de tipo _INPUTS4 (el typedef), volatile (para evitar Optimización del compilador) y extern, para que sea visible desde el código de los demás archivos: 29 extern volatile _INPUTS4 INPUTS4; 30 #define Inputs4 INPUTS4.Byte ¿NO SABE NADA DE EXTERN? UNION? NO SABE C. Finalmente, para facilitar el uso de los bits de la variable INPUTS4, de tipo _INPUTS4 que, por ejemplo, para designar el Bit0 requeriría escribir: INPUTS4.Bits.INPUTS40. Con las definiciones cada bit es Inp0, etc. Mucho más sencillo 31 #define Inp0 INPUTS4.Bits.INPUTS40 32 #define Inp1 INPUTS4.Bits.INPUTS41 33 #define Inp2 INPUTS4.Bits.INPUTS42 34 #define Inp3 INPUTS4.Bits.INPUTS43 35 #define Inp4_7 INPUTS4.Bits.spare 64) BIG CHAT, INTERRUPCIONES, COLAS Un verdadero chat envía mensajes diferentes ¡de lado y lado! Pero el sistema de botones del DEMOQE128 resulta algo inflexible para generar texto. Este ejercicio envía un Prompt, igual que el programa anterior, pero los mensajes no fluyen solamente desde el PC, sino que tocando cada uno de los 4 botones, se envían hasta cuatro mensajes al PC, dándole un aspecto un poco más realista al chat. ["Labs‐C\Lab3\SciComm\6ChatInt‐4.c"] 284 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 01 // ****************************************************************** 02 // Program 6ChatInt‐4.c: Send/Receive text to/from PC, Full Interrupt 03 // Luis G. Uribe C., L16D2013 (4 DEMOQE128 text sources) 04 #include "6ChatInt_.h" // include peripheral declarations 05 byte *Prompt = "\a\r\n\n0_Envíeme un texto, por favor <;‐) "; 06 byte *Prompts[ 4 ] = { 07 { "\a\r\n\n1_Me encanta poder hablarle hoy___ " }, 08 { "\a\r\n\n2_Cómo va todo, finalizando el 13? " }, 09 { "\a\r\n\n3_Le deseo suerte para el año 2014 " }, 10 { "\a\r\n\n4_Que tenga buena nota en Arqui‐I! " }, 11 }; 12 defineQue( inQ, 48 ); // DEFINE Que 'inQ', 48 bytes 13 // ****************************************************************** 14 void main ( void ) /*()*/ 15 { /* ChatInt.c */ 16 SysInit; // COP_Disable 17 initQue( inQ ); 18 EnableIRQ; 19 XmtRcvActivate; // Enable Xmt & Rcv, on chip DEVICES; 9600bps 20 Setup4PushBtns; // Activate 4 DEMOQE128 input push buttons 21 // ================================================================== 22 // Transmit your Prompt: Enque at once full 'Prompt' Table for Xmt: 23 SetupXmtBlk( Prompt ); // Txt Ptr; XmtControl=XMTMSG_S; XmtIntEn 24 CpuIntEn; // <<<REMEMBER: GLOBAL ENABLE INTs 25 RcvIntEn; // CommIntEn?{RCVSTATREG;RCVBUF;RcvIntEn;} 26 Forever 27 if( kbhit ) { // LOCAL (DEMOQE128) IRQ button depressed? 28 switch( Inputs4 ) { // 1:1; 2:2; 4:3; 8:4 29 case 1: 30 case 2: 31 break; 32 case 4: 33 Inputs4 = 3; 34 break; 35 case 8: 36 Inputs4 = 4; 37 break; 38 default: 39 Inputs4 = 15; 40 break; 41 } 42 SetupXmtBlk( Inputs4 == 15 ? Prompt : Prompts[Inputs4‐1]); 43 kbhit = 0; 44 } 45 Endforever 285 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 46 Wait( 0 ); 47 } /* main() */ 48 // ================================================================== 49 interrupt VectorNumber_Virq void IRQISR ( void ) /*()*/ 50 { 51 get4Inputs(); // Load Inputs4 52 kbhit = 1; 53 IRQSC_IRQACK = 1; // BSET IRQSC_IRQACK,IRQSC: ACK IRQ 54 } // .. Interrupt (Rearm IRQ Interrupts) 55 // ================================================================== 56 interrupt VectorNumber_Vsci1tx void XmtISR ( void ) /*()*/ 57 { // NOTE: putchar clears RCVRDY.bit. Very clever... 58 word c; // MUST be 'word' to hold both Chars And EOF (0xFFFF) 59 switch( XmtControl ) { 60 case XMTMSG_S: 61 if( ! putchar( *XmtPtr++ ) ) { // 'C' Strings end at '\0' 62 XmtControl = XMTDEFAULT_S; // ..(Zstrings: ASCIZ) 63 } 64 break; 65 case XMTDEFAULT_S: 66 if( ( c = deQue( inQ ) ) != EOF ) { // Did something arrive? 67 putchar( c ); // 'C' Strings end at '\0' 68 }else{ 69 XmtIntDsb; 70 } 71 break; 72 } 73 } 74 // ================================================================== 75 interrupt VectorNumber_Vsci1rx void RcvISR ( void ) /*()*/ 76 { // NOTE: getchar clears RCVRDY.bit. Very clever... 77 byte c; 78 c = getchar(); 79 if( enQue( inQ, c ) != EOF ) { 80 XmtIntEn; 81 } 82 } COMENTARIOS a ["Labs-C\Lab3\SciComm\6ChatInt-4.c"]: Este programa es tan parecido al anterior, y usted conoce tanto a estas alturas, que los comentarios serán muy breves: La definición del Prompt es idéntica al chat anterior; los 4 prompts están colocados en una matriz que contiene 4 mensajes: 286 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 06 byte *Prompts[ 4 ] = { 07 { "\a\r\n\n1_Me encanta poder hablarle hoy___ " }, 08 { "\a\r\n\n2_Cómo va todo, finalizando el 13? " }, 09 { "\a\r\n\n3_Le deseo suerte para el año 2014 " }, 10 { "\a\r\n\n4_Que tenga buena nota en Arqui‐I! " }, 11 }; Imagino que conoce TODOS los símbolos... Qué es '\a'? Continúa todo igual, hasta enviar el Prompt. El ciclo infinito se diferencia del anterior porque contiene un Switch con 4 Cases, uno para cada interruptor de la tarjeta. kbhit funciona igual que antes, el programa principal lo coloca en 0, y la IRQISR en 1. Observe bien que al oprimir cada interruptor se generan los números 1, 2, 4 y 8, pero hemos codificado los Cases para que se generen los números 0, 1, 2 y 3, que corresponden a los subíndices que identifican los 4 mensajes en el arreglo. Así que hay que hay que relacionar los números obtenidos con los deseados, de la siguiente forma (Obtenido:Generado) 1:0; 2:1; 4:2; 8:3 Pero nótese que el '0' NO CORRESPONDE a oprimir ningún botón; siempre se está generando! Así que la variable 'Inputs4', que representa los botones, tendrá un Cero SIEMPRE que no se esté oprimiendo nada. Mala selección para el Switch, porque no permite distinguir entre NO selección, y selección del número 0. Por eso escogí generar estos números, en lugar de los indicados dos párrafos atrás" (Obtenido:Generado) 1:1; 2:2; 4:3; 8:4; ahora sí es fácil saber si se oprimió algún interruptor; en caso contrario, la variable Inputs4 vale 0. Y, al usar Inputs4 como índice, se le resta un uno, para convertir estos valores intermedios, en los que en realidad deseamos. Cualquier valor NO definido (default) coloca un '15' en la variable, simulando que se oprimieron TODOS los interruptores. Si Inputs4 vale 15, se envía el PROMPT; para los otros 4 valores posibles, se le resta un uno a Inputs4, y se lo usa como índice del Arreglo, para escoger uno de los mensajes, y transmitirlo empleando 'SetupXmtBlk', que como vimos antes, inicializa el sistema de transmisión y efectivamente hace que se transmita el Bloque, con el mensaje deseado. 26 Forever 27 if( kbhit ) { // LOCAL (DEMOQE128) IRQ button depressed? 28 switch( Inputs4 ) { // 1:1; 2:2; 4:3; 8:4 29 case 1: 30 case 2: 31 break; 32 case 4: 33 Inputs4 = 3; 34 break; 35 case 8: 36 Inputs4 = 4; 37 break; 38 default: 39 Inputs4 = 15; 40 break; 287 I N G . L U I S G . U R I B E C COMPUTER ARCHITECTURE: THE MC9S08 41 } 42 SetupXmtBlk( Inputs4 == 15 ? Prompt : Prompts[Inputs4‐1]); 43 kbhit = 0; 44 } 45 Endforever La rutina IRQISR es casi exacta a la del ejercicio previo; se diferencia en que se leen y adecúan los interruptores, usando la Macro get4Inputs, ya analizada. El resto sigue igual. 49 interrupt VectorNumber_Virq void IRQISR ( void ) /*()*/ 50 { 51 get4Inputs(); // Load Inputs4 Las dos rutinas finales, XmtISR y RcvISR son exactamente iguales a las del ejercicio anterior. FIN DE LA OBRA Pronto incluiré más ejercicios en Leguaje "C", como Manejo y Optimización de Tablas, Implementación de FMS y muchos otros temas interesantes. mailto:
[email protected] 288