Vistas de página en total

lunes, 16 de diciembre de 2013

EMULADOR 8051 (1/12) (INTERPRETE)

   Como parte fundamental de un emulador está el desensamblador.
    Ya tenemos resuelto el problema del Hardware, y conocemos como vamos a controlar las señales provenientes del microprocesador y la paca prototipo. Para ello he puesto los esquemas de las tarjetas y las funciones que darán el interface con las tarjetas.

    Ahora podemos ponernos al meollo de la cuestión. El entrono del depurador.
    Al menos debe de contener las siguientes características.
              - Debe de tener una ventana de datos de memoria, en la que se puedan modificar y visualizar estos.
              - Debe de ser capaz de mostrar el programa desensambaldo.
              - Debe mostrar los registros del microprocesador.
              - Debe de tener modo de ejecución continuo, paso a paso y puntos de ruptura.
              - Debe de se amigable, aunque lo estemos realizando en DOS y en Ensamblador.

    El hecho de usar ensamblador y no utilizar un entorno gráfico, fue debido en su momento a la necesidad de velocidad. Ahora, aunque los ordenadores son más rápidos, la velocidad nunca bien mal.
    ¿Por qué tanta necesidad de velocidad? Porque el traceo lo hace el ordenador directamente. Es el ordenador el que da los pulsos de reloj, para ir ejecutando las instrucciones, y además entre pulsos va a tomar datos de la placa a través de los buses. Por ello, si queremos tracear a una buena velocidad, necesitamos que el manejo del Hard sea lo más rápido posible. Necesitamos que la interpretación del código, el desensamblado también, y sobre todo necesitamos que la respuesta en pantalla sea instantanea.

    Es por ese motivo que la función Puts y Printf, que en su momento ya he puesto en el blog, no esté hecha mediante llamadas al DOS, sino mediante volcado directo a pantalla.

    Para  empezar el depurador, por lo tanto, voy  a comenzar por la rutina principal. La que desensambla.
    Para realizar el desensamblador de una forma muy rápida y con poco código, creo una tabla de desensamblado, en la cuál asignaremos un a serie de datos a cada código que encontremos en código máquina.
   
     Así mediante una tabla de indexado, llamada Tabla, accederemos a una tabla de interpretación de código. Cuando leamos un byte de código máquina, usaremos Tabla localizar el formato de la instrución que codifica.
   A través de ese puntero recorreremos la lista correspondiente de datos de la instrucción, que nos indicará:  

    1. Byte 0 =  tipo instrucción
      •  0  ----  inmediato            (1 byte)
      •  1  ----  texto + num 8 bits   (2 bytes)
      •  2  ----  texto + num 16 bits  (3 bytes)
      •  3  ----  texto + num 8bits + texto
      •  4  ----  texto + num 16 bits + texto
      •  5  ----  texto + num 8 bits relativo + texto + num 8 bits
      •  6  ----  texto + num 8 bits relativo
      •  7  ----  texto + num 8 bits relativo + texto
      •  8  ----  texto + num 8bits + texto + num 8 bits
      •  9  ----  texto + num 8 bits + texto + num 8 bits relativo
      • 10  ----  texto + num 11bits  (2 bytes)
      • 11  ----  texto + num 8 bits ref_bit
      • 12  ----  texto + num 8 bits ref_bit + texto
      • 13  ----  texto + num 8 bits ref_bit + texto + num 8 bits relativo
      • 0ffh  ----  nueva tabla INSTRUCCION COMPUESTA
    1. byte 1 = Ciclos de reloj de la instrución
    2. byte 2= numero de bytes de la instrucción
    3. byte 3 en adelante =  Nemónico  
La tabla de indexación Tabla, se enlaza a nivel de compilación con la lista de instruciiones mediante una macro que crea todas las etiquetas de forma automática.
     Estas macros son INSTRUC y TABLA_INST. La primera crea la etiqueta con los valores del contador de TABLA_INST y esta última lleva la cuenta del bucle, en este caso 100h, colocando delante un nemónico inicial "I", que es enviado en todas las llamadas posteriores a INSTRUC, para que lo coloque delante del número del contador.
     Esta función interpretará el código de insgtrucción indicando el formato que debe usarse en la lectura de los siguientes bytes. Para ello, interpreta el código de instrucción y devuelve un puntero a la cadena de interpretación. Esta cadena contiene la estructura de textos que han de colocarse, insertando entre medias los datos que correspondan.
      Por lo tanto esta función es subrutina de una de mayor nivel que desensamblará la instrucción completa.
        Este interprete es un proceso muy sencillo que he utilizado en otros lenguajes para desensamblar otros códigos máquina, resultando muy eficaz y sencillo de programar.
        Esta función no solo servirá para reconocer la instrucción al presentaral en pantalla o listarla. Sino mucho más importante será usada por las rutinas de ejecución para conocer el numero de pulsos de reloj de cada instrucción de ensamblador, y poder así ejecutar la instrucción por completo.

INTERPRETA.ASM
; Copyright (C) 2013  José Ángel Moneo Fernández

;    This program is free software: you can redistribute it and/or modify
;    it under the terms of the GNU General Public License as published by
;    the Free Software Foundation, either version 3 of the License, or
;   (at your option) any later version.

;    This program is distributed in the hope that it will be useful,
;    but WITHOUT ANY WARRANTY; without even the implied warranty of
;    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;    GNU General Public License for more details.

;    You should have received a copy of the GNU General Public License
;    along with this program.  If not, see <http://www.gnu.org/licenses/


include main.mac
include stdio.mac
include window.mac
include 8051.mac
include 48iocent.mac

 public _interpreta

;definición de instrución en la tabla
INSTRUC MACRO ETI,X
IFDEF ETI&X
        DB X
        DW ETI&X
ENDIF
endm
;macro para definir tabla de instrucciones
TABLA_INST MACRO ETI
        LB=0
          REPT 100h
          INSTRUC ETI,%LB
          LB=LB+1
          ENDM
 ENDM

DIR_RESET EQU 0  
 
.model compact,pascal


.data




;CONTROL
;Lista de instrucciones

