;********************************************************************** ; Este programa se distribuye según esta licencia: * ; * ; http://creativecommons.org/licenses/by-nc-sa/2.5/es/ * ; * ; léala antes de seguir adelante o de hacer uso de él * ; * ;********************************************************************** ; * ; Filename: ClonadorInfrarrojo.asm * ; Date: 28-05-07 * ; * ; Author: Emilio Florido * ; * ;********************************************************************** ; * ; Files required: * ; 12F629.lkr * ; * ;********************************************************************** ; * ; Notas: Este programa tiene dos partes diferenciadas. Si GP3 * ; vale 1 funciona en "modo testeo" y si GP3 vale 0 funciona en * ; "modo reproducción". * ; En "modo testeo", dependiendo de lo que valga GP1, lee desde la * ; entrada (GP2) el nivel de la señal y en tanto sea contrario al * ; valor de GP1 espera. Cuando sea igual, comienza a contar en * ; intervalos de 8 microsegundos hasta que o bien cambie el nivel * ; de entrada o bien pasen más de 2 milisegundos. Si ocurre lo * ; primero, almacena el número de intervalos de 8 microsegundos y * ; repite el proceso. Si ocurre lo segundo, pueden darse dos casos: * ; Si GP1 coincide con el nivel de la señal que se está testeando * ; es que se ha sobrepasado el preescalador elegido y se indica * ; error (el led de GP4 parpadea permaneciendo más tiempo encendido * ; que apagado). Si GP1 no coincide con el nivel de la señal de * ; entrada se da por concluido el testeo, se almacena el número de * ; intervalos de 8 microsegundos alcanzado, se almacenan todos los * ; valores en la eeprom y se repite de nuevo todo el proceso... * ; a no ser que al tratar de almacenar el valor este sobrepase el * ; máximo permitido -0x2f- en cuyo caso lo que se haría es indicar * ; error (el led de GP4 parpadea permaneciendo el mismo tiempo * ; apagado que encendido). * ; * ; En "modo reproducción" se leen los valores almacenados en eeprom * ; y se van "sacando" por GP2. En cuanto detecte un 0xff como * ; longitud de datos termina el proceso. Mientras se están sacando * ; valores, el led de GP5 permanece encendido. * ; * ;********************************************************************** list p=12F629 ; list directive to define processor #include ; processor specific variable definitions errorlevel -302 ; suppress message 302 from list file __CONFIG _CP_OFF & _CPD_OFF & _BODEN_OFF & _MCLRE_OFF & _WDT_OFF & _PWRTE_ON & _INTRC_OSC_NOCLKOUT ; Con _MCLRE_OFF además que se puede usar GP3 como entrada y no como Reset, hace que al dejar ; "al aire" la patilla 5 del uC arranque sin problemas (si se pone _MCLRE_ON y la patilla ; 5 se deja suelta da problemas al arrancar) ; para que sea más fácil leer el código Modo equ 3 Entrada equ 2 ; tanto la entrada como la salida comparten la misma Salida equ 2 ; patilla, ya que _nunca_ coinciden al mismo tiempo NivelArranque equ 1 TamanoBuffer equ 0x2f ; usará 47 bytes como buffer de datos ; variables del programa Temporal1 equ 0x20 ; variables usadas para las pausas, retardos y varios Temporal2 equ 0x21 Temporal3 equ 0x22 GBufferDatos equ 0x23 ; almacena por donde comienza el buffer de datos de cada código FinalBuffer equ 0x24 ; hasta donde puede llegar el buffer ; en teoría desde el registro 0x25 hasta el 0x5f se podrían usar para los datos a guardar ; pero he preferido dejar algunos "libres" por si acaso :-) ; en el código se accede a ellos de forma indirecta usando FSR BufferDatos equ 0x30 ;********************************************************************** RESET_VECTOR CODE 0x000 ; processor reset vector ; call 0x3FF ; así tomaría el valor "grabado" en fábrica movlw 0x34 ; pero yo lo he leído previamente :-) bsf STATUS,RP0 ; selecciona el banco 1 de registros movwf OSCCAL ; ajuste fino del oscilador interno ; configuración bcf STATUS,RP0 ; selecciona el banco 0 de registros movlw b'00000111' ; deshabilita el comparador interno (CM2=1,CM1=1, CM0=1) movwf CMCON ; ya que "por defecto" está habilitado movlw b'00111111' ; se asegura de que en cuanto se programe GPIO -un par de líneas movwf GPIO ; más abajo- todo esté "en alto" bsf STATUS,RP0 ; selecciona el banco 1 de registros movlw b'01010010' ; asigna T0CS al reloj interno, el preescalador al TIMER0 movwf OPTION_REG ; y el divisor a 1:8 (los tres últimos bits) -y permite pullup- clrf WPU ; habilita el pull-up de todas las entradas movlw b'00001110' ; GP1, GP2 y GP3 entradas, el resto salidas. Supone que comenzará en movwf TRISIO ; modo testeo... aunque luego puede que cambie (0 -> salida, 1 -> entrada) ; por defecto, deja el banco 0 de registros seleccionado bcf STATUS,RP0 ; selecciona el banco 0 de registros ; antes de empezar mejor esperar a que todo se "estabilice" movlw d'250' call pausa_en_milisegundos movlw d'250' call pausa_en_milisegundos ; primero comprueba si está en modo "testeo" o en modo "reproducción" btfss GPIO,Modo ; si GP3 (que es el "modo") está "al aire" entiende modo testeo goto modo_reproduccion ; aquí comienza el modo "testeo" movlw BufferDatos ; el valor de comienzo del buffer de datos movwf GBufferDatos ; se almacena aquí ; calcula y almacena hasta donde puede llegar el buffer movlw TamanoBuffer addwf GBufferDatos,0 movwf FinalBuffer call limpia_eeprom ; toda la eeprom se pone a 0xff bsf STATUS,RP0 ; selecciona el banco 1 de registros clrf EEADR ; pone a cero el registro de direcciones de la eeprom ; para asegurarse de que todo se almacenará de ahí en adelante bcf STATUS,RP0 ; selecciona el banco 0 de registros modo_testeo bcf GPIO,4 ; indica el modo testeo encendiendo el LED de GP4 ;-) ; carga el "puntero" con la dirección donde se almanceran los conteos movfw GBufferDatos ; recupera en donde comienza el buffer de datos movwf FSR ; FSR hace de "puntero" incf FSR,1 ; la primera posición del buffer se reserva para el número de "muestras" ; determina si la señal de comienzo será un 0 ó un 1, según GP1 (está "asociado" con NivelArranque) btfss GPIO,NivelArranque goto espera_en_uno ; si va a ser un 1 lo que determine el arranque, en tanto haya un 0 en la entrada ; habrá que esperar indefinidamente espera_en_cero btfss GPIO,Entrada goto espera_en_cero goto comienza_testeo_cero ; si va a ser un 0 lo que determine el arranque, en tanto haya un 1 en la entrada ; habrá que esperar indefinidamente espera_en_uno btfsc GPIO,Entrada goto espera_en_uno goto comienza_testeo_uno comienza_testeo_uno bcf INTCON,2 ; se asegura de que no hubiera marca de overflow en TIMER0 clrf TMR0 ; pone TIMER0 a cero testeo_espera_uno btfss INTCON,2 ; si no hay overflow (TMR0 FF-> 00) sigue testeando goto sigue_espera_uno ; continua testeando btfsc GPIO,NivelArranque ; si el nivel de arranque era 0, el overflow indicará goto fin_testeo ; que ya ha terminado un código, en otro caso, habrá que goto error_preescalador ; ajustar el preescalador sigue_espera_uno btfss GPIO,Entrada goto testeo_espera_uno ; si es cero, repite movfw TMR0 ; almacena el valor hasta el que ha llegado el TIMER0 movwf INDF ; en donde indique el "puntero" incf FSR,1 ; incrementa el puntero para la próxima vez movfw FSR ; comprueba que no se sobrepase el buffer subwf FinalBuffer,0 btfsc STATUS,Z ; si resulta ser cero es que ya está al máximo goto buffer_excedido comienza_testeo_cero bcf INTCON,2 ; se asegura de que no hubiera marca de overflow en TIMER0 clrf TMR0 ; pone TIMER0 a cero testeo_espera_cero btfss INTCON,2 ; mientras no haya overflow (TMR0 FF-> 00) goto sigue_espera_cero ; continua testeando btfss GPIO,NivelArranque ; si el nivel de arranque era 0, el overflow indicará goto fin_testeo ; que ya ha terminado un código, en otro caso, habrá que goto error_preescalador ; ajustar el preescalador sigue_espera_cero btfsc GPIO,Entrada goto testeo_espera_cero movfw TMR0 ; almacena el valor hasta el que ha llegado el TIMER0 movwf INDF ; en donde indique el "puntero" incf FSR,1 ; incrementa el puntero para la próxima vez movfw FSR ; comprueba que no se sobrepase el buffer subwf FinalBuffer,0 btfsc STATUS,Z ; si resulta ser cero es que ya está al máximo goto buffer_excedido goto comienza_testeo_uno fin_testeo movfw FSR ; recupera por donde va el puntero movwf Temporal1 ; y lo almacena provisionalmente movfw GBufferDatos ; toma el valor inicial del puntero subwf FSR,0 ; le resta por donde va movwf Temporal2 ; guarda el resultado de la resta movlw 1 ; y le quita uno -ya que el puntero está señalando uno más allá- subwf Temporal2,1 movfw GBufferDatos ; vuelve a tomar el valor inicial del puntero movwf FSR ; y hace apuntar a él el puntero movfw Temporal2 ; para almacenar ahí cuantos cambios de bits tiene el código movwf INDF ; he cambiado para que _siempre_ comience cada comando al comienzo del buffer, pero para no ; variar todo lo que ya hay, he preferido inicializar de nuevo GBufferDatos y no hay nada más que tocar ; movfw Temporal1 ; recupera por donde iba el puntero movlw BufferDatos ; toma el valor del comienzo del buffer de datos -así cada vez comienza de nuevo- movwf GBufferDatos ; y ese será su nuevo valor inicial call almacena_eeprom ; guarda los datos nuevos a continuación de los que ya hubiera bsf GPIO,4 ; indica fin del modo testeo apagando el LED de GP4 ;-) ; antes de hacer una nueva adquisición, espera medio segundo movlw d'250' call pausa_en_milisegundos movlw d'250' call pausa_en_milisegundos goto modo_testeo buffer_excedido ; enciende y apaga GP4 para indicar este error bsf GPIO,4 ; apaga el LED de GP4 movlw d'250' call pausa_en_milisegundos bcf GPIO,4 ; enciende el LED de GP4 movlw d'250' call pausa_en_milisegundos goto buffer_excedido error_preescalador ; si llega aquí es que el preescalador habrá que ajustarlo, pues lo ha excedido bsf GPIO,4 ; apaga el LED de GP4 movlw d'50' call pausa_en_milisegundos bcf GPIO,4 ; enciende el LED de GP4 movlw d'250' call pausa_en_milisegundos movlw d'250' call pausa_en_milisegundos bsf GPIO,4 ; apaga el LED de GP4 movlw d'50' call pausa_en_milisegundos bcf GPIO,4 ; enciende el LED de GP4 movlw d'250' call pausa_en_milisegundos movlw d'250' call pausa_en_milisegundos goto error_preescalador vueltas ; sleep goto vueltas ; XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX ; XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX modo_reproduccion ; reprograma de forma que GP2 sea salida ya que está en "modo reproducción" bsf STATUS,RP0 ; selecciona el banco 1 de registros movlw b'00001010' ; GP1 y GP3 entradas, el resto salidas. movwf TRISIO ; (0 -> salida, 1 -> entrada) clrf EEADR ; se asegura que los datos leídos de la eeprom comiencen desde 0 bcf STATUS,RP0 ; de ahora en adelante, por defecto selecciona el banco 0 de registros bsf GPIO,Salida ; por defecto, supone que el valor de salida será un 1 btfsc GPIO,NivelArranque ; a ver si por defecto hay que sacar un 1 ó un 0 bcf GPIO,Salida ; vaya, pues hay que sacar un 0 por defecto :-) ; espera un poco más antes de enviar el primer comando movlw 6 ; como un segundo y medio movwf Temporal3 antes_del_primer_comando movlw d'250' call pausa_en_milisegundos decfsz Temporal3,1 goto antes_del_primer_comando enviar_comando bcf GPIO,5 ; indica el modo reproducción encendiendo el LED de GP5 ;-) movlw BufferDatos ; el valor de comienzo del buffer de datos movwf FSR ; FSR hace de "puntero" call lee_byte_eeprom ; a la vuelta, en W viene el valor del byte leído movwf Temporal1 ; almacena el total de bytes del comando para saber cuantas veces leer movwf INDF ; y además guarda el valor en el buffer incf FSR,1 ; incrementa el puntero ; si el número de bytes a enviar fuese 0xff, da por concluído el proceso addlw 1 ; aprovecha que W contiene aún el número de bytes btfsc STATUS,Z goto fin_reproduccion rellena_buffer call lee_byte_eeprom ; toma un valor de la eeprom movwf INDF ; y lo almacena en el buffer incf FSR,1 ; incrementa el puntero decfsz Temporal1,1 ; a ver si hay que dar más vueltas goto rellena_buffer ; ahora que está el buffer preparado, es el momento de enviar los datos movlw BufferDatos ; el valor de comienzo del buffer de datos movwf FSR ; FSR hace de "puntero" movfw INDF ; recupera cuantos bytes hay que enviar movwf Temporal1 ; y lo almacena incf FSR,1 ; incrementa el puntero de forma que señale al primer dato envia_buffer btfsc GPIO,Salida ; si la salida estaba a 1 la pasa a 0 y viceversa goto cambia_a_cero bsf GPIO,Salida ; saca un 1 goto envia_buffer1 cambia_a_cero bcf GPIO,Salida ; saca un 0 envia_buffer1 clrf TMR0 ; inicializa el TIMER0 envia_buffer2 movfw INDF ; recupera el tiempo que hay que activar la salida subwf TMR0,0 ; y comprueba si ya ha llegado a él el TIMER0 (deja el resultado en W) btfss STATUS,Z goto envia_buffer2 ; y si no es así, vuelve a comprobarlo hasta que lo sea incf FSR,1 ; incrementa el puntero decfsz Temporal1,1 ; a ver si hay que dar más vueltas goto envia_buffer ; se asegura que la salida al final de cada código se quede con el nivel que le corresponde bsf GPIO,Salida ; por defecto, supone que el valor de salida será un 1 btfsc GPIO,NivelArranque ; a ver si por defecto hay que sacar un 1 ó un 0 bcf GPIO,Salida ; vaya, pues hay que sacar un 0 por defecto :-) ; entre comando y comando espera un poco movlw d'250' call pausa_en_milisegundos movlw d'250' call pausa_en_milisegundos movlw d'250' call pausa_en_milisegundos bsf GPIO,5 ; apaga el LED de GP5 movlw d'250' call pausa_en_milisegundos movlw d'250' call pausa_en_milisegundos movlw d'250' call pausa_en_milisegundos goto enviar_comando ; repite el proceso de envío fin_reproduccion bsf GPIO,5 ; apaga el LED de GP5 bsf STATUS,RP0 ; selecciona el banco 1 de registros movlw b'00001110' ; GP1, GP2 y GP3 entradas, el resto salidas. Así no "fuerza" la movwf TRISIO ; la salida -ya que ahora es entrada- y el mando a distancia funciona mejor a_dormir ; sleep goto a_dormir pausa_en_milisegundos movwf Temporal2 pausa_2 movlw d'110' ; vueltas para un milisegundo movwf Temporal1 pausa_1 nop nop nop nop nop nop decfsz Temporal1,1 ; ¿ha pasado ya un milisegundo? goto pausa_1 decfsz Temporal2,1 ; ¿han pasado ya los milisegundos fijados? goto pausa_2 return limpia_eeprom movlw 0x80 ; en la eeprom caben 128 bytes movwf Temporal1 movlw 0xff ; toda la eeprom se llena de 0xff bsf STATUS,RP0 ; selecciona el banco 1 de registros movwf EEDATA ; guarda el dato a grabar en el registro de datos de la eeprom clrf EEADR ; pone a cero el registro de direcciones de la eeprom limpia_byte_eeprom bsf STATUS,RP0 ; selecciona el banco 1 de registros (no se puede quitar, aquí es la vuelta) bsf EECON1,WREN ; habilita la escritura en la eeprom movlw 0x55 ; cadena "mágica" para realizar una escritura movwf EECON2 movlw 0xaa movwf EECON2 bsf EECON1,WR ; comienza la escritura espera_byte_limpio btfsc EECON1,WR ; ¿ha terminado de grabar? goto espera_byte_limpio bcf EECON1,WREN ; evita escrituras accidentales en la eeprom ; a ver si se ha grabado correctamente. Es _imprescindible_ pues muchas veces falla al grabar muy seguido movf EEDATA,W ; EEDATA no ha cambiado por haber hecho la escritura bsf EECON1,RD ; lee de la eeprom xorwf EEDATA,W ; compara lo leído con lo que se intentó grabar btfss STATUS,Z ; si no son iguales, es que ha fallado la grabación goto limpia_byte_eeprom ; la mejor forma de gestionar el error es intentar grabar de nuevo incf EEADR,1 ; avanza una dirección en la eeprom bcf STATUS,RP0 ; selecciona el banco 0 de registros decfsz Temporal1,1 ; a ver si hay que dar más vueltas goto limpia_byte_eeprom return almacena_eeprom movlw BufferDatos ; aquí comienza el buffer con los datos movwf FSR ; pasa el valor a FSR que hará de "puntero" movfw INDF ; el primer dato es el número de bytes a grabar movwf Temporal1 ; además W queda ya cargado con el primer byte que se grabará incf Temporal1,1 ; hay que grabar uno más para cubrir el byte que indica la longitud bsf STATUS,RP0 ; selecciona el banco 1 de registros movwf EEDATA ; guarda el dato a grabar en el registro de datos de la eeprom siguiente_grabacion bsf STATUS,RP0 ; selecciona el banco 1 de registros (no se puede quitar, aquí es la vuelta) bsf EECON1,WREN ; habilita la escritura en la eeprom movlw 0x55 ; cadena "mágica" para realizar una escritura movwf EECON2 movlw 0xaa movwf EECON2 bsf EECON1,WR ; comienza la escritura espera_grabacion_completada btfsc EECON1,WR ; ¿ha terminado de grabar? goto espera_grabacion_completada bcf EECON1,WREN ; evita escrituras accidentales en la eeprom ; a ver si se ha grabado correctamente. Es _imprescindible_ pues muchas veces falla al grabar muy seguido movf EEDATA,W ; EEDATA no ha cambiado por haber hecho la escritura bsf EECON1,RD ; lee de la eeprom xorwf EEDATA,W ; compara lo leído con lo que se intentó grabar btfss STATUS,Z ; si no son iguales, es que ha fallado la grabación goto siguiente_grabacion ; la mejor forma de gestionar el error es intentar grabar de nuevo bcf STATUS,RP0 ; selecciona el banco 0 de registros incf FSR,1 ; incrementa el puntero movfw INDF ; toma el nuevo valor a grabar bsf STATUS,RP0 ; selecciona el banco 1 de registros incf EEADR,1 ; avanza una dirección en la eeprom movwf EEDATA ; guarda el dato a grabar en el registro de datos de la eeprom ; en la eeprom solo caben 128 bytes, así que hay que evitar sobreescribirla movlw 0x7f ; se queda con los 7 primeros bits de EEADR (según la documentación andwf EEADR,1 ; pag 47, 8.2 el último bit siempre debe ser 0) clrw ; solo si ha dado la "vuelta" EEADR valdrá cero xorwf EEADR,W ; si esta operacion es "cero", es que la eeprom ya está "a tope" bcf STATUS,RP0 ; selecciona el banco 0 de registros btfsc STATUS,Z goto buffer_excedido decfsz Temporal1,1 ; a ver si hay que dar más vueltas goto siguiente_grabacion return lee_byte_eeprom bsf STATUS,RP0 ; selecciona el banco 1 de registros bsf EECON1,RD ; lee el dato movf EEDATA,W ; y lo almacena en W incf EEADR,1 ; señala a la siguiente dirección que se leerá bcf STATUS,RP0 ; selecciona el banco 0 de registros return ; un poco de propaganda para la posteridad :-) data '*','E','m','i','l','i','o','*' data '(','2','9','-','0','5','-','0','7',')' end ; fin del programa