TRABAJO – EMULADOR DE INSTRUCCIONES PARA EL PIC, CON COMUNICACIÓN POR PUERTO PARALELO MIEMBROS DEL GRUPO: Fernández Perdomo, Enrique Mora Madrid, Gastón Martín Ventura García, Yeray INFORMACIÓN ACADÉMICA: Prácticas de Diseño de Sistemas Basados en Microprocesador 3º Ingeniería en Informática – 2º Cuatrimestre Índice 1. Objetivos .......................................................................................................................................................3 1.1. Protocolo del Puerto Paralelo ...................................................................................................................3 1.2. Intérprete Gráfico del PC (Emulador) ........................................................................................................4 1.3. Ejecución de Instrucciones Emuladas en el PIC ..........................................................................................4 2. Fundamento Teórico .......................................................................................................................................5 2.1. Descripción General ................................................................................................................................5 2.1.1. Acceso Directo al Puerto ...................................................................................................................5 2.1.2. Registro de Datos (D) .......................................................................................................................5 2.1.3. Registro de Estado (S).......................................................................................................................6 2.1.4. Registro de Control (C) .....................................................................................................................6 2.1.5. Bit de Puerto Bidireccional.................................................................................................................6 2.1.6. Bit de Interrupción ............................................................................................................................7 2.1.7. Interrupciones con el Puerto Paralelo .................................................................................................7 2.2. Programación..........................................................................................................................................7 2.2.1. Obtención del Puerto........................................................................................................................7 2.2.2. Acceso a los Puertos.........................................................................................................................8 2.2.3. Detección del Tipo de Puerto ............................................................................................................9 2.3. Descripción del Conector Físico..............................................................................................................10 2.3.1. Velocidad ......................................................................................................................................11 2.3.2. Características Técnicas ..................................................................................................................11 3. Desarrollo ....................................................................................................................................................13 3.1. Protocolo del Puerto Paralelo .................................................................................................................13 3.1.1. Definición y Explicación del Protocolo ..............................................................................................13 3.1.2. Envío desde el PC ..........................................................................................................................13 3.1.3. Recepción desde el PC ...................................................................................................................15 3.1.4. Envío desde el PIC..........................................................................................................................17 3.1.5. Recepción desde el PIC ..................................................................................................................19 3.1.6. Inicialización y Acceso al Puerto Paralelo desde el PC.......................................................................20 3.1.7. Inicialización del PIC.......................................................................................................................23 3.2. Intérprete Gráfico del PC (Emulador.exe) ................................................................................................24 3.2.1. Traducción de Lenguaje Natural......................................................................................................24 3.2.1.1. Fase 1: Traducción de Instrucciones .........................................................................................24 3.2.1.2. Fase 2: Compilación de Instrucciones.......................................................................................28 3.2.2. Manejo del Programa Gráfico.........................................................................................................30 3.2.2.1. Creación de Programas emulados............................................................................................32 3.3. Programa de Emulación de Instrucciones del PIC.....................................................................................34 3.4. Rutinas de Retardo para el PIC ...............................................................................................................37 3.5. Fichero Cabecera para el PIC ................................................................................................................37 3.6. Directivas especiales de Ensamblador MASM32 ......................................................................................38 3.7. Interrelación entre Programas .................................................................................................................44 3.7.1. Programa del PC............................................................................................................................44 3.7.2. Programa del PIC ...........................................................................................................................44 3.8. Ejecución emulada en Proteus® de la Placa PIC-Trainer ..........................................................................45 4. Conclusiones................................................................................................................................................48 4.1. Protocolo del Puerto Paralelo .................................................................................................................48 4.2. Intérprete Gráfico del PC (Emulador) ......................................................................................................49 4.3. Ejecución de Instrucciones Emuladas en el PIC ........................................................................................49 5. Bibliografía...................................................................................................................................................50 6. Anexo ..........................................................................................................................................................51 6.1. Códigos del PIC ....................................................................................................................................51 6.1.1. Emulador de Comandos (main.asm)................................................................................................51 6.1.2. Protocolo del Puerto Paralelo (paralelo.asm) ....................................................................................53 6.1.3. Rutinas de Retardo (retardo.asm).....................................................................................................55 6.1.4. Fichero Cabecera (InstrPIC16F84.h)................................................................................................57 6.2. Códigos del PC .....................................................................................................................................59 Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 1 6.2.1. Interfaz del Programa Gráfico Principal (Emulador.inc) .....................................................................59 6.2.2. Programa Gráfico Principal (Emulador.asm).....................................................................................61 6.2.3. Interfaz del Intérprete de Comandos (TraduceIns.inc)........................................................................72 6.2.4. Intérprete de Comandos (TraduceIns.asm) .......................................................................................73 6.2.5. Fichero de Reglas de Sintaxis (reglasComp.txt) .................................................................................76 6.2.6. Interfaz del Compilador de Comandos (Compilador.inc)...................................................................77 6.2.7. Compilador de Comandos (Compilador.asm) ..................................................................................77 6.2.8. Interfaz del Acceso al Puerto Paralelo (Paralelo.inc) ..........................................................................82 6.2.9. Acceso al Puerto Paralelo (Paralelo.asm) .........................................................................................83 6.2.10. Interfaz del Protocolo del Puerto Paralelo (Protocolo.inc).................................................................84 6.2.11. Protocolo del Puerto Paralelo (Protocolo.asm) ................................................................................84 6.2.12. Interfaz de la Librería Winio (WinIo.inc) .........................................................................................86 Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 2 1. Objetivos En este trabajo se ha realizado un emulador de instrucciones, el cual consistirá en un programa gráfico para Win32, que permitirá interpretar instrucciones y convertirlas a un pseudocódigo que posteriormente se tratarán con valores hexadecimales de un byte. Con esto se consigue poder enviar cada comando y sus argumentos al microcontrolador PIC16F84. En el PIC se tendrá todos los comandos reales, que se emularán desde el programa del PC. De este modo, finalmente se ejecutarán en el PIC los comandos emulados, que serían como pseudoinstrucciones de un más alto nivel. Por otro lado, será necesaria la implementación de un protocolo de comunicación entre el PC y el PIC (y viceversa, es decir, que tendrá que ser bidireccional). Esto protocolo se desarrollará en el marco del puerto paralelo, con una transmisión de un byte en cada transmisión (8 bits), usando dos señales de control o sincronismo, a parte de las del dato. En los siguientes apartados se comenta de forma algo más detallada aquéllos aspectos desarrollados en el ámbito de cada una de las tareas antes mencionadas. 1.1. Protocolo del Puerto Paralelo En cuanto al protocolo del puerto paralelo, se tratará su implementación software, tanto en el PC como en el PIC. Hay que destacar el hecho de que serán implementaciones similares aunque en lenguajes ensamblador ligeramente distintos (ensamblador del PIC, frente al ensamblador gráfico MASM32). Pero además de esto nos enfrentaremos con el hecho de que algunas líneas de control usarán lógica negada, aspecto que habrá que tratar con especial cuidado. De este modo, para el diseño de la interfaz de conexión y comunicación por puerto paralelo, el primer paso será el diseño hardware con un cable paralelo conectado desde la ranura del PC al zócalo libre de la placa PIC-Trainer, para poder recibir y enviar datos entre PC y PIC. Una vez realizada la construcción hardware se creará el diseño conceptual del protocolo y su posterior implementación, tanto en el PIC como en el PC. Este protocolo constará de las típicas instrucciones de envío y recepción, que se usarán en PC y PIC, y como es obvio, cuando el PC envíe el PIC recibirá, y viceversa. Igualmente, en la fase de implementación software se diseñará una etapa previa de inicialización del protocolo, que atañerá a los puertos del PIC, y estado del puerto paralelo del PC. Así, se definirá lo que puede entenderse como un estado de reposo. Mientras en el PIC el proceso de inicialización atañerá exclusivamente al modo de los puertos (entrada o salida) y su contenido, en el caso del PC también se tratará el proceso de toma de las direcciones en que está mapeado el puerto paralelo. Finalmente, hay que mencionar que al desarrollar el programa del PC en modo gráfico estaremos arrancando nuestro programa en modo usuario, en lo que se conoce como el modo protegido de funcionamiento típico del entorno gráfico de Windows. En este modo, no se podrá tener acceso a toda la memoria ni a los puertos del PC. Esto se debe a que es el sistema operativo (en este caso Windows), el que lo controla. Para solventar este problema se usará la librería WinIo, que se comentará en la memoria, y que permite el uso de la memoria a través de una función API específica que esta librería aporta y, lo que es más interesante, las operaciones de entrada y salida (in y out, respectivamente) sobre los puertos del sistema (el PC) serán permitidas. Con esta librería podrá realizarse todo este proceso incluso en máquinas con Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 3 Windows de redes (NT y XP), en los que la protección del equipo es aún mayor. Todo esto será posible siempre y cuando en la BIOS del PC se haya configurado con anterioridad el modo Bidireccional. 1.2. Intérprete Gráfico del PC (Emulador) El intérprete gráfico tendrá la finalidad de poder cargar programas en un lenguaje emulado, que permite el uso de comandos con parámetros de entrada y salida, que emulan instrucciones que realizan una determinada tarea en el PIC. De este modo, se tendrá una típica interfaz de programa gráfico, desarrollada en ensamblador MASM32, en la que se puede editar el código de nuestra aplicación, así como compilar y ver resultados. Además, nuestros programas podrán guardarse fácilmente, sin problema alguno. Además de las opciones de compilación, se tendrán opciones de tratamiento especial y testeo del puerto, con finalidades de depuración, fundamentalmente. Para que el conjunto de instrucciones o comandos posible, así como su conjunto de parámetros de entrada y salida, quede bien definido, se hará uso de un fichero especial. Este será un fichero típico de sintaxis en el que se definirán las reglas de estos comandos y sus mencionados parámetros de entrada y salida. Finalmente, sólo hay que indicar el hecho de que el proceso de compilación de nuestros programas conllevará la comunicación con el PIC a través del puerto paralelo, con el protocolo desarrollado, para que el comando deseado se ejecute en el PIC, con los parámetros de entrada indicados, e igualmente se devuelvan los resultados en los parámetros de salida indicados. Para facilitar el manejo del programa, el lenguaje emulado creado incluirá la posibilidad de definición de variables en la cabecera de nuestro programa, para su posterior uso entre comandos. 1.3. Ejecución de Instrucciones Emuladas en el PIC En el PIC16F84 se cargará un programa con todas las librerías de manejo de la placa PIC-Trainer y PIC-Trainer Plus. El programa principal esperará el envío de un comando. Una vez recibido éste, se bifurcará a la zona apropiada para su ejecución en el PIC. Con esto se tendrá cerrado el bucle de emulación. Si los comandos a ejecutar requieren el uso de parámetros de entrada, el programa se dispondrá a recibirlos, mientras que desde el PC se enviarán. Con ello, los comandos ejecutados tendrán mayor versatilidad. Finalmente, el programa tendrá las rutinas necesarias para el correcto envío y recepción de comandos y parámetros, para lo cual se usa el protocolo diseñado para el puerto paralelo. Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 4 2. Fundamento Teórico A continuación se comentan por separado los aspecto programáticos y físicos del Puerto Paralelo, desde su interfaz, como conexionado físico. 2.1. Descripción General El puerto paralelo se identifica por su dirección de I/O base y se identifica ante sistemas DOS por el número LPT. Cuando arranca la máquina, la BIOS chequea direcciones específicas de I/O en busca de puertos paralelos y construye una tabla de las direcciones halladas en la posición de memoria 40h:8h (o 0h:0408h). Esta tabla contiene hasta tres palabras de 16 bits. Cada palabra es la dirección de I/O base del puerto paralelo. La primera palabra corresponde a LPT1, la segunda a LPT2 y la tercera a LPT3. Hay que agregar que en DOS tenemos el dispositivo PRN que es un alias a uno de los dispositivos LPT (generalmente es LPT1, pero se puede cambiar con la orden MODE) Las direcciones estándar para los puertos paralelos son 03BCh,0378h y 0278h (chequeadas en este orden). Desde el punto de vista del software, el puerto paralelo son tres registros de 8 bits cada uno, ocupando tres direcciones de I/O consecutivas de la arquitectura x86. 2.1.1. Acceso Directo al Puerto El puerto, como se mencionó antes, consiste de tres registros de 8 bits ubicados en direcciones adyacentes del espacio de I/O de la PC. Los registros se definen relativos a la dirección de I/O base (variable IOBase) y son: IOBase+0 : registro de datos IOBase+1 : registro de estado IOBase+2 : registro de control 2.1.2. Registro de Datos (D) Se puede leer y escribir. La forma de leer y escribir puertos con lenguajes de programación estándares se puede ver en la sección Acceso a los puertos. Escribiendo un dato al registro, causa que el mismo aparezca en los pines 2 a 9 del conector del puerto. Leyendo el registro, se lee el último dato escrito (NO lee el estado de los pines; para ello hay que usar un puerto bidireccional). Nro.Bit 7 x . . . . . . . 6 . x . . . . . . 5 . . x . . . . . 4 . . . x . . . . 3 . . . . x . . . 2 . . . . . x . . 1 . . . . . . x . 0 . . . . . . . x Descripción D7 (pin 9), 1=Alto, 0=Bajo D6 (pin 8), 1=Alto, 0=Bajo D5 (pin 7), 1=Alto, 0=Bajo D4 (pin 6), 1=Alto, 0=Bajo D3 (pin 5), 1=Alto, 0=Bajo D2 (pin 4), 1=Alto, 0=Bajo D1 (pin 3), 1=Alto, 0=Bajo D0 (pin 2), 1=Alto, 0=Bajo Cuando se indica Alto o Bajo se refiere a la tensión de salida (~5V para el 1 físico y ~0V para el 0 físico, respectivamente). Esto es porque la lógica puede ser positiva (un 1 lógico equivale a Alto o 5V) o negada (un 0 lógico equivale a Bajo o 0V). Con respecto a esto debemos decir que para negar algo le anteponemos el carácter / Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 5 (representando la barra que se coloca encima). El estándar es que las salidas sean LS TTL (low schottky TTL), aunque las hay que son de tipo OC (colector abierto). La corriente que pueden entregar (source) es de 2,6 mA máximo y pueden absorber (sink) un máximo de 24 mA. En el puerto original de IBM hay condensadores de 2,2 nF a masa. Las tensiones para el nivel bajo son entre 0 y 0,8V y el nivel alto entre 2,4V y 5V. 2.1.3. Registro de Estado (S) El registro de estado está en IOBase+1. Es de sólo lectura (las escrituras serán ignoradas). La lectura da el estado de los cinco pines de entrada al momento de la lectura. En la tabla siguiente los nombres de los pines se dejaron en inglés porque es como generalmente se identifican. Nro.Bit 7 x . . . . . 6 . x . . . . 5 . . x . . . 4 . . . x . . 3 . . . . x . 2 . . . . . x 1 . . . . . x 0 . . . . . x Descripción S7 : Busy (pin 11), 0=Alto, 1=Bajo S6 : Ack (pin 10), 1=Alto, 0=Bajo S5 : No paper (pin 12), 1=Alto, 0=Bajo S4 : Selected (pin 13), 1=Alto, 0=Bajo S3 : Error (pin 15), 1=Alto, 0=Bajo Sin definir La línea Busy tiene, generalmente, una resistencia de pull-up interna. El estándar es que sean entradas tipo LS TTL. 2.1.4. Registro de Control (C) El registro de control se encuentra en IOBase+2. Es de lectura/escritura. Nro.Bit 7 x . . . . . . 6 x . . . . . . 5 . x . . . . . 4 . . x . . . . 3 . . . x . . . 2 . . . . x . . 1 . . . . . x . 0 . . . . . . x Sin usar C5 : Control puerto bidireccional C4 : Interrupt control, 1=enable, 0=disable C3 : Select (pin 17), 1=Bajo, 0=Alto C2 : Initialize (pin 16), 1=Alto, 0=Bajo C1 : Auto Feed (pin 14), 1=Bajo, 0=Alto C0 : Strobe (pin 01), 1=Bajo, 0=Alto Los cuatro bits inferiores son salidas. La lectura devuelve lo último que se escribió a dichos bits. Son TTL a colector abierto con resistencias de pull-up de 4700 ohms, por lo que un dispositivo externo puede forzar el estado de los pines sin dañar el driver. Esto permite utilizar estas cuatro líneas como entradas. Para ello, ponemos en alto las cuatro salidas (escribiendo 0000100b en IOBase+2) lo que hace que las salidas “floten”. Ahora, un dispositivo externo puede forzar a bajo alguna de las salidas con lo que, leyendo el puerto, sabemos si esto sucedió o no. Es posible realizar esta técnica en salidas totem-pole (como D0-D7) pero no recomendamos su uso porque habría que tener un conocimiento preciso de la corriente ya que se puede sobrecargar los transistores de salida, dañando el driver (especialmente en puertos integrados LSI). 2.1.5. Bit de Puerto Bidireccional Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 6 El bit C5, está disponible sólo si se trata de un puerto bidireccional; en los puertos comunes actúa como los bits C6 y C7. Si C5=1, el buffer de los datos de salida se pone en alta impedancia, “desconectando” dicho buffer de los pines 2 a 9 del conector del puerto (D0 a D7). Si se escribe al registro de datos, se escribe al buffer pero no a la salida. Esto permite que al leer el puerto, se lea el estado de las entradas y no lo que hay en buffer. En las computadoras IBM PS/2, para habilitar el puerto paralelo bidireccional, además de lo antes descrito, se debe poner a 1 el bit 7 del registro del puerto 102h (opciones de configuración). En computadoras que no tengan puerto paralelo bidireccional compatible PS/2 hay que modificar uno o más bits de algún puerto específico correspondiente al chipset de la placa. A veces se habilita por setup o por jumper en la placa del puerto. 2.1.6. Bit de Interrupción En trabajos normales de impresión ni el BIOS ni el DOS hacen uso de la interrupción. El hecho de poseer una línea de interrupción que está conectada directamente al PIC (Programmable Interrupt Controller), lo hace muy útil para experimentación en data-loggers por ejemplo. El bit de interrupción está conectado al control de un buffer de tres estados. Cuando C4=1, se activa el buffer y su entrada, S6, se conecta a la línea IRQ (en general es IRQ7 o IRQ5). La lectura del bit, nos devuelve el estado del mismo (es decir si el buffer está en alta impedancia o no). Se producirá una interrupción, cuando haya un flanco descendente en el pin correspondiente a S6 (ver Descripción del conector físico). A continuación, se describen los pasos para poder utilizar interrupciones. 2.1.7. Interrupciones con el Puerto Paralelo Primero que nada debemos habilitar el buffer que conecta la línea ACK con la línea IRQ. Esto lo hacemos poniendo a 1 el bit 4 del registro de control (IOBase+2). Luego debemos preparar una ISR (Interrupt Service Routine) que atienda la interrupción recordando enviar la señal EOI (20h) al registro de control del PIC (puerto 20h) al salir de la rutina. La interrupción software corresponde al número 0Dh para IRQ5 y 0Fh para IRQ7. Finalmente se habilita con 0 la interrupción IRQ5 (o IRQ7) escribiendo al bit 5 (o 7) del registro de interrupciones del PIC (puerto 21h). Para desinstalar la ISR, deshabilitamos la IRQ5 (o IRQ7) escribiendo un 1 al bit (o 7) del registro de interrupciones del PIC (puerto 21h). Luego hacemos que C4=0. 2.2. Programación Por programación entenderemos la programación necesaria para el puerto paralelo, desde el PC, por lo general en máquinas Pentium y 80x86, con Windows como sistema operativo. 2.2.1. Obtención del Puerto Como ya se mencionó anteriormente las direcciones de I/O de los puertos paralelo se almacenan en una tabla ubicada en 40h:8h (0h:408h). Entonces, éste sería un método de obtener las direcciones. A continuación se muestra como obtener dichas direcciones en distintos lenguajes. Ensamblador Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 7 ;en SI tengo la dirección de memoria: ;LPT1 = 0408h ;LPT2 = 0408h + 2h = 040Ah ;LPT3 = 040Ah + 2h = 040Ch mov si,0408h ;SI = 0408h xor ax,ax ;AX = 0 push ds ;Mete DS en la pila mov ds,ax ;DS = AX = 0 mov ax,word ptr [SI] ;AX = [0h:SI] pop ds ;recupero DS de la pila ;ahora en AX tengo la dirección base Windows En entorno Windows, se complica un poco ya que tenemos varios métodos. 1- Verificar en las direcciones estándar que el puerto tenga retención de datos escritos en él. Es armar la tabla que realiza la BIOS por nosotros mismos. Este método falla si el puerto es bidireccional (o ECP o EPP), si algún controlador prohíbe el acceso, si estamos bajo WinNT o si el puerto está en una dirección no estándar. Ver la sección Acceso a los puertos para más detalles y véase la sección Detección de tipo de puerto, para buscar por uno mismo las direcciones (y de paso detectar que tipo de puerto es). Ahora lo único que podría hacer fallar la prueba es si algún controlador de dispositivo prohíbe el acceso (o WinNT, claro). 2- Tener la posibilidad de leer la tabla que la BIOS genera cuando arranca la máquina. Debemos contar con alguna función que nos permita leer una dirección de memoria como si estuviéramos en modo real de la CPU. 3- Bajo WinNT, podemos obtener (mediante las funciones de la API, RegOpenKey y RegQueryValue) la información del registro de: [HKEY_LOCAL_MACHINE\Enum\Root\ [HKEY_LOCAL_MACHINE\Enum\BIOS\ [HKEY_DYN_DATA\Config Manager\Enum\ 4- En Win9x,Me, podemos hacer el enfoque de la opción 2, pero con la API de Windows. Utilizamos la función de la API Toolhelp32ReadProcessMemory (que reside en Kernel32 y que no se encuentra en NT). 2.2.2. Acceso a los Puertos A continuación se darán las funciones a utilizar para leer y escribir puertos en distintos lenguajes. En Ms-Dos no tenemos ningún tipo de restricción de acceder a los puertos. En Windows 3.x, 9x y Me tampoco hay restricciones (a no ser que el puerto esté bajo el control de un controlador de dispositivo virtual). En Windows NT, el sistema operativo tiene control total sobre la máquina por lo que hay que pedir un permiso que se hace mediante un driver. Ensamblador Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 8 Los opcodes (códigos de operación) IN y OUT permiten leer y escribir, respectivamente, datos a un puerto cualquiera. La secuencia a seguir para leer podría ser: mov dx,Puerto ;DX = Puerto (puede ser cte. o ref. de memoria si es variable). in al,dx ;Leo del puerto DX y lo guardo en AL Y para escribir: mov dx,Puerto ;DX = Puerto (puede ser cte. o ref. de memoria si es variable). out dx,al ;Manda AL al puerto DX 2.2.3. Detección del Tipo de Puerto En esta sección indicaremos los pasos a seguir para detectar si el puerto es SPP (común), bidireccional compatible PS/2, ECP o EPP. Describiremos los pasos generales; luego se tendrá que adaptar al lenguaje ensamblador. Se testea en orden descedente de complejidad, es decir primero ECP, luego EPP, bidireccional y finalmente SPP (esto se debe realizar así para no fallar en la detección). Detectando ECP Este es el método que recomienda Microsoft: Leer el control de registro extendido (ECR) en Base+402h. Base se refiere a la dirección inicial donde se comienza a mapear el puerto (03BCh,0378h y 0278h). Verificar que el bit 0 sea 1 (FIFO vacía) y que el bit 1 sea 0 (FIFO no está llena). Estos bits podrían ser distintos que los bits correspondientes en el puerto de control (Base+2h). Para verificar esto, cambiamos el estado de algún bit del puerto Base+2h y verificamos que no haya cambiado en Base+402h. Una prueba adicional es escribir 34h al ECR y leerlo. Los bits 0 1 y son de sólo lectura, por lo tanto, si leemos 35h, es probable que tengamos un puerto ECP. Detectando EPP Además de los tres registros de un puerto SPP, un puerto EPP tiene cinco registros más mapeados desde Base+3h a Base+7h. Estos registros adicionales proveen una manera de testear la presencia de un EPP, escribiendo ciertos valores y leyendo el resultado (de la misma manera que se testea un puerto SPP). Al igual que al detectar puertos SPP, se recomienda escribir un par de valores y leerlos. Dichos valores podrían ser 55h y AAh. Hay que asegurarse de poner S0 a 0 (EPP timeout), antes de leer o escribir a estas direcciones extendidas. Otro dato importante es que no existen puertos EPP en la dirección base 03BCh. Detectando SPP Para detectar si es SPP, hacemos el procedimiento que realiza la BIOS al arranque. Verificamos la retención del puerto, que será posible en un puerto SPP. Para ello escribimos un par de valores (55h y AAh) a las direcciones base (03BCh,0378h y 0278h) y leemos el registro verificando que sean los mismo valores escritos. Detectando puerto bidireccional (PS/2) Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 9 Aquí debemos habilitar el puerto bidireccional (con C5=1) y hacer la misma prueba que para un SPP para verificar que no haya retención. 2.3. Descripción del Conector Físico El puerto es un conector hembra DB25 (cuyo diagrama y señales utilizadas podemos ver en la siguiente figura) con doce salidas latcheadas (que tienen memoria/buffer intermedio) y cinco entradas, con 8 líneas de masa. La función normal es transferir datos a una impresora a través de las 8 líneas de datos, usando las señales restantes como control de flujo. Como se mencionó anteriormente, la conexión del puerto paralelo al mundo externo se realiza por un conector hembra DB25. Viendo el conector al frente y con la parte que tiene más pines hacia arriba, se numera de menor a mayor de derecha a izquierda en ambas filas de pines (1 a 13 arriba y 14 a 25 abajo). En las tablas vistas en las secciones correspondientes a cada registro se vio que cada bit tiene un nombre característico. Estos nombres son las funciones que cumplen los bits en una conexión con una impresora. Además de la lógica que posee cada señal (que es un aspecto físico del conector), tenemos que la impresora tiene su propia lógica para con los nombres de los pines. Este es el momento para hacer mención a las líneas que nosotros utilizaremos para implementar nuestro cacle paralelo. En primer lugar necesitaremos 2 líneas de control para que el PC y el PIC sean capaces de entenderse sin confusión. Estas serán la línea STROBE y ACK, y como parece obvio para la transmisión de los datos emplearemos las correspondientes 8 líneas de datos del puerto. Las 2 líneas de control se conectan al puerto A y como estas señales son tan importantes deberemos establecer que una de esas líneas es intocable por el usuario, en cambio la de datos no tienes esa relevancia por que aunque se escriba en ellas el PC y el PIC seguirán comunicándose y determinando si todo el proceso ha sido correcto. Por eso no hay que confundirse si el nombre de la señal establece una lógica contraria a la lógica real del puerto; son cosas distintas. Por ejemplo, la expresión /Ack, donde Ack viene de Reconocido o Aceptado, nos indica que una aceptación (Ack=Alto=Verdadero) la tenemos que reconocer porque la impresora nos envía un Bajo, que por la característica de S6 equivale a un 0 Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 10 lógico. En la columna de entrada/salida (I/O) se refiere al lado de la PC. En la columna Cent pin, indicamos el pin correspondiente al conector Centronics usado en las impresoras. Algunos nombres no se corresponden con los de las tablas anteriores. Es a propósito para indicar los posibles nombres que puede tener una señal. I/ O O DB25 pin 1 Cent pin 1 Bit reg. /C0 Señal Descripción /Strobe O O O O O O O O I 2 3 4 5 6 7 8 9 10 2 3 4 5 6 7 8 9 10 D0 D1 D2 D3 D4 D5 D6 D7 S6 Data0 Data1 Data2 Data3 Data4 Data5 Data6 Data7 /Ack A bajo por más de 0.5uS para indicar a la impresora que se enviarán datos Bit menos significativo de Data I 11 11 /S7 Busy I I O 12 13 14 12 13 14 S5 S4 /C1 PaperEnd SelectIn /AutoFd I 15 32 S3 /Error Masa Bit más significativo de Data Pulso bajo de ~5uS, indica que se recibieron datos en impresora. En alto nos indica que la impresora Está ocupada En alto nos indica que no hay papel En alto para impresora seleccionada Si ponemos en bajo, el papel se mueve una línea luego de la impresión En bajo nos indica error (no hay papel, está fuera de línea, error no det.) Si enviamos un pulso en bajo > 50uS La impresora se resetea En bajo seleccionamos impresora (en gral. no se usa, ya que SelectIn se fija a alto) Masa retorno de par trenzado Masa Masa Masa lógica Masa chasis O 16 31 C2 /INIT O 17 36 C3 /Select - 18-25 19-30 y 33 18-25 16 18-25 17 2.3.1. Velocidad Un puerto paralelo ISA normal toma un ciclo-ISA para leer o escribir. Como en muchos sistemas, la velocidad del bus ronda los 1,3 Mhz, podemos decir que la lectura la podemos hacer cada 1 uS (idealmente, ya que siempre tenemos otras instrucciones software, etc, etc.). Algunos puertos soportan un modo “turbo” que elimina los 3 estados de espera de la CPU, con lo que la velocidad de lectura/escritura al puerto se duplica (2,7 MHz). 2.3.2. Características Técnicas Las características técnicas del puerto paralelo son: Tensión de alimentación (VDD) ............................................... 9 a 12 volts D.C. Tensión de alimentación alta potencia (VHH)............................ 0 a 15 volts Tensión de Salida VCC........................................................... 5 Vcc regulados Capacidad de carga de la salida VCC..................................... 1A Cantidad de entradas TTL ....................................................... 4 Niveles aceptables para entradas TTL ...................................... 0v a 5v Cantidad de salidas TTL (TTLx) ................................................ 8 Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 11 Capacidad de carga en salidas TTL ......................................... 2 mA Cantidad de salidas de alta potencia (HPx)............................... 7 Capacidad de carga en salidas de alta potencia....................... 500 mA Dimensiones .......................................................................... 150mm x 100mm Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 12 3. Desarrollo Para la elaboración de esta práctica, la idea consiste en la comunicación de un PC y un PIC a través de un puerto paralelo, el cual esta conectado a un sócalo de la placa del PIC-Trainer, que a su vez éste se conecta a: los cables correspondientes a los datos enviados o recibidos desde el PC a las 8 líneas del puerto B del PIC, y las dos líneas necesarias para el control de las transferencias (mediante un pequeño protocolo) conectadas al puerto A. Estas líneas no deberán ser empleadas por el usuario, puesto que si son tocadas, se perderá el sincronismo entre el PC y el PIC a la hora de la transferencia. Por último decir que hay una línea más que es la tierra del paralelo, que se conecta a la tierra de referencia del PIC, a través del mismo sócalo. Todo esto es la idea de lo que haremos, puesto que esto que se ha dicho viene soportado por unos ficheros donde se ha implementado el protocolo de envío y recepción de datos (protocolo.asm), así como el programa cargado en el PIC, y el entorno gráfico desde el que se manejan las transferencias (emulador.asm Æ emulador.exe). 3.1. Protocolo del Puerto Paralelo En los siguientes apartados se trata el protocolo de comunicación a través del puerto paralelo. Para ello se empieza por definir y explicar dicho protocolo de forma conceptual. Seguidamente se trata el envío y recepción, tanto desde el PIC como desde el PC. Del mismo modo se comentan otros aspectos relacionados, así como la inicialización y estado por defecto o de reposo de los elementos conectados al puerto, haciendo especial hincapié en las líneas de control del mismo. 3.1.1. Definición y Explicación del Protocolo El protocolo no es más que una serie de reglas que se deben cumplir para sincronizar el receptor y el emisor en una transmisión de información. Es te sincronismo se consigue gracias a un control a la hora de transmitir y recibir que a continuación empezaremos a comentar. Un hecho clave es que un dato no puede ser enviado si el receptor no esta listo para recibir la información debido a que pueda estar realizando otra tarea, (recibiendo de otro emisor, o tratando la información anteriormente recibida), si no se quiere perder la información. Una vez que el emisor este listo para su labor, deberá informar al emisor para que este comience a transmitir. A parte de esto tendremos unos estados en los que lo que hacemos es esperar un tiempo para evitar la desincronización. Teniendo estos conceptos claros empezaremos a describir tal proceso de forma breve y directamente sobre el código en forma de comentarios. Hay que tener en cuenta el hecho de que las líneas de control asociadas al puerto A del PIC (A3 y A4) son en lógica inversa. 3.1.2. Envío desde el PC Todos estos pasos que describiremos en este apartados y en los posteriores están implementados en el archivo protocolo.asm. Enviar proc Dato:DWORD ; Envía Dato de PC al PIC Æ Realiza el envío del byte Dato desde el PC al PIC, según el protocolo. ; PASO 1: Esperar a que el PIC esté listo para recibir el Dato (Estado de Reposo). ;El PIC estará listo cuando al(PIC_LISTO) == 0 .repeat Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 13 Paralelo.asm ;;invocamos al procedimiento de lectura del puerto paralelo implementado en el fichero invoke LeerPuerto,offset RegEstado ; registro de estado es donde se controla en que estado se encuentra el puerto and al,PIC_LISTO .until !al ; PASO 2: El PC pone el Dato en el puerto (se lo envía al PIC), primero se pone el puerto de salida; aunque normalmente no es necesario hacerlo, pues es el estado por defecto) invoke LeerPuerto,offset RegControl ;se lee el registro del control para porder modificar su valor cambiando solo los bits que nos hacen falta mov ah,C5 not ah and al,ah invoke EscribirPuerto,offset RegControl mov eax,Dato mov al,byte ptr [eax] invoke EscribirPuerto,offset RegDato ; PASO 3: Indicación de que PC va a transmitir al PIC invoke LeerPuerto,offset RegControl mov ah,PC_LISTO not ah and al,ah ; Se pone a 0 al “PC_LISTO”, (aunque realmente en la linea a 1 ya que es lógica inversa) invoke EscribirPuerto,offset RegControl ; PASO 4: Esperar a que el PIC indique que ha recibido correctamente el Dato .repeat invoke LeerPuerto,offset RegEstado and al,PIC_LISTO .until al ; El PIC indica la toma del dato con al(PIC_LISTO) == 1 ; PASO 5: El PC deja de indicar que transmite el Dato al PIC, pues se terminó la transmisión, permitiendo que el PIC comience a trabajar invoke LeerPuerto,offset RegControl or al,PC_LISTO ; Se pone a 1 al “PC_LISTO”, (aunque realmente en la linea a 0 ya que es lógica inversa) invoke EscribirPuerto,offset RegControl ; PASO 6: Esperar a que el PIC esté de nuevo en Estado de Reposo, que es realmente de ejecución del comando/instrucción. Este paso es opcional, pero es más seguro hacerlo. .repeat invoke LeerPuerto,offset RegEstado and al,PIC_LISTO .until !al ; El PIC estará en reposo (ejecutando el comando) cuando al(PIC_LISTO) == 0 ; FINAL: Ahora el PIC procesará el comando/instrucción asociada al comando, mientras el PC espera que el usuario solicite la ejecución de otro nuevo ret Enviar endp En el siguiente diagrama se muestra todo el proceso a modo de diagrama de flujo: Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 14 3.1.3. Recepción desde el PC Siguiendo la línea del envío desde el PC, tendremos la siguiente explicación: Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 15 Recibir proc uses edx Dato:DWORD ; Recibe Dato desde PIC ; Realiza la recepción del byte Dato enviado desde el PIC, devolviéndolo en Dato, según el protocolo. ; PASO 1: Poner PC en estado de envio (PC_LISTO == 1); a 0 en la linea invoke LeerPuerto,offset RegControl or al,PC_LISTO invoke EscribirPuerto,offset RegControl ; PASO 2: Se pone el puerto de Datos de Entrada (Entrada/Salida) invoke LeerPuerto,offset RegControl or al,C5 invoke EscribirPuerto,offset RegControl ; PASO 3: Esperar a que el PIC indique el Dato está listo para recibirse .repeat invoke LeerPuerto,offset RegEstado and al,PIC_LISTO .until al ; El PIC indica que el dato está listo con al(PIC_LISTO) == 1 sin logica negada ; PASO 4: El PC lee el Dato invoke LeerPuerto,offset RegDato mov edx,Dato mov byte ptr [edx],al invoke LeerPuerto,offset RegControl mov ah,C5 not ah and al,ah invoke EscribirPuerto,offset RegControl ; PASO 5: El PC indica que ya recibió el Dato (PC_LISTO == 0) invoke LeerPuerto,offset RegControl mov ah,PC_LISTO not ah and al,ah invoke EscribirPuerto,offset RegControl ; PASO 6: El PC espera que el PIC deje de enviar el Dato (a que se de cuenta de que el PC ya recibió el Dato) .repeat invoke LeerPuerto,offset RegEstado and al,PIC_LISTO .until !al ; El PIC finalizará la transmisión, con al(PIC_LISTO) == 0 sin logica negada ; PASO 7: Poner PC en estado de no reposo; a 0 en la linea invoke LeerPuerto,offset RegControl or al,PC_LISTO invoke EscribirPuerto,offset RegControl ; FINAL: Ahora el PC ha recibido el Dato y lo procesará, mientras el PIC estará en reposo, sin transmitir, pero podrá estar en la ejecución de un comando ret Recibir endp Su diagrama de flujo será: Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 16 3.1.4. Envío desde el PIC En el envío de datos desde el PIC el protocolo empleado, que sería algo simétrico y relacionado directamente con la recepción del PC, pues se deben producir sincrónicamente, para lo cual se emplean bucles con condiciones en determinados puntos, con las señales de control PC_LISTO y PIC_LISTO. A continuación vemos todo el Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 17 proceso con el siguiente diagrama de flujo, que equivale directamente a la explicación conceptual de su funcionamiento. La implementación de estos pasos es sencilla y directa, además de bastante corta, pudiendo verse el código en el Anexo. Sólo cabe mencionar el hecho de que los bucles se realizan sin etiquetas, si bien con etiquetas, tanto el funcionamiento como ocupación del código una vez compilado serían el mismo; no obstante, se hace con saltos por Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 18 distancia para dar más legibilidad al código y menor tamaño pues son salto a la posición inmediatamente anterior, es decir, a la instrucción previa (goto $-1). El dato a enviar se indicará desde el programa que realiza la llamada en el programa del PIC, poniendo dicho dato previamente en la variable global DatoPP, como si se tratara de un parámetro de entrada a la función. Por otro lado, se mantiene el valor que estaba en el puerto B en la variable DatoPORTB, para restaurar su valor tras la recepción, asignándoselo a PORTB. 3.1.5. Recepción desde el PIC En la recepción de datos desde el PIC el protocolo empleado, que sería algo simétrico y relacionado directamente con envío desde el PC, pues se deben producir sincrónicamente, para lo cual se emplean bucles con condiciones en determinados puntos, con las señales de control PC_LISTO y PIC_LISTO. A continuación vemos todo el proceso con el siguiente diagrama de flujo, que equivale directamente a la explicación conceptual de su funcionamiento. Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 19 La implementación de estos pasos es sencilla y directa, como en el caso del envío desde el PIC, además de bastante corta, pudiendo verse el código en el Anexo. El dato recibido se almacena en la variable global DatoPP. Como será visible desde el exterior, se podrá conocer luego el valor del dato recibido y trabajar con él en el PIC. Por otro lado, se mantiene el valor que estaba en el puerto B en la variable DatoPORTB, para restaurar su valor tras la recepción, asignándoselo a PORTB. 3.1.6. Inicialización y Acceso al Puerto Paralelo desde el PC Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 20 Lo que vamos a hacer ene este apartado es describir de forma breve y clara en que consiste la inicialización del puerto paralelo, así como los pasos que deberemos tomar para tal objetivo, y para la interactuación con el mismo (Lectura / Escritura Directa). Este archivo es paralelo.asm. .386 .model flat,stdcall option casemap:none ;se incluyen las librerias necesarias para permitir la lectura y escritura en el puerto gracias al WinIo, la cual permite saltarse la protección de windows. Ademas se incluye el fichero de sintaxis. include Paralelo.inc include WinIo.inc includelib WinIo.lib .const ;se inicializan los registros de los puertos paralelos que puede contener un ordenador personal típico, asi se contribuye a la generalización. LPT1 LPT2 LPT3 equ 0408h equ LPT1+2 equ LPT2+2 .data? ;Expliquemos brevemente la funcion de cada reg del puerto. REGDATO: en este registro se debe almacenar el dato a enviar antes de dicha instrucción, y por otro lada es el registro que deberemos leer una vez recibidos los datos, mediante una operación de recibir. REGESTADO: este registro no se emplea en su totalidad sino que se utiliza un único bit 6 (S6 o ACK), que usa logica Positiva. Este bit se usa con PIC_LISTO. REGCONTROL: en este registro pasa lo mismo que en el anterior, simplemente utilizamos el bit 0 (C0 o ~Strobe) que utiliza lógica inversa. Este bit se usa con el PC_LISTO, (verificar sincronismo). Tambien se utiliza el bit 5 (C5), empleado para el control del puerto bidireccionado. RegDato RegEstado RegControl dw ? dw ? dw ? .code ;se implementa un procedimiento para la obtención del puerto paralelo TomarPuerto proc uses edx Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 21 ; Con la llamada a InitializeWinIo se permite el acceso a Memoria y Entrada/Salida directo. ; Luego se toma la dirección de los registros del puerto paralelo. invoke InitializeWinIo invoke GetPhysLong,LPT1,offset RegDato mov dx,RegDato inc dx mov RegEstado,dx inc dx mov RegControl,dx ret TomarPuerto endp ;se implementa otro procedimiento para la implementación de la lectura desde el puerto LeerPuerto proc uses edx Reg:DWORD mov edx,Reg mov dx,word ptr [edx] in al,dx ret LeerPuerto endp ;y otro procedimiento mas para la escritura en el mismo EscribirPuerto proc uses edx Reg:DWORD mov edx,Reg mov dx,word ptr [edx] out dx,al ret EscribirPuerto endp end En el siguiente diagrama de bloques se muestra los que sería el proceso de inicialización, descompuesto por pasos: Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 22 3.1.7. Inicialización del PIC En la inicialización se consigue preparar el PIC, y en concreto sus puertos para una correcta comunicación con el PC, es decir, se podrán ejecutar las rutinas de envío y recepción del PIC sin problema. La inicialización será, por tanto, necesaria antes de realizar la primera operación de transmisión por el puerto paralelo, tanto si se trata de un envío o una recepción. También será necesaria la inicialización si se han hecho operaciones que hayan podido alterar el estado de los puertos respecto a la configuración necesaria para enviar/recibir correctamente. Esto obliga a que en ocasiones, las operaciones de enviar/recibir deban estar precedidas por una llamada a la función de Inicializar. En el siguiente diagrama de flujo queda patente la que sería la explicación conceptual de su proceso, en el que se tiene PORTB de entrada y PORTA de salida, exceptuando el bit de PC_LISTO, que estará como entrada. Se observa igualmente, que al inicializar, se limpia PORTA (se pone a 0), teniendo en cuenta que esta limpieza no afectará a PC_LISTO, por ser de entrada. Por su parte, PORTB se restaura al valor de DatoPORTB que mantiene el valor que debe tenerse en el puerto B. También es interesante el hecho de que se desactivan las cargas pull-up para PORTB (donde este parámetro procede). La finalidad de esto es que por defecto el puerto B está de entrada y el PC estará de salida, para mandar los comandos o cualquier otra tarea. Es necesario realizar esto, dado que hay tareas externas al protocolo que pudieran activar dichas cargas pull-up. Así, con todo esto se tiene lo que sería el Estado por Defecto o de Reposo del PIC. Finalmente, hay que mencionar que los valores por defecto de los bits de control, aunque son modificables en tiempo de compilación, ocupan los dos bits más significativos del puerto A (PORTA). En concreto, se están usando con la siguiente configuración: ; Posiciones de los bits de control (en PORTA) PIC_LISTO equ 3 PC_LISTO equ 4 Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 23 Como ya se mencionó, PC_LISTO, que proviene del PC, será de entrada y PIC_LISTO, al igual que el resto de bits de PORTA, serán de salida, como es lógico. Por otro lado, aunque es característico del propio puerto paralelo, hay que decir que la señal que se usa de PC_LISTO, en concreto la línea del puerto paralelo usada para ello (se trata de ~STROBE), usa lógica negada, la cual se tiene en cuenta en el protocolo, y es un aspecto muy importante en su implementación y entendimiento del protocolo. Por su parte, la línea del puerto paralelo (se trata de ACK) usada para la señal de PIC_LISTO, usa lógica normal, por lo que se tratamiento, entendimiento e implementación serán más sencillas. Por otro lado, aunque ya se han ido mencionado en las anteriores explicaciones, tenemos dos variables globales. Se trata de DatoPP y DatoPORTB, definidos de la siguiente forma y declarados como globales (global). d_prot udata DatoPP DatoPORTB res 1 res 1 Como ya se ha dicho, DatoPP se usa para tomar el dato a enviar desde el PIC al PC, o bien para recibir el dato enviado desde el PC al PIC (el que recibe el PIC). Y por otro lado, DatoPORTB se encarga de salvar/mantener el valor del puerto B (PORTB), para restaurarlo tras cada transmisión. 3.2. Intérprete Gráfico del PC (Emulador.exe) En este apartado se explican de forma separada los diferentes puntos que forman parte del programa del PC que funciona como intérprete de comandos para enviárselos al PIC, para que éste los ejecute. Esto sería lo que entenderemos por emulador. La parte referente al protocolo del puerto paralelo desde el PC hacia el PIC, como ya se comentó anteriormente por separado, no se tratará aquí, si bien sí forma parte del programa gráfico del PC. 3.2.1. Traducción de Lenguaje Natural A continuación vamos a explicar el proceso que seguimos para la traducción de las instrucciones en lenguaje natural a código ensamblador que pueda entender el PIC. Este proceso se desarrolla en dos pasos. 3.2.1.1. Fase 1: Traducción de Instrucciones Un primer paso es el que se recoge en el fichero TraduceIns.asm. Este consta de dos funciones: RellenaListasIns y RellenaEstructuras. Este primer paso se lleva a cabo automáticamente al ejecutarse el programa. A continuación mostramos sus interfaces y procederemos a explicarlas. RellenaListaIns proc uses edi hInstance: HINSTANCE, hMainHeap: DWORD hInstance: Es el manejador de nuestro fichero ejecutable. hMainHeap: Es el manejador de la pila principal del programa. La dirección de comienzo de la misma. Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 24 RellenaEstructuras proc uses edi esi hHeap:DWORD, pBuffer:DWORD, nSize:DWORD, ArrayOffset:DWORD, pArray:DWORD hHeap: Es el manejador de la pila del programa. La dirección de comienzo de la pila donde almacenaremos nuestras estructuras. pBuffer: Puntero al buffer que contiene la ristra que vamos a tratar y a partir de la cual crearemos la estructura. nSize: Tamaño de dicha ristra. ArrayOffset: Desplazamiento en el vector de colores pArray: Puntero a la Matriz de Sintaxis La función RellenaListasIns lo que hace es crear una matriz de estructuras organizadas por su primera letra, a partir de un fichero con un formato determinado. Cada estructura contiene información acerca de una instrucción en lenguaje natural. Dichas estructuras tienen la siguiente forma: INSTRUCCION struct longitud dd ? instruccion dd ? pos db ? nent db ? nsal db ? color dd ? sig dd ? INSTRUCCION ends ; Longitud de la instruccion para una comparación más rápida ; Nombre de la instruccion en lenguaje natural ; Número que identifica a la instrucción. Para acceder a ella ; Número de parámetros de entrada ; Número de parámetros de salida ; Puntero al Color ; Puntero a la siguiente estructura tipo instruccion El parámetro ‘pos’, contiene un número que, a la hora de la compilación, indicará al procedimiento encargado de enviar el comando al PIC, que comando es el que desea mandarle mediante lo que sería un switch en lenguaje de alto nivel. Los colores que queremos utilizar están almacenados en un vector de colores definido de la siguiente forma: ASMColorArray dd 0h,0805F50h,0FFh,666F00h,44F0h,5F8754h,4 dup(0FF0000h) El parámetro color, es un puntero a una de las posiciones de dicho vector. A continuación mostramos el formato que debe seguir el fichero a partir del cual se cargarán las estructuras. Una cabecera Æ [REGLAS] Una instrucción en cada línea de la siguiente forma: “Identificador” = “Nombre Instrucción” “Posición Instrucción” “Nº Param Entrada” “Nº Param Salida” Nuestro fichero es tiene este aspecto: LeerPuertoA = LeerPuertoA 1 0 1 LeerPuertoB = LeerPuertoB 2 0 1 Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 25 EscribirPuertoB = EscribirPuertoB 3 1 0 LeerTeclado = LeerTeclado 4 0 1 EscribirBus = EscribirBus 5 2 0 LeerBus = LeerBus 6 1 1 LeerNBus = LeerNBus 7 2 1 EscribirNBus = EscribirNBus 8 3 0 EnviarComandoLCD = EnviarComandoLCD 9 1 0 EnviarDatoLCD = EnviarDatoLCD 10 1 0 El proceso seguido para rellenar las estructuras es el siguiente: 1. Definimos los identificadores de nuestras instrucciones y de la cabecera del fichero: ASMSection db "REGLAS",0 I1Clave db "LeerPuertoA",0 I2Clave db "EscribirPuertoB",0 I3Clave db "LeerTeclado",0 I4Clave db "EscribirBus",0 I5Clave db "LeerBus",0 I6Clave db "EscribirNBus",0 I7Clave db "LeerNBus",0 I8Clave db "EnviarComandoLCD",0 I9Clave db "EnviarDatoLCD",0 I10Clave db "LeerPuertoB",0 2. Tomamos las ristras de formato del fichero utilizando la API de Windows GetPrivateProfileString (que se encarga, dada una sección y una palabra clave, de devolver el resto de la ristra asociada a esa palabra clave. Retorna el nº de caracteres del la ristra). Así: invoke GetPrivateProfileString,addr ASMSection,addr I1Clave,…,…,…,… devolvería en el parámetro indicado un puntero a “LeerPuertoA 1 0 1”. 3. Ahora le pasamos a RellenaEstructuras dicha ristra que se encargará de almacenar en una estructura tipo INSTRUCCIÓN como la que mostramos antes. Para rellenar cada estructura se realiza un simple tratamiento de ristras. Los pasos que se seguimos son: 3.1. Leemos la ristra pasada hasta el primer espacio y almacenamos en la estructura INSTRUCCIÓN el nombre de la misma, la longitud y el color. (El color se va asignando por orden en el vector, así a la primera instrucción se le asigna el color 1, a la segundo el 2 y sucesivamente. 3.2. Utilizando el nombre de la instrucción, comprobamos si en la Matriz de Sintaxis, existe ya una instrucción que empiece por dicha letra. Si no es así, pues almacenamos en la matriz un puntero a dicha estructura. En caso afirmativo lo que hacemos es que, el puntero de la matriz que apuntaba a la estructura cuya instrucción empezaba por la misma letra, lo cambiamos a nuestra nueva estructura; y el campo siguiente de nuestra nueva estructura hacemos que apunte a la que acabamos de quitar. Veámoslo gráficamente para una mayor comprensión. Inicialmente vamos a introducir nuestra primera estructura Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 26 LeerPuertoA pos long nent nsal col sig MATRIZ DE SINTAXIS null null null null null null null Al no haber ninguna instrucción en la matriz de sintaxis que empiece por L, la insertamos en la primera posición que encontremos libre. MATRIZ DE SINTAXIS null null null null null null LeerPuertoA pos long nent nsal col sig A continuación introduciremos la siguiente instrucción. LeerPuertoB pos long nent nsal col sig MATRIZ DE SINTAXIS null null null null null null LeerPuertoA pos long nent nsal col sig Al ya existir una estructura cuya instrucción empieza por la letra L el resultado final sería: MATRIZ DE SINTAXIS null null null null null null LeerPuertoB 3.3. pos Long nent nsal col sig LeerPuertoA pos long nent nsal col sig Por último, leyendo de espacio en espacio, rellenamos los campos pos, nent, nsal, que contendrán la posición de la instrucción a la hora de Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 27 traducirla y el número de parámetros de entrada y de salida respectivamente. 3.2.1.2. Fase 2: Compilación de Instrucciones El proceso de compilación, recogido en el fichero Compila.asm se basa en tres funciones con la siguiente interfaz: Compila proc uses edx hWnd:DWORD,hREd:DWORD,hHeap:DWORD hWnd: Manejador de la ventana (Window) hREd: Manejador del cuadro de texto enriquecido (RichEditText) hHeap: Manejador de la pila (Heap) CompilaLinea proc uses esi edi Linea:DWORD Linea: Puntero a la ristra que contiene la instrucción a tratar Ejecutar proc uses edx Comando:DWORD, Entrada:DWORD, Salida:DWORD Comando: Es la posición de la instrucción que indicará el comando a enviar al PIC. Entrada: Puntero al vector de parámetros de Entrada Salida: Puntero al vector de parámetros de Salida. Dicho proceso se fundamenta en 6 pasos: 1. Se lee la primera línea que es la que contendrá las definiciones de las variables a utilizar, y se almacenan estas en memoria en forma de listas de estructuras del tipo VARIABLES. Para ello utilizaremos dos funciones cuyas interfaces detallamos a continuación: VARIABLES struct nombre dd ? valor db ? sig dd NULL VARIABLES ends ;puntero al nombre de la variable ASCII ;puntero a la sig estructura ListaVariables proc uses edi hHeap:DWORD,Buffer:DWORD hHeap: Manejador de la Pila del programa Buffer: Buffer con la ristra de variables a tratar InsertarVariable proc uses edi hHeap:DWORD, VarActual:DWORD, pVarSig:DWORD hHeap: Manejador de la Pila del programa VarActual: Dirección donde esté almacenada la ristra de la variable actual (se pasa por valor, es de entrada) Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 28 pVarSig: Puntero a la variable siguiente (se pasa por puntero, es de entrada/salida) eax: Devuelve puntero a la estructura de la variable El mecanismo para la creación de listas de variables es parecido al que utilizamos para la creación de la lista de instrucciones pero más modulado lo que conlleva una mejor comprensión: 1.1. El primer paso dentro del procedimiento ListaVariables es separar la directiva de variables (var) y la primera variable de la línea, para a continuación comprobar si en efecto coincide con la cabecera de definición de variables (idVar). Todo lo esto se hace con un mero tratamiento de ristras. 1.2. Procedemos a insertar todas las variables en la lista. Para ello nos posicionamos en la siguiente variable y se la pasamos como parámetro a la función InsertarVariable. Esta función creará una estructura del tipo VARIABLES a partir del puntero a la variable actual y devolverá en eax la dirección de la misma. 1.3. Cuando se retorne desde InsertarVariable a ListaVariables se actualizará el campo sig de la variable anterior y se hará que apunte a la nueva estructura creada. 1.4. De este modo se irá creando la lista hasta llegar a la última variable, que estará delimitada por el retorno de carro. 2. Una vez almacenadas las variables a utilizar, se irá compilando línea a línea, es decir, instrucción a instrucción, mediante un bucle que irá leyendo línea por línea y pasándoselo como parámetro a un procedimiento denominado CompilaLinea. 3. Cuando CompilaLinea recibe una instrucción lee primeramente el nombre de la misma y la busca en la Matriz de Sintaxis que habíamos confeccionado anteriormente. Una vez la encuentra, pasa la información que contiene la estructura de la instrucción a variables locales (el número de parámetros de entrada y salida, y la posición de la instrucción). 4. A partir de estos datos, sabiendo que cada parámetro en las instrucciones está separado por el espacio en blanco y que primero aparecen los parámetros de entrada y luego los de salida, los va leyendo uno a uno y almacenándolos en sendos vectores de parámetros de entrada y de salida. El vector de parámetros de salida contiene realmente punteros a las zonas de memoria donde debemos almacenar dichos valores. Veamos como tratamos cada uno: 4.1. Parámetros de Entrada: En los parámetros de entrada pueden ocurrir dos cosas: que sea un valor numérico o que sea una variable. Para diferenciar una de otra, estudiamos el primer carácter del parámetro. Si es un número, lo asumimos como un valor y si es una letra como una variable. En caso de que sea una variable, la buscamos en la lista confeccionada anteriormente, leemos el valor que tiene, y lo almacenamos en el vector de parámetros de entrada. Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 29 4.2. Parámetros de Salida: Una vez leída la variable de la que se trata, copiamos al vector de parámetros de salida la dirección de donde se va a almacenar el valor de esa variable. 5. Es en este momento, una vez ya obtenidos los parámetros necesarios, cuando llamamos al procedimiento Ejecutar pasándole como parámetros la dirección de los vectores de entrada y salida y la posición de la instrucción a ejecutar. Este, mediante una estructura selectiva mandará al PIC la instrucción correspondiente utilizando las funciones de Enviar y Recibir definidas en Protocolo.asm. 6. Una vez interpretadas todas las líneas acabaría el proceso de compilación. 3.2.2. Manejo del Programa Gráfico En este apartado lo que se intenta conseguir es una descripción apta desde el punto de vista de usuario sobre el manejo de El Emulador. Pues bien, brevemente detallaremos de que consta el entorno gráfico de este programa. En primer lugar tenemos una barra de tareas como la que se muestra en la siguiente figura. Como se puede comprobar a parte del típico botón de ayuda, tenemos cinco menús con lo que podremos interactuar de manera sencilla. El primer menú que aparece es el de Archivo, el cual es común a todos los programas de entorno Windows, y cuyas características principales son: Nuevo. Creación de un nuevo archivo. Abrir. Apertura de un archivo creado con anterioridad. Guardar/ Guardad como… Permite salvar un archivo modificado o recién creado. Imprimir. Impresión del archivo actual. Salir. Salir de la ejecución del programa. En el siguiente menú nos encontramos con las típicas operaciones que podemos hacer con el fichero actualmente cargado, en lo que se refiere a la edición como copiar, cortar, pegar, así como la búsqueda de palabras claves, etc. Y que como es obvio no vamos a detallar de una forma más amplia. Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 30 Este sin duda es el menú más importante, y en consecuencia el que más nos atañe, Proyecto. Pues bien, este menú consta de: Compilar: realiza la compilación del archivo actual para conseguir el código hexadecimal que se cargara en el pic, para su ejecución. Esto se consigue gracias a la utilización del fichero compila.asm. Este archivo toma las variables declaradas (ya se dirá luego como se hace) y ejecuta cada linea que contiene comandos válidos, asociando cada variable a su valor. Compilar linea: su tarea es la misma que la de compilar, el único detalle es que sólo complia la línea seleccionada. Esta opción da mucha libertad a la hora de compilar trozos de código, y hacer una especie de depuración manual del archivo. Enviar Dato: su objetivo primordial es la comunicación del PC con el microcontrolador, de tal forma que se envia un valor enterto que se mostrará en el puerto B del PIC. Esta opción es una instrucción básica. Recibir Dato: realiza la misma labor que la opción enviar dato, pero en sentido opuesto. Ahora es el PIC quien envia el dato y el PC quien lo recibe. Una vez obtenido el dato se muestra una caja de texto, con el valor leido del puerto paralelo. Testear Puerto: esta función lo que hace es comprobar si la comunicación del PC y el microcontrolador a través del puerto paralelo es válida, dicho de otro modo, si el puerto esta listo para recibir y enviar datos. A continuación mostraremos la implementación del código referente a este apartado y cuyo código se encuentra en protocolo.asm. TestPuerto proc uses eax ecx edx ;----------------------------------------------; Bucle de testeo para el puerto Paralelo ;----------------------------------------------- Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 31 ; Direcciones standard del puerto paralelo: ; Dato --> 378h ; Estado --> 379h ; Control --> 37ah ;----------------------------------------------mov cx,1 ; Valores por defecto para puerto (dx) y dato (al) mov dx, RegDato mov dx, RegEstado mov dx, RegControl mov al,20h ; 20h activaria C5 en RegControl .while cx==1 out dx,al in al,dx .endw ret TestPuerto endp Por último quedan dos menús que no tienes demasiada importancia, como lo son el menú ver y el menú opciones. Pues bien, una vez explicados cada menú y señalado donde se puede realizar cada operación, lo siguiente que vamos a explicar será la creación de programas emulados, que nuestro programa será capaz de interpretar y compilar, para su ejecución emulada desde el PIC. 3.2.2.1. Creación de Programas emulados Para la creación de programas emulados debemos tener claros los siguientes aspectos: Declaración de variables. Las variables no son más que un nombre y un valor asociado. El nombre sirve para que al analizar el texto podamos distinguirla. Las variables se declaran con la palabra clave var <espacio>, a continuación se declaran las variables que vamos a emplear separadas por espacios. Solo se diferencian de los números por ese hecho (que son números), ya que su implementación es igual. EJEMPLO: se usa de la siguiente forma: se escribe en la primera línea la definición de variables: var a b c Parámetros de las instrucciones. Las instrucciones tienen sus propios parámetros, y estos van separados por espacios. Las instrucciones que se pueden implementar son las siguientes: INS PosInstr NºParamEnt NºParamSal [REGLAS] Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 32 LeerPuertoA= LeerPuertoA 0 0 1 LeerPuertoB= LeerPuertoB 1 0 1 EscribirPuertoA= EscribirPuertoA 2 1 0 EscribirPuertoB= EscribirPuertoB 3 1 0 LeerTeclado= LeerTeclado 4 0 1 EscribirBus= EscribirBus 5 2 0 LeerBus= LeerBus 6 1 1 EscribirNBus= EscribirNBus 7 3 0 LeerNBus= LeerNBus 8 2 1 EnviarDatoLCD= EnviarDatoLCD 9 1 0 EnviarComandoLCD= EnviarComandoLCD 10 1 0 Mostrar= Mostrar 11 1 0 Asignar= Asignar 12 1 1 ; Comandos del LCD; se deben ejecutar EnviarComandoLCD [COMANDOSLCD] LimpiarDisplay= LimpiarDisplay 0 0 0 IrAInicio= IrAInicio 1 0 0 Modo= Modo 2 2 0 DisplayOnOFF= DisplayOnOFF 3 3 0 Desplazamiento= Desplazamiento 4 2 0 FunctionSet= FunctionSet 5 3 0 DireccionCGRAM= DireccionCGRAM 6 1 0 DireccionDDRAM= DireccionDDRAM 7 1 0 Estado= Estado 8 0 2 EscribirRAM= 9 1 0 LeerRAM= 10 0 1 siempre y obligatoriamente después de En cada línea sólo se podrá especificar un comando. Un ejemplo de esta implementación sería: var a ; se declara la variable a (se inicializan siempre a 0) LeerPuertoB a ; el valor devuelto se asigna a la variable a (parámetro de salida) Mostrar a ; toma el valor de la variable a (parámetro de entrada) y lo muestro en messageBox (si no está implementada hay que implementarla, me refiero a la función Mostar, que sería Mostrar 1 0 en el fichero de sintaxis, pues tiene 1 parametro entrada y 0 parametro salida) Otros ejemplos serían: Ejemplo 1: var a LeerPuertoA a Mostrar a LeerPuertoB a Mostrar a LeerTeclado a Mostrar a LeerBus 112 a Mostrar a Ejemplo 2: Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 33 var a LeerTeclado a EnviarDatoLCD "Ha pulsado: " EnviarDatoLCD a LeerTeclado a EnviarComandoLCD 1 EnviarDatoLCD "Ha pulsado: " EnviarDatoLCD a 3.3. Programa de Emulación de Instrucciones del PIC El programa de emulación de instrucciones del PIC es una versión mejorada y adaptada al protocolo de puerto paralelo del programa desarrollado para el mismo fin por el grupo que hizo una práctica similar en años previos. Como se ha dicho las tareas fundamentales han sido la adaptación a una filosofía de comunicación paralela, que conlleva el hecho del uso de más líneas, en concreto todo el puerto B y dos líneas de control de puerto A. Esto habrá que tenerlo en consideración al realizar la implementación de este programa, por lo que habrá que adaptarlo. Igualmente, se ha aprovechado para hacer que el programa de emulación sea más compacto, es decir, conseguir que ocupe menos en la memoria del PIC, y que se ejecute más rápido. Para empezar, el programa recibe el comando a ejecutar y bifurca a una zona encargada de procesarlo. A continuación se puede ver como la bifurcación se implementa relativa al PCL. En cuanto ha esto hay que mencionar el hecho de que se tiene que tener cuidado con el valor de el registro de control de página (PCLATH). Este valor es modificado por la función de conversión de las teclas del teclado. Por ello se salva el valor de PCLATH para luego restaurarlo y evitar problemas. LeerComando call Inicializar call Recibir ; Recibir Comando movfw DatoPP addwf PCL goto leerA ; 0 --> leer del puerto A goto leerB ; 1 --> leer del puerto B goto escribirA ; 2 --> escribir en puerto A goto escribirB ; 3 --> escribir en puerto B goto leerteclado ; 4 --> leer teclado hexadecimal goto escribir_1_byte ; 5 --> escribir un solo byte en bus I2C goto leer_1_byte ; 6 --> leer un solo byte del bus I2C goto escribir_n_bytes ; 7 --> escribir "n" bytes en bus I2C goto leer_n_bytes ; 8 --> leer "n" bytes del bus I2C goto env_dato_lcd ; 9 --> enviar dato al LCD goto env_com_lcd ; 10 --> enviar comando al LCD goto terror ; ERROR --> si no es ningún comando A continuación, se muestran explicaciones breves de cada uno de los comandos que procesa el PIC. 0. Leer Puerto A. Simplemente se pone PORTA de entrada, se lee su valor y se envía al PC. Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 34 1. Leer Puerto B. Se pone PORTB como salida y se le asigna el valor del mismo, almacenado en DatoPORTB. Una vez hecho esto se toma su valor y se envía al PC. 2. Escribir Puerto A. Simplemente se recibe del PC el dato a escribir, se asegura que PORTA esté de salida y se escribe en PORTA. 3. Escribir Puerto B. Simplemente se recibe del PC el dato a escribir, se asegura que PORTB esté de salida y se escribe en PORTB, y a su vez se salva su valor en DatoPORTB para que no se pierda tras el uso del protocolo del puerto paralelo. 4. Leer Teclado. Para la lectura de teclas de teclado, para lo cual se usa la librería tecla.inc, se hace uso de la rutina Key_Scan, que detecta la tecla que haya sido pulsada; en caso de no haberse pulsado ninguna tecla devuelve el código 0x80. De este modo en le rutina del comando LeerTeclado se hace un bucle en el que se llama a Key_Scan hasta que devuelva una tecla pulsada (un código distinto de 0x80). Una vez se tiene el código de la tecla pulsada se convierte a ASCII con la rutina (también de la librería tecla.inc) Cods_Tecla. Esta rutina usa tablas referenciadas por el registro PCL y se hace uso de la directiva de precompilación PAGESEL, de forma que hay una posible modificación del registro indicador de la página actual (PCLATH). Este hecho obliga a salvar el valor de PCLATH antes de la llamada a Cods_Tecla y restaurar su valor después de la llamada. Una vez se tiene el valor ASCII de la tecla pulsada, que ocupa 8 bits, es decir, su tamaño es el mismo que el número de líneas de PORTB. Esto permite que no se requiera ningún tratamiento especial a la hora de enviarlo. Así, se envía al PC, tras Inicializar antes, para reconfigurar el estado de los puertos. 5. Escribir 1 byte en el Bus I2C. Para este comando se emplea la librería i2c.inc. En este comando en concreto se usa la función ENVIAR_1, que envía/escribe un dato en una dirección del bus I2C. De este modo, se recibe del PC el dato y la dirección, y luego se envía al bus I2C, proceso tan simple como llamar a ENVIAR_1 tras haber configurado bien los parámetros de entrada DATO (con el dato recibido) y DIRECCION (con la dirección recibida). 6. Leer 1 byte en el Bus I2C. Para este comando se emplea la librería i2c.inc. En este comando en concreto se usa la función RECIBIR_1, que recibe/lee un dato en una dirección del bus I2C. De este modo, se recibe del PC la dirección, y luego se recibe del bus I2C el dato presente en ella, proceso tan simple como llamar a RECIBIR _1 tras haber configurado bien el parámetro de entrada DIRECCION (con la dirección recibida). Finalmente, el dato leído se envía al PC. 7. Escribir N byte en el Bus I2C. Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 35 Para este comando se emplea la librería i2c.inc. En este comando en concreto se usa la función ENVIAR, que envía/escribe un determinado número de datos a partir de una dirección del bus I2C. De este modo, se recibe del PC el número de bytes a escribir y la dirección de partida. El siguiente paso consiste en un sencillo bucle que recibe del PC el dato a escribir en el bus I2C (tras Inicializar, para reconfigurar los puertos para la transmisión por el puerto paralelo) y lo envía/escribe en el bus I2C con la función ENVIAR. Además, en el bucle se controlará que se envíen tantos datos como se hayan indicado, tarea que también es controlada por la función ENVIAR, pues con la variable CONT_BYTES sabe cuantos bytes quedan por enviarse al bus I2C (funciona como variable global de estado del bus I2C). Por otro lado se siguen usando DATO y DIRECCION, como con ENVIAR_1. 8. Leer N byte en el Bus I2C. Para este comando se emplea la librería i2c.inc. En este comando en concreto se usa la función RECIBIR, que recibe/lee un determinado número de datos a partir de una dirección del bus I2C. De este modo, se recibe del PC el número de bytes a leer y la dirección de partida. El siguiente paso consiste en un sencillo bucle que se llama a RECIBIR para leer el dato del bus I2C y luego se envía al PC (tras Inicializar, para reconfigurar los puertos para la transmisión por el puerto paralelo). Además, en el bucle se controlará que se envíen tantos datos como se hayan indicado, tarea que también es controlada por la función RECIBIR, pues con la variable CONT_BYTES sabe cuantos bytes quedan por recibirse al bus I2C (funciona como variable global de estado del bus I2C). Por otro lado se siguen usando DATO y DIRECCION, como con RECIBIR_1. 9. Enviar Dato LCD. En este caso la librería empleada es lcd.inc. La filosofía de su uso consiste en entrar en un bucle de envío de datos al LCD, usándose el valor 0 como indicador del final del envío de datos al LCD, momento en el que se abandonará la rutina de env_dato_lcd. De este modo, en el bucle se empieza por recibir el dato del PC para mandarlo al LCD. A continuación se llama a la función DISPLAY_CONF, que inicia el LCD configurándolo correctamente para que no se vea el cursor, tenga autoincremento; en cuanto al foco se mantendrá el anterior, entendiendo por ello la última posición en que estaba, normalmente en la primera línea del LCD. Así, a continuación se comprueba que el dato recibido no sea un 0, en cuyo caso se abandona la rutina, y se manda el dato al LCD con la función LCD_DATO, poniendo el dato a enviarle en el acumulador W previamente, como parámetro de entrada. 10. Enviar Comando LCD. Como en la rutina previa, se usa la librería lcd.inc. En este caso la rutina es mucho más sencilla y bastará con empezar recibiendo del PC el comando a mandar al LCD y posteriormente llamando a la función LCD_REG poniendo en el acumulador W (como parámetro de entrada) el comando que ejecutará en el LCD. Finalmente, en caso de que no sea un comando válido, fenómeno que no deberá ocurrir porque el PC conoce los comandos posibles y no provocará el error, se bifurca a una rutina de Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 36 error en la que el PIC queda parado. Podría intentarse un diagnóstico de errores, e incluso recuperarse, pero en principio puede ser una tarea compleja, pues es necesario obtener un estado de reposo en PIC y PC, “matando” las tareas de envío/recepción tanto en el PIC como en el PC. 3.4. Rutinas de Retardo para el PIC Para facilitar la tarea de temporizaciones exactas en el PIC se ha desarrollado una librería para las mismas. Las rutinas de temporización que podrán usarse son las siguientes: 1. Retardo de 1 bucle. Se puede crear un retardo de 6 a 768 microsegundos. Se controla con la variable global N1, que obedece a la siguiente ecuación: 3*(N1+1) + 2 = X microsegundos. Hay que añadir que en esa ecuación se tienen en cuenta los 2 microsegundos que se pierden en la asignación de un valor a N1. 2. Retardo de 2 bucles. Se puede crear un retardo de 16 a 197628 microsegundos. Se controla con las variables globales N1 y N2, que obedece a la siguiente ecuación: 3*N2*(N1+3) + N2 + 3 + 4 = X microsegundos. Hay que añadir que en esa ecuación se tienen en cuenta los 4 microsegundos que se pierden en la asignación de un valor a N1 y a N2. 3. Retardo de 3 bucles. Se puede crear un retardo de 26 a 50396928 microsegundos. Se controla con las variables globales N1, N2 y N3 que obedece a la siguiente ecuación: 3*N3*N2*N1 + 10*N3*(N2+1) + 3 + 6 = X microsegundos. Hay que añadir que en esa ecuación se tienen en cuenta los 6 microsegundos que se pierden en la asignación de un valor a N1, N2 y a N3. 4. Retardo de 1 milisegundo. Genera directamente un retardo de 1 milisegundo (RetardoMS). 5. Retardo de 1 centésima de segundo. Genera directamente un retardo de 1 centésima de segundo (RetardoCS). 6. Retardo de 1 décima de segundo. Genera directamente un retardo de 1 décima de segundo (RetardoDS). 7. Retardo de 1 segundo. Genera directamente un retardo de 1 segundo (RetardoS). 3.5. Fichero Cabecera para el PIC Por fichero cabecera para el PIC se entiende el fichero creado bajo el nombre de InstrPIC16F84.h. Este fichero se ha creado para que sea el único que deba incluirse al crear ficheros para programas del PIC16F84. Se basa en el fichero Instr.h ya creado para la Práctica 1, Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 37 por lo que no será necesario comentarlo a fondo, sino que a continuación se enumeran grosso modo sus utilidades fundamentales: 1. Inclusión de fichero de macros Standard del PIC16F84. Equivale simplemente a las típicas instrucciones de: processor PIC16F84 #include <p16f84.inc> 2. Inhibición de Mensajes y Avisos. Se trata de que se puede escribir el código con las tabulaciones deseadas sin que el compilador muestre sus avisos. Se usa la cláusula errorlevel. 3. Directivas y Macros para control de Entrada/Salida de los Puertos. Se resume a las directivas Sal y Ent, y la macro Mod_Tris. 4. Macros para Relojes. Se trata de las macros para detener e inicializar el reloj del PIC (timer). 5. Operaciones Aritméticas, Manejo de Registros y Control de Flujo. Se trata de compactar las operaciones aritméticas y de manejo de registros para poder hacer de registro a registro, en lugar de referidas al acumulador W. Se implementan con macros. Igualmente se tiene instrucciones típicas de control de flujo a modo de condiciones de un if condición then. 6. Salvado y Restauración de Registros. Se trata de la emulación típica de instrucciones tipo PUSH y POP; en este caso salvan el acumulador y registro de estado. Finalmente, hay que decir, que bastará con poner en nuestros códigos la inclusión de este fichero cabecera, de la siguiente forma: #include "Lib\InstrPIC16F84.h" 3.6. Directivas especiales de Ensamblador MASM32 Al usar en lenguaje ensamblador MASM32 (Microsoft® Macro Assambler de 32 bits), específico para entorno gráfico de Win32 en máquinas con plataformas Windows® que funcionarán con modo protegido de ejecución de los programas. El concepto de modo protegido de ejecución se tendrán programas que al ejecutarse sólo podrán ejecutar instrucciones de propósito general, mientras que el set de instrucciones privilegiadas no podrán ejecutarlas, pues los programas se ejecutan en modo usuario y es necesario que primero pasen a modo protegido para poder hacerlo. De forma general, no es necesario pasar al modo protegido, de hecho no tenemos tal derecho. Sin embargo, podremos usar el numeroso número de funciones de la API de Windows® que permiten el acceso a numerosos recursos, cuyo acceso real se hará de forma controlada por el Sistema Operativo. Así, se tendrán que usar directivas especiales para indicar el tipo de programa que diseñaremos, pues en ocasiones podemos incluso crear programas para ejecución en modo Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 38 privilegiado. Por lo general podemos definir el modo de direccionamiento y demás características del modo de ejecución de nuestro programa que se explican brevemente a continuación, de forma categorizada. Igualmente, con MASM32 tenemos la posibilidad del uso del típico precompilador (que no se explicará por ser ampliamente conocido), pero además de una herramienta superior a las macros, que permiten el uso de sentencias de control de flujo, cuyo funcionamiento y traducción a ensamblador puro es inmediata. Mientras las macros sustituyen código en una zona contigua, estas directivas permitirán situar código en puntos estratégicos, como se explicará más adelante. Otro aspecto importante que se permite y comentará a continuación es la posibilidad de llamar a función con paso de parámetros, con equilibrado automático de la pila del programa. 1. Directivas de Tipo de Programa. Todo programa en ensamblador para entorno Win32 debe tener una estructura como la siguiente: .386 .model flat,stdcall option casemap:none El significado de las directives anteriores sería el siguiente: 1.1. Tipo de lenguaje o equipo. Se trata de definir el tipo de máquina que usaremos, que por lo tanto tendrá un lenguaje ensamblador similar al de todos los Pentium y 80x86, pero de una versión a otra aparecen nuevas instrucciones y diferencias en el funcionamiento del procesador y el modo en que la máquina ejecuta los programas, desde la pila hasta el procesador, pues ciertas tareas pueden realizarse de forma distinta. El tipo de máquina más común cuando se programa en entorno gráfico Win32 es el .386, ya que permite usar entorno gráfico de Windows con compatibilidad para prácticamente todos los sistemas. 1.2. Modelo de Direccionamiento y Paso de Parámetros. Con la directiva .model se indica el tipo de direccionamiento de memoria. Prácticamente siempre se usa el modo flat (modo plano). En este modo se puede direccional toda la memoria directamente, desaparece el concepto de segmento, de modo que podemos direccional los 4 Gb posibles de memoria, con los 32 bits de los registros. Por otro lado, en cuanto al paso de parámetros se puede indicar el modo en que se hará en las llamadas a las rutinas que se creen. Esto define el sentido de paso de los mismos y quién se encargará de compensar la pila, bien el programa llamado o el llamador. El modo típico es el stdcall, que sería el modo Standard de llamada, en el que se llena la pila con los parámetros ordenados desde el último al primero (de derecha a izquierda) y es el llamado el encargado de compensar la pila tras la llamada, en el retorno de la subrutina. En Win32 siempre se usa el modo stdcall, si bien hay una excepción con la función wsprintf(), que es conocida por admitir cualquier número de parámetros, y por ello se requiere el convenio inverso en el paso de parámetros. 1.3. Opciones para MASM. Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 39 Finalmente, con la directiva option, se puede indicar al MASM que compila o funciones en distintos modos. Así, por ejemplo, con option casemap:none, se le indica que las etiquetas sean sensibles a mayúsculas y minúsculas. 2. Directivas de Control de Flujo. Las directivas de control de flujo son herramientas propias de lenguajes de alto nivel, pero en el lenguaje ensamblador MASM se dispone de ellas. Sin embargo, esto no significa que estemos usando un lenguaje de alto nivel, pues de hecho no hay optimización de código (más que la que muchos compiladores ya usan, en casos muy concretos). De hecho, las directivas de control de flujo usadas con MASM funcionan como si fueran unas macros más potentes, que ponen el código necesario no sólo en la zona donde se escribe la directiva, sino en otras zonas no contiguas, pero muy evidentes. De este modo, aunque se usan estas directivas de control de flujo, siempre sabemos en que instrucciones se traduce nuestro programa; además, esto es fundamental, pues el código final será ensamblador puro y de no saber en que se traducen dichas directivas no sabremos depurar, ante la incapacidad de entender el código, pero como ya se ha mencionado será muy sencillo. A continuación se muestra la conversión que se hace de las directivas de control de flujo a ensamblador puro, para cada una de ellas. 2.1. Si Condicional. Una estructura condicional tipo .if puede admitir, en su forma más compleja, la siguiente forma: .if condiciónIF1 ;códigoIF1 .elseif condiciónIF2 ;códigoIF2 .else ;códigoIELSE .endif En primer lugar hay que tener en cuenta que las condiciones que pueden tenerse serán de muy diversos tipos y por ello se traducirán de distintas formas. Por lo general se tiene una comparación acompañada de un salto condicional en función del tipo de comparación. A continuación se muestra una serie de equivalencias que cubren la mayoría de los casos: eax==1 eax>1 !eax eax!=0 eax eax==0 se traduce como se traduce como se traduce como se traduce como se traduce como se traduce como cmp eax,1 cmp eax,1 or eax,eax or eax,eax or eax,eax or eax,eax Se observa en primer lugar que las condiciones eax y eax==0 son equivalentes (al igual que las versiones negadas). Así, las comparaciones que tienen un 0, por lo general, no requieren el operador cmp. Por otro lado, para tratar el dominio que deja a un lado u otro una comparación, se usarán los saltos, y dependerá su uso de la función que se pretenda hacer, es decir, a veces interesa usar para saltar y otras para no saltar. En los anteriores casos bastará usar jz y jnz, además jbe para la comparación eax>1. Volviendo con la sentencia .if, su traducción a ensamblador puro será: condiciónIF1 Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 40 jxx ELSEIF1 ;códigoIF1 jmp finIF ELSEIF1: condiciónIF2 jxx ELSE ;códigoIF2 jmp finIF ELSE: ;códigoIELSE finIF: Este esquema se repetirá tantas veces como sea necesario en función del número de sentencias de tipo .elseif. 2.2. Bucles While. El formato tipo de una sentencia con .while sería: .while condiciónWHILE ; código .break .break .if condiciónBREAK .endw Suponiendo una condición normal, el compilador traduce la comparación. Por ejemplo, para eax != 0 simplemente haría falta hacer: or eax,eax jnz seguir Como se observa el compilador hará la mejor traducción de la comparación posible haciendo uso de una operación aritmética y un salto condicional en la mayoría de los casos. Para la implementación en ensamblador puro del bucle del ejemplo tendríamos un código como el siguiente (hecho con etiquetas para darle mayor comprensibilidad): jmp fin inicio: ; código jmp salir condiciónBREAK jxx salir fin: salir: condiciónWHILE jxx inicio A parte del hecho de que los saltos de las condiciones dependerán de la misma, se observa que el bucle comienza con un característico salto a una posición final en la que está la condición, para evaluarla la primera vez. Además, luego se comprobará directamente al llegarse al final del bucle. Si se cumple la condición del bucle se va a la primera instrucción del bucle. En cuanto a los puntos de ruptura (.break) interiores al bucle, si se llega a ellos se salta directamente fuera del bucle. Si son puntos de ruptura condicionales (.break .if condiciónBREAK) primero se evalúa la condición y luego se saldrá del bucle si procede. En el caso de bucles con condiciones para bucles infinitos, del estilo: Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 41 .while TRUE ; código .endw El compilador identifica que es un bucle infinito y hace la traducción sin condición, de forma que en el caso anterior tenemos: inicio: salir: ; código jmp inicio Como es lógico, habrá puntos de ruptura, para salir de este tipo de bucle. 2.3. Bucles Repeat. De forma similar a los bucles .while, en el caso de los bucles .repeat , su forma será: .repeat ; código .break .break .if condiciónBREAK .until condiciónREPEAT En este caso, no será necesario explicar las sentencias de punto de ruptura, ya mencionadas anteriormente. En el caso de los bucles .repeat, siempre se entra una vez, por lo que no hay que evaluar la condición por primera vez. Además, la evaluación de la condición tiene el sentido contrario al que tiene en los bucles .while. Esto significa que si la condición no se cumple se sigue iterando, pero si sí se cumple se saldrá. Así, el código ensamblador puro equivalente será: inicio: ; código jmp salir condiciónBREAK jxx salir salir: condiciónREPEAT jxx inicio 3. Funciones con paso de parámetros. Cuando usamos funciones en MASM32 se llamarán de la siguiente forma, como alternativa más cómoda al call: invoke funcion, parametro1, parametro2, …,parametroN A la hora de llamar a la función, los parámetros se meterán directamente en la pila, de derecha a izquierda (según el modelo stdcall), y se autocompensará. De este modo el equivalente ensamblador será el siguiente, en la llamada anterior: push parametroN ;[…] push parametro2 Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 42 push parametro1 call funcion 4. Directivas especiales para incluir ficheros y Modulación del código. Con el ensamblador MASM32 se facilita la inclusión de librerías del sistema, con numerosas APIs, que podrán usarse directamente. Para incluirlas se puede usar la directiva includelib (en lugar del include para ficheros). Esta directiva hace que sólo se carguen aquellas librerías de APIs que sean necesarias, y la optimización podría ampliarse hasta el punto de que sólo se carguen las APIs que use nuestro programe, además del hecho de poder usarlas cargadas en memoria como recurso común para múltiples procesos. Por otro lado, a la hora de modular el código de nuestros programas, podemos usar la directiva externdef, con la gran potencialidad de funcionar a la vez como el extern y global conocidos de otros lenguajes de ensamblador. El compilador es capaz de reconocer automáticamente si externdef debe traducirse por extern (si la variable no es declarada en el fichero) o por global (si la variable sí es declarada en el fichero). Finalmente, las funciones se exportando mostrando al exterior su prototipo, cuya forma de declararlos es: funcion PROTO :tipoParametro1, :tipoParametro2, …, :tipoParametroN 5. Secciones del programa. Cuando se crean programas de ensamblador para entornos de Win32, tendremos las siguientes posibilidades de secciones de código: .const Æ Declaración de constantes .data? Æ Contiene variables sin inicializar, lo cual es interesante, pues no es más que una reserva para el momento de ejecución, por lo que nuestro fichero ejecutable no crecerá en tamaño. .data Æ Contiene variable inicializadas .code Æ Contiene el código de programa Finalmente, cuando el programa que se genere sea el principal, se deberá poner una etiqueta de comienzo y la típica directiva end con la etiqueta de comienzo: inicio: ; código end inicio Por otro lado, cuando el programa usa entorno gráfico hay que crear el manejador de la ventana, la rutina que la crea, habitualmente llamada WinMain, y la rutina de control y procesamiento de mensajes, habitualmente llamada WinProc. Esta última rutina es la que implementa el código de las acciones para servir a los mensajes que nos mande Windows, tras detectar que se han producido y son de nuestro programa (nuestra ventana activa). 6. Otras Directivas más comunes. Finalmente, se puede hacer uso de útiles directivas como: Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 43 uses registro1 registro2 … registroN Æ Se encarga de salvar y restaurar automáticamente (con instrucciones push y pop) los registros indicados. Se indica en la declaración de la interfaz de los procedimientos (no en el prototipado). addr Variable Æ Toma la dirección de la Variable. Su ventaja es que permite tomar direcciones de variables locales (LOCAL Variable, que se declaran dentro de los procedimientos y que se crean en tiempo de ejecución, almacenándose en la pila de ejecución del programa), lo cual se traduce en el uso de la operación lea. Pero además funcionará incluso si no es una variable local, sino global. Sin embargo, no pueden usarse en operaciones con registros, pues habitualmente se emplean para el paso de parámetros. En caso de querer tomar direcciones con instrucciones de registros podemos usar lea directamente, o bien, si son variables globales, podemos emplear la directiva offset, que si recordamos que estamos en el modelo plano (flat) tomará toda la dirección de 32 bits, pues no hay segmentos, ya que todo el espacio de memoria es visible. 3.7. Interrelación entre Programas A continuación se muestran sendos diagramas en los que se puede ver claramente la interrelación que existe entre los distintos módulos de programas desarrolladas, tanto para la parte del PC, como para la parte del PIC, tratadas seguidamente por separado. 3.7.1. Programa del PC En el caso del PC, el programa usado para crear los códigos y compilarlos es el RadAsm, a la vez que se usa el OllyDbg para la depuración, de los que se tiene más información en la bibliografía. Al hacer programas gráficos se generan numerosos ficheros que no son de especial interés en el desarrollo, si bien son necesarios para el correcto funcionamiento del programa. En el siguiente diagrama se muestran los programas con código significativo que se han desarrollado, es decir, aquéllos en los que se han implementados aspectos importantes del programa desarrollado. 3.7.2. Programa del PIC Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 44 En el caso del PIC, se ha usado el conocido compilador y depurador (entorno IDE completo) denominado MPLAB. En este caso se muestra un diagrama con los ficheros desarrollados en ensamblador PIC, así como las librerías creadas. Hay que indicar, que aunque no se muestre en el diagrama, existirán los correspondientes ficheros fuentes para las librerías creadas. De este modo, el diagrama resultante es el siguiente. 3.8. Ejecución emulada en Proteus® de la Placa PIC-Trainer Los programas desarrollados para el PIC pueden ser emulados con ayuda del emulador de circuitos electrónicos, llamado Proteus®. En este emulador, el primer paso será construir un diseño similar al de la placa PIC-Trainer y PIC-Trainer Plus de la que se dispone físicamente en los laboratorios. A continuación puede verse el diseño equivalente realizado, cuya construcción se fundamenta en la teoría electrónica y los propios esquemáticos de las placas del laboratorio, que se puede consultar en la memoria de la Práctica 1. Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 45 En el diseño se observa que está un PIC16F84 que se conecta con conmutadores para emular el puerto A y el puerto B. A su vez, ambos disponen de displays de 7 segmentos, con la finalidad de ver el estado de ambos puertos. En el caso del puerto A se observa que la línea de PC_LISTO viene indicada, pues equivaldrá a la señal que viene del PC, la cual usaremos como control. Por otro lado, la línea de PIC_LISTO, no tiene conmutador de entrada, pues es la que se usa para el control por parte del PIC hacia el PC. Por otro lado, se pueden ver un LCD virtual, con un funcionamiento idéntico al de las placas del laboratorio, con la única diferencia de que funciona pasivamente, pues no se despierta de los bits BUSY en la rutina LCD_INI. Igualmente tenemos un teclado hexadecimal que devuelve los mismos valores que el de la placa PIC-Trainer Plus; su único problema es la orientación, pero para darle más comprensibilidad, se muestran los números y letras, de valores hexadecimales, para que sea equivalente al de las placas del laboratorio. El PIC16F84 de la placa emulada tendrá cargado el mismo programa que cargaremos en la placa real. De este modo, el control del PC lo haremos manualmente con la línea del puerto A indicada a tal efecto. Así, los pasos a seguir serán tan sencillos como poner el código de un comando y usar el bit de PC_LISTO para que se realice la recepción de dicho comando a emular. Con ello el programa bifurca a la zona que lo tratará. Conociendo su código podremos saber que datos recibe o envía el PIC al PC, para ese comando, y de ese modo podremos ver claramente como funciona todo perfectamente. Como ejemplo sencillo se puede usar el comando de escritura de datos en el LCD (comando 9, que en binario es 1001). Una vez se bifurque al comando bastará emular envíos del PC al PIC de cada dato a escribir en el LCD, como puede ser el “0” (en hexadecimal 0x30). Para terminar el subprograma de emulación del comando de escribir datos en el LCD, bastará mandar un valor 0 en binario, en cuyo caso se volverá a esperar por un comando nuevo. En la siguiente captura puede verse el estado del LCD tras recibir el “0”: Así, se podrán seguir escribiendo más caracteres en el LCD, y cuando se mande un 0 binario (equivalente a la terminación de ristra), se terminará la transmisión de datos al LCD, el cual mantendrá los datos recibidos. Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 46 En este caso se muestra sólo una sección de la placa emulada, y es que hay que entenderla como una placa que consta de varias secciones. Para terminar falta comentar como se ha realizado la emulación del bus I2C. Aunque se dispone de elementos con protocolo I2C en Proteus®, su funcionamiento parece ser pasivo y no se ajusta a nuestras necesidades. Por ello, se emula poniendo dos conmutadores que funcionan en modo pull-up. Para su uso es necesario usarlos en modo depuración, para mandar las señales oportunas en el momento adecuado según las especificaciones de respuesta de los elementos esclavos conectados al bus, durante una operación de envío o recepción del bus I2C. Estos conectores son los siguientes: Presentes en la zona derecha de esta sección de la placa emulada: Con esto se recogen todas las posibilidades de la placa real PIC-Trainer y PIC-Trainer Plus, si bien, falta una semántica de uso para el bus I2C, al que se pueden conectar multitud de elementos que realizarían numerosas tareas. Una posible mejor implementación emulada del bus I2C en Proteus® podría realizarse con varias PIC, funcionando uno como maestro y el resto como esclavos. Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 47 4. Conclusiones En este apartado se tratarán por separado las conclusiones y problemas que se han tenido en las diferentes secciones en las que se divide el trabajo realizado, y que se muestran en los siguientes apartado. Igualmente se muestran las posibles mejoras y ampliaciones que se pueden realizar sobre el trabajo realizado. 4.1. Protocolo del Puerto Paralelo En la realización del protocolo del puerto paralelo, se puede empezar mencionando el hecho, en cuanto al aspecto hardware, que a la hora de conectar los cables del puerto paralelo con el zócalo de conexión con la placa, la soldadura resulta una opción compleja. En su lugar se optó por una solución menos ortodoxa pero eficiente, que consiste en el uso de las patillas de los zócalos para conectarlas a los cables por su parte superior y por su parte inferior al zócalo. Este sistema permite la facilidad de poder cambiar el conexionado con el zócalo, lo que hace que tengamos un conexionado mucho más flexible. Sin embargo, es menos robusto, aunque esto dependerá del mecanismo de conexión de los cables con la parte superior de las patillas. Para ello existen tres posibilidades: 1. Pegamento Æ Puede provocar el aislamiento, y con ello la no conducción. Además, suele ser poco robusto y tiende a soltarse. 2. Presión Æ Consiste simplemente en hacer presión en la unión, evitándose el problema del aislamiento eléctrico, pero se puede perder en robustez. 3. Soldadura Æ Es un método más complejo, pero no excesivamente, y permite tener la máxima flexibilidad y robustez conjuntamente. En nuestro caso se optó por la presión, pero podría mejorarse, realizando la soldadura de los cables necesarios. Pasando a los aspectos del software, podríamos empezar por el diseño del algoritmo del protocolo. Este algoritmo funciona bastante bien, pero hace uso de un bit del puerto paralelo que hace uso de lógica negada, por lo que se complica enormemente su implementación y comprensión. Por ello, una buena opción sería tomar una línea que no fuera de lógica negada. En concreto necesitaríamos una línea de entrada salida, que por ello sería del registro de control del puerto paralelo, para sustituir a la línea usada para PC_LISTO, que funciona con la línea de lógica de ~STROBE, que se trata del bit 0 del registro de control. Una posible mejora, pero que en realidad no es necesaria, podría ser la realización de un cambio en el estado de reposo (el fijado en la inicialización). Este cambia tendría por objeto mantener el registro de datos del PC en modo entrada (en realidad sería entrada/salida), mientras que en el PIC sería de salida en PORTB. Esto permitiría ver en todo momento el valor de PORTB. Sin embargo, no es algo absolutamente necesario, pues para ello se tiene el programa gráfico del PC, que en caso de leer el valor del puerto B del PIC, devolverá el valor que éste tiene, aunque no sea visible. Una de las mejoras más recomendables, pero de tremenda complejidad, sería hacer que el protocolo no use espera activa, haciendo uso de las interrupciones del puerto paralelo. Para ello habría que habilitarlas y hacer un uso correcto de las mismas. Pero igualmente se tendría que diseñar el programa del PC con tareas, para ganar en eficiencia en el mismo y evitar un consumo excesivo de CPU. Esto también podría llevarse a aplicarlo al PIC, durmiéndolo mientras Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 48 no se reciba nada y usando la señal de PC_LISTO para poder despertar al PIC y que termine recibiendo el dato. Finalmente, se puede señalar que se observa que el protocolo usado es bastante rápido y permite evitar el uso de retardos, que tal vez podrían usarse para garantizar un mejor funcionamiento, pero que en principio, y gracias a las dos señales de control se puede evitar. De este modo se tiene un protocolo de transmisión rápido y con un buen ancho de bus, atendiendo a la tarea y medios que estamos usando. 4.2. Intérprete Gráfico del PC (Emulador) En el intérprete gráfico realizado, se tiene una buena interfaz gráfica que permite una fácil creación de programas emulados, así como una sencilla compilación y uso. Siempre estará abierto el programa a una ampliación en el set de instrucciones, la cual partiría del hecho de añadir más instrucciones o comandos, con sus respectivos parámetros de entrada y salida, en el fichero de sintaxis (reglasComp.txt). Una mejora posible, aunque no trivial sería la de facilitar que el proceso de añadir instrucciones se haga simplemente en el fichero de sintaxis, sin requerir tocar código del programa. Esto debe tratarse con especial cuidado para evitar que se desborde la pila del programa y nuestro programa falle. Por otro lado, podría añadirse opciones mejoradas, como la opción de un depurador con ejecución paso a paso, pero que en principio requieren bastante tiempo, tanto el dedicado a la interfaz gráfica, como el dedicado a la programación, si bien ya tiene toda la base implementada. Por último, y como ya se mencionó anteriormente, una buena mejora para dotar de eficiencia al programa, consiste en hacer que cada función funcione como una tarea aparte. De hecho, en el caso de uso de un depurador, el uso de tareas y control de concurrencia será absolutamente necesaria. 4.3. Ejecución de Instrucciones Emuladas en el PIC En el programa del PIC se partía del código desarrollado en años previos. En esta ocasión se ha mejorado y adaptado al protocolo del puerto paralelo. El nuevo código controla con cuidado los cambios de páginas, según el valor de PCLATH. Una vez controlado esto, se pueden realizar los saltos relativos a PCL sin problemas. Con esto, el tamaño de nuestro programa se reduce exponencialmente. Además, el programa final tiene el código reducido al máximo, tanto para obtener menos ocupación en el PIC, como conseguir mayor velocidad de ejecución. Finalmente, hay que añadir que el programa puede requerir el uso de ciertos retardos en algunas zonas, para lo cual se dispone de una librería de retardos bastante completa y explicada a lo largo de la memoria. Con ella se pueden poner retardos en aquellas zonas críticas del programa del PIC. Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 49 5. Bibliografía Programas: Microsoft Word (Realización de la Memoria) MS Visio (Diseño de diagramas de flujo) EditPlus (Visualización de código ensamblador, con sintaxis de ensamblador PIC) MPLAB (IDE para la programación en ensamblador PIC y su depuración) Proteus (Diseño de circuito de la placa PIC Trainer y ejecución simulado de programas) Cargador de Programas en PIC (de Microchip®) Internet Explorer (Búsqueda de documentación e información) Acrobat Reader (Visualización de documentación) CallDelay (Programa de generación de rutinas de retardo) MASM32 (Compilador de Microsoft Macro Assambler para Win32) RadASM (Entorno IDE para programación en Win32, usando en este caso MASM32) OllyDbg (Depurador Externo para la depuración de programas Win32) Fuentes Teóricas: Ayuda de MPLAB (Uso de MPLAB y consulta del ensamblador PIC) Ayuda de MASM32 (Conocimiento del ensamblador MASM32) Ayuda de las API de Windows® (Conocimiento de la Interfaz de las APIs) Ayuda de RadASM (Uso de RadASM y diseño de programas gráficos) Manual de Ensamblador en Win32 (Programación en ensamblador para Win32) Documentación de la asignatura DSBM Manual de Microcontrolador PIC (Información del PIC + Ensamblador PIC) http://x-robotics.com (Información de Microcontroladores y Simuladores) Páginas Webs Interesantes http://www.movsd.com Æ Web Oficial del MASM32 http://radasm.visualassembler.com Æ Ensamblador RadAsm para MASM, TASM,... http://board.win32asmcommunity.net Æ Foros de ensamblador en Win32 http://home.t-online.de/home/Ollydbg/ Æ Depurardor OllyDbg, para programas de entorno gráfico Win32 Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 50 6. Anexo En esta sección nos limitaremos a presentar los códigos desarrollados, en algunos casos acompañados de una breve explicación de la tarea que realizan. Cada código se presenta según el fichero en que estaba contenido, lo que permite observar la modulación que se ha adoptado en los programas desarrollados. 6.1. Códigos del PIC Los códigos del PIC, desarrollados bajo MPLAB®, son los siguientes, creados en ficheros .asm, pero que en ocasiones se han convertido a librerías (.lib) para su mejor manejo. 6.1.1. Emulador de Comandos (main.asm) Es el programa principal del PIC, que recibe el comando y bifurca inmediatamente al punto del programa que lo procesará. El procesamiento de un comando podrá implicar otras transmisiones con el PC, que pueden ser tanto de envío como recepción. Todo ello puede verse en el siguiente código, que aunque es el que se usa en la práctica, también está abierto a ciertas modificaciones ya explicadas en su momento. #include "Lib\InstrPIC16F84.h" #include "Lib\i2c.inc" #include "Lib\lcd.inc" #include "Lib\paralelo.inc" #include "Lib\retardo.inc" #include "Lib\tecla.inc" radix dec ; Números decimales por defecto origen code 0 goto LeerComando main code LeerComando call Inicializar call Recibir ; Recibir Comando movfw DatoPP addwf PCL goto leerA ; 0 --> leer del puerto A goto leerB ; 1 --> leer del puerto B goto escribirA ; 2 --> escribir en puerto A goto escribirB ; 3 --> escribir en puerto B goto leerteclado ; 4 --> leer teclado hexadecimal goto escribir_1_byte ; 5 --> escribir un solo byte en bus I2C goto leer_1_byte ; 6 --> leer un solo byte del bus I2C goto escribir_n_bytes ; 7 --> escribir "n" bytes en bus I2C goto leer_n_bytes ; 8 --> leer "n" bytes del bus I2C goto env_dato_lcd ; 9 --> enviar dato al LCD goto env_com_lcd ; 10 --> enviar comando al LCD goto terror ; ERROR --> si no es ningún comando leerA Mod_Tris Ent,PORTA movar DatoPP,PORTA call Inicializar ; Restaura el modo de PORTA call Enviar ; Envía el Dato (de PORTA), los valores de PORTA[3..4] son espúreos, por usarse en el protocolo goto LeerComando leerB Mod_Tris Sal,PORTB movar PORTB,DatoPORTB ; Mod_Tris Ent,PORTB movar DatoPP,PORTB ; Si PC estuviera como entrada Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 51 call Inicializar call Enviar goto LeerComando escribirA ; Este comando no es visible en la placa PIC-Trainer!!! call Recibir Mod_Tris Sal,PORTA movar PORTA,DatoPP goto LeerComando escribirB call Recibir Mod_Tris Sal,PORTB movar PORTB,DatoPP ;Mod_Tris Ent,PORTB movar DatoPORTB,PORTB goto LeerComando ; Si PC estuviera como entrada leerteclado call Key_Scan ; Se realiza barrido del teclado movlw 0x80 subwf Tecla,W ; Se ha pulsado alguna tecla??? btfsc STATUS,Z goto leerteclado ; Se sigue explorando el teclado ; Se salva PCLATH (DatoPP se usa como variable auxiliar), pues Cods_Tecla lo modifica, y se restaura movar DatoPP,PCLATH movfw Tecla call Cods_Tecla movar PCLATH,DatoPP movar DatoPP,POS ; Valor hexadecimal de la tecla call Inicializar call Enviar goto LeerComando escribir_1_byte call Recibir movar DATO,DatoPP call Recibir movar DIRECCION,DatoPP clrf BUS_STATE ; Resetea registro estado del bus I2C por si tiene basura call ENVIAR_1 ; Rutina de escritura de 1 byte en el bus goto LeerComando leer_1_byte call Recibir movar DIRECCION,DatoPP clrf BUS_STATE call RECIBIR_1 ; Resetea registro estado del bus I2C por si tiene basura ; Rutina de lectura de 1 byte en el bus movar DatoPP,DATO call Inicializar call Enviar goto LeerComando escribir_n_bytes call Recibir movar CONT_BYTE, DatoPP call Recibir movar DIRECCION,DatoPP bucle1 call Inicializar call Recibir movar DATO,DatoPP clrf BUS_STATE call ENVIAR movfw CONT_BYTE btfss STATUS,Z ; Quedan bytes por recibir? goto bucle1 goto LeerComando leer_n_bytes Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 52 call Recibir movar CONT_BYTE, DatoPP call Recibir movar DIRECCION,DatoPP bucle2 ;call RetardoMS ; Retardo entre byte y byte clrf BUS_STATE call RECIBIR movar DatoPP,DATO call Inicializar call Enviar movfw CONT_BYTE btfss STATUS,Z ; Quedan bytes por recibir? goto bucle2 goto LeerComando env_dato_lcd call Inicializar ; Prepara puertos para poder usar protocolo call Recibir call DISPLAY_CONF ; Prepara puertos y LCD para ser usados movfw DatoPP btfsc STATUS,Z ; Si el dato recibido es 0 --> No lo envía y termina el comando goto LeerComando movfw DatoPP ; Necesario para que el LCD no se quede BUSY!!! call LCD_DATO ;call RetardoMS ; Retardo en función de la velocidad del LCD goto env_dato_lcd env_com_lcd call Recibir movfw DatoPP call LCD_REG goto LeerComando terror BANK1 clrf TRISB ; Equivale a Mod_Tris Sal,PORTB BANK0 movar PORTB,DatoPORTB goto $ ; Se detiene el PIC en este punto end 6.1.2. Protocolo del Puerto Paralelo (paralelo.asm) Contiene las rutinas de Inicializar, Enviar y Recibir para el PIC, a parte de las variables DatoPP para envío y recepción, y DatoPORTB para mantener el dato del puerto B. #include "Lib\InstrPIC16F84.h" global Inicializar, Enviar, Recibir, DatoPP, DatoPORTB ; Posiciones de los bits de control (en PORTA) PIC_LISTO equ 3 PC_LISTO equ 4 d_prot udata DatoPP DatoPORTB res 1 res 1 c_prot code Inicializar ; Se inicializan los puertos de la siguiente forma: ; PORTB --> Entrada; Contiene DatoPORTB (sería un parámetro de entrada) ; PORTA --> Salida, con PC_LISTO como Entrada; Se mantiene a 0 BANK1 movlw Ent movwf TRISB ; PORTB como entrada Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 53 clrf TRISA bsf TRISA,PC_LISTO ; PORTA como salida, y PC_LISTO como entrada bsf OPTION_REG,NOT_RBPU ; Desactivar cargas pull-up BANK0 clrf PORTA ; Limpia PORTA movfw DatoPORTB PORTB = DatoPORTB movwf PORTB return Recibir ; PASO 1: Poner PIC en Estado de Reposo (poner PIC_LISTO a 0) bcf PORTA,PIC_LISTO ; PASO 2: Esperar a que el PC haya puesto el Dato (listo para transmitir) btfss PORTA,PC_LISTO ; Si PORTA(PC_LISTO) == 1 ==> Pasa a PASO 4 goto $-1 ; PASO 3: Tomar el Dato movar DatoPP,PORTB ; PASO 4: PIC indica que leyo el Dato (poner PIC_LISTO a 1) bsf PORTA,PIC_LISTO ; PASO 5: Esperar a que PC se diera cuenta de que se leyo el Dato ; (se termino la transmision) btfsc PORTA,PC_LISTO ; Si PORTA(PC_LISTO) == 0 ==> Pasa a PASO 7 goto $-1 ; PASO 6: Se restaura el valor de PORTB movar PORTB,DatoPORTB ; PASO 7: Se pasa a Estado de Reposo (poner PIC_LISTO a 0) bcf PORTA,PIC_LISTO return Enviar ; PASO 1: Se comprueba que el PC quiere iniciar una recepcion (PC_LISTO == 0) por la logica negada btfsc PORTA,PC_LISTO ; Si PORTA(PC_LISTO) == 0 ==> Pasa a PASO 2 goto $-1 ; PASO 2: Poner PORTB como Salida BANK1 clrf TRISB BANK0 ; PASO 3: Poner el Dato en PORTB movar PORTB,DatoPP ; PASO 4: Indicar que el Dato esta listo para enviarse (PIC_LISTO == 1) bsf PORTA,PIC_LISTO ; PASO 5: Esperar a que el PC haya recibido el Dato (PC_LISTO == 1) por la logica negada btfss PORTA,PC_LISTO ; Si PORTA(PC_LISTO) == 1 ==> Pasa a PASO 7 goto $-1 ; PASO 6: Se pone PORTB como Entrada y se restaura el valor de PORTB Mod_Tris Ent,PORTB movar PORTB,DatoPORTB ; PASO 7: Se pasa a Estado de Reposo (poner PIC_LISTO a 0) bcf PORTA,PIC_LISTO return end Para facilitar el uso de las rutinas del protocolo del puerto paralelo desarrolladas, se ha creado una librería para las mismas, bajo el nombre paralelo.lib. Su creación es bastante sencilla, realizable a partir del fichero objeto (paralelo.o) que se obtiene a partir Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 54 de la compilación del fichero paralelo.asm. Bastará ejecutar el siguiente comando de consola con el programa mplib.exe: mplib.exe /c paralelo.lib paralelo.o 6.1.3. Rutinas de Retardo (retardo.asm) Las rutinas de retardo creadas son de propósito general, como ya se indicó, e incluyen a todas éstas: #include "Lib\InstrPIC16F84.h" radix dec ; Números decimales, por defecto global Retardo1, Retardo2, Retardo3, RetardoMS, RetardoCS, RetardoDS, RetardoS, N1, N2, N3 d_retardo udata N1 res 1 N2 res 1 N3 res 1 Naux1 res 1 Naux2 res 1 c_retardo code ;------------------------------------------------------------; Retardo de 6 a 768 microsegundos, usa 1 factor, por lo que ; soporta retardo de tiempos primos ;------------------------------------------------------------; Duración = N1*3 - 1 + 4 ; Duración = 3*(N1+1) microsegundos, 0 < N1 <= 255 ; Usar MatLab con solve('3*(N1+1) + 2 = X') ; Si se cuenta la asiganción moval N,microsegundos (+2) ;-----------------------------------------------------------Retardo1 decfsz N1 ; 1 + (1) goto $-1 ; 2 return ; 2+2 ;------------------------------------------------------------; Retardo de 16 a 197628 microsegundos, usa 2 fatores, por lo ; que soporta retardo de tiempos factorizables a 2 valores ;------------------------------------------------------------; Duración = N2*( 3*(N1+1) + 7 ) - 1 + 4 ; Duración = 3*N2*(N1+3) + N2 + 3 microsegundos, ; 0 < N1,N2 <= 255 ; Usar MatLab con solve('3*N2*(N1+3) + N2 + 3 + 4 = X') ; Si se cuentan las asiganciones moval N1,microsegundos y ; moval N2,microsegundos (+4) ;------------------------------------------------------------Retardo2 movfw N1 ; 2 movwf Naux1 movfw Naux1 ; 2 movwf N1 call Retardo1 ; 3*(N1+1) decfsz N2 ; 1 + (1) goto $-4 ; 2 return ; 2+2 ;------------------------------------------------------------; Retardo de 26 a 50396928 microsegundos, usa 3 fatores, por ; lo que soporta retardo de tiempos factorizables a 3 valores ;------------------------------------------------------------; Duración = N3*( (3*N2*(N1+3) + N2 + 3) + 7 ) - 1 + 4 ; Duración = 3*N3*N2*N1 + 10*N3*N2 + 10*N3 + 3 ; Duración = 3*N3*N2*N1 + 10*N3*(N2+1) + 3 microsegundos, ; 0 < N1,N2,N3 <= 255 ; Usar MatLab con solve('3*N3*N2*N1 + 10*N3*(N2+1) + 3 + 6 = X') ; Si se cuentan las asiganciones moval N1,microsegundos, ; moval N2,microsegundos y moval N3,microsegundos (+6) Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 55 ;-----------------------------------------------------------Retardo3 movfw N2 ; 2 movwf Naux2 movfw Naux2 ; 2 movwf N2 call Retardo2 ; 3*N2*(N1+3) + N2 + 3 decfsz N3 ; 1 + (1) goto $-4 ; 2 return ; 2+2 ;------------------------------------------------------------; Retardo de 1 milisegundo ;------------------------------------------------------------; Duración = 2 + 768 + 2 + 222 + 2 + 4 = 1000 microsegundos ;------------------------------------------------------------RetardoMS movlw 255 ; 2 movwf N1 call Retardo1 ; 3*(255+1) = 768 movlw 74 ; 2 movwf N1 call Retardo1 ; 3*(73+1) = 222 goto $+1 ; 2 return ; 2+2 ;------------------------------------------------------------; Retardo de 1 centésima de segundo ;------------------------------------------------------------; Duración = 2 + 2 + 9961 + 2 + 27 + 2 + 4 ; Duración = 10000 microsegundos ;------------------------------------------------------------RetardoCS movlw 252 ; 2 movwf N1 movlw 13 ; 2 movwf N2 call Retardo2 ; 3*13*(252+3) + 13 + 3 = 9961 movlw 8 ; 2 movwf N1 call Retardo1 ; 3*(8+1) = 27 goto $+1 ; 2 return ; 2+2 ;------------------------------------------------------------; Retardo de 1 décima de segundo ;------------------------------------------------------------; Duración = 2 + 2 + 99973 + 2 + 15 + 2 + 4 ; Duración = 100000 microsegundos ;------------------------------------------------------------RetardoDS movlw 255 ; 2 movwf N1 movlw 130 ; 2 movwf N2 call Retardo2 ; 3*130*(253+3) + 130 + 3 = 99973 movlw 4 ; 2 movwf N1 call Retardo1 ; 3*(4+1) = 15 goto $+1 ; 2 return ; 2+2 ;------------------------------------------------------------; Retardo de 1 segundo ;------------------------------------------------------------; Duración = 2 + 2 + 2 + 999813 + 2 + 174 + 1 + 4 ; Duración = 1000000 microsegundos ;------------------------------------------------------------RetardoS movlw 255 ; 2 movwf N1 movlw 215 ; 2 movwf N2 Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 56 movlw 6 ; 2 movwf N3 call Retardo3 ; 3*6*215*255 + 10*6*(215+1) + 3 = 999813 movlw 57 ; 2 movwf N1 call Retardo1 ; 3*(57+1) = 174 nop ; 1 return ; 2+2 end Para facilitar el uso de las rutinas de retardo, se ha creado una librería para las mismas, bajo el nombre retardo.lib. Su creación es bastante sencilla, realizable a partir del fichero objeto (retardo.o) que se obtiene a partir de la compilación del fichero retardo.asm. Bastará ejecutar el siguiente comando de consola con el programa mplib.exe: mplib.exe /c retardo.lib retardo.o En cuanto al uso de librerías hay que indicar que si bien sólo se cargan los miembros que se usan, para que ocurra así debe evitarse la declaración extern. Por ello, si se usa el fichero cabecera de la librería (ratardo.inc) se incluirá siempre todo. Además, si se pretende que sólo se incluyan ciertas funciones, será necesario que se creen en distintos miembros, es decir, distintos ficheros objeto, lo cual sería una tarea tedioso y poco recomendable. 6.1.4. Fichero Cabecera (InstrPIC16F84.h) Este fichero es una extensión del ya desarrollado en la Práctica 1. Incluye el fichero de macros Standard del PIC16F84 más una serie de macros especiales para desarrollo de aplicaciones en el PIC16F84. Su código es el siguiente: processor PIC16F84 #include <p16f84.inc> ;---------------------------------------------------------; Inhibición de Mensajes y Avisos ;---------------------------------------------------------errorlevel -203 ; Operación en Columna 1 errorlevel -205 ; Directiva en Columna 1 errorlevel -206 ; Macro en Columna 1 errorlevel -207 ; Etiqueta No en Columna 1 errorlevel -216 ; Aviso de radix configurado en proyecto y código errorlevel -302 ; Registro que no es de banco0 (asegurarse de que el banco sea el correcto) errorlevel -305 ; Destino de operación por defecto (1 --> File) ;---------------------------------------------------------; Directivas de definición de Instrucciones fijas ; de uso común. ;---------------------------------------------------------Sal equ 0x00 ; Puerto de Salida Ent equ 0xff ; Puerto de Entrada ; Ent funcionará igualmente para PORTA (aunque TRISA se pondrá a 0x1f) ;---------------------------------------------------------; Mod_Tris ;---------------------------------------------------------; Macro que estable el puerto de entrada y salida. ;---------------------------------------------------------Mod_Tris MACRO INOUT, PUERTO BANK1 movlw INOUT movwf PUERTO ; Equivale a TRIS, porque accedemos en banco 1 BANK0 ENDM #define BANK0 bcf STATUS, RP0 Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 57 #define BANK1 bsf STATUS, RP0 ;---------------------------------------------------------; Macros para compactar instrucciones de determinadas ; tareas. ;---------------------------------------------------------; DetenerReloj --> Detiene Timer0 DetenerReloj MACRO BANK1 bsf OPTION_REG,T0CS BANK0 ENDM ; ReiniciarReloj --> Re-inicia Timer0 ReiniciarReloj MACRO BANK1 bcf OPTION_REG,T0CS BANK0 ENDM ;---------------------------------------------------------; Macros para definición de Pseudo-Instruccion más ; potentes, para la programación en lenguaje ensamblador. ;---------------------------------------------------------; moval a0, a1 --> a0 = a1 (a1 = literal) (modifica W) moval MACRO a0, a1 movlw a1 movwf a0 ENDM ; movar a0, a1 --> a0 = a1 (a1 = registro) (modifica W) movar MACRO a0, a1 movfw a1 movwf a0 ENDM ; addl a0, a1 --> a0 += a1 (a1 = literal) (modifica W) addl MACRO a0, a1 movfw a0 addlw a1 movwf a0 ENDM ; addr a0, a1 --> a0 += a1 (a1 = registro) (modifica W) addr MACRO a0, a1 movfw a1 addwf a0,F ENDM ; subl a0, a1 --> a0 -= a1 (a1 = literal) (modifica W) subl MACRO a0, a1 movlw a1 subwf a0,F ENDM ; subr a0, a1 --> a0 -= a1 (a1 = registro) (modifica W) subr MACRO a0, a1 movfw a1 subwf a0,F ENDM ; xorl a0, a1 --> a0 += a1 (a1 = literal) (modifica W) xorl MACRO a0, a1 movfw a0 xorlw a1 movwf a0 ENDM ; xorr a0, a1 --> a0 += a1 (a1 = registro) (modifica W) xorr MACRO a0, a1 movfw a1 Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 58 xorwf a0,F ENDM ; eq a0, a1, etiqueta --> a0 == a1 ==> no salta a etiqueta (a1 = literal) (modifica W) eq MACRO a0, a1, etiqueta local iguales subl a0,a1 skpnz goto iguales addl a0,a1 goto etiqueta iguales addl a0,a1 ENDM ; neq a0, a1, etiqueta --> a0 != a1 ==> no salta a etiqueta (a1 = literal) (modifica W) neq MACRO a0, a1, etiqueta local distintos subl a0,a1 skpz goto distintos addl a0,a1 goto etiqueta distintos addl a0,a1 ENDM ; ge a0, a1, etiqueta --> a0 >= a1 ==> no salta a etiqueta (a1 = literal) (modifica W) ge MACRO a0, a1, etiqueta local no_mayor subl a0,a1 bc no_mayor addl a0,a1 goto etiqueta no_mayor addl a0,a1 ENDM ; pushi w_temp, status_temp --> Salva W y STATUS en memoria (w_temp = W; status_temp = STATUS) pushi MACRO w_temp, status_temp movwf w_temp movar status_temp,STATUS ENDM ; popi w_temp, status_temp --> Recupear W y STATUS de memoria (W = w_temp; STATUS = status_temp) popi MACRO w_temp, status_temp movar STATUS,status_temp movfw w_temp ENDM 6.2. Códigos del PC A continuación se muestran sólo los ficheros principales del programa de entorno gráfico desarrollado. En principio, al ser un programa de entorno gráfico, serán necesarios otros ficheros de imágenes, contenidos especiales, etc. sin los que el programa no funcionará ni podrá ser compilado, pero que no tiene código propiamente dicho, y que en ocasiones su generación es automática con las herramientas de desarrollo usadas, en concreto el entorno IDE RadAsm®, usando el ensamblador gráfico MASM32®, y el depurador de programas Win32 denominado OllyDbg®. 6.2.1. Interfaz del Programa Gráfico Principal (Emulador.inc) include windows.inc include user32.inc Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 59 include kernel32.inc include shell32.inc include comctl32.inc include comdlg32.inc include gdi32.inc include masm32.inc includelib user32.lib includelib kernel32.lib includelib shell32.lib includelib comctl32.lib includelib comdlg32.lib includelib gdi32.lib includelib masm32.lib ;Modo Depuración (DEBUG == 1) DEBUG = 1 WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD WndProc PROTO :DWORD,:DWORD,:DWORD,:DWORD .const IDB_TBRBMP equ 150 IDR_ACCEL equ 200 ;Find.dlg IDD_FINDDLG equ 2000 IDC_FINDTEXT equ 2001 IDC_BTN_REPLACE equ 2007 IDC_REPLACETEXT equ 2002 IDC_REPLACESTATIC equ 2009 IDC_BTN_REPLACEALL equ 2008 IDC_CHK_WHOLEWORD equ 2004 IDC_CHK_MATCHCASE equ 2003 IDC_RBN_DOWN equ 2005 IDC_RBN_UP equ 2006 ;Emulador.dlg IDD_DLG equ 1000 IDC_SBR equ 1003 IDC_TBR equ 1001 IDC_RED equ 1002 IDM_MENU equ 10000 ;Emulador.mnu IDM_FILE_NEW equ 10001 IDM_FILE_OPEN equ 10002 IDM_FILE_SAVE equ 10003 IDM_FILE_SAVEAS equ 10004 IDM_FILE_PRINT equ 10005 IDM_FILE_EXIT equ 10006 IDM_EDIT_UNDO equ 10101 IDM_EDIT_REDO equ 10102 IDM_EDIT_DELETE equ 10103 IDM_EDIT_CUT equ 10104 IDM_EDIT_COPY equ 10105 IDM_EDIT_PASTE equ 10106 IDM_EDIT_SELECTALL equ 10107 IDM_EDIT_FIND equ 10108 IDM_EDIT_FINDNEXT equ 10110 IDM_EDIT_FINDPREV equ 10111 IDM_EDIT_REPLACE equ 10109 IDM_VIEW_TOOLBAR equ 10008 IDM_VIEW_STATUSBAR equ 10009 IDM_OPTION_FONT equ 10007 IDM_HELP_ABOUT equ 10201 IDM_ENVIAR_DATO equ 10010 IDM_RECIBIR_DATO equ 10011 IDM_TEST equ 10012 IDM_COMPILAR equ 10013 IDM_COMPILAR_LINEA equ 10014 ;structure for ToolBar buttons tbrbtns TBBUTTON <0,0,TBSTATE_ENABLED,TBSTYLE_SEP,0,0> TBBUTTON <6,IDM_FILE_NEW,TBSTATE_ENABLED,TBSTYLE_BUTTON,0,0> TBBUTTON <7,IDM_FILE_OPEN,TBSTATE_ENABLED,TBSTYLE_BUTTON,0,0> TBBUTTON <8,IDM_FILE_SAVE,TBSTATE_ENABLED,TBSTYLE_BUTTON,0,0> Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 60 TBBUTTON <0,0,TBSTATE_ENABLED,TBSTYLE_SEP,0,0> TBBUTTON <0,IDM_EDIT_CUT,TBSTATE_ENABLED,TBSTYLE_BUTTON,0,0> TBBUTTON <1,IDM_EDIT_COPY,TBSTATE_ENABLED,TBSTYLE_BUTTON,0,0> TBBUTTON <2,IDM_EDIT_PASTE,TBSTATE_ENABLED,TBSTYLE_BUTTON,0,0> TBBUTTON <0,0,TBSTATE_ENABLED,TBSTYLE_SEP,0,0> TBBUTTON <3,IDM_EDIT_UNDO,TBSTATE_ENABLED,TBSTYLE_BUTTON,0,0> TBBUTTON <4,IDM_EDIT_REDO,TBSTATE_ENABLED,TBSTYLE_BUTTON,0,0> TBBUTTON <5,IDM_EDIT_DELETE,TBSTATE_ENABLED,TBSTYLE_BUTTON,0,0> TBBUTTON <0,0,TBSTATE_ENABLED,TBSTYLE_SEP,0,0> TBBUTTON <12,IDM_EDIT_FIND,TBSTATE_ENABLED,TBSTYLE_BUTTON,0,0> TBBUTTON <13,IDM_EDIT_REPLACE,TBSTATE_ENABLED,TBSTYLE_BUTTON,0,0> TBBUTTON <0,0,TBSTATE_ENABLED,TBSTYLE_SEP,0,0> TBBUTTON <14,IDM_FILE_PRINT,TBSTATE_ENABLED,TBSTYLE_BUTTON,0,0> TBBUTTON <0,0,TBSTATE_ENABLED,TBSTYLE_SEP,0,0> ;Number of buttons in tbrbtns ntbrbtns equ 18 .data RichEditDLL db 'riched20.dll',0 ClassName db 'DLGCLASS',0 AppName db 'El Emulador',0 AboutMsg db 'RadASM El Emulador',13,10,'Saldriamos Nosotros',0 Replace db 'Remplazar ..',0 OpenFileFail db 'No se puede abrir el fichero',0 SaveFileFail db 'No se puede guardar el fichero',0 WannaSave db 'Desea guardar los cambios realizados',0Dh,0 NewFile db '(Sin Título)',0 szNULL db 0 szFont db 'Courier New',0 msgDatoRecibido db 'Dato Recibido a través del Puerto Paralelo:',0 .data? hRichEdDLL dd ? hInstance dd ? CommandLine dd ? hIcon dd ? hAccel dd ? hWnd HWND ? hREd HWND ? hFind HWND ? FileName db MAX_PATH dup(?) fView dd ? TabSize dd ? lfnt LOGFONT <> hFont dd ? rgb dd ? findbuff db 256 dup(?) replacebuff db 256 dup(?) ft FINDTEXTEX <> fr dd ? fres dd ? 6.2.2. Programa Gráfico Principal (Emulador.asm) .386 .model flat,stdcall option casemap:none include Protocolo.inc include Compilador.inc include Emulador.Inc include TraduceIns.inc .data? hMainHeap dd ? MatrizDeSintaxis dd ? .code start: invoke GetModuleHandle,NULL mov hInstance,eax Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 61 invoke GetCommandLine mov CommandLine,eax invoke InitCommonControls invoke LoadLibrary,offset RichEditDLL mov hRichEdDLL,eax ;Creamos la pila del programa invoke GetProcessHeap mov hMainHeap,eax invoke RellenaListaIns,hInstance,hMainHeap push ecx push edx mov ecx, offset MatrizDeSintaxis mov edx, dword ptr[eax] mov dword ptr[ecx],edx pop edx pop ecx invoke WinMain,hInstance,NULL,CommandLine,SW_SHOWDEFAULT push eax invoke FreeLibrary,hRichEdDLL pop eax invoke ExitProcess,eax WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD LOCAL wc:WNDCLASSEX LOCAL msg:MSG mov wc.cbSize,sizeof WNDCLASSEX mov wc.style,CS_HREDRAW or CS_VREDRAW mov wc.lpfnWndProc,offset WndProc mov wc.cbClsExtra,NULL mov wc.cbWndExtra,DLGWINDOWEXTRA push hInst pop wc.hInstance mov wc.hbrBackground,NULL mov wc.lpszMenuName,IDM_MENU mov wc.lpszClassName,offset ClassName invoke LoadIcon,NULL,IDI_APPLICATION mov hIcon,eax mov wc.hIcon,eax mov wc.hIconSm,eax invoke LoadCursor,NULL,IDC_ARROW mov wc.hCursor,eax invoke RegisterClassEx,addr wc invoke CreateDialogParam,hInstance,IDD_DLG,NULL,offset WndProc,NULL mov hWnd,eax invoke ShowWindow,hWnd,SW_SHOWNORMAL invoke UpdateWindow,hWnd invoke LoadAccelerators,hInstance,IDR_ACCEL mov hAccel,eax invoke Inicializar ; Inicialización del Puerto Paralelo y librería WinIo .while TRUE invoke GetMessage,addr msg,NULL,0,0 .break .if !eax invoke IsDialogMessage,hFind,addr msg .if !eax invoke TranslateAccelerator,hWnd,hAccel,addr msg .if !eax invoke TranslateMessage,addr msg invoke DispatchMessage,addr msg .endif .endif .endw mov eax,msg.wParam ret WinMain endp StreamInProc proc hFile:DWORD,pBuffer:DWORD,NumBytes:DWORD,pBytesRead:DWORD invoke ReadFile,hFile,pBuffer,NumBytes,pBytesRead,0 xor eax,1 ret StreamInProc endp StreamOutProc proc hFile:DWORD,pBuffer:DWORD,NumBytes:DWORD,pBytesWritten:DWORD Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 62 invoke WriteFile,hFile,pBuffer,NumBytes,pBytesWritten,0 xor eax,1 ret StreamOutProc endp SetWinCaption proc LOCAL buffer[sizeof AppName+3+MAX_PATH]:BYTE LOCAL buffer1[4]:BYTE ;Add filename to windows caption invoke lstrcpy,addr buffer,offset AppName mov eax,' - ' mov dword ptr buffer1,eax invoke lstrcat,addr buffer,addr buffer1 invoke lstrcat,addr buffer,offset FileName invoke SetWindowText,hWnd,addr buffer ret SetWinCaption endp SaveFile proc lpFileName:DWORD LOCAL hFile:DWORD LOCAL editstream:EDITSTREAM invoke CreateFile,lpFileName,GENERIC_WRITE,FILE_SHARE_READ,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,0 .if eax!=INVALID_HANDLE_VALUE mov hFile,eax ;stream the text to the file mov editstream.dwCookie,eax mov editstream.pfnCallback,offset StreamOutProc invoke SendMessage,hREd,EM_STREAMOUT,SF_TEXT,addr editstream invoke CloseHandle,hFile ;Set the modify state to false invoke SendMessage,hREd,EM_SETMODIFY,FALSE,0 mov eax,FALSE .else invoke MessageBox,hWnd,offset SaveFileFail,offset AppName,MB_OK mov eax,TRUE .endif ret SaveFile endp SaveEditAs proc LOCAL ofn:OPENFILENAME LOCAL buffer[MAX_PATH]:BYTE ;Zero out the ofn struct invoke RtlZeroMemory,addr ofn,sizeof ofn ;Setup the ofn struct mov ofn.lStructSize,sizeof ofn push hWnd pop ofn.hwndOwner push hInstance pop ofn.hInstance mov ofn.lpstrFilter,NULL mov buffer[0],0 lea eax,buffer mov ofn.lpstrFile,eax mov ofn.nMaxFile,sizeof buffer mov ofn.Flags,OFN_FILEMUSTEXIST or OFN_HIDEREADONLY or OFN_PATHMUSTEXIST or OFN_OVERWRITEPROMPT mov ofn.lpstrDefExt,NULL ;Show save as dialog invoke GetSaveFileName,addr ofn .if eax invoke SaveFile,addr buffer .if !eax ;The file was saved invoke lstrcpy,offset FileName,addr buffer invoke SetWinCaption mov eax,FALSE .endif .else Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 63 .endif ret SaveEditAs endp mov eax,TRUE SaveEdit proc ;Check if filrname is (Untitled) invoke lstrcmp,offset FileName,addr NewFile .if eax invoke SaveFile,offset FileName .else invoke SaveEditAs .endif ret SaveEdit endp WantToSave proc LOCAL buffer[512]:BYTE LOCAL buffer1[2]:BYTE invoke SendMessage,hREd,EM_GETMODIFY,0,0 .if eax invoke lstrcpy,addr buffer,offset WannaSave invoke lstrcat,addr buffer,offset FileName mov ax,'?' mov word ptr buffer1,ax invoke lstrcat,addr buffer,addr buffer1 invoke MessageBox,hWnd,addr buffer,offset AppName,MB_YESNOCANCEL or MB_ICONQUESTION .if eax==IDYES invoke SaveEdit .elseif eax==IDNO mov eax,FALSE .else mov eax,TRUE .endif .endif ret WantToSave endp LoadFile proc lpFileName:DWORD LOCAL hFile:DWORD LOCAL editstream:EDITSTREAM LOCAL chrg:CHARRANGE ;Open the file invoke CreateFile,lpFileName,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0 .if eax!=INVALID_HANDLE_VALUE mov hFile,eax ;Copy buffer to FileName invoke lstrcpy,offset FileName,lpFileName ;stream the text into the richedit control push hFile pop editstream.dwCookie mov editstream.pfnCallback,offset StreamInProc invoke SendMessage,hREd,EM_STREAMIN,SF_TEXT,addr editstream invoke CloseHandle,hFile invoke SendMessage,hREd,EM_SETMODIFY,FALSE,0 mov chrg.cpMin,0 mov chrg.cpMax,0 invoke SendMessage,hREd,EM_EXSETSEL,0,addr chrg invoke SetWinCaption mov eax,FALSE .else invoke MessageBox,hWnd,offset OpenFileFail,offset AppName,MB_OK or MB_ICONERROR mov eax,TRUE .endif ret LoadFile endp OpenEdit proc LOCAL ofn:OPENFILENAME LOCAL buffer[MAX_PATH]:BYTE Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 64 ;Zero out the ofn struct invoke RtlZeroMemory,addr ofn,sizeof ofn ;Setup the ofn struct mov ofn.lStructSize,sizeof ofn push hWnd pop ofn.hwndOwner push hInstance pop ofn.hInstance mov ofn.lpstrFilter,NULL mov buffer[0],0 lea eax,buffer mov ofn.lpstrFile,eax mov ofn.nMaxFile,sizeof buffer mov ofn.lpstrDefExt,NULL mov ofn.Flags,OFN_FILEMUSTEXIST or OFN_HIDEREADONLY or OFN_PATHMUSTEXIST ;Show the Open dialog invoke GetOpenFileName,addr ofn .if eax invoke LoadFile,addr buffer .endif ret OpenEdit endp Find proc frType:DWORD ;Get current selection invoke SendMessage,hREd,EM_EXGETSEL,0,offset ft.chrg ;Setup find mov eax,frType and eax,FR_DOWN .if eax .if fres!=-1 inc ft.chrg.cpMin .endif mov ft.chrg.cpMax,-1 .else mov ft.chrg.cpMax,0 .endif mov ft.lpstrText,offset findbuff ;Do the find invoke SendMessage,hREd,EM_FINDTEXTEX,frType,offset ft mov fres,eax .if eax!=-1 ;Mark the foud text invoke SendMessage,hREd,EM_EXSETSEL,0,offset ft.chrgText .else ;Region searched .endif ret Find endp FindDlgProc proc hWin:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM LOCAL hCtl:DWORD mov eax,uMsg .if eax==WM_INITDIALOG mov eax,hWin mov hFind,eax .if lParam mov eax,BN_CLICKED shl eax,16 or eax,IDC_BTN_REPLACE invoke PostMessage,hWin,WM_COMMAND,eax,0 .endif ;Put text in edit boxes invoke SendDlgItemMessage,hWin,IDC_FINDTEXT,EM_LIMITTEXT,255,0 invoke SendDlgItemMessage,hWin,IDC_FINDTEXT,WM_SETTEXT,0,offset findbuff invoke SendDlgItemMessage,hWin,IDC_REPLACETEXT,EM_LIMITTEXT,255,0 invoke SendDlgItemMessage,hWin,IDC_REPLACETEXT,WM_SETTEXT,0,offset replacebuff ;Set check boxes mov eax,fr and eax,FR_MATCHCASE .if eax Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 65 invoke CheckDlgButton,hWin,IDC_CHK_MATCHCASE,BST_CHECKED .endif mov eax,fr and eax,FR_WHOLEWORD .if eax invoke CheckDlgButton,hWin,IDC_CHK_WHOLEWORD,BST_CHECKED .endif ;Set find direction mov eax,fr and eax,FR_DOWN .if eax mov eax,IDC_RBN_DOWN .else mov eax,IDC_RBN_UP .endif invoke CheckDlgButton,hWin,eax,BST_CHECKED .elseif eax==WM_COMMAND mov eax,wParam mov edx,eax shr edx,16 and eax,0FFFFh .if edx==BN_CLICKED .if eax==IDOK ;Find the text invoke Find,fr .elseif eax==IDCANCEL ;Close the find dialog invoke SendMessage,hWin,WM_CLOSE,NULL,NULL .elseif eax==IDC_BTN_REPLACE invoke GetDlgItem,hWin,IDC_BTN_REPLACEALL mov hCtl,eax invoke IsWindowEnabled,hCtl .if !eax ;Enable Replace all button invoke EnableWindow,hCtl,TRUE ;Set caption to Replace... invoke SetWindowText,hWin,offset Replace ;Show replace invoke GetDlgItem,hWin,IDC_REPLACESTATIC invoke ShowWindow,eax,SW_SHOWNA invoke GetDlgItem,hWin,IDC_REPLACETEXT invoke ShowWindow,eax,SW_SHOWNA .else .if fres!=-1 invoke SendMessage,hREd,EM_EXGETSEL,0,offset ft.chrg invoke SendMessage,hREd,EM_REPLACESEL,TRUE,offset replacebuff invoke lstrlen,offset replacebuff add eax,ft.chrg.cpMin mov ft.chrg.cpMax,eax invoke SendMessage,hREd,EM_EXSETSEL,0,offset ft.chrg .endif invoke Find,fr .endif .elseif eax==IDC_BTN_REPLACEALL .if fres==-1 invoke Find,fr .endif .while fres!=-1 mov eax,BN_CLICKED shl eax,16 or eax,IDC_BTN_REPLACE invoke SendMessage,hWin,WM_COMMAND,eax,0 .endw .elseif eax==IDC_RBN_DOWN ;Set find direction to down or fr,FR_DOWN mov fres,-1 .elseif eax==IDC_RBN_UP ;Set find direction to up and fr,-1 xor FR_DOWN mov fres,-1 .elseif eax==IDC_CHK_MATCHCASE Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 66 ;Set match case mode invoke IsDlgButtonChecked,hWin,IDC_CHK_MATCHCASE .if eax or fr,FR_MATCHCASE .else and fr,-1 xor FR_MATCHCASE .endif mov fres,-1 .elseif eax==IDC_CHK_WHOLEWORD ;Set whole word mode invoke IsDlgButtonChecked,hWin,IDC_CHK_WHOLEWORD .if eax or fr,FR_WHOLEWORD .else and fr,-1 xor FR_WHOLEWORD .endif mov fres,-1 .endif .elseif edx==EN_CHANGE ;Update text buffers .if eax==IDC_FINDTEXT invoke SendDlgItemMessage,hWin,eax,WM_GETTEXT,sizeof findbuff,offset findbuff mov fres,-1 .elseif eax==IDC_REPLACETEXT invoke SendDlgItemMessage,hWin,eax,WM_GETTEXT,sizeof replacebuff,offset replacebuff mov fres,-1 .endif .endif .elseif eax==WM_ACTIVATE mov fres,-1 .elseif eax==WM_CLOSE mov hFind,0 invoke DestroyWindow,hWin invoke SetFocus,hREd .else mov eax,FALSE ret .endif mov eax,TRUE ret FindDlgProc endp DoToolBar proc hInst:DWORD,hToolBar:HWND LOCAL tbab:TBADDBITMAP ;Set toolbar struct size invoke SendMessage,hToolBar,TB_BUTTONSTRUCTSIZE,sizeof TBBUTTON,0 ;Set toolbar bitmap push hInst pop tbab.hInst mov tbab.nID,IDB_TBRBMP invoke SendMessage,hToolBar,TB_ADDBITMAP,15,addr tbab ;Set toolbar buttons invoke SendMessage,hToolBar,TB_ADDBUTTONS,ntbrbtns,offset tbrbtns mov eax,hToolBar ret DoToolBar endp SetFormat proc hWin:DWORD LOCAL chrg1:CHARRANGE LOCAL chrg2:CHARRANGE LOCAL pf:PARAFORMAT2 LOCAL cf:CHARFORMAT LOCAL tp:DWORD LOCAL buffer[16]:BYTE LOCAL pt:POINT LOCAL hDC:HDC ;Save modify state invoke SendMessage,hWin,EM_GETMODIFY,0,0 push eax Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 67 ;Save selection invoke SendMessage,hWin,EM_EXGETSEL,0,addr chrg1 invoke SendMessage,hWin,EM_HIDESELECTION,TRUE,0 ;Select all text mov chrg2.cpMin,0 mov chrg2.cpMax,-1 invoke SendMessage,hWin,EM_EXSETSEL,0,addr chrg2 ;Set font charset mov cf.cbSize,sizeof cf mov cf.dwMask,CFM_CHARSET or CFM_FACE or CFM_SIZE or CFM_COLOR mov al,lfnt.lfCharSet mov cf.bCharSet,al mov al,lfnt.lfPitchAndFamily mov cf.bPitchAndFamily,al invoke lstrcpyn,addr cf.szFaceName,offset lfnt.lfFaceName,LF_FACESIZE mov eax,lfnt.lfHeight neg eax mov ecx,15 mul ecx mov cf.yHeight,eax mov eax,rgb mov cf.crTextColor,eax invoke SendMessage,hWin,EM_SETCHARFORMAT,SCF_SELECTION,addr cf ;Get tab width invoke GetDC,hWin mov hDC,eax invoke SelectObject,hDC,hFont push eax mov eax,'WWWW' mov dword ptr buffer,eax invoke GetTextExtentPoint32,hDC,addr buffer,4,addr pt pop eax invoke SelectObject,hDC,eax invoke ReleaseDC,hWin,hDC mov eax,pt.x mov ecx,TabSize mul ecx mov ecx,15 mul ecx shr eax,2 mov tp,eax ;Set tab stops mov pf.cbSize,sizeof pf mov pf.dwMask,PFM_TABSTOPS mov pf.cTabCount,MAX_TAB_STOPS xor eax,eax xor edx,edx mov ecx,MAX_TAB_STOPS @@: add eax,tp mov dword ptr pf.rgxTabs[edx],eax add edx,4 loop @b invoke SendMessage,hWin,EM_SETPARAFORMAT,0,addr pf ;Restore modify state pop eax invoke SendMessage,hWin,EM_SETMODIFY,eax,0 ;Restore selection invoke SendMessage,hWin,EM_EXSETSEL,0,addr chrg1 invoke SendMessage,hWin,EM_HIDESELECTION,FALSE,0 ret SetFormat endp WndProc proc hWin:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM LOCAL pt:POINT LOCAL rect:RECT LOCAL ht:DWORD LOCAL hCtl:HWND LOCAL chrg:CHARRANGE LOCAL cf:CHOOSEFONT LOCAL buffer[256]:BYTE LOCAL Dato:BYTE ; Dato de Envío/Recepción IF DEBUG Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 68 LOCAL Num:DWORD ; Para conversión de Entero a Ristra LOCAL Ristra:DWORD ENDIF LOCAL RDato[8]:BYTE ; Ristra de Bytes que componen el Dato, para su muestreo mov eax,uMsg .if eax==WM_INITDIALOG push hWin pop hWnd mov fr,FR_DOWN mov fView,3 mov TabSize,4 ;Set the toolbar buttons invoke GetDlgItem,hWin,IDC_TBR invoke DoToolBar,hInstance,eax ;Set FileName to NewFile invoke lstrcpy,offset FileName,offset NewFile invoke SetWinCaption ;Get handle of RichEdit window and give it focus invoke GetDlgItem,hWin,IDC_RED mov hREd,eax invoke SendMessage,hREd,EM_SETTEXTMODE,0,TM_PLAINTEXT ;Set event mask invoke SendMessage,hREd,EM_SETEVENTMASK,0,ENM_SELCHANGE ;Set the text limit. The default is 64K invoke SendMessage,hREd,EM_LIMITTEXT,-1,0 ;Create font invoke lstrcpy,offset lfnt.lfFaceName,offset szFont mov lfnt.lfHeight,-12 mov lfnt.lfWeight,400 invoke CreateFontIndirect,offset lfnt mov hFont,eax ;Set font & format invoke SetFormat,hREd ;Init RichEdit invoke SendMessage,hREd,EM_SETMODIFY,FALSE,0 invoke SendMessage,hREd,EM_EMPTYUNDOBUFFER,0,0 invoke SetFocus,hREd .elseif eax==WM_COMMAND ;Menu and toolbar has the same ID's mov eax,wParam and eax,0FFFFh .if eax==IDM_FILE_NEW invoke WantToSave .if !eax invoke SetWindowText,hREd,offset szNULL invoke lstrcpy,offset FileName,offset NewFile invoke SetWinCaption .endif invoke SetFocus,hREd .elseif eax==IDM_FILE_OPEN invoke WantToSave .if !eax invoke OpenEdit .endif invoke SetFocus,hREd .elseif eax==IDM_FILE_SAVE invoke SaveEdit invoke SetFocus,hREd .elseif eax==IDM_FILE_SAVEAS invoke SaveEditAs invoke SetFocus,hREd .elseif eax==IDM_FILE_PRINT .elseif eax==IDM_FILE_EXIT invoke SendMessage,hWin,WM_CLOSE,0,0 .elseif eax==IDM_EDIT_UNDO invoke SendMessage,hREd,EM_UNDO,0,0 .elseif eax==IDM_EDIT_REDO invoke SendMessage,hREd,EM_REDO,0,0 .elseif eax==IDM_EDIT_DELETE invoke SendMessage,hREd,EM_REPLACESEL,TRUE,0 .elseif eax==IDM_EDIT_CUT invoke SendMessage,hREd,WM_CUT,0,0 Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 69 .elseif eax==IDM_EDIT_COPY invoke SendMessage,hREd,WM_COPY,0,0 .elseif eax==IDM_EDIT_PASTE invoke SendMessage,hREd,WM_PASTE,0,0 .elseif eax==IDM_EDIT_SELECTALL mov chrg.cpMin,0 mov chrg.cpMax,-1 invoke SendMessage,hREd,EM_EXSETSEL,0,addr chrg .elseif eax==IDM_EDIT_FIND .if hFind==0 invoke CreateDialogParam,hInstance,IDD_FINDDLG,hWin,offset FindDlgProc,FALSE .else invoke SetFocus,hFind .endif .elseif eax==IDM_EDIT_REPLACE .if hFind==0 invoke CreateDialogParam,hInstance,IDD_FINDDLG,hWin,addr FindDlgProc,TRUE .else invoke SetFocus,hFind .endif .elseif eax==IDM_EDIT_FINDNEXT mov al,findbuff .if al invoke Find,FR_DOWN .endif .elseif eax==IDM_EDIT_FINDPREV mov al,findbuff .if al invoke Find,0 .endif .elseif eax==IDM_VIEW_TOOLBAR invoke GetDlgItem,hWin,IDC_TBR mov hCtl,eax xor fView,1 mov eax,fView and eax,1 .if eax invoke ShowWindow,hCtl,SW_SHOWNA .else invoke ShowWindow,hCtl,SW_HIDE .endif invoke SendMessage,hWin,WM_SIZE,0,0 .elseif eax==IDM_VIEW_STATUSBAR invoke GetDlgItem,hWin,IDC_SBR mov hCtl,eax xor fView,2 mov eax,fView and eax,2 .if eax invoke ShowWindow,hCtl,SW_SHOWNA .else invoke ShowWindow,hCtl,SW_HIDE .endif invoke SendMessage,hWin,WM_SIZE,0,0 .elseif eax==IDM_OPTION_FONT invoke RtlZeroMemory,addr cf,sizeof cf mov cf.lStructSize,sizeof cf mov eax,hWin mov cf.hwndOwner,eax mov cf.lpLogFont,offset lfnt mov cf.Flags,CF_SCREENFONTS or CF_EFFECTS or CF_INITTOLOGFONTSTRUCT mov eax,rgb mov cf.rgbColors,eax invoke ChooseFont,addr cf .if eax invoke DeleteObject,hFont invoke CreateFontIndirect,offset lfnt mov hFont,eax mov eax,cf.rgbColors mov rgb,eax invoke SetFormat,hREd Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 70 .endif invoke SetFocus,hREd .elseif eax==IDM_HELP_ABOUT invoke ShellAbout,hWin,offset AppName,offset AboutMsg,hIcon invoke SetFocus,hREd .elseif eax==IDM_ENVIAR_DATO invoke SendMessage,hREd,EM_GETSELTEXT,0,addr findbuff mov edx,offset findbuff mov al,byte ptr [edx] mov Dato,al sub Dato,30h mov Dato,al invoke Enviar,addr Dato .elseif eax==IDM_RECIBIR_DATO mov eax,0 invoke Recibir,addr Dato mov dl,Dato;byte ptr [edx] mov dh,0 invoke dwtoa,edx,addr Ristra IF DEBUG ; En modo Depuración se muestra mensaje con el dato recibido invoke MessageBox,hWnd,addr Ristra,offset msgDatoRecibido,MB_OK ENDIF .elseif eax==IDM_TEST invoke TestPuerto .elseif eax==IDM_COMPILAR invoke Compila,hWnd,hREd,hMainHeap,addr MatrizDeSintaxis .elseif eax==IDM_COMPILAR_LINEA invoke SendMessage,hREd,EM_GETLINE,edx,addr buffer invoke MessageBox,hWnd,addr buffer,addr buffer,MB_OK invoke atodw,addr buffer invoke CompilaLinea,addr buffer,addr MatrizDeSintaxis,hWnd .endif .elseif eax==WM_NOTIFY mov edx,lParam mov eax,(NMHDR ptr [edx]).code .if eax==TTN_NEEDTEXT ;Toolbar tooltip mov edx,(NMHDR ptr [edx]).idFrom invoke LoadString,hInstance,edx,addr buffer,sizeof buffer lea eax,buffer mov edx,lParam mov (TOOLTIPTEXT ptr [edx]).lpszText,eax .elseif wParam==IDC_RED ;Auto horizontal scroll text into view invoke GetCaretPos,addr pt invoke SendMessage,hREd,EM_GETRECT,0,addr rect mov eax,rect.right sub eax,pt.x .if eax<20 inc rect.left inc rect.top sub rect.right,70 invoke SendMessage,hREd,EM_SETRECT,0,addr rect invoke SendMessage,hREd,EM_SCROLLCARET,0,0 add rect.right,70 invoke SendMessage,hREd,EM_SETRECT,0,addr rect .endif .endif .elseif eax==WM_SIZE mov eax,fView and eax,1 .if eax ;Resize toolbar invoke GetDlgItem,hWin,IDC_TBR mov hCtl,eax invoke MoveWindow,hCtl,0,0,0,0,TRUE ;Get height of toolbar invoke GetWindowRect,hCtl,addr rect mov eax,rect.bottom sub eax,rect.top .endif push eax Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 71 mov eax,fView and eax,2 .if eax ;Resize statusbar invoke GetDlgItem,hWin,IDC_SBR mov hCtl,eax invoke MoveWindow,hCtl,0,0,0,0,TRUE ;Get height of statusbar invoke GetWindowRect,hCtl,addr rect mov eax,rect.bottom sub eax,rect.top .endif push eax ;Get size of windows client area invoke GetClientRect,hWin,addr rect ;Subtract height of statusbar from bottom pop eax sub rect.bottom,eax ;Add height of toolbar to top pop eax add rect.top,eax ;Get new height of RichEdit window mov eax,rect.bottom sub eax,rect.top mov ht,eax ;Resize RichEdit window invoke MoveWindow,hREd,0,rect.top,rect.right,ht,TRUE .elseif eax==WM_CLOSE invoke WantToSave .if !eax invoke DestroyWindow,hWin .endif .elseif eax==WM_DESTROY invoke DeleteObject,hFont invoke PostQuitMessage,NULL .elseif eax==WM_CONTEXTMENU mov eax,wParam .if eax==hREd mov eax,lParam and eax,0FFFFh mov pt.x,eax mov eax,lParam shr eax,16 mov pt.y,eax invoke GetMenu,hWin invoke GetSubMenu,eax,1 invoke TrackPopupMenu,eax,TPM_LEFTALIGN or TPM_RIGHTBUTTON,pt.x,pt.y,0,hWin,0 xor eax,eax ret .endif .else invoke DefWindowProc,hWin,uMsg,wParam,lParam ret .endif xor eax,eax ret WndProc endp end start 6.2.3. Interfaz del Intérprete de Comandos (TraduceIns.inc) INSTRUCCION struct longitud dd ? ; Longitud de la instruccion para una comparación más rápida instruccion dd ? ; Nombre de la instruccion en lenguaje natural pos db ? ; Número que identifica a la instrucción. Para acceder a ella nent db ? ; Número de parámetros de entrada nsal db ? ; Número de parámetros de salida color dd ? ; Puntero al Color sig dd ? ; Puntero a la siguiente estructura tipo instruccion INSTRUCCION ends Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 72 ;Numero de elementos de la estructura numcamp equ 7 RellenaListaIns PROTO :DWORD,:DWORD 6.2.4. Intérprete de Comandos (TraduceIns.asm) .386 .model flat,stdcall option casemap:none include shlwapi.inc ;Para el manejo de Ristras include kernel32.inc include user32.inc include WINDOWS.INC include TraduceIns.inc includelib shlwapi.lib includelib kernel32.lib includelib user32.lib .data WordFileName db "\reglasComp.txt",0 ASMSection db "REGLAS",0 I1Clave db "LeerPuertoA",0 I2Clave db "EscribirPuertoB",0 I3Clave db "LeerTeclado",0 I4Clave db "EscribirBus",0 I5Clave db "LeerBus",0 I6Clave db "EscribirNBus",0 I7Clave db "LeerNBus",0 I8Clave db "EnviarComandoLCD",0 I9Clave db "EnviarDatoLCD",0 I10Clave db "LeerPuertoB",0 I11Clave db "Mostrar",0 I12Clave db "Asignar",0 ZeroString db 0 ASMColorArray dd 0h,0805F50h,0FFh,666F00h,44F0h,5F8754h,6 dup(0FF0000h) CommentColor dd 808000h .data? MatrizDeSintaxis dd ? .code RellenaEstructuras proc uses edi esi hHeap:DWORD,pBuffer:DWORD, nSize:DWORD, ArrayOffset:DWORD,pArray:DWORD LOCAL pStruct: DWORD ;Puntero a la zona de memoria donde almacenaremos nuestra estructura ;Inicializamos la variables mov edi,pBuffer invoke CharLower,edi mov ecx,nSize ;Almacenaremos el Nombre de la Instruccion, el tamaño y el color AlmacenaNomIns: invoke StrChrI,pBuffer," " mov byte ptr [eax],0 inc eax mov esi,eax ;Guardamos el principio del primer dato ;Reservamos espacio para nuestra estructura invoke HeapAlloc,hHeap,HEAP_ZERO_MEMORY,sizeof INSTRUCCION push esi mov pStruct,eax ;Almacenamos la longitud de la Palabra invoke lstrlen,edi mov esi,pStruct assume esi:ptr INSTRUCCION mov [esi].longitud,eax ;Caragamos un puntero al color Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 73 push ArrayOffset pop [esi].color ;Reservamos espacio para guardar la palabra y lo inicializamos a 0 inc eax invoke HeapAlloc,hHeap, HEAP_ZERO_MEMORY,eax mov [esi].instruccion,eax ;Guardamos en instruccion la direccion de la memoria reservada mov edx,eax invoke lstrcpy,edx,edi ;Copiamos la ristra a donde acabamos de reservar memoria mov eax,pArray .if dword ptr [eax]==0 mov dword ptr [eax],esi .else push dword ptr [eax] pop [esi].sig mov dword ptr [eax],esi .endif pop esi ;Almacenaremos el Numero de la Instruccion AlmacenaNumIns: mov edi,esi invoke StrChrI,edi," " mov byte ptr [eax],0 inc eax mov esi,eax invoke StrToInt,edi push esi mov esi,pStruct mov esi,pStruct assume esi: ptr INSTRUCCION mov [esi].pos,al pop esi ;Almacenamos el numero de parámetros de entrada AlmacenaNumParamEnt: mov edi,esi invoke StrChrI,edi," " mov byte ptr [eax],0 inc eax mov esi,eax invoke StrToInt,edi push esi mov esi,pStruct assume esi: ptr INSTRUCCION mov [esi].nent,al pop esi ;Almacenamos el numero de parámetros de salida AlmacenaNumParamSal: mov edi,esi invoke StrToInt,edi push esi mov esi,pStruct assume esi: ptr INSTRUCCION mov [esi].nsal,al pop esi Terminar: ret RellenaEstructuras endp RellenaListaIns proc uses edi hInstance:HINSTANCE, hMainHeap:DWORD LOCAL buffer[1024]:BYTE LOCAL pTemp:DWORD LOCAL BlockSize:DWORD ;======================================================== ; Inicializamos a Cero la Memoria ;======================================================== ;invoke RtlZeroMemory,addr MatrizDeSintaxis,sizeof MatrizDeSintaxis ;======================================================== ; Obtenemos el Path del fichero ejecutable y le añadimos el fichero ; de sintaxis Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 74 @@: ;======================================================== invoke GetModuleFileName,hInstance,addr buffer,sizeof buffer invoke lstrlen,addr buffer mov ecx,eax dec ecx lea edi,buffer add edi,ecx std mov al,"\" repne scasb cld inc edi mov byte ptr [edi],0 invoke lstrcat,addr buffer,addr WordFileName ;======================================================== ; Comprobamos que existe el archivo ;======================================================== invoke GetFileAttributes,addr buffer .if eax!=-1 ;======================================================== ; Tomar un bloque de memoria de la pila para las ristras ;======================================================== mov BlockSize,1024*10 invoke HeapAlloc,hMainHeap,0,BlockSize mov pTemp,eax invoke GetPrivateProfileString,addr ASMSection,addr I1Clave,addr ZeroString,pTemp,BlockSize,addr buffer .if eax!=0 inc eax mov edx,offset ASMColorArray invoke RellenaEstructuras,hMainHeap,pTemp,eax,edx,addr MatrizDeSintaxis .endif @@: invoke GetPrivateProfileString,addr ASMSection,addr I2Clave,addr ZeroString,pTemp,BlockSize,addr buffer .if eax!=0 mov edx,offset ASMColorArray add edx,4 invoke RellenaEstructuras,hMainHeap,pTemp,eax,edx,addr MatrizDeSintaxis .endif @@: invoke GetPrivateProfileString,addr ASMSection,addr I3Clave,addr ZeroString,pTemp,BlockSize,addr buffer .if eax!=0 mov edx,offset ASMColorArray add edx,8 invoke RellenaEstructuras,hMainHeap,pTemp,eax,edx,addr MatrizDeSintaxis .endif @@: invoke GetPrivateProfileString,addr ASMSection,addr I4Clave,addr ZeroString,pTemp,BlockSize,addr buffer .if eax!=0 mov edx,offset ASMColorArray add edx,12 invoke RellenaEstructuras,hMainHeap,pTemp,eax,edx,addr MatrizDeSintaxis .endif @@: invoke GetPrivateProfileString,addr ASMSection,addr I5Clave,addr ZeroString,pTemp,BlockSize,addr buffer .if eax!=0 mov edx,offset ASMColorArray add edx,16 invoke RellenaEstructuras,hMainHeap,pTemp,eax,edx,addr MatrizDeSintaxis .endif @@: invoke GetPrivateProfileString,addr ASMSection,addr I6Clave,addr ZeroString,pTemp,BlockSize,addr buffer .if eax!=0 mov edx,offset ASMColorArray add edx,20 invoke RellenaEstructuras,hMainHeap,pTemp,eax,edx,addr MatrizDeSintaxis .endif @@: invoke GetPrivateProfileString,addr ASMSection,addr I7Clave,addr ZeroString,pTemp,BlockSize,addr buffer .if eax!=0 mov edx,offset ASMColorArray add edx,24 invoke RellenaEstructuras,hMainHeap,pTemp,eax,edx,addr MatrizDeSintaxis Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 75 @@: .endif invoke GetPrivateProfileString,addr ASMSection,addr I8Clave,addr ZeroString,pTemp,BlockSize,addr buffer .if eax!=0 mov edx,offset ASMColorArray add edx,28 invoke RellenaEstructuras,hMainHeap,pTemp,eax,edx,addr MatrizDeSintaxis .endif @@: invoke GetPrivateProfileString,addr ASMSection,addr I9Clave,addr ZeroString,pTemp,BlockSize,addr buffer .if eax!=0 mov edx,offset ASMColorArray add edx,32 invoke RellenaEstructuras,hMainHeap,pTemp,eax,edx,addr MatrizDeSintaxis .endif @@: invoke GetPrivateProfileString,addr ASMSection,addr I10Clave,addr ZeroString,pTemp,BlockSize,addr buffer .if eax!=0 mov edx,offset ASMColorArray add edx,36 invoke RellenaEstructuras,hMainHeap,pTemp,eax,edx,addr MatrizDeSintaxis .endif @@: invoke GetPrivateProfileString,addr ASMSection,addr I11Clave,addr ZeroString,pTemp,BlockSize,addr buffer .if eax!=0 mov edx,offset ASMColorArray add edx,40 invoke RellenaEstructuras,hMainHeap,pTemp,eax,edx,addr MatrizDeSintaxis .endif @@: invoke GetPrivateProfileString,addr ASMSection,addr I12Clave,addr ZeroString,pTemp,BlockSize,addr buffer .if eax!=0 mov edx,offset ASMColorArray add edx,44 invoke RellenaEstructuras,hMainHeap,pTemp,eax,edx,addr MatrizDeSintaxis .endif invoke HeapFree,hMainHeap,0,pTemp .endif mov eax,offset MatrizDeSintaxis ret RellenaListaIns endp end 6.2.5. Fichero de Reglas de Sintaxis (reglasComp.txt) Estas son las reglas de los comandos y sus parámetros de entrada y salida: ;El significado de las instrucciones es el siguiente: INS PosInstr NºParamEnt NºParamSal [REGLAS] LeerPuertoA= LeerPuertoA 0 0 1 LeerPuertoB= LeerPuertoB 1 0 1 EscribirPuertoA= EscribirPuertoA 2 1 0 EscribirPuertoB= EscribirPuertoB 3 1 0 LeerTeclado= LeerTeclado 4 0 1 EscribirBus= EscribirBus 5 2 0 LeerBus= LeerBus 6 1 1 EscribirNBus= EscribirNBus 7 3 0 LeerNBus= LeerNBus 8 2 1 EnviarDatoLCD= EnviarDatoLCD 9 1 0 EnviarComandoLCD= EnviarComandoLCD 10 1 0 Mostrar= Mostrar 11 1 0 Asignar= Asignar 12 1 1 ; Comandos del LCD; se deben ejecutar siempre y obligatoriamente después de EnviarComandoLCD [COMANDOSLCD] LimpiarDisplay= LimpiarDisplay 0 0 0 IrAInicio= IrAInicio 1 0 0 Modo= Modo 2 2 0 Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 76 DisplayOnOFF= DisplayOnOFF 3 3 0 Desplazamiento= Desplazamiento 4 2 0 FunctionSet= FunctionSet 5 3 0 DireccionCGRAM= DireccionCGRAM 6 1 0 DireccionDDRAM= DireccionDDRAM 7 1 0 Estado= Estado 8 0 2 EscribirRAM= 9 1 0 LeerRAM= 10 0 1 6.2.6. Interfaz del Compilador de Comandos (Compilador.inc) Compila PROTO :DWORD, :DWORD, :DWORD, :DWORD CompilaLinea PROTO :DWORD, :DWORD, :DWORD Ejecutar PROTO :BYTE, :DWORD, :DWORD, :DWORD 6.2.7. Compilador de Comandos (Compilador.asm) .386 .model flat,stdcall option casemap:none include Compilador.inc include Protocolo.inc include TraduceIns.inc include windows.inc include user32.inc include masm32.inc include kernel32.inc include shlwapi.inc ;Para el manejo de Ristras includelib shlwapi.lib includelib kernel32.lib includelib user32.lib includelib masm32.lib VARIABLES struct nombre dd ? ;puntero al nombre de la variable ASCII valor db ? sig dd NULL ;puntero a la sig estructura VARIABLES ends InsertarVariable PROTO :DWORD,:DWORD,:DWORD ListaVariables PROTO :DWORD,:DWORD .data? pVar dd ? paramEnt db 256 dup(?) paramSal dd 256 dup(?) .data idVar db 'var',0 .code InsertarVariable proc uses edi hHeap:DWORD,VarActual:DWORD,pVarSig:DWORD ;----------------------------------------------------------------------; hHeap --> Manejador de la Pila del programa ; VarActual --> Dirección donde esté almacenada la ristra de ; la variable actual (se pasa por valor, es de entrada) ; pVarSig --> Puntero a la variable siguiente (se pasa por puntero, ; es de entrada/salida) ; eax --> Devuelve puntero a la estructura de la variable ; Los registros de propósito general se verán alterados tras la llamada ;----------------------------------------------------------------------; PASO 1: Separar variable actual (*VarActual) de variable siguiente (*VarSig) mov eax,pVarSig mov eax,[eax] mov byte ptr [eax],0 ; PASO 2: Incrementar el puntero a la variable siguiente (VarSig) Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 77 mov eax,pVarSig inc dword ptr [eax] ; PASO 3: Reserva de memoria para insertar la variable actual (toma de bloque) ; y se asigna el puntero a pActual (señalado por ppActual) invoke HeapAlloc,hHeap,HEAP_ZERO_MEMORY,sizeof VARIABLES assume edi: ptr VARIABLES mov edi,eax ; PASO 4: Reserva de memoria para el nombre de la variable invoke lstrlen,VarActual inc eax invoke HeapAlloc,hHeap,HEAP_ZERO_MEMORY,eax ; PASO 5: Creación de estructura (inicializada a 0) mov [edi].nombre,eax invoke lstrcpy,[edi].nombre,VarActual mov [edi].valor,0 mov [edi].sig,NULL ; PASO 6: Retorno del puntero a la estructura de la variable mov eax,edi ret InsertarVariable endp ListaVariables proc uses edi hHeap:DWORD,Buffer:DWORD LOCAL VarActual:DWORD ; Puntero al inicio de la variable actual LOCAL VarSig:DWORD ; Puntero al inicio de la variable siguiente LOCAL pAnterior:DWORD ; Puntero a la estructura de la variable anterior ; PASO 1: Separación de la directiva de variables y primera variable invoke StrChrI,Buffer," " mov byte ptr [eax],0 mov VarActual,eax inc VarActual ; PASO 2: Comprobamos que es la cabecera de definición de variables (idVar) invoke lstrcmp,Buffer,addr idVar .if eax != 0 ret .endif mov pAnterior,0 ; PASO 3: Inserción de todas las variables en la lista .while TRUE ; PASO 3.1: Preparación/Búsqueda de la siguiente variable invoke StrChrI,VarActual," " .if eax != NULL ; PASO 3.2a: Inserción de la variable actual en la lista mov VarSig,eax invoke InsertarVariable,hHeap,VarActual,addr VarSig .if pAnterior==0 mov pVar,eax .else mov edi,pAnterior assume edi: ptr VARIABLES mov [edi].sig,eax .endif ; PASO 3.3a: Actualización del puntero a la estructura de la variable ; anterior (pAnterior) y de la variable actual (VarActual = VarSig) mov pAnterior,eax mov eax,VarSig mov VarActual,eax .else ; PASO 3.2b: Búsqueda del final de la última variable (siempre se encuentra) invoke StrChrI,VarActual,0dh ; PASO 3.3b: Inserción de la última variable en la lista mov VarSig,eax invoke InsertarVariable,hHeap,VarActual,addr VarSig .if pAnterior==0 mov pVar,eax .else mov edi,pAnterior Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 78 assume edi: ptr VARIABLES mov [edi].sig,eax .endif .break .endif .endw ret ListaVariables endp Compila proc uses edx hWnd:DWORD,hREd:DWORD,hHeap:DWORD,MatrizDeSintaxis:DWORD LOCAL Buffer[256]:BYTE ; Buffer para almacenamiento temporal de cada línea LOCAL NLineas:DWORD ; Número de Líneas ; PASO 1: Obtener nº líneas invoke HeapAlloc,hHeap,0,1024*10 invoke SendMessage,hREd,EM_GETLINECOUNT,0,0 ; eax = Número de líneas (0 si no hay ninguna) .if eax==0 mov eax,1 ret ; ERROR 1: No hay líneas que compilar .endif mov NLineas,eax ; PASO 2: Almacenar estructuras de variables invoke SendMessage,hREd,EM_GETLINE,0,addr Buffer invoke ListaVariables,hHeap,addr Buffer ; PASO 3: Compilar líneas mov edx,1 .while edx < NLineas pusha invoke SendMessage,hREd,EM_GETLINE,edx,addr Buffer invoke CompilaLinea,addr Buffer,MatrizDeSintaxis,hWnd popa inc edx .endw mov eax,0 ret Compila endp CompilaLinea proc uses esi edi Linea:DWORD, MatrizDeSintaxis:DWORD, hWnd:DWORD LOCAL nEnt: BYTE ;Nº parámetros de entrada de la Instrucción LOCAL nSal: BYTE ;Nº parámetros de salida de la Instrucción LOCAL pActual:PTR DWORD ;Puntero al elemento actual en el vector de parametros ;de entrada o en el de salida LOCAL cmd: BYTE ;Servira para elegir que comando se le va a enviar al PIC LOCAL posPUno: DWORD ;Almacena la posición del primer parámetro de la instruccion ;PASO 1:Guardamos en edi un puntero a la matriz de sintaxis mov eax,MatrizDeSintaxis mov edi,dword ptr[eax] push edi ;PASO 2:Buscamos la instrucción en nuestra estructura invoke lstrlen,Linea mov edx,eax .if edx > 0 mov esi,Linea ;esi--> Puntero a la ristra ;PASO 2.a: Avanzamos hasta el primer espacio en blanco invoke StrChrI,Linea," " mov byte ptr [eax],0 ;Ponemos el final de ristra y ahora comparamos inc eax ;avanzamos mov posPUno,eax ;Guardamos la posición del primer parámetro invoke CharLower,Linea invoke lstrlen,Linea mov edx,eax ;PASO 2.b: Una vez separado el nombre de la instruccion recorremos la lista ;en busca de esa instruccion pop edi assume edi:ptr INSTRUCCION .while edi != 0 .if edx == [edi].longitud push edx invoke lstrcmpi,[edi].instruccion,esi Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 79 pop edx .if eax == 0 jmp LeeDatosInstruccion .endif .endw .endif push [edi].sig ;Avanzamos a la siguiente estructura pop edi ; EsmsDeInsNoValida: ret LeeDatosInstruccion: ;PASO 3: Almacenamos en nuestras variables locales los datos de la instruccion mov al,[edi].pos mov cmd,al mov al,[edi].nent mov nEnt,al mov al,[edi].nsal mov nSal,al LeeParametrosDeEntrada: ;PASO 4: Leemos los parámetros de entrada mov ecx,posPUno xor eax,eax mov eax,offset paramEnt mov pActual,eax mov dl,nEnt ;dl -> Nº de parámetros de entrada para saber cuantas variables debemos leer .while dl > 0 push edx mov esi,ecx ;copiamos la direccion en esi invoke StrChrI,esi," " ;volvemos a encontrar el primer espacio en blanco .if eax == NULL ;Si solo hay parámetros de entrada invoke StrChrI,esi,13 .endif mov byte ptr [eax],0 inc eax push eax ;Guardamos en la pila--> Comienzo del sig parámetro ;PASO 4.a: Primero miramos si es un numero .if (byte ptr [esi] <= "9") && (byte ptr [esi] >= "0") invoke StrToInt,esi mov edx,dword ptr[pActual] mov byte ptr[edx],al ;PASO 4.b: Si no es un número buscamos de que variable se trata .else mov edi,pVar assume edi: ptr VARIABLES .repeat invoke lstrcmpi,esi,[edi].nombre .if eax == 0 .break .endif mov edi,[edi].sig .until edi == NULL ;PASO 4.b.1: Si la variable existe copiamos en el vector de parámetros ;de entrada su valor .if eax == 0 mov al,[edi].valor mov edx, dword ptr[pActual] mov byte ptr[edx],al .else ret .endif .endif inc pActual pop ecx ;-->Recuperamos el comienzo del siguiente parámetro pop edx ;-->Recuperamos el numero de parámetros que debemos leer dec dl .endw LeeParametrosDeSalida: ;PASO 5: Leemos los parámetros de salida. Tenemos que ;ecx->Posición del primer parametro de salida mov eax,offset paramSal mov pActual,eax Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 80 mov dl,nSal .while dl > 0 push edx mov esi,ecx ;copiamos la direccion en esi invoke StrChrI,esi," " ;Volvemos a encontrar el primer espacio en blanco .if eax == NULL ;Si solo hay parámetros de entrada invoke StrChrI,esi,13 .endif mov byte ptr [eax],0 inc eax push eax ;Guardamos en la pila --> Comuenzo del sig parámetro ;PASO 5.a: Buscamos de que variable se trata mov edi,pVar assume edi: ptr VARIABLES .repeat invoke lstrcmpi,esi,[edi].nombre .if eax == 0 .break .endif mov edi,[edi].sig .until edi == NULL ;PASO 5.a.1: Si la variable existe, guardamos la dirección del valor de la ;variable en el vector de parámetros de salida. .if eax == 0 lea eax,[edi].valor mov edx,pActual mov dword ptr[edx],eax .else ret .endif inc pActual pop ecx ;-->Recuperamos la posicion del siguiente parámetro pop edx ;-->Recuperamos el numero de parámetros que debemos leer dec dl .endw ;-----------------------------------------------------; Ejecutamos ;-----------------------------------------------------invoke Ejecutar,cmd,addr paramEnt,addr paramSal,hWnd .endif ret CompilaLinea endp Ejecutar proc uses edx Comando:BYTE, Entrada:DWORD, Salida:DWORD, hWnd:DWORD LOCAL buffer[2]: BYTE mov dl,Comando ; Enviar al PIC el comando que tiene que servir .if dl <= 0ah invoke Enviar,addr Comando .endif ; Se ejecuta el comando indicado .if dl==00h ; Leer PORTA mov eax,Salida mov eax,dword ptr[eax] invoke Recibir, eax ;Esperar que PIC envie el dato leido .elseif dl==01h ; Leer PORTB mov eax,Salida mov eax,dword ptr[eax] invoke Recibir, eax ;Esperar que PIC envie el dato leido ;.elseif dl==2 ; Escribir PORTA <-- No visible desde el PIC .elseif dl==03h ; Escribir PORTB invoke Enviar,Entrada .elseif dl==04h ; Leer Teclado mov eax,Salida mov eax,dword ptr[eax] invoke Recibir, eax .elseif dl==05h ; Escribir valor en bus I2C ; Dato = Dato a Transmitir mov eax,Entrada invoke Enviar,eax ; Dato = Direccion Fuente de I2C, para escribir el dato Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 81 inc eax invoke Enviar,eax .elseif dl==06h ; Leer valor del bus I2C ;Dato = Direccion Fuente del I2C invoke Enviar, Entrada ;RETARDO mov eax,Salida mov eax,dword ptr[eax] invoke Recibir, eax .elseif dl==07h ; Escribir N valores en bus I2C ; eax = N Valores mov eax,Entrada mov cl,byte ptr [eax];ecx = N Valores a trasmitir invoke Enviar,eax inc eax ;eax = Direccion destino invoke Enviar,eax inc eax ;ecx = N Valores a trasmitir .while cl > 0 invoke Enviar,eax inc eax dec cl .endw .elseif dl==08h ; Leer N valores del bus I2C ; eax = N Valores mov eax,Entrada mov cl,byte ptr [eax];ecx = N Valores a trasmitir invoke Enviar,eax inc eax ;eax = Direccion destino invoke Enviar,eax mov edx,Salida mov eax,dword ptr[edx] ;ecx = N Valores a trasmitir .while cl > 0 invoke Recibir,eax inc edx dec cl .endw .elseif dl==09h ; Enviar Dato a LCD ;Dato = Dato a transmitir al LCD invoke Enviar, Entrada .elseif dl==0ah ; Enviar Comando a LCD ; Entrada = Comando a transmitir invoke Enviar, Entrada .elseif dl==0bh; Mostrar mov eax, Entrada mov dl, byte ptr[eax] add dl,30h lea ecx,buffer mov byte ptr[ecx], dl inc ecx mov byte ptr[ecx], 0 invoke MessageBox,hWnd,addr buffer,addr buffer,MB_OK .elseif dl == 0ch;Asignar mov eax,Entrada mov dl,byte ptr[eax] mov eax,Salida mov eax,dword ptr[eax] mov byte ptr[eax],dl .endif ret Ejecutar endp end 6.2.8. Interfaz del Acceso al Puerto Paralelo (Paralelo.inc) externdef RegDato:WORD externdef RegEstado:WORD Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 82 externdef RegControl:WORD TomarPuerto PROTO LeerPuerto PROTO :DWORD EscribirPuerto PROTO :DWORD 6.2.9. Acceso al Puerto Paralelo (Paralelo.asm) .386 .model flat,stdcall option casemap:none include Paralelo.inc include WinIo.inc includelib WinIo.lib .const LPT1 equ 0408h LPT2 equ LPT1+2 LPT3 equ LPT2+2 .data? RegDato dw ? RegEstado dw ? RegControl dw ? .code TomarPuerto proc uses edx ;----------------------------------------------; Obtención del puerto paralelo ;----------------------------------------------; Puerto Paralelo: ; LPT1 = 0408h ; LPT2 = 0408h + 2h = 040ah ; LPT3 = 040ah + 2h = 040ch ; ; Los valores típicos de cada registro son: ; RegDato = 0378h ; RegEstado = 0379h ; RegControl = 037ah ; Con la llamada a InitializeWinIo se permite el ; acceso a Memoria y Entrada/Salida directo. ; Luego se toma la dirección de los registros ; del puerto paralelo. ;----------------------------------------------invoke InitializeWinIo invoke GetPhysLong,LPT1,offset RegDato mov dx,RegDato inc dx mov RegEstado,dx inc dx mov RegControl,dx ret TomarPuerto endp LeerPuerto proc uses edx Reg:DWORD ;----------------------------------------------; Leer del Puerto Paralelo ;----------------------------------------------mov edx,Reg mov dx,word ptr [edx] in al,dx ret LeerPuerto endp EscribirPuerto proc uses edx Reg:DWORD ;----------------------------------------------; Escribir en el Puerto Paralelo ;----------------------------------------------mov edx,Reg mov dx,word ptr [edx] out dx,al ret Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 83 EscribirPuerto endp end 6.2.10. Interfaz del Protocolo del Puerto Paralelo (Protocolo.inc) Inicializar PROTO Enviar PROTO :DWORD Recibir PROTO :DWORD TestPuerto PROTO 6.2.11. Protocolo del Puerto Paralelo (Protocolo.asm) .386 .model flat,stdcall option casemap:none include Paralelo.inc include Protocolo.inc .const PC_LISTO equ 00000001b ; Bit del Puerto Paralelo que indica el estado del PC (usa logica inversa) PIC_LISTO equ 01000000b ; Bit del Puerto Paralelo que indica el estado del PIC (no usa logica inversa) C5 equ 00100000b ; Bit del puerto bidireccional (C5 == 1 --> Entrada (E/S); C5 == 0 --> Salida) .code Inicializar proc ;----------------------------------------------; Inicializa el Puerto Paralelo ;----------------------------------------------; Se toman los registros del Puerto Paralelo y ; se asegura que esté en modo bidireccional. ;----------------------------------------------invoke TomarPuerto ; Desactivación del modo bidireccional, para que inicialmente ; sea de salida (y no tome valores que le entren, lo que se usara ; en Enviar) invoke LeerPuerto,offset RegControl mov ah,C5 not ah and al,ah invoke EscribirPuerto,offset RegControl ; Se pone a 0ffh el puerto de Datos para que no afecte mov al,0ffh invoke EscribirPuerto,offset RegDato invoke LeerPuerto,offset RegControl or al,PC_LISTO ; Se pone a 1 (en realidad 0) al(PC_LISTO) invoke EscribirPuerto,offset RegControl ret Inicializar endp Enviar proc Dato:DWORD ;----------------------------------------------; Envía Dato de PC al PIC ;----------------------------------------------; Realiza el envío del byte Dato desde el PC ; al PIC, según el protocolo. ;----------------------------------------------; PASO 1: Esperar a que el PIC esté listo para recibir el Dato (Estado de Reposo) .repeat invoke LeerPuerto,offset RegEstado and al,PIC_LISTO .until !al ; El PIC estará listo cuando al(PIC_LISTO) == 0 ; PASO 2: El PC pone el Dato en el puerto (se lo envía al PIC) ; (primero se pone el puerto de salida; aunque normalmente ; no es necesario hacerlo, pues es el estado por defecto) invoke LeerPuerto,offset RegControl Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 84 mov ah,C5 not ah and al,ah invoke EscribirPuerto,offset RegControl mov eax,Dato mov al,byte ptr [eax] ; No hay que controlar invoke EscribirPuerto,offset RegDato ; PASO 3: Indicación de que PC va a transmitir al PIC invoke LeerPuerto,offset RegControl mov ah,PC_LISTO not ah and al,ah ; Se pone a 0 (en la linea a 1) al(PC_LISTO) invoke EscribirPuerto,offset RegControl ; PASO 4: Esperar a que el PIC indique que ha recibido correctamente el Dato .repeat invoke LeerPuerto,offset RegEstado and al,PIC_LISTO .until al ; El PIC indica la toma del dato con al(PIC_LISTO) == 1 ; PASO 5: El PC deja de indicar que transmite el Dato al PIC, pues se terminó ; la transmisión, permitiendo que el PIC comience a trabajar invoke LeerPuerto,offset RegControl or al,PC_LISTO ; Se pone a 1 (en realidad 0) al(PC_LISTO) invoke EscribirPuerto,offset RegControl ; PASO 6: Esperar a que el PIC esté de nuevo en Estado de Reposo, que es realmente de ; ejecución del comando/instrucción. Este paso es opcional, pero es más seguro hacerlo .repeat invoke LeerPuerto,offset RegEstado and al,PIC_LISTO .until al ; El PIC estará en reposo (ejecutando el comando) cuando al(PIC_LISTO) == 0 ; FINAL: Ahora el PIC procesará el comando/instrucción asociada al comando, ; mientras el PC espera que el usuario solicite la ejecución de otro nuevo ret Enviar endp Recibir proc uses edx Dato:DWORD ;----------------------------------------------; Recibe Dato desde PIC ;----------------------------------------------; Realiza la recepción del byte Dato enviado ; desde el PIC, devolviéndolo en Dato, según el ; protocolo. ;----------------------------------------------; PASO 1: Poner PC en estado de envio (PC_LISTO == 1); a 0 en la linea invoke LeerPuerto,offset RegControl or al,PC_LISTO invoke EscribirPuerto,offset RegControl ; PASO 2: Se pone el puerto de Datos de Entrada (Entrada/Salida) invoke LeerPuerto,offset RegControl or al,C5 invoke EscribirPuerto,offset RegControl ; PASO 3: Esperar a que el PIC indique el Dato está listo para recibirse .repeat invoke LeerPuerto,offset RegEstado and al,PIC_LISTO .until al ; El PIC indica que el dato está listo con al(PIC_LISTO) == 1 sin logica negada ; PASO 4: El PC lee el Dato invoke LeerPuerto,offset RegDato mov edx,Dato mov byte ptr [edx],al invoke LeerPuerto,offset RegControl mov ah,C5 not ah and al,ah Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 85 invoke EscribirPuerto,offset RegControl ; PASO 5: El PC indica que ya recibió el Dato (PC_LISTO == 0) invoke LeerPuerto,offset RegControl mov ah,PC_LISTO not ah and al,ah invoke EscribirPuerto,offset RegControl ; PASO 6: El PC espera que el PIC deje de enviar el Dato (a que se de cuenta ; de que el PC ya recibió el Dato) .repeat invoke LeerPuerto,offset RegEstado and al,PIC_LISTO .until !al ; El PIC finalizará la transmisión, con al(PIC_LISTO) == 0 sin logica negada ; PASO 7: Poner PC en estado de no reposo; a 0 en la linea invoke LeerPuerto,offset RegControl or al,PC_LISTO invoke EscribirPuerto,offset RegControl ; FINAL: Ahora el PC ha recibido el Dato y lo procesará, mientras el PIC estará ; en reposo, sin transmitir, pero podrá estar en la ejecución de un comandp ret Recibir endp TestPuerto proc uses eax ecx edx ;----------------------------------------------; Bucle de testeo para el puerto Paralelo ;----------------------------------------------; Direcciones standard del puerto paralelo: ; Dato --> 378h ; Estado --> 379h ; Control --> 37ah ;----------------------------------------------mov cx,1 ; Valores por defecto para puerto (dx) y dato (al) mov dx, RegDato mov dx, RegEstado mov dx, RegControl mov al,20h ; 20h activaria C5 en RegControl .while cx==1 out dx,al in al,dx .endw ret TestPuerto endp end 6.2.12. Interfaz de la Librería Winio (WinIo.inc) Las funciones de las que dispone son: ; C:\MASM32\DRO\WinIo.lib PROTOTYPES ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ GetPhysLong PROTO :DWORD,:DWORD GetPortVal PROTO :DWORD,:DWORD,:DWORD InitializeWinIo PROTO InstallWinIoDriver PROTO :DWORD,:DWORD MapPhysToLin PROTO :DWORD,:DWORD,:DWORD RemoveWinIoDriver PROTO SetPhysLong PROTO :DWORD,:DWORD SetPortVal PROTO :DWORD,:DWORD,:DWORD ShutdownWinIo PROTO UnmapPhysicalMemory PROTO :DWORD,:DWORD Trabajo – Emulador de Instrucciones para el PIC, con Comunicación por Puerto Paralelo 86