; estructura de la entrada en la tabla
     ;byte 0 =tipo instruciión
               ;0 inmediato            (1 byte)
               ;1 texto + num 8 bits   (2 bytes)
               ;2 texto + num 16 bits  (3 bytes)
               ;3 texto + num 8bits + texto
               ;4 texto + num 16 bits + texto
                ;5 texto + num 8 bits relativo + texto + num 8 bits
               ;6 texto + num 8 bits relativo
               ;7 texto + num 8 bits relativo + texto
               ;8 texto + num 8bits + texto + num 8 bits
               ;9 texto + num 8 bits + texto + num 8 bits relativo
               ;10 texto + num 11bits  (2 bytes)
               ;11 texto + num 8 bits ref_bit
               ;12 texto + num 8 bits ref_bit + texto
               ;13 texto + num 8 bits ref_bit + texto + num 8 bits relativo
               ;0ffh nueva tabla INSTRUCCION COMPUESTA
     ;byte 1 = Ciclos de reloj de la instrución
     ;byte 2= numero de bytes de la instruciión
     ;Resto hasta "0" Nemónico
   
I0 DB 0,12,1,"NOP",0
I1 DB 10,24,2,"AJMP ",0
I2 DB 2,24,3,"LJMP ",0
I3 DB 0,12,1,"RR A",0
I4 DB 0,12,1,"INC A",0
I5 DB 1,12,2,"INC ",0
I6 DB 0,12,1,"INC @R0",0
I7 DB 0,12,1,"INC @R1",0
I8 DB 0,12,1,"INC R0",0
I9 DB 0,12,1,"INC R1",0
I10 DB 0,12,1,"INC R2",0
I11 DB 0,12,1,"INC R3",0
I12 DB 0,12,1,"INC R4",0
I13 DB 0,12,1,"INC R5",0
I14 DB 0,12,1,"INC R6",0
I15 DB 0,12,1,"INC R7",0
I16 DB 13,24,3,"JBC ",0,",",0
I17 db 10,24,2,"ACALL ",0
I18 DB 2,24,3,"LCALL ",0
I19 DB 0,12,1,"RRC A",0
I20 DB 0,12,1,"DEC A",0
I21 DB 1,12,2,"DEC ",0
I22 DB 0,12,1,"DEC @R0",0
I23 DB 0,12,1,"DEC @R1",0
I24 DB 0,12,1,"DEC R0",0
I25 DB 0,12,1,"DEC R1",0
I26 DB 0,12,1,"DEC R2",0
I27 DB 0,12,1,"DEC R3",0
I28 DB 0,12,1,"DEC R4",0
I29 DB 0,12,1,"DEC R5",0
I30 DB 0,12,1,"DEC R6",0
I31 DB 0,12,1,"DEC R7",0
I32 DB 13,24,3,"JB ",0,",",0
I33 DB 10,24,2,"AJMP ",0
I34 DB 0,24,1,"RET",0
I35 DB 0,12,1,"RL A",0
I36 DB 1,12,2,"ADD A,#",0
I37 DB 1,12,2,"ADD A,",0
I38 DB 0,12,1,"ADD A,@R0",0
I39 DB 0,12,1,"ADD A,@R1",0
I40 DB 0,12,1,"ADD A,R0",0
I41 DB 0,12,1,"ADD A,R1",0
I42 DB 0,12,1,"ADD A,R2",0
I43 DB 0,12,1,"ADD A,R3",0
I44 DB 0,12,1,"ADD A,R4",0
I45 DB 0,12,1,"ADD A,R5",0
I46 DB 0,12,1,"ADD A,R6",0
I47 DB 0,12,1,"ADD A,R7",0
I48 DB 13,24,3,"JNB ",0,",",0
I49 db 10,24,2,"ACALL ",0
I50 DB 0,24,1,"RETI",0
I51 DB 0,12,1,"RLC A",0
I52 DB 1,12,2,"ADDC A,#",0
I53 DB 1,12,2,"ADDC A,",0
I54 DB 0,12,1,"ADDC A,@R0",0
I55 DB 0,12,1,"ADDC A,@R1",0
I56 DB 0,12,1,"ADDC A,R0",0
I57 DB 0,12,1,"ADDC A,R1",0
I58 DB 0,12,1,"ADDC A,R2",0
I59 DB 0,12,1,"ADDC A,R3",0
I60 DB 0,12,1,"ADDC A,R4",0
I61 DB 0,12,1,"ADDC A,R5",0
I62 DB 0,12,1,"ADDC A,R6",0
I63 DB 0,12,1,"ADDC A,R7",0
I64 DB 13,24,3,"JC ",0,",",0
I65 db 10,24,2,"AJMP ",0
I66 DB 3,12,2,"ORL ",0,",A",0
I67 DB 8,24,3,"ORL ",0,",#",0
I68 DB 1,12,2,"ORL A,#",0
I69 DB 1,12,2,"ORL A,",0
I70 DB 0,12,1,"ORL A,@R0",0
I71 DB 0,12,1,"ORL A,@R1",0
I72 DB 0,12,1,"ORL A,R0",0
I73 DB 0,12,1,"ORL A,R1",0
I74 DB 0,12,1,"ORL A,R2",0
I75 DB 0,12,1,"ORL A,R3",0
I76 DB 0,12,1,"ORL A,R4",0
I77 DB 0,12,1,"ORL A,R5",0
I78 DB 0,12,1,"ORL A,R6",0
I79 DB 0,12,1,"ORL A,R7",0
I80 DB 13,24,3,"JNC ",0,",",0
I81 db 10,24,2,"ACALL ",0
I82 DB 3,12,2,"ANL ",0,",A",0
I83 DB 8,24,3,"ANL ",0,",#",0
I84 DB 1,12,2,"ANL A,#",0
I85 DB 1,12,2,"ANL A,",0
I86 DB 0,12,1,"ANL A,@R0",0
I87 DB 0,12,1,"ANL A,@R1",0
I88 DB 0,12,1,"ANL A,R0",0
I89 DB 0,12,1,"ANL A,R1",0
I90 DB 0,12,1,"ANL A,R2",0
I91 DB 0,12,1,"ANL A,R3",0
I92 DB 0,12,1,"ANL A,R4",0
I93 DB 0,12,1,"ANL A,R5",0
I94 DB 0,12,1,"ANL A,R6",0
I95 DB 0,12,1,"ANL A,R7",0
I96 DB 13,24,3,"JZ ",0,",",0
I97 db 10,24,2,"AJMP ",0
I98 DB 3,12,2,"XRL ",0,",A",0
I99 DB 8,24,3,"XRL ",0,",#",0
I100 DB 1,12,2,"XRL A,#",0
I101 DB 1,12,2,"XRL A,",0
I102 DB 0,12,1,"XRL A,@R0",0
I103 DB 0,12,1,"XRL A,@R1",0
I104 DB 0,12,1,"XRL A,R0",0
I105 DB 0,12,1,"XRL A,R1",0
I106 DB 0,12,1,"XRL A,R2",0
I107 DB 0,12,1,"XRL A,R3",0
I108 DB 0,12,1,"XRL A,R4",0
I109 DB 0,12,1,"XRL A,R5",0
I110 DB 0,12,1,"XRL A,R6",0
I111 DB 0,12,1,"XRL A,R7",0
I112 DB 13,24,3,"JNZ ",0,",",0
I113 db 10,24,2,"ACALL ",0
I114 DB 11,24,2,"ORL C,",0
I115 DB 0,24,1,"JMP @A+DPTR",0
I116 DB 1,12,2,"MOV A,#",0
I117 DB 8,24,3,"MOV ",0,",#",0
I118 DB 1,12,2,"MOV @R0,#",0
I119 DB 1,12,2,"MOV @R1,#",0
I120 DB 1,12,2,"MOV R0,#",0
I121 DB 1,12,2,"MOV R1,#",0
I122 DB 1,12,2,"MOV R2,#",0
I123 DB 1,12,2,"MOV R3,#",0
I124 DB 1,12,2,"MOV R4,#",0
I125 DB 1,12,2,"MOV R5,#",0
I126 DB 1,12,2,"MOV R6,#",0
I127 DB 1,12,2,"MOV R7,#",0
I128 DB 6,24,2,"SJMP ",0
I129 db 10,24,2,"AJMP ",0
I130 DB 11,24,2,"ANL C,",0
I131 DB 0,24,1,"MOVC A,@A+PC",0
I132 DB 0,48,1,"DIV AB",0
I133 DB 8,24,3,"MOV ",0,",",0
I134 DB 3,24,3,"MOV ",0,",@R0",0
I135 DB 3,24,3,"MOV ",0,",@R1",0
I136 DB 3,24,3,"MOV ",0,",R0",0
I137 DB 3,24,3,"MOV ",0,",R1",0
I138 DB 3,24,3,"MOV ",0,",R2",0
I139 DB 3,24,3,"MOV ",0,",R3",0
I140 DB 3,24,3,"MOV ",0,",R4",0
I141 DB 3,24,3,"MOV ",0,",R5",0
I142 DB 3,24,3,"MOV ",0,",R6",0
I143 DB 3,24,3,"MOV ",0,",R7",0
I144 DB 2,24,3,"MOV DPTR,#",0
I145 db 10,24,0,"ACALL ",0
I146 DB 12,24,0,"MOV ",0,",C",0
I147 DB 0,24,0,"MOVC A,@A+PC",0
I148 DB 1,12,0,"SUBB A,#",0
I149 DB 1,12,0,"SUBB A,",0
I150 DB 0,12,0,"SUBB A,@R0",0
I151 DB 0,12,0,"SUBB A,@R1",0
I152 DB 0,12,0,"SUBB A,R0",0
I153 DB 0,12,0,"SUBB A,R1",0
I154 DB 0,12,0,"SUBB A,R2",0
I155 DB 0,12,0,"SUBB A,R3",0
I156 DB 0,12,0,"SUBB A,R4",0
I157 DB 0,12,0,"SUBB A,R5",0
I158 DB 0,12,0,"SUBB A,R6",0
I159 DB 0,12,0,"SUBB A,R7",0
I160 DB 11,24,0,"ORL C,/",0
I161 db 10,24,0,"AJMP ",0
I162 DB 11,24,0,"MOV C,",0
I163 DB 0,24,0,"INC DPTR",0
I164 DB 0,48,0,"MUL AB",0
I165 DB 0,12,0,"????",0
I166 DB 1,12,0,"MOV @R0,",0
I167 DB 1,12,0,"MOV @R1,",0
I168 DB 1,12,0,"MOV R0,",0
I169 DB 1,12,0,"MOV R1,",0
I170 DB 1,12,0,"MOV R2,",0
I171 DB 1,12,0,"MOV R3,",0
I172 DB 1,12,0,"MOV R4,",0
I173 DB 1,12,0,"MOV R5,",0
I174 DB 1,12,0,"MOV R6,",0
I175 DB 1,12,0,"MOV R7,",0
I176 DB 11,24,0,"ANL C,/",0
I177 db 10,24,0,"ACALL ",0
I178 DB 11,12,0,"CPL ",0
I179 DB 0,12,0,"CPL C",0
I180 DB 9,24,0,"CJNE A,#",0,",",0
I181 DB 9,24,0,"CJNE A,",0,",",0
I182 DB 9,24,0,"CJNE @R0,#",0,",",0
I183 DB 9,24,0,"CJNE @R1,#",0,",",0
I184 DB 9,24,0,"CJNE R0,#",0,",",0
I185 DB 9,24,0,"CJNE R1,#",0,",",0
I186 DB 9,24,0,"CJNE R2,#",0,",",0
I187 DB 9,24,0,"CJNE R3,#",0,",",0
I188 DB 9,24,0,"CJNE R4,#",0,",",0
I189 DB 9,24,0,"CJNE R5,#",0,",",0
I190 DB 9,24,0,"CJNE R6,#",0,",",0
I191 DB 9,24,0,"CJNE R7,#",0,",",0
I192 DB 1,24,0,"PUSH ",0
I193 db 10,24,0,"AJMP ",0
I194 DB 11,12,0,"CLR ",0
I195 DB 0,12,0,"CLR C",0
I196 DB 0,12,0,"SWAP A",0
I197 DB 1,12,0,"XCH A,",0
I198 DB 0,12,0,"XCH A,@R0",0
I199 DB 0,12,0,"XCH A,@R1",0
I200 DB 0,12,0,"XCH A,R0",0
I201 DB 0,12,0,"XCH A,R1",0
I202 DB 0,12,0,"XCH A,R2",0
I203 DB 0,12,0,"XCH A,R3",0
I204 DB 0,12,0,"XCH A,R4",0
I205 DB 0,12,0,"XCH A,R5",0
I206 DB 0,12,0,"XCH A,R6",0
I207 DB 0,12,0,"XCH A,R7",0
I208 DB 1,24,0,"POP ",0
I209 db 10,24,0,"ACALL ",0
I210 DB 11,12,0,"SETB ",0
I211 DB 0,12,0,"SETB C",0
I212 DB 0,12,0,"DA A",0
I213 DB 9,24,0,"DJNZ ",0,",",0
I214 DB 0,12,0,"XCHD A,@R0",0
I215 DB 0,12,0,"XCHD A,@R1",0
I216 DB 6,24,0,"DJNE R0,",0,",",0
I217 DB 6,24,0,"DJNE R1,",0,",",0
I218 DB 6,24,0,"DJNE R2,",0,",",0
I219 DB 6,24,0,"DJNE R3,",0,",",0
I220 DB 6,24,0,"DJNE R4,",0,",",0
I221 DB 6,24,0,"DJNE R5,",0,",",0
I222 DB 6,24,0,"DJNE R6,",0,",",0
I223 DB 6,24,0,"DJNE R7,",0,",",0
I224 DB 0,24,0,"MOVX A,@DPTR",0
I225 db 10,24,0,"AJMP ",0
I226 DB 0,24,0,"MOVX A,@R0",0
I227 DB 0,24,0,"MOVX A,@R1",0
I228 DB 0,12,0,"CLR A",0
I229 DB 1,12,0,"MOV A,",0
I230 DB 0,12,0,"MOV A,@R0",0
I231 DB 0,12,0,"MOV A,@R1",0
I232 DB 0,12,0,"MOV A,R0",0
I233 DB 0,12,0,"MOV A,R1",0
I234 DB 0,12,0,"MOV A,R2",0
I235 DB 0,12,0,"MOV A,R3",0
I236 DB 0,12,0,"MOV A,R4",0
I237 DB 0,12,0,"MOV A,R5",0
I238 DB 0,12,0,"MOV A,R6",0
I239 DB 0,12,0,"MOV A,R7",0
I240 DB 0,24,0,"MOV @DPTR,A",0
I241 db 10,24,0,"ACALL ",0
I242 DB 0,24,0,"MOVX @R0,A",0
I243 DB 0,24,0,"MOVX @R1,A",0
I244 DB 0,12,0,"CPL A",0
I245 DB 3,12,0,"MOV ",0,",A",0
I246 DB 0,12,0,"MOV @R0,A",0
I247 DB 0,12,0,"MOV @R1,A",0
I248 DB 0,12,0,"MOV R0,A",0
I249 DB 0,12,0,"MOV R1,A",0
I250 DB 0,12,0,"MOV R2,A",0
I251 DB 0,12,0,"MOV R3,A",0
I252 DB 0,12,0,"MOV R4,A",0
I253 DB 0,12,0,"MOV R5,A",0
I254 DB 0,12,0,"MOV R6,A",0
I255 DB 0,12,0,"MOV R7,A",0


Tabla LABEL BYTE
   Tabla_inst I        ;tabla a lista de instrucciones
   db 0ffh              ;localizador final tabla

no_conocido db 0,0,0,"????",0      ; codigo no desensamblable
nada db 0

   
.code
           
_interpreta proc uses si bx
           ;se indicará en DX direccion de la
           ;instruccion a decodificar y devolverá
           ;en ax la direccion de la lista de interpretado
        mov si,offset Tabla

busca_mas:
        leeinst
        inc dx
 busca:
        cmp al,[si]
        jz localizado
        jb n_conoc
        cmp byte ptr [si],0ffh
        jz n_conoc
        add si,3
        jmp busca
n_conoc:
        mov bx,offset no_conocido
        jmp fin

localizado:
        mov bx,[si+1]           ;se lee la direccion de la instruccion
        mov al,[bx]             ;de la secuencia se lee el indicador
        cmp al,0ffh             ;indicador de instruccion compuesta
        jne fin
        mov si,[BX+1]
        jmp busca_mas
 fin:
        mov ax,bx
        ret
_interpreta endp

end

jueves, 12 de diciembre de 2013

LIBRERÍA EMULADOR 8051

   Librería de funciones básicas de control de la targeta  8051 del emulador.



EMU8051.ASM


; Copyright (C) 2013  José Ángel Moneo Fernández

;    This program is free software: you can redistribute it and/or modify
;    it under the terms of the GNU General Public License as published by
;    the Free Software Foundation, either version 3 of the License, or
;   (at your option) any later version.

;    This program is distributed in the hope that it will be useful,
;    but WITHOUT ANY WARRANTY; without even the implied warranty of
;    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;    GNU General Public License for more details.

;    You should have received a copy of the GNU General Public License
;    along with this program.  If not, see <http://www.gnu.org/licenses/

include io.mac
include 48iocent.mac
include 8051.mac

PASO EQU 1

        public _Dma_on
        public _Dma_off
        public _lee
        public _reset
        public _trace

        public _Guarda
        public _Toma  
       
        extrn des_base:word
        extrn Dir_cur:word
        extrn CONTROL:word
   
.model compact,pascal


.data
dir dw 0
paro db "Microprocesador Parado",0
.code

_Dma_on proc near
        pioconfig p0,PSALIDA
        ret
_Dma_on endp

_Dma_off proc near
        pioconfig P1,PA PB PCL PCH
        pioconfig P0,PSALIDA
        outpio p0,BUSRQPC_ON
        ret
_Dma_off endp


;lectura zona programa
_leeinst proc uses bx cx dx
        Dma_on
        pioconfig P1,PCL
        outpio P1,WR_ON
        outpio P1,RD_ON
        outpio P1,ALE_ON
        outpio P1,PSEN_ON
        outpio PA1,dl
        pioconfig P1,PA PCL
        outpio PB1,dh
        outpio P1,WR_ON
        outpio P1,RD_ON
        outpio P1,PSEN_OFF   ;LA LECTURA SE HACE CON RD=1 Y PSEN=0 POR SER PROGRAMA
        inpio al,PA1
        push ax
        outpio P1,RD_ON
        Dma_off
        pop ax

        ret
_leeinst endp

;lectura un dato de  area de datos
;dir =dirección de lectura en ram del prototipo
;devuelve el dato en al
_lee proc near uses bx cx dx,dir
        mov dx,dir
        Dma_on
        pioconfig P1,PCL
        outpio P1,WR_ON
        outpio P1,RD_ON   ;PSEN=1 POR SER LECTURA DE DATOS
        outpio P1,ALE_ON
        outpio P1,PSEN_ON
        outpio PA1,dl
        pioconfig P1,PA PCL
        outpio PB1,dh
        outpio P1,WR_ON
        outpio P1,PSEN_ON
        inpio al,PA1
        push ax
        outpio P1,RD_ON
        Dma_off
        pop ax
        ret
_lee endp


;Guarda de zona de datos
; dir =dirección de volcado en ram del prototipo
; dirección =puntero a los datos a volcar
; long =cantidad de datos
_Guarda proc uses bx dx,dir,dato,long
        mov dx,dir
        mov bx,dato
        mov cx,long
        Dma_on
        pioconfig P1,PCL
        outpio P1,WR_ON
        outpio P1,RD_ON   ;PSEN=1 POR SER LECTURA DE DATOS
        outpio P1,PSEN_ON
Guard:
        outpio P1,ALE_ON
        outpio PA1,dl
        outpio PB1,dh
        outpio P1,ALE_OFF
        outpio PA1,[bx]
        outpio P1,WR_OFF
        outpio P1,WR_ON
        inc bx
        inc dx
        loop guard
        Dma_off
        ret
_guarda endp

;toma una zona de datos
 ;lee una zona de memoria
; dir =dirección de volcado en ram del prototipo
; dirección =puntero a los datos a volcar
; long =cantidad de datos
_Toma proc uses bx dx,dir,datos,long
        mov dx,dir
        mov bx,dato
        mov cx,long
        Dma_on
Tom:
        lee dx,bx,cx
        mov [bx],al
        inc bx
        inc dx
        loop Tom
        Dma_off
        ret
_Toma endp

;ejecuta una instruciión
;en pulsos se pasará el numero de pulsos de reloj de la instrucción

_trace proc uses cx dx, pulsos
        pioconfig P1,PCL
        outpio P1,WR_ON
        outpio P1,RD_ON   ;PSEN=1 POR SER LECTURA DE DATOS
        outpio P1,PSEN_ON
        outpio P1,ALE_ON
        mov dx,Dir_cur
        outpio PA1,dl
        outpio P1,ALE_OFF
        Dma_off
        mov cx,pulsos
        dec cx
buc:
        outpio P0,CLK_ON
        outpio P0,CLK_OFF
        loop buc
busca_ale:
        inpio dl,PC1
        test dl,ALE
        jnz falta
        test dl,PSEN
        jnz final
falta:
        inpio dl,PA1
        inpio dh,PB1
        mov Dir,dx
        outpio P0,CLK_ON
        outpio P0,CLK_OFF
        jmp short busca_ale
final:  mov dx,dir
        mov Dir_cur,dx
        and CONTROL, not PASO
        ret
_trace endp

  ;resetea el programa
_reset proc uses cx
        mov Dir_cur,0
        pioconfig P0,PA PB
        outpio P0,EXTERNO_OFF
        outpio p0,BUSRQPC_ON
        pioconfig P1,PA PB PCH
        outpio P1,RESET_ON
        outpio P1,INT0_ON
        outpio P1,INT1_ON
        mov cx,12
bucr:
        outpio P0,CLK_ON
        outpio P0,CLK_OFF
        loop bucr
        outpio P1,RESET_OFF
        mov cx,12
        call _trace
        pioconfig P1,PA PB PCH PCL
        mov des_base,0
        ret
_reset endp

     end

domingo, 8 de diciembre de 2013

Concatenar un número indeterminado de cadenas

Como respuesta a una pregunta de un lector del blog, voy a añadir esta entrada.

   Si no me equivoco al interpretar su pregunta, el problema que se quiere es poder concatenar dos o más cadenas en una destino.
   Para hacerlo de forma elegante, voy a crear una macro.
    Esta macro va a concatenar las cadenas mediante llamadas consecutivas a la función strcat.
    La ventaja de la macro es que podemos añadir la llamada adicional solo si se pasa como parámetro el puntero a una nueva cadena, de forma que podemos dejar definida la función para un número indeterminado de cadenas. En este caso lo haré de 2 hasta 7.

   Para ello vamos a usar la función strcat para unir las dos primeras cadenas a la tercera. Luego, cuando exista el siguiente parámetro, tomaremos la dirección del destino, calcularemos su longitud y la sumaremos a su dirección de puntero. Así crearemos un puntero al final de la cadena destino. Esto lo haré en SI, para poder direccionarlo como [si] y pasarlo como parámetro a la macro strcpy. Así con esa nueva dirección puedo llamar de nuevo a strcpy y acoplar la cadena a la que ya existía.
    También lo podía haber hecho mediante sucesivas llamadas a strcat destino,destino,fuente?, pero de esta segunda forma perdería tiempo en copiar muchas veces la misma cadena.

    Si llamo a la macro
            Add_str destino,fuente1,fuente2  ;copia dos cadenas en destino
            Add_str destino,fuente1,fuente2,fuente3   ;copia tres cadenas en destino
        etc

    Add_Str macro destino,fuente1,fuente2,fuente3,fuente4,fuente5,fuente6,fuente7
               strcat destino,fuente1,fuente2      ;primera llamada con el mínimo permitido
            push si
           ifnb <fuente3>
               strlen destino
               lea si,destino
               add si,ax
               strcpy [si],fuente3      ;primera llamada
           else
                 ifnb <fuente4>
                     strlen destino
                      lea si,destino
                      add si,ax
                      strcpy [si],fuente4      ;segunda llamada
                  else
                      ifnb <fuente5>
                           strlen destino
                             lea si,destino
                             add si,ax
                            strcpy [si],fuente5      ;tercera llamada
                      else
                            ifnb <fuente6>
                                strlen destino
                                 lea si,destino
                                add si,ax
                                strcpy [si],fuente6      ;cuarta llamada
                             else
                                 ifnb <fuente7>
                                     strlen destino
                                     lea si,destino
                                    add si,ax
                                    strcpy [si],fuente7      ;quinta llamada
                                 endif
                            endif
                     enidf
              endif
         endif
         pop si
endm

  Realmente lo he creado como una nueva función, pero podría haber ampliado simplemente las que ya tenía publicadas en la cabecera string.mac.
   Por ejemplo, podríamos haberla llamado strcat directamente, pues en su modo más corto llama directamente a esa función. 

    Hay que tener en cuenta que el enlace está enviado a una versión inicial de las macros. En ella no usábamos el comando USE del TASM 2.0.
   Para pasar al modo de programación actual, en el que si usamos ya el comando USE del TASM2.0 habrá que aplicar la conversión indicada en la entrada del blog "pasamos a integrar TASM2.0"
   Con esta salvedad, colocaré ahora una versión actualizada de strcat y otra de strcpy, que podrán hacer la misma función que la anterior.

    Si en vez de crear una función nueva ampliamos strcat para que tenga polimorfismo, quedará:

strcat macro destino,fuente1,fuente2,fuente3,fuente4,fuente5,fuente6,fuente7
 ifndef _strcat
      extrn _strcat:near
 endif
        lea ax,destino
        push ax
       lea ax,fuente1
        push ax
        lea ax,fuente2
        push ax
        call _strcat                ;primera llamada. Sumamos fuente1, y fuente2 en destino
  
; a partir de ahí todo es igual    
        push si
           ifnb <fuente3>
               strlen destino
               lea si,destino
               add si,ax
               strcpy [si],fuente3      ;primera llamada
           else
                 ifnb <fuente4>
                     strlen destino
                      lea si,destino
                      add si,ax
                      strcpy [si],fuente4      ;segunda llamada
                  else
                      ifnb <fuente5>
                           strlen destino
                             lea si,destino
                             add si,ax
                            strcpy [si],fuente5      ;tercera llamada
                      else
                            ifnb <fuente6>
                                strlen destino
                                 lea si,destino
                                add si,ax
                                strcpy [si],fuente6      ;cuarta llamada
                             else
                                 ifnb <fuente7>
                                     strlen destino
                                     lea si,destino
                                    add si,ax
                                    strcpy [si],fuente7      ;quinta llamada
                                 endif
                            endif
                     enidf
              endif
         endif
         pop si 

  endm

    El ahorro es simplemente tener una sola función que vale para todo, ya que no tiene sentido strcat si Add_Str hace lo mismo.
 
     Pero claro, igualmente podríamos pensar con strcpy. Dado que strcat no es mas que dos llamadas a strcpy, podríamos construir strcat mediante strcpy, y si hacemos polimorfismo, nos bastará con strcpy, para realizar todas las posibilidades.

      ¿Que Sacrificamos?
      Sacrificamos código. Si usamos macro, cada llamada consumirá más código, que si usamos la llamada a la función strcat. Es por eso que cree strcat. Sin embargo, Add_Str si interesa hacerlo en macro, ya que concatenar  más de una cadena a la vez es más raro, y tener una función que no se usa, también puede aumentar el código, al incorporar librerías innecesarias.

    De todas formas vamos a ver como crear un strcpy,que sirva para todo.

strcpy macro destino,fuente1,fuente2,fuente3,fuente4,fuente5,fuente6,fuente7
ifndef _strcpy
    extrn _strcpy:near
endif
        lea ax,destino
        push ax
        lea ax,fuente1    ;copia inicial
        push ax
        call _strcpy
; a partir de ahí todo es igual , pero con una llamada más
        push si
           ifnb <fuente2>
               strlen destino
               lea si,destino
               add si,ax
               strcpy [si],fuente2      ;primera llamada
           else
                 ifnb <fuente3>
                     strlen destino
                      lea si,destino
                      add si,ax
                      strcpy [si],fuente3     ;segunda llamada
                  else
                      ifnb <fuente4>
                           strlen destino
                             lea si,destino
                             add si,ax
                            strcpy [si],fuente4      ;tercera llamada
                      else
                            ifnb <fuente5>
                                strlen destino
                                 lea si,destino
                                add si,ax
                                strcpy [si],fuente5      ;cuarta llamada
                             else
                                 ifnb <fuente6>
                                     strlen destino
                                     lea si,destino
                                    add si,ax
                                    strcpy [si],fuente6       ;quinta llamada
                                 else
                                    ifnb <fuente7>
                                         strlen destino
                                         lea si,destino
                                         add si,ax
                                         strcpy [si],fuente7       ;quinta llamada

                                    endif
                                 endif
                            endif
                     enidf
              endif
         endif
         pop si 
 endm

    Como se ve, hará lo mismo, y podrá usarse tanto para copiar una cadena, como para acoplar de 2 a 7 cadenas.
    Pero claro, para sumar dos cadenas perderemos más espacio si lo usamos varias veces, frente a usar strcat.

CABECERA EMULADOR 8051

    Vamos a empezar con el programa de control para el emulador 8051, definiendo la cabera para sus funciones básicas de control.
      En este caso lo primero será definir las señales de control y las funciones básicas de lectura, escritura y manejo de la memoria a través del DMA. Así mismo añado en esta cabecera la función Trace para controlar el reloj y ejecutar una instrucción.
   
EMU5051.MAC
; Copyright (C) 2013  José Ángel Moneo Fernández

;    This program is free software: you can redistribute it and/or modify
;    it under the terms of the GNU General Public License as published by
;    the Free Software Foundation, either version 3 of the License, or
;   (at your option) any later version.

;    This program is distributed in the hope that it will be useful,
;    but WITHOUT ANY WARRANTY; without even the implied warranty of
;    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;    GNU General Public License for more details.

;    You should have received a copy of the GNU General Public License
;    along with this program.  If not, see <http://www.gnu.org/licenses/

;identificacion señales
;PC1 -  PUERTO "C" ALTO DE LA TARJETA 48IOCENT
INT0 EQU 1    ;SEÑAL DE INTERRUPCIÓN 1
INT1 EQU 2    ;SEÑAL DE INTERRUPCIÓN 2
RESET EQU 4   ; RESET
WR EQU 20h     ; SEÑAL DE ESCRITURA
RD EQU 10h    ;SEÑAL DE LECTURA
PSEN EQU 40h    ; SEÑAL PSEN
ALE EQU 80h     ;SEÑAL Ale


;salidas control 
;PC0 . PUERTO "C" BAJO DEL LA TARJETA 48IOCENT
CLK_ON EQU 11B    ; ACTIVAR SEÑAL DE RELOJ 
CLK_OFF EQU 10B    ; DESACTIVAR SEÑAL DE RELOJ
BUSRQPC_ON EQU 01B  ; ACTIVAR SOLICITUD DE BUS
BUSRQPC_OFF EQU 00B  ;DESCTIVAR SEÑAL DE RELOJ
EXTERNO_ON EQU 101B   ;ACTIVAR BUS EXTERNO
EXTERNO_OFF EQU 100B   ;DESCTIVAR BUS EXTERNO

;control PC1
PSEN_ON EQU 1101B    ;ACTIVAR PSEN
PSEN_OFF EQU 1100B   ;DESACTIVAR PSEN
ALE_ON EQU 1111B      ;ACTIVAR ALE
ALE_OFF EQU 1110B    ;DESACTIVAR ALE


WR_ON EQU 1011B    ;ACTIVAR WR
WR_OFF EQU 1010B ; DESCTIVAR WR
RD_ON EQU 1001B  ; ACTIVAR RD
RD_OFF EQU 1000B   ;DESACTIVAR RD
RESET_ON EQU 101B   ; ACTIVA RESET
RESET_OFF EQU 100B   ;DESACTIVAR OFF
INT1_ON EQU 11B   ; ACTIVAR INT1
INT1_OFF EQU 10B   ; DESACTIVAR INT1
INT0_ON EQU 1    ; ACTIVAR INT0
INT0_OFF EQU 0   ; DESACTIVAR INT0


;MACROS de control de tarjeta
;activa DMA
Dma_on macro
ifndef _Dma_on
     extrn _Dma_on:near
endif
    call _dma_on   
endm

;desactiva DMA
Dma_Off macro
ifndef _Dma_off
     extrn _Dma_off:near
endif
    call _dma_off   
endm

;Lee de zona de programa
LeeInst macro 
ifndef _LeeInst
     extrn _LeeInst:near
endif
    call _LeeInst
endm

;Lee de zona de datos
;lectura un dato de  area de datos
;dir =dirección de lectura en ram del prototipo
;devuelve el dato en al
Lee macro dir
ifndef _Lee
     extrn _Lee:near
endif
    lea ax,dir
    push ax
    call _Lee
endm

;Guarda de zona de datos
; dir =dirección de volcado en ram del prototipo
; dirección =puntero a los datos a volcar
; long =cantidad de datos
Guarda macro dir,datos,long
ifndef _Guarda
     extrn _Guarda:near
endif
    mov ax,dir
    push ax
    lea ax, datos
    push ax
    mov ax,long
    push ax
    call _Guarda
endm

;lee una zona de memoria
; dir =dirección de volcado en ram del prototipo
; dirección =puntero a los datos a volcar
; long =cantidad de datos
Toma macro dir
ifndef _Toma  dir,datos,long
     extrn _Toma:near
endif
    mov ax,dir
    push ax
    lea ax, dato
    push ax
        mov ax,long
    push ax
    call _Toma 
endm


;tracea una instrución. 
;En el parámetro se le pasa el número de pulsos  de la instruciión
Trace macro pulsos
ifndef _Trace
     extrn _Trace:near
endif
    mov ax,pulsos 
    pusxh ax
    call _Trace  
endm


jueves, 5 de diciembre de 2013

EMULADOR 8051 (HARDWARE)

   Propósito

 Para poder seguir ampliando el blog y subiendo el nivel de los programas, siempre basándome solamente en código propio, hoy voy a colocar una tarjeta muy sencillas, que unida a la anterior tarjeta de 48IO, me permitió en su día crear una de mis mejores herramientas de desarrollo.
    En este caso el resultado del conjunto es un emulador de 8051. 
 
   Esta tarjeta es una versión para el 8051 de un emulador Z80 previo que desarrolle durante las vacaciones de navidad de 1993. Por ello este año cumple 20 años. Y por eso, como si se tratase de una patente, lo hago de  dominio público.


    Este emulador permitirá insertar un pincho en el lugar del microprocesador y tracear, ejecutar, y ver la ejecución de un programa sobre un hardware que desarrollemos con 8051.

 Historia

    Esto ahora está muy extendido, pero en 1993, que es cuando lo desarrollé era una herramienta muy cara.
   Este al igual que otro que podré para Z80, se puede realizar por 30€, y el coste entonces de uno equivalente estaba en 6000€. Por lo que valía la pena hacerlo.

   Me lo intentaron copiar, por supuesto, pero no llegue a comercializarlos, por estar metido en otra guerra. Ahora servirá simplemente para mostrar código ASM. No solo el hard es sencillo, sino además el software de emulación está enteramente en ensamblador. 
   Esto me permite ahora colocar todo el código del depurador, el cual incorpora un  desensamblador, ventanas de Dump, Gestión de la tarjeta 48iocent, Traceo, control DMA etc.

 Hardware

  La tarjeta es esta:


EMULADOR 8051

  



     Como se puede ver la tarjeta no puede ser mas simple. 
      Básicamente esta tarjeta es un medio de conseguir acceder a la tarjeta a probar y al micro de forma separada. 
     Mediante una serie de elementos, vamos a poder aislar el microprocesador de la placa, para poder leer y manejar el Hard de la placa a probar desde el PC. 
     Igualmente estos elementos de la placa nos permitirán tener conectado al microprocesador 8051 a la placa de prueba cuando lo deseemos, pudiendo poner en marcha el sistema.
       En ningún momento ejecutaremos instrucciones, ni secuencias desde el PC. Esto no maneja la placa de prueba emulando al 8051. Lo que realmente hace es conectar a este con la placa cuando lo necesitemos. De esta forma, la ejecución es siempre real. Pudiendose poner el sistema en marcha a la velocidad de reloj real.
       Para conseguir mayor velocidad en el traceo, realicé el soft en ensamblador.

Principio de Funcionamiento

       El principio del sistema es DMA. Generamos DMA, para acceder al programa interno de la placa a probar, para conocer su estado de memoria y sus datos, y para leer y escribir el programa o los datos. 
       En este caso, como el 8051 no tiene gestión de DMA, hemos colocado unos buffer que realizarán esta función. Los integrados U2,U3 y U4 tienen la misión de aislar el 8051 del bus de la tarjeta. De esta forma podremos tomar el control de la tarjeta desde el PC cuando lo necesitemos. Esto nos genera un control DMA, básico para poder tracear.
     Esta tarjeta emulador será transparente 100% para el programa. No consume interrupciones, no consume memoria, no consume, puertos. Podrá ser enchufado a cualquier placa que se desarrolle o haya sido ya montada. No necesita tener el programa en el PC, pues el emulador toma el programa de la propia tarjeta. Por ello se puede tracear una tarjeta desconocida, con solo soltar el micro y conecta en su lugar el zócalo de este emulador.
        

Funcionamiento

      El conector JP1 es el conector en el que están, en orden, los 6 puertos de la tarjeta 48iocent. En este caso mediante un conector con cable plano de 60 pines, se conecta a esta tarjeta, y por el otro lado el cable se divide para ir conectado 6 conectores de 10 pines para cable plano. Uno a cada puerto,de la tarjeta 48IOCEN.
       El conector JP2 es en cambio un conector de 40 pines para cable plano de formato DIP40, el cual será usado para enchufar en el zócalo en que irá luego el microprocesador.

       Dado que esto se usará con una placa real, el reloj de ejecución debería de poder ser el el real. Por ello el U7, se encargará de generar un reloj mediante el cristal que se encuentre en la placa de prueba, preparado para el 8051. La frecuencia generada por este reloj podrá ser usada como frecuencia de reloj del 8051. Así mismo, mediante un señal recogida del PC a través de la tarjeta 48iocent, podremos generar otro reloj controlado por el PC. De esta forma se podrá trabajar con la placa de prueba a la velocidad real, o a velocidad controlada. Incluso se podrá detener la ejecución en cualquier momento, y en cualquier ciclo de reloj del microcontrolador. Esto nos permite verificar mediante un simple milímetro cualquier el estado de cualquier señal del bus, en cualquier estado de acceso a programa, datos, buses, etc.
   La frecuencia de reloj que llega al microporcesador U6, estará tomada de este generador interno de la placa Emulador 8051, o de una señal que viene del PC a través de la tarjeta 48IOcent. Esta selección se hace mediante U5. Mediante esta selección podremos pasar de Modo Run,en tiempo real, a Mode Trace, paso a paso, o como he dicho, parar la instrución en cualquier punto de ejecución.  Esto nos permite tracear prácticamente a nivel de microinstrución.
   
     El Acceso Directo a Memoria, DMA se activa mediante la señal RQPC, la cual bloque el paso de datos desde el microprocesador a la placa prototipo. Para ello, la señal procedente de la placa 48IOcent. Es enviada a un inversor (U3B), para que active o desactive los buffers U1, U2 y U4. El U3B simplemente mantiene la lógica, para que un nivel alto en RQPC bloquee las puertas y pase los integrados 74LS245 a estado flotante. De esta forma quedan los buses libres para que el PC pueda escribir en ellos.

    U1 y U2 aíslan el bus de datos y direcciones entre el microprocesador de la tarjeta y el zócalo del micro de la tarjeta a probar. Aísla el micro para crear el DMA. U2 funcionará en modo bidireccional, seleccionado por RD.

     U4 aísla las señales de control de microprocesador, para que sea el PC y no el micro el que controle el bus de la placa.

      Cuando el micro esté ejecutando, la señal RQPC estará a 0, y los puertos de la tarjeta 48IOcENT, que estén conectados a los buses de datos, direcciones y control estarán en estado flotante, para no interferir.
       Cuando deseemos conectarnos y leer la memoria de la tarjeta prototipo analizada pondremos RQPC a 1 y activaremos los puertos de la tarjeta 48IOCEN, para que desde y por ellos activemos las señales de control, datos y direcciones de la placa.
    
   La señal del pin 16 que va a la placa prototipo, y sería el WR del micro original ,está procesada por medio de U5 de manera que no solo mande escribir en la memoria, siempre que se de orden de leer desde el PC.

   Ahora solo queda realizar un programa capaz de controlar esta tarjeta, y desensamblar  y tracear el programa que contenga el prototipo.

     Para ello, como siempre, primero crearé una libería de funciones para el simulador 8051, la cual, configurará la carta 48IOCENT, para cada operación, y nos permitirá realizar todas las funciones de acceso a memoria de la tarjeta y de traceo. Así mismo tendremos las funciones de pantalla para realizar el dump, visualizar los registros del microprocesador, y visualizar el programa.

       Un caso muy especial es sacar los registros del microprocesador al exterior.



  
Prototipo inicial del emulador
Prototipo unido a la tarjeta prototipo 48IOCENT
                   


Prototipo 8051, ya en placa



lunes, 2 de diciembre de 2013

LIBRERÍA 48IOCENT

   
   Ya estamos listos para crear las funciones de control de la tarjera de 48 E/S. Esta librería la asociaremos a la general o crearemos una nueva para  nuestro hard.


    Dado que las funciones van asociadas al puerto paralelo, y dado que solo existe uno normalmente en los ordenadores, no defino dos puertos y trabajo directamente con el lpt1. Por eso en el programa trabajo directamente con las direcciones del puerto centronics, ya que crear variables y asignarlas a diferentes direcciones, creo que resulta un trabajo innecesario, dado el uso que haremos de las funciones.

    
48IOCENT.ASM
; Copyright (C) 2013  José Ángel Moneo Fernández

;    This program is free software: you can redistribute it and/or modify
;    it under the terms of the GNU General Public License as published by
;    the Free Software Foundation, either version 3 of the License, or
;   (at your option) any later version.

;    This program is distributed in the hope that it will be useful,
;    but WITHOUT ANY WARRANTY; without even the implied warranty of
;    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;    GNU General Public License for more details.

;    You should have received a copy of the GNU General Public License
;    along with this program.  If not, see <http://www.gnu.org/licenses/


;CONTROL DE ACCIONES SOBRE LOS 8255
;ESTAS VARIABLES MUEVEN ELAS SEÑALES DE CONTROL
;DE LOS BUFER Y LATCH DE LA TARJETA PARA ABRIR
;PASO A LOS DATOS DENTRO ENTRE EL CENTRONICS Y
;LOS PUERTOS DE LOS 8255
RDL   EQU 0010b     ;LECTURA DE NIBLE BAJO
RDH   EQU 0100B     ;LECTURA DEL NIBLE ALTO
WR    EQU 0001B     ;ESCRITURA
ENDIR EQU 1000B     ;DIRECCIONAMIENTO

.model compact,pascal
.code
        public _inpio
        public _outpio
;lee un dato del puero 8255 indicado y lo devuelve en al
_inpio proc near uses bx cx dx,PUERTO
      
        push PUERTO             ;Selecciona el puerto indicado
        call _selecreg
        add sp,2
        mov al,RDH               ;Activa la orden RDh lectura parte alta
        out dx,al
        mov bx,379h
        xchg bx,dx
        in al,dx                ;lee el pueto 379h
        shl al,1                ; sube 1 bit lo leido para dejarlo como parte
        and al,0f0h
        mov ch,al               ;alta
        xchg bx,dx
        mov al,RDL              ;activa la orden RDl lectura parte baja
        out dx,al
        xchg bx,dx
        in al,dx                ;lee el puerto 379h y toma la parte baja
        mov bl,al
        mov cl,3
        shr bl,cl               ;traslada los bit abajo para posicionarlos
        and bl,0fh              ;elimina la posible basura
        or ch,bl                ;une lo leido a lo leido anteriormente
        mov al,ch               ;pasa el resulatado a al
        ret
_inpio endp

 ;envío  un dato del puero 8255 indicado
_outpio proc near   uses bx cx dx,PUERTO,DATO:byte

        push PUERTO             ;Selecciona el puerto indicado
        call _selecreg
        add sp,2
        mov dx,378h             ;lanzo el numero
        mov al,DATO
        out dx,al
        mov dx,37ah
        mov al,WR               ;indico WR
        out dx,al
        xor al,al               ;desactivo WR
        out dx,al

        ret
_outpio endp

        
;selecciona un registro interno de la targeta
; ESTA FUNCIÓN ES INTERNA A ESTA LIBRERÍA Y PERMITE
; ESCRIBIR LA DICRECIÓN DEL PUERTO ACTIVADA, PARA QUE
; SE GUARDE EN EL REGISTRO DE DIRECCIONES
; INTERNO DE LA TARJETA 48IOCENT
_selecreg proc near uses dx bx,puerto:byte    ;registro indicado en pila

        mov dx,37ah     ;elijo puerto de control del centronics
        mov al,ENDIR
        out dx,al        ;activo lach de direccion y desactivo resto
        mov bx,378h
        xchg bx,dx
        mov al,puerto
        out dx,al        ;lanzo la direccion indicada
        xchg dx,bx
        xor al,al
        out dx,al       ;deselecciono el lach de direccion

        ret
_selecreg endp

end