;***************************************************************************************
;Proyecto: PLC con microcontrolador PIC 16F873.
;Versión: 0.0.6
;Revision: B
;Diseñado para: Ciberar - Sistemas & Desarrollos
;Autor: Claudio J. Pérez (interrupt_request@hotmail.com)
;***************************************************************************************
;Descripción:
;	El objetivo es hacer un PLC, con 8 entradas y 8 salidas, usando un 16F873.
;El PLC debe tener una interface RS-232 para recibir el programa la desde la PC.
;Las instrucciones se almacenan en una memoria EEPROM 24LC64, para no limitar la cantidad
;de pasos de la programación. Por esto el PLC debe leer cada instrucción, decodificarla, 
;y ejecutarla, luego leer la siguiente instruccion y el ciclo se repite.
;Para editar el programa este debe tener una interface serial para comunicarse con una PC.
;
;	Watchdog: Off          Power Up Timer: On
;	Cristal: 4.0 Mhz       Tipo de Reloj: XT
;	Code Protection: Off
;***************************************************************************************
;	Control cambios
;***************************************************************************************
; 07/05/03  Inicio del proyecto 0.0.1
; 21/05/03  Version 0.0.2.
;	    Se inician las rutinas para la programación usando la agenda Casio.
; 22/05/03  Se logra la recepcion del Header de la comunicacion CASIO.
; 24/05/03  Se recibe una sentencia de la Agenda, se convierte y se almacena en el buffer.
; 25/05/03  Se inicia la rutina de procesamiento de la sentencia, usando tablas.
; 26/05/03  PROBLEMA: Aunque se optimizo al maximo, la rutina de comunicacion con la agenda
;           es muy grande.
;           SOLUCION: En vez de usar un 16F84 se cambia el PIC por un 16F873.
; 31/05/03  Se optimiza la rutina de comunicacion con la memoria usando lecturas secuenciales.
; 01/06/03  Se inicia la rutina de los timers de 16 bits, con base de tiempo variable.
; 03/06/03  PROBLEMA: Al activar la interrupcion del timer parece que no se lee bien la
;           memoria. Sin interrupciones funciona bien.
;           SOLUCION: Se modifica la rutina de almacenamiento de contexto.
; 04/06/03  Se termina la rutina de interrupcion de los timers con base de tiempo variable.
; 08/06/03  Se termina la rutina de ejecucion de la sentencia TON.
; 10/06/03  Se agrega la instruccion OTT, para esto se necesita memorizar el estado anterior
;           de cada salida o registro auxiliar.
; 11/06/03  Se termina la rutina para procesar la instruccion OTT desde la agenda.
; 12/06/03  Se agregan los registros M0 y M1.
; 14/06/03  Se agrega la rutina de conversion de BCD a binario 16 bits.
; 16/06/03  Se termina la rutina para procesar la instruccion TON desde la agenda.
; 01/07/03  Se desiste de la comunicacion con la agenda, esto genera la revision B.
;           Se borran todas las rutinas innecesarias.
;           Se agregan las instrucciones TOF, TIP y TFL. Se mueven los registros de los
;           timers a la pagina 1 de la RAM.
;           PROBLEMA: Problema con las interrupciones de los timers.
;           SOLUCION: Se modifica la rutina de ajuste del timer, ahora el registro
;           ContTimer esta en el banco 1 de RAM, tambien se ajusta los ciclos de la demora.
; 02/07/03  Se agregan las instrucciones CTU, CTD y RES.
; 03/07/03  Se agrega el modo up/down a los contadores, para esto se agrega la instruccion
;           CTK y el bit DIR.
; 04/07/03  Se optimizan las rutinas de decodificacion y ejecucion de la instruccion, 
;           mediante el uso de tablas de saltos.
; 05/07/03  Se modifica la pila usando un solo registro, aumentando los niveles de la
;           misma a ocho.
; 06/07/03  Se modifica el hardware para poder usar un bootloader. Se agrega un registro
;           de desplazamiento paralelo-serie 74LS165 para leer las entradas.
; 12/07/03  Se cambia el registro de desplazamiento 74LS165 por un CD4021.
;           Se agrega un registro de desplazamiento CD4094 para las salidas.
;           PROBLEMA: Al encender a veces el CD4094 se inicializa con cualquier valor.
;           SOLUCION: Se agrega un pullup a la pata DIN.
; 14/07/03  Se modifica las rutinas RS-232 usando la USART con polling. Se agrega la
;           rutina de comunicacion con la PC por interrupciones para recibir y almacenar
;           el programa a ejecutar. Se modifican las rutinas de lectura de la instruccion,
;           decodificacion y ejecucion agregando la bandera BandDownload para permitir
;           que luego de la comunicacion se inicie la ejecucion del nuevo programa.
;           Se agrega como subrutina la inicializacion de los timers y contadores.
;           PROBLEMA: Se descubre que los tiempos del los timers no son exactos.
;           SOLUCION: Al leer la memoria se deshabilitaban las interrupciones, luego se
;           descubre que la interrupcion del timer 0 no es exacta, se pierde 1 ciclo ???
;           Se intenta agregar al timer 1 un cristal de 32 khz.
; 20/07/03  Se modifica el hardware agregando el cristal de 32Khz al timer 1 para usarlo
;           como reloj de tiempo real.
;           PROBLEMA: Al agregar las rutinas del timer, el plc deja de funcionar.
;           SOLUCION: Se habia modificado la rutina de lectura de la instruccion.
; 21/07/03  Se modifica la rutina de actualizacion de los timers para usar el timer 1 en
;           vez del timer 0. Para esto se agrega una Bandera que indica cual interrupcion
;           sucedio. Se cambian los prescaler de 16 bits, por otros de 8 bits.
; 22/07/03  Se modifica el mapa de la memoria serial, se agregan luego del registro de
;           configuracion los valores de los horarios. Tambien se modifica la rutina de 
;           comunicacion con la PC. Se modifica la rutina de debug de la instruccion para
;           que envie el byte leido y espere que se presione y suelte Q0,7 para enviar el;           siguiente.
; 24/07/03  Se agrega la rutina que compara los valores de los horarios con el dia y la
;           hora actual y los activa si corresponde.
; 26/07/03  PROBLEMA: Los horarios no funcionan ! Los valores de los registros cambian
;           de forma aleatoria.
;           SOLUCION: Se modifica la rutina de inicializacion de los registros de los
;           horarios.
; 27/07/03  Se agregan dos horarios semanales mas. Ahora existen H0, H1, H2 y H3.
;           Se modifican las rutina de Inicializacion de los horarios, Activacion de los
;           horarios y la de Inicializacion del PLC (se agrega una verificacion de los
;           horarios al encenderse).
;***************************************************************************************
	List	P=16F873
	#include <P16F873.INC>  ;Declaraciones estándar de Microchip
	#include <Macros8.INC>  ;Macros para operaciones en 8 bits.
	#include <Macros16.INC> ;Macros para operaciones en 16 bits.

	__CONFIG   _CP_OFF & _WDT_OFF & _BODEN_OFF & _PWRTE_ON & _XT_OSC & _LVP_OFF & _DEBUG_OFF & _WRT_ENABLE_OFF

;***************************************************************************************
;	Declaración de equivalencias
;***************************************************************************************
;Define el modo Depuracion, en este modo se deshabilitan las demoras.
;#define	Debug	        1
;Permite que cada instruccion leida sea enviada a la PC.
;#define        DebugInst       1
;Permite que se envie a la PC el estado en el ciclo de scan.
;#define        DebugState      1
;Permite que se envie a la PC los registros de hora, fecha y diadesemana del PLC.
;#define        DebugTime      1

;Destino de la operacion
W		equ	0x00            ;El resultado se almacena en W.
F		equ	0x01            ;El resultado se almacena en el mismo registro.

;Constantes del protocolo con la PC.
Cmnd_ProgramID  equ     0xA5            ;Indica que la PC quiere modificar el programa.
Cmnd_Ack        equ     0x02            ;Indica que se debe enviar otro byte.

;Instrucciones del PLC
Inst_LD		equ	0x01            ;LD.
Inst_LDB	equ	0x02            ;LDB.
Inst_OTE	equ	0x03            ;OTE.
Inst_OTL	equ	0x04            ;OTL.
Inst_OTU	equ	0x05            ;OTU.
Inst_OTT	equ	0x06            ;OTT.
Inst_OSR	equ	0x07            ;OSR.
Inst_OSF	equ	0x08            ;OSF.
Inst_AND	equ	0x09            ;AND.
Inst_ANDNOT	equ	0x0A            ;ANDNOT.
Inst_OR		equ	0x0B            ;OR.
Inst_ORNOT	equ	0x0C            ;ORNOT.
Inst_ANDLD	equ	0x0D            ;ANDLD.
Inst_ORLD	equ	0x0E            ;ORLD.
Inst_TON	equ	0x0F            ;TON	Temporizador de retardo a la conexion.
Inst_TOF	equ	0x10            ;TOF	Temporizador de retardo a la desconexion.
Inst_TIP	equ	0x11            ;TIP	Temporizador de activacion durante impulso.
Inst_TFL	equ	0x12            ;TFL	Temporizador de activacion/desactivacion ciclica.
Inst_CTU	equ	0x13            ;CTU	Incrementa un contador de 16 bits.
Inst_CTD	equ	0x14            ;CTD	Decrementa un contador de 16 bits.
Inst_RES	equ	0x15            ;RES	Reset de un temporizador o contador.
Inst_CTK	equ	0x16            ;CTK	Incrementa/Decrementa un contador de 16 bits segun DIR.
Inst_HOR        equ     0x17            ;HOR    Horario semanal.
Inst_END	equ	0xFF            ;END	Indica fin del programa.

UserRegisters	equ	0x20            ;Inicio de los registros de usuario del PIC 16F873.

	CBLOCK		UserRegisters

	;Usadas por la rutina de servicio de interrupción.
	wtemp                           ;Almacena el estado del registro W durante las interrupciones.
	stemp                           ;Almacena el estado del registro STATUS durante las interrupciones.
	pclathtemp                      ;Almacena el estado del registro PCLATH durante las interrupciones.
	fsrtemp                         ;Almacena el estado del registro FSR durante las interrupciones.

	;Usadas por la rutina de retardo.
	loops                           ;Almacena el retardo en mseg.
	loops2                          ;Registro temporario.

	;Usadas por las rutinas de transmision y recepcion serial con USART.
	TXRXBand                        ;Guarda varias banderas de la comunicacion.
	RXEstado                        ;Almacena el estado de la recepcion serial.
	
	;Usadas por las rutinas de escritura y lectura
	;de la memoria eeprom I2C (24LC64).
	eeprom                          ;Buffer para varios bits.
	addrL                           ;Lsb de la direccion.
	addrH                           ;Msb de la direccion.
	datai_m                         ;Este registro guarda el byte leido.
	datao_m                         ;Este registro guarda el byte a enviar.
	slave_m                         ;Direccion del dispositivo en el bus (1010xxx0)
	txbuf_m                         ;Buffer de transmision.
	count_m                         ;Contador de bits.
	pollcnt                         ;Contador para el polling de grabacion.

        ;Usadas por la rutina de comunicacion con la PC.
        Mem_ContLSB                     ;Almacena el LSB de la cantidad de bytes a grabar.
        Mem_ContMSB                     ;Almacena el MSB de la cantidad de bytes a grabar.
        Mem_AddrLSB                     ;Almacena el LSB de la direccion a grabar.
        Mem_AddrMSB                     ;Almacena el MSB de la direccion a grabar.

	;Usadas por las rutinas de suma y resta de 16 bits.
	AccAL                           ;LSB de AccA
	AccAH                           ;MSB de AccA
	AccBL                           ;LSB de AccB
	AccBH                           ;MSB de AccB

        ;Usadas por las rutinas de lectura del registro de desplazamiento CD4021.
        Recep_4021                      ;Almacena el byte a recibir.
        Cont_4021                       ;Cuenta los bits recibidos.

        ;Usadas por las rutinas de escritura del registro de desplazamiento CD4094.
        Trans_4094                      ;Almacena el byte a enviar.
        Cont_4094                       ;Cuenta los bits a transmitir.

        ;Usadas por la rutina de reloj de tiempo real.
	ContSeg			        ;Almacena los segundos.
	ContMin			        ;Almacena los minutos.
	ContHor			        ;Almacena las horas.
	ContDias		        ;Almacena los dias.
	ContMeses		        ;Almacena los meses.
	ContAniosH		        ;Almacena el MSB de los años.
	ContAniosL		        ;Almacena el LSB de los años.
	ContDiadeSemana 	        ;Almacena el dia de la semana (0-6).
	BandReloj		        ;Almacena varias banderas.

        ;Usadas por el Sistema Operativo.
	PLC_Acc                         ;Acumulador del PLC.
	PLC_ContProgL                   ;Contador de programa del PLC, byte LSB.
	PLC_ContProgH                   ;Contador de programa del PLC, byte MSB.
	Reg_Inst                        ;Almacena la instruccion a ejecutar.
	Reg_Param1                      ;Almacena el primer parametro de la instruccion.
	Reg_Param2                      ;Almacena el segundo parametro de la instruccion.
	Reg_Param3                      ;Almacena el tercer parametro de la instruccion.
	Reg_Param4                      ;Almacena el cuarto parametro de la instruccion.
        ContByte                        ;Almacena la cantidad de parametros de la instruccion.
	PLC_Aux                         ;Registro auxiliar usado en las tablas.
	Pila                            ;Registro pila.

	;Usados por el usuario.
	;Imagenes de las entradas y salidas.
	Entradas0                       ;Imagenes de las entradas 0:7 0x4D
	Ent0Ant                         ;Estado anterior de las entradas.
	Salidas0                        ;Imagenes de las salidas  0:7 0x4E
	Sal0Ant                         ;Estado anterior de las salidas.

	;Reles auxiliares o Marcas.
	Registro0                       ;1er Registro auxiliar.
	Reg0Ant                         ;Estado anterior del 1er registro.
	Registro1                       ;2do Registro auxiliar.
	Reg1Ant                         ;Estado anterior del 2do registro.
	Registro2                       ;3er Registro auxiliar.
	Reg2Ant                         ;Estado anterior del 3er registro.
	Registro3                       ;4to Registro auxiliar.
	Reg3Ant                         ;Estado anterior del 4to registro.

	;Contadores binarios de 16 bits.
	;Bits del registro CntConfig:
        ;- CTK_AccAnt    (4)
        ;- DIR           (3)
	;- CTU_AccAnt    (2)
	;- DN            (1)
	;- CTD_AccAnt    (0)
	CntAuxL                         ;Registro auxiliar usado por las rutinas de contadores.
	CntAuxH                         ;Registro auxiliar usado por las rutinas de contadores.
	Cnt0Config                      ;Configuracion y banderas del Contador0.
	Cnt0LCount                      ;LSB de la Cuenta del Contador0.
	Cnt0HCount                      ;MSB de la Cuenta del Contador0.
	Cnt1Config                      ;Configuracion y banderas del Contador1.
	Cnt1LCount                      ;LSB de la Cuenta del Contador1.
	Cnt1HCount                      ;MSB de la Cuenta del Contador1.
	Cnt2Config                      ;Configuracion y banderas del Contador2.
	Cnt2LCount                      ;LSB de la Cuenta del Contador2.
	Cnt2HCount                      ;MSB de la Cuenta del Contador2.
	Cnt3Config                      ;Configuracion y banderas del Contador3.
	Cnt3LCount                      ;LSB de la Cuenta del Contador3.
	Cnt3HCount                      ;MSB de la Cuenta del Contador3.
	Cnt4Config                      ;Configuracion y banderas del Contador4.
	Cnt4LCount                      ;LSB de la Cuenta del Contador4.
	Cnt4HCount                      ;MSB de la Cuenta del Contador4.
	Cnt5Config                      ;Configuracion y banderas del Contador5.
	Cnt5LCount                      ;LSB de la Cuenta del Contador5.
	Cnt5HCount                      ;MSB de la Cuenta del Contador5.
	Cnt6Config                      ;Configuracion y banderas del Contador6.
	Cnt6LCount                      ;LSB de la Cuenta del Contador6.
	Cnt6HCount                      ;MSB de la Cuenta del Contador6.
	Cnt7Config                      ;Configuracion y banderas del Contador7.
	Cnt7LCount                      ;LSB de la Cuenta del Contador7.
	Cnt7HCount                      ;MSB de la Cuenta del Contador7.
	ENDC

	;Todas los registros de los timers estan en la pagina 1 de RAM.
	CBLOCK		0xA1	        ;Se reserva A0 para wtemp.

	;Usadas por los timers.
	TimerAuxL                       ;Registro auxiliar usado durante el incremento de los timers.
	TimerAuxH                       ;Registro auxiliar usado durante el incremento de los timers.
	TimerCont                       ;Contador usado en la rutina de actualizacion de los timers.
	TimerFSR                        ;Almacena el FSR del ultimo timer.
	TimerPresc                      ;Almacena el prescaler de la configuracion del timer.
        BandTimers                      ;Almacena varias banderas.

	;Bits del registro TimConfig:
	;- Prescaler     (7-6)
	;- Tipo de Timer (5-4)  00-TON, 01-TOF, 10-TDR, 11-TAD.
	;- Actualizando  (3)
	;- PLC_AccAnt    (2)
	;- DN            (1)
	;- TimerActivado (0)
	;Timers de 16 bits.
	Tim0Config                      ;Configuracion y banderas del Timer0.
	Tim0Presc                       ;Prescaler del Timer0.	
	Tim0LCount                      ;LSB de la Cuenta del Timer0.
	Tim0HCount                      ;MSB de la Cuenta del Timer0.
	Tim0LPreset                     ;LSB del Preset del Timer0.
	Tim0HPreset                     ;MSB del Preset del Timer0.

	Tim1Config                      ;Configuracion y banderas del Timer1.
	Tim1Presc                       ;Prescaler del Timer1.
	Tim1LCount                      ;LSB de la Cuenta del Timer1.
	Tim1HCount                      ;MSB de la Cuenta del Timer1.
	Tim1LPreset                     ;LSB del Preset del Timer1.
	Tim1HPreset                     ;MSB del Preset del Timer1.

	Tim2Config                      ;Configuracion y banderas del Timer2.
	Tim2Presc                       ;Prescaler del Timer2.
	Tim2LCount                      ;LSB de la Cuenta del Timer2.
	Tim2HCount                      ;MSB de la Cuenta del Timer2.
	Tim2LPreset                     ;LSB del Preset del Timer2.
	Tim2HPreset                     ;MSB del Preset del Timer2.

	Tim3Config                      ;Configuracion y banderas del Timer3.
	Tim3Presc                       ;Prescaler del Timer3.
	Tim3LCount                      ;LSB de la Cuenta del Timer3.
	Tim3HCount                      ;MSB de la Cuenta del Timer3.
	Tim3LPreset                     ;LSB del Preset del Timer3.
	Tim3HPreset                     ;MSB del Preset del Timer3.

	Tim4Config                      ;Configuracion y banderas del Timer4.
	Tim4Presc                       ;Prescaler del Timer4.
	Tim4LCount                      ;LSB de la Cuenta del Timer4.
	Tim4HCount                      ;MSB de la Cuenta del Timer4.
	Tim4LPreset                     ;LSB del Preset del Timer4.
	Tim4HPreset                     ;MSB del Preset del Timer4.

	Tim5Config                      ;Configuracion y banderas del Timer5.
	Tim5Presc                       ;Prescaler del Timer5.
	Tim5LCount                      ;LSB de la Cuenta del Timer5.
	Tim5HCount                      ;MSB de la Cuenta del Timer5.
	Tim5LPreset                     ;LSB del Preset del Timer5.
	Tim5HPreset                     ;MSB del Preset del Timer5.

	Tim6Config                      ;Configuracion y banderas del Timer6.
	Tim6Presc                       ;Prescaler del Timer6.
	Tim6LCount                      ;LSB de la Cuenta del Timer6.
	Tim6HCount                      ;MSB de la Cuenta del Timer6.
	Tim6LPreset                     ;LSB del Preset del Timer6.
	Tim6HPreset                     ;MSB del Preset del Timer6.

	Tim7Config                      ;Configuracion y banderas del Timer7.
	Tim7Presc                       ;Prescaler del Timer7.
	Tim7LCount                      ;LSB de la Cuenta del Timer7.
	Tim7HCount                      ;MSB de la Cuenta del Timer7.
	Tim7LPreset                     ;LSB del Preset del Timer7.
	Tim7HPreset                     ;MSB del Preset del Timer7.

        ;Usadas por los horarios semanales.
	HorarioTemp                     ;Contador usado en la rutina de actualizacion de los horarios.
	HorarioFSR                      ;Almacena el FSR del ultimo timer.

	;Bits del registro HorConfig:
        ;- DiaDesdeMSB     (7)          ;Bit 3 del Dia Desde.
        ;- DiaDesde        (6)          ;Bit 2 del Dia Desde.
        ;- DiaDesdeLSB     (5)          ;Bit 1 del Dia Desde.
        ;- DiaHastaMSB     (4)          ;Bit 3 del Dia Hasta.
        ;- DiaHasta        (3)          ;Bit 2 del Dia Hasta.
        ;- DiaHastaLSB     (2)          ;Bit 1 del Dia Hasta.
	;- DN              (1)          ;Indica que la hora actual esta dentro del intervalo.
	;- BandVerHorario  (0)          ;Indica que paso otro minuto y se debe verificar.
	;Horarios semanales.
	Hor0Config                      ;Configuracion y banderas del Horario0.
        Hor0HorDesde                    ;Hora Desde del Horario0.
        Hor0MinDesde                    ;Min Desde del Horario0.
        Hor0HorHasta                    ;Hora Hasta del Horario0.
        Hor0MinHasta                    ;Min Hasta del Horario0.

	Hor1Config                      ;Configuracion y banderas del Horario1.
        Hor1HorDesde                    ;Hora Desde del Horario1.
        Hor1MinDesde                    ;Min Desde del Horario1.
        Hor1HorHasta                    ;Hora Hasta del Horario1.
        Hor1MinHasta                    ;Min Hasta del Horario1.

      	Hor2Config                      ;Configuracion y banderas del Horario2.
        Hor2HorDesde                    ;Hora Desde del Horario2.
        Hor2MinDesde                    ;Min Desde del Horario2.
        Hor2HorHasta                    ;Hora Hasta del Horario2.
        Hor2MinHasta                    ;Min Hasta del Horario2.

	Hor3Config                      ;Configuracion y banderas del Horario3.
        Hor3HorDesde                    ;Hora Desde del Horario3.
        Hor3MinDesde                    ;Min Desde del Horario3.
        Hor3HorHasta                    ;Hora Hasta del Horario3.
        Hor3MinHasta                    ;Min Hasta del Horario3.

	ENDC
;---------------------------------------------------------------------------------------
;Definiciones de bits.
;---------------------------------------------------------------------------------------
;Bits usados por las rutina de recepción y transmisión serial RS-232.
#define	TX		PORTC, 6		;RC6 es TX de la transmision RS-232.
#define	RX		PORTC, 7		;RC7 es RX de la transmision RS-232.
#define BandRXTimeOut	TXRXBand, 0		;Indica el timout de recepción.
#define BandTOut	TXRXBand, 1		;Indica que ya paso el tiempo de espera.
#define BandRXError	TXRXBand, 2		;Indica que ocurrio un error en la recepcion.
#define BandRXByte	TXRXBand, 3		;Indica que se recibio un byte.

;#define LinkSwitch	PORTC, 7		;Entrada de programacion.

;Bits usados por las rutinas de escritura y lectura
;de la memoria eeprom I2C (24LC64).
di_m      		equ    7		;Bit recibido de la eeprom.
do_m      		equ    6       		;Bit a enviar a la eeprom.
wrerr			equ    5		;Error en escritura de la eeprom.
rderr			equ    4		;Error en lectura de la eeprom.
First			equ    3		;Indica que se debe leer desde el principio.
#define	sda		PORTC, 4		;RC4 es SDA de la memoria EEPROM.
#define	scl		PORTC, 3		;RC3 es SCL de la memoria EEPROM.
#define	sdadir		TRISC, 4		;Se usan para configurar los pines como
#define	scldir		TRISC, 3		;entradas o salidas.

;Definir el registro PLC_ContProg de 16 bits.
#define	PLC_ContProg	PLC_ContProgH, PLC_ContProgL
PLC_InicioProg		equ	D'21'		;El programa empieza en 21.
                                                ; 1-Registro de Configuracion.
                                                ;20-Registros de horarios.

;Leds indicadores
#define	LedRun		PORTA, 2		;RA2 es el led del modo Run.
#define	LedPgm		PORTA, 3		;RA3 es el led del modo Prog.

;Bits usados por los contadores y los timers.
DN                      equ     0x01            ;Bit usado por los contadores y timers.
DIR                     equ     0x03            ;Bit usado por los contadores.

;Pines del Registro de desplazamiento CD4021.
#define	DOUT_4021       PORTC, 5	        ;RC5 es Data Out del registro de desplazamiento.
#define	CK_4021	        PORTC, 2	        ;RC2 es Clock Input del registro de desplazamiento.
#define	SL_4021	        PORTA, 4	        ;RA4 es Shift Load del registro de desplazamiento.

;Pines del registro de desplazamiento CD4094.
#define	LE_4094	        PORTB, 7	        ;RB7 es Latch Enable del registro de desplazamiento.
#define	CK_4094	        PORTB, 6	        ;RB6 es Clock Input del registro de desplazamiento.
#define	DIN_4094        PORTB, 5	        ;RB5 es Data In del registro de desplazamiento.

;Bits de la rutina de comunicacion con la PC.
#define	BandDownload    eeprom, 2	        ;Indica que se acaba de actualizar el programa.

;Bits usados por la rutina de reloj de tiempo real.
#define	BandTimeChange  BandReloj, 0	        ;Esta bandera indica que se actualizo el reloj.

;Banderas usadas por los timers.
#define BandBase10ms    BandReloj, 1            ;Esta bandera indica que ocurrio la int
                                                ;del timer 0.
;***************************************************************************************
;       Vector de Reset
;***************************************************************************************
Reset	ORG	.0		;El vector de reset esta en la posicion 0.
	goto	Config		;Ir a configurar los puertos.

;***************************************************************************************
;       Vector de Interrupcion
;***************************************************************************************
	ORG	.4		;El vector de interrupcion esta en la posicion 4
	goto	Interrupt	;Ir a la rutina de interrupcion

;***************************************************************************************
;       Subrutinas                                                         
;***************************************************************************************
;---------------------------------------------------------------------------------------
;Esta tabla retorna la mascara para un bit especifico.
;El bit debe estar en W, antes de llamar la tabla.
;Es usada por la rutina que activa los timers.
;---------------------------------------------------------------------------------------
Tabla_Masc
	addwf	PCL, F
	retlw	B'00000001'	;Mascara para el bit 0.
	retlw	B'00000010'	;Mascara para el bit 1.
	retlw	B'00000100'	;Mascara para el bit 2.
	retlw	B'00001000'	;Mascara para el bit 3.
	retlw	B'00010000'	;Mascara para el bit 4.
	retlw	B'00100000'	;Mascara para el bit 5.
	retlw	B'01000000'	;Mascara para el bit 6.
	retlw	B'10000000'	;Mascara para el bit 7.
	return


;---------------------------------------------------------------------------------------
;Subrutina de retardo en milisegundos.
;El registro W debe contener el numero de milisegundos del retardo.
;---------------------------------------------------------------------------------------
Retardo 
	ifndef	Debug
	movwf   loops     	;
top2    movlw   D'110'    	;
        movwf   loops2    	;
top     goto	$+1
        goto	$+1
        goto	$+1
        decfsz  loops2, F 	;pregunta si termino 1 ms
        goto    top
        decfsz  loops, F  	;pregunta si termina el retardo
        goto    top2
	endif
        return


;---------------------------------------------------------------------------------------
;Esta rutina recibe un byte en forma serial de la PC.
;La rutina no retorna hasta recibir un byte.
;El byte recibido queda en el registro W.
;---------------------------------------------------------------------------------------
Recibir_232
	btfss	PIR1, RCIF	        ;Se recibio un byte ?
	goto	Recibir_232             ;No, esperar.
	movf	RCREG, W	        ;Si, leer el buffer de recepcion.
	return			        ;Salir.


;---------------------------------------------------------------------------------------
;Esta rutina envía un byte en forma serial a la PC.
;La rutina no retorna hasta comenzar a enviar el byte.
;El byte a enviar debe estar en el registro W antes de llamar a la rutina.
;---------------------------------------------------------------------------------------
Enviar_232
	btfss	PIR1, TXIF    	        ;Ya se envio el byte anterior ?        
    	goto	Enviar_232	        ;No, esperar que se envie.
	movwf	TXREG		        ;Si, copiar al buffer de transmision.
	return			        ;Salir.


;---------------------------------------------------------------------------------------
;Estas rutinas suman y restan binarios de 16 bits.
;Los numeros son AccB y AccA.
;D_sub (AccB = AccB - AccA)
;D_add (AccB = AccB + AccA)
;Basado en la Nota de Aplicacion 526 de Microchip.
;---------------------------------------------------------------------------------------
D_sub   call    neg_A          ;At first negate ACCa; Then add
D_add   movf    AccAL,W
	addwf   AccBL, F       ;add lsb
	btfsc   STATUS,C       ;add in carry
	incf    AccBH, F
	movf    AccAH,W
	addwf   AccBH, F       ;add msb
	return
neg_A   comf    AccAL, F       ; negate AccA ( -AccA -> AccA )
	incf    AccAL, F
	btfsc   STATUS,Z
	decf    AccAH, F
	comf    AccAH, F
	return


;---------------------------------------------------------------------------------------
;Esta rutina genera el bit de start para la memoria.
;(Bajando SDA mientras SCL esta en 1).
;---------------------------------------------------------------------------------------
BSTART
        bsf     sda		;Asegurarse que sda esta en uno.
	bsf	STATUS, RP0	;Cambio al banco 1 de RAM.
        bcf	sdadir		;Configurar sda y scl como
	bcf	scldir		;salidas.
	bcf	STATUS, RP0	;Volver al banco 0 de RAM.
	bcf     scl		;Asegurarse que scl esta en cero.
        nop
        bsf     scl		;Subir scl.
	goto	$+1
	goto	$+1        
        nop
	;Bajar SDA mientras SCL esta en uno.
        bcf     sda		;Bajar sda.
	goto	$+1
	goto	$+1
        nop			;Ajuste de tiempo.
        bcf     scl		;Bajar scl.
	goto	$+1
        return


;---------------------------------------------------------------------------------------
;Esta rutina genera el bit de stop para la memoria.
;(Subiendo SDA mientras SCL esta en 1).
;---------------------------------------------------------------------------------------
BSTOP
	bsf	STATUS, RP0	;Cambio al banco 1 de RAM.
        bcf	sdadir		;Configurar sda y scl como
	bcf	scldir		;salidas.
	bcf	STATUS, RP0	;Volver al banco 0 de RAM.
        bcf     sda		;Asegurarse que sda esta en cero.
        goto	$+1
        nop
        bsf     scl		;Subir scl.
        goto	$+1    
        nop
	;Subir SDA mientras SCL esta en uno.
        bsf     sda		;Subir sda.
        goto	$+1
        bcf     scl		;Bajar scl.
        goto	$+1
        return


;---------------------------------------------------------------------------------------
;Esta rutina envia un bit a la memoria.
;Se envia el bit guardado en 'do_m'.
;---------------------------------------------------------------------------------------
BITOUT_M
	bsf	STATUS, RP0	;Cambio al banco 1 de RAM.
        bcf	sdadir		;Configurar sda y scl como
	bcf	scldir		;salidas.
	bcf	STATUS, RP0	;Volver al banco 0 de RAM.
        btfss   eeprom, do_m    ;do=1 ?
        goto    bitlow         	;No, ir a bitlow.
        bsf     sda		;Subir sda.
        goto    clkout         	;Activar el reloj.
bitlow  bcf     sda		;Bajar sda.
clkout  bsf     scl		;Enviar un pulso de reloj.
        goto	$+1
        goto	$+1
        bcf     scl		;Volver scl a cero.
	return


;---------------------------------------------------------------------------------------
;Esta rutina recibe un bit de la memoria.
;El bit recibido queda en 'di_m'.
;---------------------------------------------------------------------------------------
BITIN_M
        bsf     eeprom, di_m    ;Se supone que di sea un uno.
	bsf	STATUS, RP0	;Cambio al banco 1 de RAM.
        bsf	sdadir		;Configurar sda como entrada.
	bcf	STATUS, RP0	;Volver al banco 0 de RAM.        
        bsf     sda		;Subir sda para leer la entrada.
        bsf     scl		;Subir scl.
        goto	$+1
        goto	$+1
        nop                     ;
        btfss   sda		;Se recibio un 1 ?
        bcf     eeprom, di_m    ;No, poner di en cero.
        bcf     scl		;Bajar scl.
        return


;Esta rutina transmite un byte a la memoria.
;El byte a transmitir debe estar en el registro 'txbuf'.
;Luego de enviar el byte envia un pulso adicional de reloj
;para recibir el acknoledge de la memoria.
TX_BYTE_M 	
	movlw   .8
        movwf   count_m        	;Se enviaran 8 bits.
TXLP    bcf     eeprom, do_m    ;Se supone que se enviara un cero.
        btfsc   txbuf_m, 7      ;MSB de txbuf es cero ?
        bsf     eeprom, do_m   	;No, es un uno.
        call    BITOUT_M       	;Enviar el bit a la memoria.
        rlf     txbuf_m, F     	;Rotar txbuf un bit a la izquierda.
        decfsz  count_m, F     	;Ya se enviaron los 8 bits ?
        goto    TXLP           	;No, enviar el proximo bit.
        call    BITIN_M        	;Leer el bit de acknoledge.
	btfss	eeprom, di_m	;Si bit acknoledge = 0,
	bcf	eeprom, rderr	;indicar lectura OK,
	btfsc	eeprom, di_m	;sino
	bsf	eeprom, rderr	;indicar error en lectura.
	return


;Esta rutina recibe un byte de la memoria.
;El byte recibido queda en el registro 'datai_m'.
;Luego envia un NAK para indicar que no se leeran mas bytes.
RX_BYTE_M
        clrf    datai_m         ;Borrar el buffer de recepcion.
        movlw   .8              ;Se recibiran 8 bits.
        movwf   count_m
        bcf     STATUS, C      	;Borrar el carry.
RXLP    rlf     datai_m, F     	;Rotar datai_m un bit a la izquierda.
        call    BITIN_M        	;Leer un bit de la memoria.
        btfsc   eeprom, di_m	;Se recibio un 0 ?
        bsf     datai_m,0 	;No, es un 1.
        decfsz  count_m, F     	;Se recibieron los 8 bits ?
        goto    RXLP           	;No, recibir el siguiente bit.
        bsf     eeprom, do_m   	;Si, enviar un 1 a la memoria (NAK)
        call    BITOUT_M       	;para finalizar la transmision.
        return


;Esta rutina lee un byte de la memoria en modo de acceso aleatorio.
;La direccion a leer debe estar en los registros 'addrH' y 'addrL', 
;siendo el primero el MSB y el segundo el LSB.
;El byte leido queda en el registro 'datai_m'.
;Para acceder en modo aleatorio se debe configurar el puntero de
;posicion antes de cada lectura.
;Esto se logra enviando un byte de control en 'modo escritura' con
;la direccion que se quiere leer.
READ_BYTE
        bsf     STATUS, RP0
        bcf	PIE1, RCIE              ;Deshabilitar la interrupcion por fin de recepcion.
        btfsc   PIE1, RCIE
        goto    READ_BYTE               ;No, esperar que se deshabiliten.
        bcf     STATUS, RP0

      	btfss	eeprom, First	        ;Se debe posicionar el puntero ?
	goto	Read_NormalByte         ;No, es un byte normal.
	bcf	eeprom, First	        ;Solo se activa una vez.	
	clrf	PLC_ContProgH	        ;Ubicar el puntero donde empieza el 	
	movlw	PLC_InicioProg	        ;programa del PLC.
	movwf	PLC_ContProgL
Read_NormalByte
        movf    PLC_ContProgH, W
        movwf   addrH
        movf    PLC_ContProgL, W
        movwf   addrL
	call    BSTART      	        ;Enviar el bit de start.
                             	        ;
                             	        ;Ahora enviar el byte de control como
				        ;si se quisiera escribir en la direccion
				        ;para posicionar el puntero.
	bcf	slave_m, 0	        ;Preparar para escritura.
	movf	slave_m, W	        ;Copiar la direccion de la memoria
	movwf	txbuf_m		        ;al buffer de transmision
        call    TX_BYTE_M      	        ;y enviarlo a la memoria.
        movf    addrH, W      	        ;Copiar el MSB de la direccion
        movwf   txbuf_m       	        ;al buffer de transmision
        call    TX_BYTE_M      	        ;y enviarlo a la memoria.
        movf    addrL, W      	        ;Copiar el LSB de la direccion
        movwf   txbuf_m       	        ;al buffer de transmision
        call    TX_BYTE_M      	        ;y enviarlo a la memoria.
                             	        ;
                             	        ;Ahora leer un byte de la memoria.
                             	        ;
        call    BSTART      	        ;Enviar otro bit de start.
	bsf	slave_m, 0	        ;Preparar para lectura.
	movf	slave_m, W	        ;Copiar la direccion de la memoria
	movwf	txbuf_m		        ;al buffer de transmision
        call    TX_BYTE_M      	        ;y enviarlo a la memoria.
        call    RX_BYTE_M      	        ;Leer un byte de la memoria.
        call    BSTOP       	        ;Enviar el bit de stop para finalizar la transmision.
        bsf     STATUS, RP0
        bsf	PIE1, RCIE              ;Rehabilitar la interrupcion por fin de recepcion.
        bcf     STATUS, RP0
	return


;---------------------------------------------------------------------------------------
;Esta rutina escribe un byte en la memoria usando polling.
;El byte a escribir debe estar en el registro 'datao_m'
;La direccion a escribir debe estar en los resigros 'addrH' y 'addrL',
;siendo el primero el MSB y el segundo el LSB.
;Para determinar el fin de la escritura se utiliza polling.
;Para esto se envia un byte de control en 'modo escritura' y se lee
;el bit de acknoledge. Si el bit es 0, el ciclo de escritura termino,
;si es 1, todavia continua el ciclo de escritura.
;---------------------------------------------------------------------------------------
WRITE_BYTEP
	bcf	eeprom, wrerr	        ;Se presupone que no se va a producir un error.
        bcf	slave_m, 0	        ;Preparar para escritura.
	call	BSTOP		        ;Enviar bit de stop para finalizar cualquier operacion anterior.
byte    call    BSTART         	        ;Enviar el bit de start.
        movf    slave_m, W    	        ;Copiar la direccion de la memoria
        movwf   txbuf_m        	        ;al buffer de transmision
        call    TX_BYTE_M      	        ;y enviarlo a la memoria.
	movf    addrH, W      	        ;Copiar el MSB de la direccion
        movwf   txbuf_m       	        ;al buffer de transmision
        call    TX_BYTE_M      	        ;y enviarlo a la memoria.
        movf    addrL, W      	        ;Copiar el LSB de la direccion
        movwf   txbuf_m                 ;al buffer de transmision
        call    TX_BYTE_M      	        ;y enviarlo a la memoria.  
        movf    datao_m, W     	        ;Copiar 'datao'
        movwf   txbuf_m        	        ;al buffer de transmision
        call    TX_BYTE_M      	        ;y enviarlo a la memoria.
        call    BSTOP          	        ;Enviar el bit de stop para iniciar
                                        ;el ciclo de escritura.
                               	        ;
        movlw   .40                     ;Iniciar el ciclo de polling
        movwf   pollcnt                 ;Como maximo sera de 40 ciclos.
poll    call    BSTART                  ;Enviar el bit de start.
	bcf	slave_m, 0              ;Preparar para escritura.
	movf	slave_m, W              ;Copiar la direccion de la memoria
        movwf   txbuf_m                 ;al buffer de transmision
        call    TX_BYTE_M               ;y enviarlo a la memoria.
        btfss   eeprom, di_m            ;Bit de Acknoledge = 1 ?
        return                          ;No, termino el ciclo de escritura.
        decfsz  pollcnt, F              ;Si, ya se probaron las 40 veces ?
        goto    poll                    ;No, seguir haciendo polling.
	bsf	eeprom, wrerr           ;Indicar error en grabacion.
	return


;---------------------------------------------------------------------------------------
;Esta rutina lee de la memoria eeprom la siguiente instruccion.
;---------------------------------------------------------------------------------------
LeerInstruc
        btfss   BandDownload            ;Se actualizo el programa ?
        goto    LeerInstruc_Next
        bcf     BandDownload            ;Si, borrar la bandera.
        call    Init_PLC                ;Parar los timers y borrar contadores.
        bsf     eeprom, First           ;Leer la primera posicion.

LeerInstruc_Next
	call	READ_BYTE		;Leer la posicion apuntada por PLC_ContProg.
        ifdef   DebugInst               ;Ver si se debe enviar la instruccion leida.
        movf    datai_m, W
        call    Enviar_232              ;Enviar el byte leido a la PC.
        ;Esperar que se pulse Q0,7.
Leer_Puls0
        call    LeerEntradas
        btfsc   Entradas0, 7
        goto    Leer_Puls0
        movlw   D'60'
        call    Retardo
        ;Esperar que se suelte pulse Q0,7.
Leer_Puls1
        call    LeerEntradas
        btfss   Entradas0, 7
        goto    Leer_Puls1
        movlw   D'60'
        call    Retardo        
        endif
        
	;Registro de estimulo (memoria.reg)
	;Posicion de memoria de programa (InjectMem)
	;Registro de RAM a estimular (datai_m = 3A)
InjectMem
	nop
	incdf	PLC_ContProg		;PLC_ContProg = PLC_ContProg + 1
	return


;---------------------------------------------------------------------------------------
;Esta tabla devuelve la cantidad de parametros de cada sentencia.
;La sentencia debe estar en el registro datai_m, la cantidad de parametros
;queda en el registro W.
;---------------------------------------------------------------------------------------
Tabla_Deco_Inst
        clrf    PCLATH                  ;Esta tabla esta en la pagina 0 de la memoria.
        movf    datai_m, W
	addwf	PCL, F
        nop
	retlw	D'2'			;La instruccion LD tiene 2 parametros.
	retlw	D'2'			;La instruccion LDB tiene 2 parametros.
	retlw	D'2'			;La instruccion OTE tiene 2 parametros.
	retlw	D'2'			;La instruccion OTL tiene 2 parametros.
	retlw	D'2'			;La instruccion OTU tiene 2 parametros.
	retlw	D'2'			;La instruccion OTT tiene 2 parametros.
	retlw	D'2'			;La instruccion OSR tiene 2 parametros.
	retlw	D'2'			;La instruccion OSF tiene 2 parametros.
	retlw	D'2'			;La instruccion AND tiene 2 parametros.
	retlw	D'2'			;La instruccion ANDNOT tiene 2 parametros.
	retlw	D'2'			;La instruccion OR tiene 2 parametros.
	retlw	D'2'			;La instruccion ORNOT tiene 2 parametros.
	retlw	D'0'			;La instruccion ANDLD no tiene parametros.
	retlw	D'0'			;La instruccion ORLD no tiene parametros.
	retlw	D'4'			;La instruccion TON tiene 4 parametros.
	retlw	D'4'			;La instruccion TOF tiene 4 parametros.
	retlw	D'4'			;La instruccion TIP tiene 4 parametros.
	retlw	D'4'			;La instruccion TFL tiene 4 parametros.
	retlw	D'3'			;La instruccion CTU tiene 3 parametros.
	retlw	D'3'			;La instruccion CTD tiene 3 parametros.
	retlw	D'1'			;La instruccion RES tiene 1 parametro.
	retlw	D'3'			;La instruccion CTK tiene 3 parametros.


;---------------------------------------------------------------------------------------
;Esta rutina decodifica la instruccion,
;De ser necesario se leen mas datos de la memoria eeprom.
;---------------------------------------------------------------------------------------
DecoInstruc
        btfsc   BandDownload            ;Se actualizo el programa ?
        return                          ;Si.
	;Ver si es la instruccion END.
	movf	datai_m, W
	xorlw	Inst_END
	btfss	STATUS, Z		;Es la instruccion END ?
	call	Tabla_Deco_Inst		;No, ver cuantos parametros tiene la instruccion.
	movwf	ContByte		;ContByte = Cantidad de bytes a leer.
	movlw	Reg_Inst
	movwf	FSR			;FSR apunta al primer registro de la instruccion.

Deco_LeerParm
	movf	datai_m, W		;Copiar el byte leido de la memoria al registro de
	movwf	INDF			;la instruccion.
	movf	ContByte, W
	btfsc	STATUS, Z		;ContByte = 0 ?
	return				;Si, ya se leyo el ultimo parametro.
        btfsc   BandDownload            ;Se actualizo el programa ?
        return                          ;Si, salir.
	call	LeerInstruc		;No, Leer otro byte de la memoria.
	incf	FSR, F			;FSR apunta al siguiente registro de la instruccion.
	decf	ContByte, F		;Indicar que se leyo otro byte.
	goto	Deco_LeerParm		;Ver si hay mas parametros.


;---------------------------------------------------------------------------------------
;Esta rutina ejecuta la instruccion,
;e incrementa el contador de programa del PLC.
;---------------------------------------------------------------------------------------
EjecInstruc
        btfsc   BandDownload            ;Se actualizo el programa ?
        return                          ;Si, salir.
	;Ver si es la instruccion Inst_END
	movf	Reg_Inst, W
	xorlw	Inst_END
	btfsc	STATUS, Z		;Es la instruccion END ?
	goto	Chk_Ejec_END		;Si, ejecutarla.

	movf	Reg_Param1, W           ;No, FSR apunta al primer parametro.
	movwf	FSR
	movf	Reg_Inst, W		;PLCAux = Reg_Inst
	movwf	PLC_Aux

	;Ver cuantos parametros tiene la instruccion.
	movlw	LOW	Tabla_Ejec_Inst	;Ver el LSB de la direccion de la tabla.
	addwf	PLC_Aux

	movlw	HIGH	Tabla_Ejec_Inst	;Cargar PCLATH con los bits altos
	btfsc	STATUS, C		;Se cruzo a otra pagina ?
	addlw	0x01			;Si, incrementar el MSB de la direccion.
	movwf	PCLATH			;No, Guardar el MSB de la direccion.
	movf	PLC_Aux, W
Tabla_Ejec_Inst
	movwf	PCL
	goto	Chk_Ejec_LD		;Ejecutar la instruccion LD.
	goto	Chk_Ejec_LDB		;Ejecutar la instruccion LDB.
	goto	Chk_Ejec_OTE		;Ejecutar la instruccion OTE.
	goto	Chk_Ejec_OTL		;Ejecutar la instruccion OTL.
	goto	Chk_Ejec_OTU		;Ejecutar la instruccion OTU.
	goto	Chk_Ejec_OTT		;Ejecutar la instruccion OTT.
	goto	Chk_Ejec_OSR		;Ejecutar la instruccion OSR.
	goto	Chk_Ejec_OSF		;Ejecutar la instruccion OSF.
	goto	Chk_Ejec_AND		;Ejecutar la instruccion AND.
	goto	Chk_Ejec_ANDNOT		;Ejecutar la instruccion ANDNOT.
	goto	Chk_Ejec_OR		;Ejecutar la instruccion OR.
	goto	Chk_Ejec_ORNOT		;Ejecutar la instruccion ORNOT.
	goto	Chk_Ejec_ANDLD		;Ejecutar la instruccion ANDLD.
	goto	Chk_Ejec_ORLD		;Ejecutar la instruccion ORLD.
	goto	Chk_Ejec_TON		;Ejecutar la instruccion TON.
	goto	Chk_Ejec_TOF		;Ejecutar la instruccion TOF.
	goto	Chk_Ejec_TIP		;Ejecutar la instruccion TIP.
	goto	Chk_Ejec_TFL		;Ejecutar la instruccion TFL.
	goto	Chk_Ejec_CTU		;Ejecutar la instruccion CTU.
	goto	Chk_Ejec_CTD		;Ejecutar la instruccion CTD.
	goto	Chk_Ejec_RES		;Ejecutar la instruccion RES.
       	goto	Chk_Ejec_CTK		;Ejecutar la instruccion CTK.

Chk_Ejec_LD
	call	Push
	clrf	PLC_Acc
	;Leer el registro apuntado por Reg_Param1.
	movf	INDF, W
	andwf	Reg_Param2, W
	xorwf	Reg_Param2, W
	btfsc	STATUS, Z
        bsf     PLC_Acc, 0              ;Salir con PLC_Acc = 0x01.
	return

Chk_Ejec_LDB
	call	Push
	clrf	PLC_Acc
	;Leer el registro apuntado por Reg_Param1.
	movf	INDF, W
	andwf	Reg_Param2, W
	xorwf	Reg_Param2, W
	btfss	STATUS, Z
        bsf     PLC_Acc, 0              ;Salir con PLC_Acc = 0x01.
	return

Chk_Ejec_OTE
	btfsc	PLC_Acc, 0		;PLC_Acc = 0 ?
	goto	Ejec_OTE_1		;No, poner el bit en 1.	
	;Poner el bit en 0.
	comf	Reg_Param2, W
	andwf	INDF, F
	return
Ejec_OTE_1
	;Poner el bit en 1.
	movf	Reg_Param2, W
	iorwf	INDF, F
	return

Chk_Ejec_OTL
	btfss	PLC_Acc, 0		;PLC_Acc = 1 ?
	return				;No, salir.
	movf	Reg_Param2, W		;Si, Poner el bit en 1.
	iorwf	INDF, F
	return

Chk_Ejec_OTU
	btfss	PLC_Acc, 0		;PLC_Acc = 1 ?
	return				;No, salir.
	comf	Reg_Param2, W		;Si, Poner el bit en 0.
	andwf	INDF, F
	return

Chk_Ejec_OTT
	btfss	PLC_Acc, 0		;El acumulador esta en 1 ?
	goto	Guardar_OTT		;No, guardar el estado del acumulador.
	incf	FSR, F			;FSR apunta al registro de estado anterior.
	movf	INDF, W			;Ver si antes estaba en 0.
	andwf	Reg_Param2, W
	btfss	STATUS, Z		;Antes estaba en 0 ?
	goto	Guardar_OTT		;No, guardar el estado del acumulador.
	decf	FSR, F			;Si, FSR apunta al registro actual.
	movf	Reg_Param2, W		;Intercambiar el estado del bit.
	xorwf	INDF, F

Guardar_OTT
	;Guardar el estado actual del acumulador.
	movf	Reg_Param1, W		;FSR apunta al registro apuntado
	movwf	FSR			;por Reg_Param1.
	incf	FSR, F			;FSR apunta al registro de estado anterior.
	btfss	PLC_Acc, 0		;El acumulador esta en 1 ?
	goto	Guardar_OTT0		;No, guardar el bit 0.
	movf	Reg_Param2, W		;Poner el bit en 1.
	iorwf	INDF, F
	return
Guardar_OTT0
	comf	Reg_Param2, W		;Poner el bit en 0.
	andwf	INDF, F
	return

Chk_Ejec_OSR
        return

Chk_Ejec_OSF
        return

Chk_Ejec_AND
	movf	INDF, W
	andwf	Reg_Param2, W
	xorwf	Reg_Param2, W
	btfss	STATUS, Z		;Si el bit de Param1 esta en 0,
	movlw	0x00			;W = 0x00
	btfsc	STATUS, Z		;sino,
	movlw	0x01			;W = 0x01.
	andwf	PLC_Acc, F		;PLC_Acc = W AND PLC_Acc.
	return

Chk_Ejec_ANDNOT
	movf	INDF, W
	andwf	Reg_Param2, W
	xorwf	Reg_Param2, W
	btfsc	STATUS, Z		;Si el bit de Param1 esta en 1,
	movlw	0x00			;W = 0x00
	btfss	STATUS, Z		;sino,
	movlw	0x01			;W = 0x01.
	andwf	PLC_Acc, F		;PLC_Acc = W AND PLC_Acc.
	return

Chk_Ejec_OR
	movf	INDF, W
	andwf	Reg_Param2, W
	xorwf	Reg_Param2, W
	btfss	STATUS, Z		;Si el bit de Param1 esta en 0,
	movlw	0x00			;W = 0x00
	btfsc	STATUS, Z		;sino,
	movlw	0x01			;W = 0x01.
	iorwf	PLC_Acc, F		;PLC_Acc = W AND PLC_Acc.
	return

Chk_Ejec_ORNOT
	movf	INDF, W
	andwf	Reg_Param2, W
	xorwf	Reg_Param2, W
	btfsc	STATUS, Z		;Si el bit de Param1 esta en 1,
	movlw	0x00			;W = 0x00
	btfss	STATUS, Z		;sino,
	movlw	0x01			;W = 0x01.
	iorwf	PLC_Acc, F		;PLC_Acc = W AND PLC_Acc.
	return

Chk_Ejec_ANDLD
        movf    Pila, W
        andlw   B'00000001'
        andwf   PLC_Acc, F
        btfss   PLC_Acc, 0
        bcf     Pila, 0
        btfsc   PLC_Acc, 0
        bsf     Pila, 0
        call    Pop                     ;Sacar el primero de la pila y ponerlo en PLC_Acc.
	return

Chk_Ejec_ORLD
        movf    Pila, W
        andlw   B'00000001'
        iorwf   PLC_Acc, F
        btfss   PLC_Acc, 0
        bcf     Pila, 0
        btfsc   PLC_Acc, 0
        bsf     Pila, 0
        call    Pop                     ;Sacar el primero de la pila y ponerlo en PLC_Acc.
	return

Chk_Ejec_TON
	call	ActTimers		;Activar el timer correspondiente.
	return				;Salir.

Chk_Ejec_TOF
	call	ActTimers		;Activar el timer correspondiente.
	return				;Salir.

Chk_Ejec_TIP
	call	ActTimers		;Activar el timer correspondiente.
	return				;Salir.

Chk_Ejec_TFL
	call	ActTimers		;Activar el timer correspondiente.
	return				;Salir.

Chk_Ejec_CTU
	call	IncCounter		;Ver si se debe incrementar el contador.
	return				;Salir.

Chk_Ejec_CTD
	call	DecCounter		;Ver si se debe decrementar el contador.
	return

Chk_Ejec_RES
	call	ResTimCont		;Resetear el contador o el timer.
	return

Chk_Ejec_CTK
	call	IncDecCounter		;Si, Incrementar o Decrementar el contador.
	return

Chk_Ejec_END
	;Salir borrando el acumulador y el contador de programa del PLC.
	clrf	PLC_Acc
	;Contador de programa del PLC -> posicion del programa en la EEPROM.
	bsf	eeprom, First		;Empezar a leer desde la primera posicion.
	return


;---------------------------------------------------------------------------------------
;Esta rutina empuja el acumulador del PLC a la pila.
;Se utiliza para almacenar valores intermedios en la pila.
;---------------------------------------------------------------------------------------
Push
        ;Si PLC_Acc=0, C=0, sino C=1.
        btfss   PLC_Acc, 0
        bcf     STATUS, C
        btfsc   PLC_Acc, 0
        bsf     STATUS, C
        ;Rotar la pila un bit a la izquierda.
        rlf     Pila, F
	return


;---------------------------------------------------------------------------------------
;Esta rutina saca el acumulador de la pila.
;---------------------------------------------------------------------------------------
Pop
        ;Rotar la pila un bit a la derecha.
        rrf     Pila, F
        ;Si C=0, PLC_Acc=0, sino PLC_Acc=1.
        btfss   STATUS, C
        bcf     PLC_Acc, 0
        btfsc   STATUS, C
        bsf     PLC_Acc, 0
	return


;---------------------------------------------------------------------------------------
;Esta rutina determina si se debe activar el timer.
;El tipo de timer esta en el registro Reg_Inst.
;La direccion del timer esta en el registro Reg_Param1.
;La base de tiempo esta en el registro Reg_Param2.
;El MSB del preset esta en el registro Reg_Param3.
;El LSB del preset esta en el registro Reg_Param4.
;---------------------------------------------------------------------------------------
ActTimers
	;Ver el estado anterior de PLC_Acc.
	movf	Reg_Param1, W
	movwf	FSR
	btfss	PLC_Acc, 0		;El acumulador esta en 1 ?
	goto	Chk_FlancoDown		;No, ver si fue un flanco de bajada.
	btfsc	INDF, 2			;Si, antes estaba en 0 ?
	goto	TimersGuardar1		;No, guardar el estado 1.

	;Flanco de subida, ver que se debe hacer de acuerdo al tipo de timer.
	bsf	INDF, 3			;Deshabilitar la interrupcion sobre el timer.
	bsf	INDF, 2			;Guardar estado actual del acumulador.
	movf	Reg_Inst, W
	xorlw	Inst_TON
	btfsc	STATUS, Z		;Es un timer TON ?
	goto	TON_FlancoUp		;Si, ver flanco subida para TON.

	movf	Reg_Inst, W
	xorlw	Inst_TOF
	btfsc	STATUS, Z		;Es un timer TOF ?
	goto	TOF_FlancoUp		;Si, ver flanco subida para TOF.

	movf	Reg_Inst, W
	xorlw	Inst_TIP
	btfsc	STATUS, Z		;Es un timer TIP ?
	goto	TIP_FlancoUp		;Si, ver flanco subida para TIP.

	movf	Reg_Inst, W
	xorlw	Inst_TFL
	btfsc	STATUS, Z		;Es un timer TFL ?
	goto	TFL_FlancoUp		;Si, ver flanco subida para TFL.

TON_FlancoUp
	;Hubo un flanco de subida, reiniciar el timer.
	bsf	INDF, 0			;Activar el timer.
	bcf	INDF, DN			;Borrar DN.
	goto	PrepararTimer		;Configurar los registros del timer.

TON_FlancoDown
	;Hubo un flanco de bajada, desactivar el timer.
	bcf	INDF, 0			;Desactivar el timer.
	bcf	INDF, DN		;Desactivar DN.
	bcf	INDF, 3			;Rehabilitar la interrupcion sobre el timer.
	return

TOF_FlancoUp
	;Hubo un flanco de subida, desactivar el timer.
	bcf	INDF, 0			;Desactivar el timer.
	bsf	INDF, DN		;Activar DN.
	bcf	INDF, 3			;Rehabilitar la interrupcion sobre el timer.
	return

TOF_FlancoDown
	;Hubo un flanco de bajada, reiniciar el timer.
	bsf	INDF, 0			;Activar el timer.	
	goto	PrepararTimer		;Configurar los registros del timer.

TIP_FlancoUp
	;Hubo un flanco de subida, reiniciar el timer.
	bsf	INDF, 0			;Activar el timer.
	bsf	INDF, DN		;Activar DN.
	goto	PrepararTimer		;Configurar los registros del timer.

TFL_FlancoUp
	;Hubo un flanco de subida, reiniciar el timer.
	bsf	INDF, 0			;Activar el timer.
	bcf	INDF, DN		;Desactivar DN.
	goto	PrepararTimer		;Configurar los registros del timer.

TFL_FlancoDown
	;Hubo un flanco de bajada, desactivar el timer.
	bcf	INDF, 0			;Desactivar el timer.
	bcf	INDF, DN		;Desactivar DN.
	bcf	INDF, 3			;Rehabilitar la interrupcion sobre el timer.
	return

Chk_FlancoDown
	btfss	INDF, 2			;Hubo un flanco descendente ?
	goto	TimersGuardar0		;No, Guardar el estado 0.

	;Flanco de bajada, ver que se debe hacer de acuerdo al tipo de timer.
	bsf	INDF, 3			;Deshabilitar la interrupcion sobre el timer.
	bcf	INDF, 2			;Guardar estado actual del acumulador.
	movf	Reg_Inst, W
	xorlw	Inst_TON
	btfsc	STATUS, Z		;Es un timer TON ?
	goto	TON_FlancoDown		;Si, ver flanco bajada para TON.

	movf	Reg_Inst, W
	xorlw	Inst_TOF
	btfsc	STATUS, Z		;Es un timer TOF ?
	goto	TOF_FlancoDown		;Si, ver flanco bajada para TOF.

	movf	Reg_Inst, W
	xorlw	Inst_TIP
	btfsc	STATUS, Z		;Es un timer TIP ?
	goto	TimersGuardar0		;Si, No hacer nada, solo guardar el estado.

	movf	Reg_Inst, W
	xorlw	Inst_TFL
	btfsc	STATUS, Z		;Es un timer TFL ?
	goto	TFL_FlancoDown		;Si, ver flanco bajada para TFL.

TimersGuardar1
	;Guardar el estado en 1 del acumulador.
	bsf	INDF, 2			;Guardar estado actual del acumulador.
	return

TimersGuardar0
	;Guardar el estado en 0 del acumulador.
	bcf	INDF, 2			;Guardar estado actual del acumulador.
	return

PrepararTimer
	;Inicializar los bits del tipo de timer de acuerdo al registro Reg_Inst.
	movf	Reg_Inst, W
	xorlw	Inst_TON
	btfss	STATUS, Z		;Es un timer TON ?
	goto	Chk_Prep_TOF		;No, ver si es un timer TOF.
	bcf	INDF, 5			;Si, es un timer
	bcf	INDF, 4			;de tipo TON.
	goto	Prep_Prescaler		;Configurar el prescaler.

Chk_Prep_TOF
	;Ver si es un timer TOF.
	movf	Reg_Inst, W
	xorlw	Inst_TOF
	btfss	STATUS, Z		;Es un timer TOF ?
	goto	Chk_Prep_TIP		;No, ver si es un timer TIP.
	bcf	INDF, 5			;Si, es un timer
	bsf	INDF, 4			;de tipo TOF.
	goto	Prep_Prescaler		;Configurar el prescaler.

Chk_Prep_TIP
	;Ver si es un timer TIP.
	movf	Reg_Inst, W
	xorlw	Inst_TIP
	btfss	STATUS, Z		;Es un timer TIP ?
	goto	Chk_Prep_TFL		;No, ver si es un timer TFL.
	bsf	INDF, 5			;Si, es un timer
	bcf	INDF, 4			;de tipo TIP.
	goto	Prep_Prescaler		;Configurar el prescaler.

Chk_Prep_TFL
	;Ver si es un timer TFL
	movf	Reg_Inst, W
	xorlw	Inst_TFL
	btfss	STATUS, Z		;Es un timer TFL ?
	return				;No, tipo de timer desconocido.
	bsf	INDF, 5			;Si, es un timer
	bsf	INDF, 4			;de tipo TFL.
	goto	Prep_Prescaler		;Configurar el prescaler.

	;Inicializar los bits del prescaler de acuerdo a Reg_Param2.
Prep_Prescaler
	btfss	Reg_Param2, 0		;Copiar el bit 0 de Reg_Param2
	bcf	INDF, 6			;en el bit 6 de TimerConfig.
	btfsc	Reg_Param2, 0
	bsf	INDF, 6
	btfss	Reg_Param2, 1		;Copiar el bit 1 de Reg_Param2
	bcf	INDF, 7			;en el bit 7 de TimerConfig.
	btfsc	Reg_Param2, 1
	bsf	INDF, 7

	;Borrar el prescaler.
	incf	FSR, F			;FSR apunta al LSB del prescaler.
	clrf	INDF			;Poner a 0 el LSB del prescaler.

	;Borrar la cuenta del timer.
	incf	FSR, F			;FSR apunta al LSB del timer.
	clrf	INDF			;Poner a 0 el LSB del timer.
	incf	FSR, F			;FSR apunta al MSB del timer.
	clrf	INDF			;Poner a 0 el MSB del timer.

	;Copiar el preset de los parametros.
	incf	FSR, F			;FSR apunta al LSB del preset.
	movf	Reg_Param4, W		;Cargar Reg_Param4 al LSB
	movwf	INDF			;del preset.
	incf	FSR, F			;FSR apunta al MSB del preset.
	movf	Reg_Param3, W		;Cargar Reg_Param3 al MSB
	movwf	INDF			;del preset.
	movlw	D'5'
	subwf	FSR, F			;FSR apunta a TimerConfig
	bcf	INDF, 3			;Rehabilitar la interrupcion sobre el timer.
	return


;---------------------------------------------------------------------------------------
;Esta rutina determina si se debe incrementar un contador.
;La direccion del contador esta en el registro Reg_Param1.
;El MSB del preset esta en el registro Reg_Param2.
;El LSB del preset esta en el registro Reg_Param3.
;---------------------------------------------------------------------------------------
DecCounter
	;Ver el estado anterior de PLC_Acc.
	movf	Reg_Param1, W
	movwf	FSR
	btfss	PLC_Acc, 0		;El acumulador esta en 1 ?
	goto	CTD_Guardar0		;No, guardar el estado 0.
	btfsc	INDF, 0			;Antes estaba en 0 ?
	goto	CTD_Guardar1		;No, guardar el estado 1.

	;Flanco de subida, ver si es distinto de 0.
	bsf	INDF, 0			;Guardar el estado 1.
Dec_Point
	incf	FSR, F			;FSR apunta al LSB del contador.
	movf	INDF, W
	btfss	STATUS, Z		;LSB distinto de 0 ?
	goto	Dec_DistintoLSB		;Si.
	incf	FSR, F			;No, ver el MSB.
	movf	INDF, W
	btfsc	STATUS, Z		;MSB distinto de 0 ?
	return				;No, salir sin decrementar.
	decf	FSR, F			;Si, FSR apunta al LSB del contador.
Dec_DistintoLSB
	decf	FSR, F			;FSR apunta a CntConfig.
	;Decrementar el contador.
	incf	FSR, F			;FSR apunta al LSB del contador.
	decf	INDF, F			;LSB = LSB - 1.
	movf	INDF, W
	xorlw	D'255'
	btfss	STATUS, Z		;LSB = 255 ?
	goto	Cnt_Com_Preset		;No, Comparar el contador con el preset.
	incf	FSR, F			;Si, FSR apunta al MSB del contador.
	decf	INDF, F			;MSB = MSB - 1.
	decf	FSR, F			;FSR apunta al LSB del contador.
	goto	Cnt_Com_Preset		;Comparar el contador con el preset.

CTD_Guardar1
	;Guardar el estado en 1 del acumulador.
	bsf	INDF, 0			;Guardar estado actual del acumulador.
	return

CTD_Guardar0
	;Guardar el estado en 0 del acumulador.
	bcf	INDF, 0			;Guardar estado actual del acumulador.
	return


;---------------------------------------------------------------------------------------
;Esta rutina determina si se debe incrementar un contador.
;La direccion del contador esta en el registro Reg_Param1.
;El MSB del preset esta en el registro Reg_Param2.
;El LSB del preset esta en el registro Reg_Param3.
;---------------------------------------------------------------------------------------
IncCounter
	;Ver el estado anterior de PLC_Acc.
	movf	Reg_Param1, W
	movwf	FSR
	btfss	PLC_Acc, 0		;El acumulador esta en 1 ?
	goto	CTU_Guardar0		;No, guardar el estado 0.
	btfsc	INDF, 2			;Antes estaba en 0 ?
	goto	CTU_Guardar1		;No, guardar el estado 1.

	;Flanco de subida, incrementar el contador.
	bsf	INDF, 2			;Guardar el estado 1.
Inc_Point
	incf	FSR, F			;FSR apunta al LSB del contador.
	incf	INDF, F			;Incrementar el LSB del contador.
	btfss	STATUS, Z		;LSB del contador = 0 ?
	goto	Cnt_Com_Preset		;No, Comparar el contador con el preset.
	incf	FSR, F			;Si, FSR apunta al MSB del contador.
	incf	INDF, F			;Incrementar MSB del contador.
	decf	FSR, F			;FSR apunta al LSB del contador.
	goto	Cnt_Com_Preset		;Comparar el contador con el preset.
	
Cnt_Com_Preset
	;Ver si la cuenta es mayor o igual que el preset.
	movf	Reg_Param3, W
	movwf	AccBL
	movf	Reg_Param2, W
	movwf	AccBH
	movf	INDF, W
	movwf	AccAL			;AccAL = LSB del contador.
	incf	FSR, F			;FSR apunta al MSB del contador.
	movf	INDF, W
	movwf	AccAH			;AccAH = MSB del contador.
	movlw	D'2'
	subwf	FSR, F			;FSR apunta a CntConfig.
	incf	AccAL, F
	btfsc	STATUS, Z
	incf	AccAH, F
	call	D_sub			;AccB = AccB - (AccA + 1)
	btfsc	STATUS, C		;La cuenta es menor que el preset ?
	bcf	INDF, DN		;Si, borrar DN.
	btfss	STATUS, C
	bsf	INDF, DN		;No, activar DN.
	return

CTU_Guardar1
	;Guardar el estado en 1 del acumulador.
	bsf	INDF, 2			;Guardar estado actual del acumulador.
	return

CTU_Guardar0
	;Guardar el estado en 0 del acumulador.
	bcf	INDF, 2			;Guardar estado actual del acumulador.
	return


;---------------------------------------------------------------------------------------
;Esta rutina resetea la cuenta de un timer o un contador.
;La direccion del timer o contador esta en el registro Reg_Param1.
;---------------------------------------------------------------------------------------
ResTimCont
	btfss	PLC_Acc, 0		;PLC_Acc = 1 ?
	return				;No, no se debe resetear nada.
	;Ver si es la direccion es un contador.
	movf	Reg_Param1, W
	sublw	Cnt7Config
	btfss	STATUS, C		;Reg_Param1 <= Cnt7Config ?
	goto	Chk_ResTim		;No, es la direccion de un timer.
	movlw	Cnt0Config
	subwf	Reg_Param1, W
	btfss	STATUS, C		;Reg_Param1 >= Cnt0Config ?
	return				;Error en la direccion, no hacer nada.

	;La direccion es la de un contador.
	bcf	INDF, DN		;Borrar DN.
	incf	FSR, F			;FSR apunta al LSB de la cuenta.
	clrf	INDF			;Borrar el LSB.
	incf	FSR, F			;FSR apunta al MSB de la cuenta.
	clrf	INDF			;Borrar el MSB.	
	return

Chk_ResTim
	;La direccion es la de un timer.
	bsf	INDF, 3			;Deshabilitar la interrupcion sobre el timer.
	bcf	INDF, DN			;Borrar DN.
	incf	FSR, F			;FSR apunta al LSB del prescaler.
	clrf	INDF			;Borrar el LSB del prescaler.
	incf	FSR, F			;FSR apunta al MSB del prescaler.
	clrf	INDF			;Borrar el MSB del prescaler.
	incf	FSR, F			;FSR apunta al LSB de la cuenta.
	clrf	INDF			;Borrar el LSB de la cuenta.
	incf	FSR, F			;FSR apunta al MSB de la cuenta.
	clrf	INDF			;Borrar el MSB de la cuenta.
	movlw	D'4'
	subwf	FSR, F			;FSR apunta a TimerConfig
	bcf	INDF, 3			;Rehabilitar la interrupcion sobre el timer.
	return


;---------------------------------------------------------------------------------------
;Esta rutina incrementa o decrementa un contador Up/Down + DIR.
;La direccion del contador esta en el registro Reg_Param1.
;El MSB del preset esta en el registro Reg_Param2.
;El LSB del preset esta en el registro Reg_Param3.
;---------------------------------------------------------------------------------------
IncDecCounter
	;Ver el estado anterior de PLC_Acc.
	movf	Reg_Param1, W
	movwf	FSR
	btfss	PLC_Acc, 0		;El acumulador esta en 1 ?
	goto	CTK_Guardar0		;No, guardar el estado 0.
	btfsc	INDF, 4			;Antes estaba en 0 ?
	goto	CTK_Guardar1		;No, guardar el estado 1.

       	;Flanco de subida, ver el bit DN.
	bsf	INDF, 4			;Guardar el estado 1.
        btfss   INDF, DIR               ;DIR = 0 ?
        goto    Inc_Point               ;Si, Incrementar el contador.
        btfsc   INDF, DIR               ;DIR = 1
        goto    Dec_Point               ;Si, Decrementar el contador.

CTK_Guardar1
	;Guardar el estado en 1 del acumulador.
	bsf	INDF, 4			;Guardar estado actual del acumulador.
	return

CTK_Guardar0
	;Guardar el estado en 0 del acumulador.
	bcf	INDF, 4			;Guardar estado actual del acumulador.
	return


;---------------------------------------------------------------------------------------
;Esta rutina graba los bytes de la sentencia en la memoria serial.
;Los bytes a grabar estan en los registros PLC_1erByte, PLC_2doByte,
;PLC_3erByte, PLC_4toByte y PLC_5toByte.
;La posicion a grabar se autoincrementa luego de cada grabacion.
;Las instrucciones se graban a partir de la posicion de memoria 1.
;La posicion 0 se reserva para grabar banderas.
;---------------------------------------------------------------------------------------
GrabarSent
	movf	PLC_ContProgH, W	;addrH = PLC_ContProgH
	movwf	addrH
	movf	PLC_ContProgL, W	;addrL = PLC_ContProgL
	movwf	addrL
	movf	INDF, W
	movwf	datao_m			;datao_m = INDF
	call	WRITE_BYTEP		;Grabar el byte usando polling.
	incf	FSR, F			;Apuntar al siguiente byte a grabar.	
	incdf	PLC_ContProg		;Apuntar a la siguiente posicion.
	decfsz	ContByte, F		;Ya se grabaron todos los bytes ?
	goto	GrabarSent		;No, grabar el siguiente byte.
	return				;Si, Volver.


;---------------------------------------------------------------------------------------
;Esta rutina configura el timer0 para overflow cada 10 ms.
;---------------------------------------------------------------------------------------
ConfigTimer10ms
	bsf	STATUS, RP0		;Cambio al banco 1 de RAM.
	movlw 	B'10000111'		;Prescaler x256 al Timer (PSA=0, PS2=1, PS1=1, PS0=1)
        movwf 	OPTION_REG    		;Pullups desactivados.
	bcf 	STATUS, RP0		;Volver al banco 0 de RAM.
	movlw	D'217'			;Inicializar el timer en 217.
	movwf	TMR0
	bcf	INTCON, T0IF		;Borrar alguna interrupcion anterior.
	bsf	INTCON, T0IE		;Habilitar la interrupcion por overflow del timer.
	return


;---------------------------------------------------------------------------------------
;Esta rutina indica que se debe ejecutar el programa, titilando el led del modo Run.
;---------------------------------------------------------------------------------------
BlinkRun
	bsf	LedRun
	movlw	D'60'
	call	Retardo
	bcf	LedRun
	movlw	D'60'
	call	Retardo
	bsf	LedRun
	movlw	D'60'
	call	Retardo
	bcf	LedRun
	movlw	D'60'
	call	Retardo        
	return


;---------------------------------------------------------------------------------------
;Esta rutina indica que se debe grabar un nuevo programa desde la agenda.
;---------------------------------------------------------------------------------------
BlinkPgm
	bsf	LedPgm
	movlw	D'100'
	call	Retardo
	bcf	LedPgm
	movlw	D'100'
	call	Retardo
	bsf	LedPgm
	movlw	D'100'
	call	Retardo
	bcf	LedPgm
	movlw	D'100'
	call	Retardo
	bsf	LedPgm
	movlw	D'100'
	call	Retardo
	bcf	LedPgm
        movlw	D'100'
	call	Retardo
	return


;---------------------------------------------------------------------------------------
;Esta rutina incrementa los timers si estan activados.
;Si la BandBase10ms=1 indica que ocurrio la interrupcion de 10ms, sino la de 1 seg.
;---------------------------------------------------------------------------------------
Inc_Timers
	bsf	STATUS, RP0		;Ir al banco 1 de RAM.
	movlw	Tim0Config		;FSR apunta a Tim0Config
	movwf	FSR
	movwf	TimerFSR		;Copiar en TimerFSR.
	movlw	D'8'			;Se verificaran 8 timers de 16 bits.
	movwf	TimerCont
Inc_Timers_A
	btfsc	INDF, 3			;Se esta actualizando el timer ?
	goto	Inc_Timers_B		;Si, ver el siguiente timer.
	btfss	INDF, 0			;Esta activado el timer ?
	goto	Inc_Timers_B		;No, ver el siguiente timer.

        ;Ver si es la interrupcion de 10ms o la de 1seg.
        btfss   BandBase10ms            ;Ocurrio la interrupcion de 10 ms ?
        goto    Inc_Int1Seg             ;No, ver la interrupcion de 1 seg.
	;Ver si la base de tiempo es 10ms.
	movf	INDF, W
	andlw	B'11000000'
	movwf	TimerPresc		;Almacenar el prescaler en TimerPresc.
	xorlw	B'00000000'
	btfsc	STATUS, Z		;La base de tiempo es 10ms ?
	goto	Inc_Count		;Si, incrementar la cuenta.
        goto    Inc_Timers_B            ;No, ver el siguiente timer.

Inc_Int1Seg
	;Ver si la base de tiempo es 1seg.
	movf	INDF, W
	andlw	B'11000000'
	xorlw	B'01000000'
	btfsc	STATUS, Z		;La base de tiempo es 1seg ?
        goto    Inc_Count               ;Si, incrementar la cuenta.

	;No, ver si la base de tiempo es 1min.
	movf	INDF, W
	andlw	B'11000000'
	xorlw	B'10000000'
	btfsc	STATUS, Z		;La base de tiempo es 1min ?
	goto	Inc_Presc		;Si, incrementar el prescaler.

Inc_Count
	;Incrementar el contador.
	movlw	D'2'			;FSR apunta al LSB del timer.
	addwf	FSR, F
	incf	INDF, F			;Incrementar LSB del timer.
	btfss	STATUS, Z		;LSB del timer = 0 ?
	goto	Com_Preset		;Comparar el contador con el preset.
	incf	FSR, F			;Si, FSR apunta al MSB del timer.
	incf	INDF, F			;Incrementar MSB del timer.
	decf	FSR, F			;FSR apunta al LSB del timer.
	goto	Com_Preset		;Comparar el contador con el preset.

Inc_Presc
	;Incrementar el prescaler.
	incf	FSR, F			;FSR apunta al LSB del prescaler.
	incf	INDF, F			;Incrementar LSB del prescaler.
        movf    INDF, W
        xorlw   D'60'
        btfss   STATUS, Z
	goto	Inc_Timers_B		;No, ver el siguiente timer.
        clrf    INDF                    ;Si, borrar el prescaler.
	goto	Inc_Count		;Incrementar el contador.

Com_Preset
	;Primero copiar el timer a TimerAux.
	movf	INDF, W			;Copiar LSB del timer en
	movwf	TimerAuxL		;TimerAuxL.
	incf	FSR, F			;FSR apunta al MSB del timer.
	movf	INDF, W			;Copiar MSB del timer en
	movwf	TimerAuxH		;TimerAuxH.

	;Comparar con el preset.
	incf	FSR, F			;FSR apunta al LSB del preset.
	movf	TimerAuxL, W		;Comparar los LSB.
	xorwf	INDF, W
	btfss	STATUS, Z		;Son iguales los LSB ?
	goto	Inc_Timers_B		;No, ver el siguiente timer.
	incf	FSR, F			;Si, FSR apunta al MSB del preset.
	movf	TimerAuxH, W		;Comparar los MSB.
	xorwf	INDF, W
	btfss	STATUS, Z		;Son iguales los MSB ?
	goto	Inc_Timers_B		;No, ver el siguiente timer.
	movlw	D'5'			;Si, preparar para activar el timer.
	subwf	FSR, F			;FSR apunta a TimConfig.

	;Ver el tipo de timer.
	movf	INDF, W			;Ver si el timer es un TON.
	andlw	B'00110000'
	xorlw	B'00000000'
	btfsc	STATUS, Z		;Es un timer TON ?
	goto	Activar_TON		;Si, Parar el timer TON.
	movf	INDF, W			;No, Ver si el timer es un TOF.
	andlw	B'00110000'
	xorlw	B'00010000'
	btfsc	STATUS, Z		;Es un timer TOF ?
	goto	Activar_TOF		;Si, Parar el timer TOF.
	movf	INDF, W			;No, ver si el timer es un TIP.
	andlw	B'00110000'
	xorlw	B'00100000'
	btfsc	STATUS, Z		;Es un timer TIP ?
	goto	Activar_TOF		;Si, Parar el timer TIP.
	movf	INDF, W			;No, ver si el timer es un TFL.
	andlw	B'00110000'
	xorlw	B'00110000'
	btfsc	STATUS, Z		;Es un timer TFL ?
	goto	Activar_TFL		;Si, seguir con el timer TFL.	

Activar_TON
	;Activar DN, deshabilitar el timer y reiniciar la cuenta.
	bsf	INDF, DN			;Activar DN.
	goto	Inc_Parar_Timer		;Deshabilitar el timer y reiniciar la cuenta.

Activar_TOF
	;Desactivar DN, deshabilitar el timer y reiniciar la cuenta.
	bcf	INDF, DN		;Desactivar DN.
	goto	Inc_Parar_Timer		;Deshabilitar el timer y reiniciar la cuenta.

Activar_TFL
	;Invertir DN, y reiniciar la cuenta.
	movf	INDF, W			;Invertir el bit DN.
	xorlw	B'00000010'
	movwf	INDF
	goto	Clr_Cnt_Timer		;Borrar la cuenta del timer.

Inc_Parar_Timer
	bcf	INDF, 0			;Parar el timer.
	incf	FSR, F			;FSR apunta al prescaler.
	clrf	INDF			;Poner a 0 el prescaler.
	incf	FSR, F			;FSR apunta al LSB del timer.
	clrf	INDF			;Poner a 0 el LSB del timer.
	incf	FSR, F			;FSR apunta al MSB del timer.
	clrf	INDF			;Poner a 0 el MSB del timer.
	incf	FSR, F			;FSR apunta al LSB del preset.
	clrf	INDF			;Poner a 0 el LSB del preset.
	incf	FSR, F			;FSR apunta al MSB del preset.
	clrf	INDF			;Poner a 0 el MSB del preset.
	goto	Inc_Timers_B		;Ver el siguiente timer.

Clr_Cnt_Timer
	;Borrar el prescaler.
	incf	FSR, F			;FSR apunta al LSB del prescaler.
	clrf	INDF			;Poner a 0 el LSB del prescaler.
	;Borrar la cuenta del timer.
	incf	FSR, F			;FSR apunta al LSB del timer.
	clrf	INDF			;Poner a 0 el LSB del timer.
	incf	FSR, F			;FSR apunta al MSB del timer.
	clrf	INDF			;Poner a 0 el MSB del timer.
	goto	Inc_Timers_B		;Ver el siguiente timer.

Inc_Timers_B
	movlw	D'6'			;TimerFSR apunta al siguiente registro
	addwf	TimerFSR, F		;de configuracion.
	movf	TimerFSR, W		;FSR apunta al siguiente registro
	movwf	FSR			;de configuracion.
	decfsz	TimerCont, F		;Ya se checkearon todos los timers ?
	goto	Inc_Timers_A		;No, ver el siguiente.
	bcf	STATUS, RP0		;Si, Ir al banco 0 de RAM.
	return				;volver.


;---------------------------------------------------------------------------------------
;Esta rutina es una demora de ciclos, se usa para ajustar el timer.
;---------------------------------------------------------------------------------------
AjustarTimer
	bsf	STATUS, RP0             ;Ir al banco 1 de RAM.
	goto	$+1
	movlw	D'82'
	movwf	TimerCont
Ajustar_loop
	decfsz	TimerCont, F
	goto	Ajustar_loop
	bcf	STATUS, RP0             ;Ir al banco 0 de RAM.
	return


;---------------------------------------------------------------------------------------
;Esta rutina lee un byte del registro de desplazamiento CD4021.
;El byte recibido queda en el registro Recep_4021.
;---------------------------------------------------------------------------------------
Leer_4021
	clrf    Recep_4021              ;Borrar el buffer de recepcion.
	movlw	D'8'		        ;Se enviaran 8 bits.
	movwf	Cont_4021
        bcf     CK_4021                 ;Desactivar Clock del registro.
        bcf     SL_4021                 ;Desactivar Shift Load del registro.
        nop
        bsf     SL_4021                 ;Latchear la entrada.
        nop
        bcf     SL_4021                 ;Desactivar Shift Load del registro.        
Next_4021        
        ;Si RX_4021=1, C=1, sino C=0.
        btfss   DOUT_4021
        bcf     STATUS, C
        btfsc   DOUT_4021
        bsf     STATUS, C
	rrf	Recep_4021, F	        ;Rotar Recep_4021 un bit a la izquierda.	
	bsf	CK_4021		        ;Enviar un pulso en CK_4021.
        nop
	bcf	CK_4021
	decfsz	Cont_4021, F	        ;Se recibieron los 8 bits ?
	goto	Next_4021		;No, recibir el siguiente bit.
	return			        ;Si, salir.


;---------------------------------------------------------------------------------------
;Esta rutina escribe un byte en el registro de desplazamiento CD4094.
;El byte a escribir debe estar en W.
;---------------------------------------------------------------------------------------
Escribir_4094
        movwf   Trans_4094              ;Trans_4094 = W.
	movlw	D'8'		        ;Se enviaran 8 bits.
	movwf	Cont_4094
        bcf     CK_4094                 ;Desactivar Clock del registro.
        bcf     LE_4094                 ;Desactivar Latch Enable del registro.
Next_4094
        rrf	Trans_4094, F	        ;Rotar Trans_4094 un bit a la izquierda.
        ;Si C=1, DIN_4094=1, sino DIN_4094=0.
        btfss   STATUS, C
        bcf     DIN_4094
        btfsc   STATUS, C
        bsf     DIN_4094
	bsf	CK_4094		        ;Enviar un pulso en CK_4094.
        nop
	bcf	CK_4094
	decfsz	Cont_4094, F	        ;Se recibieron los 8 bits ?
	goto	Next_4094		;No, recibir el siguiente bit.
        bsf     LE_4094                 ;Si, Enviar un pulso en LE_4094.
        nop
        bcf     LE_4094
	return			        ;Si, salir.


;---------------------------------------------------------------------------------------
;Esta rutina lee las entradas y actualiza sus imagenes Entradas0.
;---------------------------------------------------------------------------------------
LeerEntradas
	;Las entradas se leen del puerto C.
        call    Leer_4021               ;Leer el registro de las entradas.
        movf    Recep_4021, W
	movwf	Entradas0               ;Guardar W en las imagenes de las entradas.
	return


;---------------------------------------------------------------------------------------
;Esta rutina actualiza las salidas desde sus imagenes Salidas0.
;---------------------------------------------------------------------------------------
EnviarSalidas
        movf    Salidas0, W             ;W = Salidas0.
        call    Escribir_4094           ;Escribir Salidas0 en el registro CD4094.
	return


;---------------------------------------------------------------------------------------
;Esta rutina se ejecuta cada vez que se recibe un byte en la USART.
;---------------------------------------------------------------------------------------
UsartRX
        ifdef   DebugInst
        return
        endif
        movf    RXEstado, W
        btfss   STATUS, Z               ;RX_Estado = 0 ?
        goto    RX_Estado1              ;No, ver estado 1.

        ;Ver si la PC quiere modificar el programa.
        call    Recibir_232
        xorlw   Cmnd_ProgramID
        btfss   STATUS, Z               ;Es el byte Cmnd_ProgramID ?
        return                          ;No, esperar el comando de identificacion.
        incf    RXEstado, F             ;Si, Pasar al estado siguiente.
        movlw   Cmnd_Ack                ;Enviar un Acknoledge.
        call    Enviar_232
        movlw   ContSeg
        movwf   Mem_ContLSB             ;Apuntar al registro de los segundos.
        return

RX_Estado1
        ;Ver si la recepcion esta en el estado 1.
        movf    RXEstado, W
        xorlw   D'1'
        btfss   STATUS, Z               ;RX_Estado = 1 ?
        goto    RX_Estado2              ;No, ver estado 2.

        ;Estado 1: Recibir la Fecha y la Hora de la PC.
        movf    Mem_ContLSB, W
        movwf   FSR                     ;FSR apunta al registro de fecha y hora.
        call    Recibir_232
        movwf   INDF                    ;Copiar el byte recibido a la RAM.
        movlw   Cmnd_Ack                ;Enviar un Acknoledge.
        call    Enviar_232
        incf    Mem_ContLSB, F          ;Apuntar al siguiente registro.
        movf    Mem_ContLSB, W
        xorlw   ContDiadeSemana + 1
        btfss   STATUS, Z               ;Ya se recibieron todos los registros ?
        return                          ;No, recibir el siguiente registro.
  
        incf    RXEstado, F             ;Si, pasar al siguiente estado.
        return
        

RX_Estado2
        ;Ver si la recepcion esta en el estado 2.
        movf    RXEstado, W
        xorlw   D'2'
        btfss   STATUS, Z               ;RX_Estado = 2 ?
        goto    RX_Estado3              ;No, ver estado 3.

        ;Estado 2: Recibir el LSB de la cantidad de bytes a grabar.
        call    Recibir_232
        movwf   Mem_ContLSB
        incf    RXEstado, F             ;Pasar al estado siguiente.
        movlw   Cmnd_Ack                ;Enviar un Acknoledge.
        call    Enviar_232
        return

RX_Estado3
        ;Ver si la recepcion esta en el estado 3.
        movf    RXEstado, W
        xorlw   D'3'
        btfss   STATUS, Z               ;RX_Estado = 3 ?
        goto    RX_Estado4              ;No, ver estado 4.

        ;Estado 3: Recibir el MSB de la cantidad de bytes a grabar.
        call    Recibir_232
        movwf   Mem_ContMSB
        incf    RXEstado, F             ;Pasar al estado siguiente.
        clrf    Mem_AddrMSB             ;Empezar a grabar desde la posicion 0x01
        movlw   0x01
        movwf   Mem_AddrLSB
        movlw   Cmnd_Ack                ;Enviar un Acknoledge.
        call    Enviar_232
        return

RX_Estado4
        ;Estado 4: Cada byte recibido grabarlo en la memoria serial.
        call    Recibir_232
        movwf   datao_m
        movf    Mem_AddrLSB, W
        movwf   addrL
        movf    Mem_AddrMSB, W
        movwf   addrH
        call    WRITE_BYTEP             ;Grabar el byte en la memoria.
        movlw   Cmnd_Ack                ;Enviar un acknoledge.
        call    Enviar_232

        ;Incrementar el contador de direcciones.
        incf    Mem_AddrLSB, F
        movf    Mem_AddrLSB, W
        btfsc   STATUS, Z               ;Mem_AddrLSB = 0 ?
        incf    Mem_AddrMSB, F          ;Mem_AddrMSB = Mem_AddrMSB + 1.

        ;Decrementar el contador de bytes.
        decf    Mem_ContLSB, F
        movf    Mem_ContLSB, W
        xorlw   D'255'
        btfsc   STATUS, Z
        decf    Mem_ContMSB, F
        ;Ver si el contador de bytes = 0.
        movf    Mem_ContLSB, W
        btfss   STATUS, Z               ;El LSB de MemCont = 0 ?
        return                          ;No, esperar siguiente byte.
        movf    Mem_ContMSB, W
        btfss   STATUS, Z               ;Ya se recibieron todos los bytes ?
        return                          ;No, esperar siguiente byte.

        ;Ya se grabaron todos los bytes.
        clrf    RXEstado                ;Volver al estado 0.
        bsf     BandDownload            ;Indicar que se actualizo el programa.
        bsf     eeprom, First           ;Indicar leer las instrucciones desde el inicio.
        return


;---------------------------------------------------------------------------------------
;Esta rutina inicializa los registros y el estado del PLC.
;Resetea y desactiva los contadores, timers y horarios.
;---------------------------------------------------------------------------------------
Init_PLC
        ;Variables del PLC.
        clrf    PLC_ContProgL           ;Empezar por la posicion 0.
        clrf    PLC_ContProgH
	clrf	PLC_Acc			;Borrar al acumulador del PLC.
	clrf	Pila			;Inicializar la pila a 0.
	clrf	Reg_Inst		;Borrar el registro de instruccion.
	clrf	Reg_Param1		;Borrar el registro del 1er parametro.
	clrf	Reg_Param2		;Borrar el registro del 2do parametro.
	clrf	Reg_Param3		;Borrar el registro del 3er parametro.
	clrf	Reg_Param4		;Borrar el registro del 4to parametro.

	;Inicializar los registros de los timers y los horarios.
	bsf	STATUS, RP0             ;Ir al banco 1 de RAM.
	movlw	TimerAuxL
	movwf	FSR			;Borrar desde TimerAuxL.
Clr_NextTimer
	clrf	INDF
	incf	FSR, F
	movf	FSR, W			;Hasta Hor1MinHasta.
	xorlw	Hor1MinHasta + 1
	btfss	STATUS, Z
	goto	Clr_NextTimer
	bcf	STATUS, RP0             ;Ir al banco 0 de RAM.

	;Inicializar los registros de los contadores.
	movlw	CntAuxL
	movwf	FSR			;Borrar desde CntAuxL.
Clr_NextCounter
	clrf	INDF
	incf	FSR, F
	movf	FSR, W			;Hasta Cnt7HCount.
	xorlw	Cnt7HCount + 1
	btfss	STATUS, Z
	goto	Clr_NextCounter

	;Variables de entrada/salida.
	clrf	Salidas0		;Enviar 0 a todas las salidas.
	call	EnviarSalidas
	clrf	Sal0Ant			;Estado anterior de las salidas en 0.
	clrf	Ent0Ant			;Estado anterior de las entradas en 0.
        call    InitHorarios            ;Inicializa los registros de los horarios.
        call    ActHorarios             ;Ver si se deben activar los horarios.
        return

;Esta tabla retorna los dias que deberia tener cada mes,
;antes de iniciar el siguiente.
TablaMeses
        movf    ContMeses, W
        movwf   PLC_Aux
	movlw	LOW	Tabla_GetMonth	;Ver el LSB de la direccion de la tabla.
	addwf	PLC_Aux
	movlw	HIGH	Tabla_GetMonth	;Cargar PCLATH con los bits altos
	btfsc	STATUS, C		;Se cruzo a otra pagina ?
	addlw	0x01			;Si, incrementar el MSB de la direccion.
	movwf	PCLATH			;No, Guardar el MSB de la direccion.
	movf	PLC_Aux, W
Tabla_GetMonth
	movwf	PCL
	retlw	D'32'                   ;Enero 32 dias.
	retlw	D'29'                   ;Febrero 29 dias.
	retlw	D'32'                   ;Marzo 32 dias.
	retlw	D'31'                   ;Abril 31 dias.
	retlw	D'32'                   ;Mayo 32 dias.
	retlw	D'31'                   ;Junio 31 dias.
	retlw	D'32'                   ;Julio 32 dias.
	retlw	D'32'                   ;Agosto 32 dias.
	retlw	D'31'                   ;Setiembre 31 dias.
	retlw	D'32'                   ;Octubre 32 dias.
	retlw	D'31'                   ;Noviembre 31 dias.
	retlw	D'32'                   ;Diciembre 32 dias.

;Esta rutina es la que calcula la hora y la fecha.
;Se ejecuta una vez por segundo por la interrupcion del Timer 1.
RealTime
	incf	ContSeg, F              ;Incrementar el contador de segundos.
	movf	ContSeg, W              ;Ver si el contador llego a 60.
	xorlw	D'60'
	btfss	STATUS, Z               ;ContSeg = 60 ?
	return                          ;No, salir.
	
	;Overflow de los segundos, volver a cero e incrementar los minutos.
	clrf	ContSeg                 ;Volver los segundos a 0.
	incf	ContMin, F              ;Incrementar el contador de minutos.
	movf	ContMin, W              ;Ver si minutos = 60.
	xorlw	D'60'
	btfss	STATUS, Z               ;ContMin = 60 ?
	return                          ;No, salir.

	;Overflow de los minutos, volver a cero e incrementar las horas.
	clrf	ContMin
	incf	ContHor, F
	movf	ContHor, W              ;Ver si Horas = 24.
	xorlw	D'24'
	btfss	STATUS, Z               ;ContHor = 24 ?
	return                          ;No, salir.

	;Overflow de las horas, volver a cero e incrementar las dias.
	clrf	ContHor
	incf	ContDiadeSemana, F      ;Incrementar el dia de la semana.
        movf    ContDiadeSemana, W
        xorlw   D'8'
        btfss   STATUS, Z               ;Era domingo ?
        goto    RealTime_IncDias        ;No, continuar.
        movlw   D'1'                    ;Si, volver al dia lunes.
        movwf   ContDiadeSemana         
RealTime_IncDias
	incf	ContDias, F
        movf    ContAniosL, W           ;Copiar ContAniosL en
	call	IsLeapYear              ;Verificar si es un año bisiesto.
	btfss	STATUS, Z               ;Es un año bisiesto ?
	goto	RutinaNormal            ;No, es un año normal.

AnioBisiesto
	;Ver si es el 29 de febrero.
	movf	ContMeses, W	
	xorlw	D'2'
	btfss	STATUS, Z               ;Estamos en Febrero ?
	goto	RutinaNormal            ;No, rutina normal.
	movf	ContDias, W
	xorlw	D'29'
	btfss	STATUS, Z               ;Es el dia 29 de Febrero ?
	goto	RutinaNormal            ;No, rutina normal.
	goto	OvrflDias               ;Si, esta bien que tenga 29 dias.
	
RutinaNormal
	call	TablaMeses
	xorwf	ContDias, W
	btfss	STATUS, Z               ;Se debe iniciar un nuevo mes ?
	return                          ;No, salir.

OvrflDias
	;Overflow de los dias, volver a 1 e incrementar los meses.
	movlw	D'1'
	movwf	ContDias
	incf	ContMeses, F
	movf	ContMeses, W            ;Ver si Meses = 13.
	xorlw	D'13'
	btfss	STATUS, Z               ;ContMeses = 13 ?
	return                          ;No, salir.
	
	;Overflow de los meses, volver a 1 e incrementar los años.
	movlw	D'1'
	movwf	ContMeses
	incf	ContAniosL, F
	btfss	STATUS, Z               ;ContAniosL = 0 ?
	return                          ;No, salir.

	;Overflow del LSB de los años, incrementar el MSB de los años.
	incf	ContAniosH, F
	return                          ;Fin de la rutina.


;Esta rutina calcula si el año es bisiesto.
;Para hacer esto verifica que los bits 0 y 1 del LSB de los años
;sean ambos cero.
;Si TempYear es bisiesto, Z=1, sino, Z=0.
IsLeapYear 
	btfsc 	ContAniosL, 0
	goto 	NotLeapYear
	;
	btfsc 	ContAniosL, 1
	goto 	NotLeapYear
	;
	bsf 	STATUS, Z
	retlw 	0
	;
NotLeapYear 
	bcf     STATUS, Z
	retlw 	0


;---------------------------------------------------------------------------------------
;Esta rutina configura los registros de los horarios.
;Se leen los valores de la memoria serial y se copian en la ram.
;---------------------------------------------------------------------------------------
InitHorarios
        clrf    Mem_AddrMSB             ;Empezar por la posicion 1.
        movlw   0x01
        movwf   Mem_AddrLSB
        movlw   Hor0Config
        movwf   FSR                     ;FSR apunta a Hor0Config.
InitHor_Next
        clrf    PLC_ContProgH
        movf    Mem_AddrLSB, W
        movwf   PLC_ContProgL
        bcf     eeprom, First           ;Leer desde la posicion ContProg.
        call    READ_BYTE               ;Leer un byte de la memoria serial.
LeerMem
        nop
        movf    datai_m, W
        movwf   INDF                    ;Copiar el byte a la RAM.
        incf    Mem_AddrLSB, F
        incf    FSR, F                  ;FSR apunta al siguiente registro.
        movf    FSR, W
        xorlw   Hor3MinHasta + 1
        btfss   STATUS, Z               ;Ya se leyeron todos los registros ?
        goto    InitHor_Next            ;No, leer el siguiente registro.
        return


;---------------------------------------------------------------------------------------
;Esta rutina activa los horarios si la hora esta dentro del intervalo, y DiaDesde es
;distinto de 0.
;---------------------------------------------------------------------------------------
ActHorarios
        bsf     LedPgm
        movlw   D'100'
        call    Retardo
        bcf     LedPgm
        bsf     STATUS, RP0             ;Ir al banco 1 de RAM.
        movlw   Hor0Config
        movwf   HorarioFSR
ActHorarios_Next
        movf    HorarioFSR, W           ;FSR apunta al HorConfig siguiente.
        movwf   FSR
        bcf     INDF, DN                ;Por defecto desactivar DN del Horario.
        movf    INDF, W
        andlw   B'11100000'
        xorlw   B'00000000'
        btfsc   STATUS, Z               ;Esta activado el horario ?
        goto    ActHorarios_NextB       ;No, ver el horario siguiente.

        swapf   INDF, W
        andlw   B'00001111'
        movwf   HorarioTemp
        bcf     STATUS, C
        rrf     HorarioTemp, F          ;HorarioTemp tiene DiaDesde.

        ;Ver si ContDiadeSemana >= DiaDesde.
        movf    HorarioTemp, W
        bcf     STATUS, RP0
        subwf   ContDiadeSemana, W
        bsf     STATUS, RP0
        btfss   STATUS, C               ;ContDiadeSemana >= DiaDesde ?
        goto    ActHorarios_NextB       ;No, ver el siguiente Horario.

        movf    INDF, W
        andlw   B'00011100'
        movwf   HorarioTemp
        bcf     STATUS, C
        rrf     HorarioTemp, F
        rrf     HorarioTemp, F          ;HorarioTemp tiene DiaHasta.

        ;Ver si ContDiadeSemana <= DiaHasta
        bcf     STATUS, RP0
        movf    ContDiadeSemana, W
        bsf     STATUS, RP0
        subwf   HorarioTemp, W
        btfss   STATUS, C               ;ContDiadeSemana <= DiaHasta ?
        goto    ActHorarios_NextB       ;No, ver el siguiente Horario.

        ;1) - Si (Hora:Min >= HoraDesde:MinDesde)(2) y (Hora:Min <= HoraHasta:MinHasta)(3)
        ;2) - Si (Hora > HoraDesde)(4) o [(Hora = HoraDesde)(5) y (Min >= MinDesde)(6)]
        ;3) - Si (Hora < HoraHasta)(7) o [(Hora = HoraHasta)(8) y (Min <= MinHasta)(9)]

        ;Ver si (Hora > HoraDesde)(4)
ActHor_4
        incf    FSR, F                  ;FSR apunt a HoraDesde.
        bcf     STATUS, RP0
        movf    ContHor, W
        bsf     STATUS, RP0
        subwf   INDF, W
        btfss   STATUS, C               ;Hora > HoraDesde ?
        goto    ActHor_7                ;Si, ver si (Hor:Min <= HorHasta:MinHasta).

ActHor_5
        ;Ver si (Hora = HoraDesde) y (Min >= MinDesde).
        movf    INDF, W                 ;Ver si (Hor = HoraDesde)
        bcf     STATUS, RP0
        xorwf   ContHor, W
        bsf     STATUS, RP0
        btfss   STATUS, Z               ;Hora = HoraDesde ?
        goto    ActHorarios_NextB       ;No, Hora < HoraDesde, ver el siguiente horario.
        
ActHor_6
        ;Ver si (Min >= MinDesde).
        incf    FSR, F                  ;FSR apunta a MinDesde.
        movf    INDF, W
        bcf     STATUS, RP0
        subwf   ContMin, W
        bsf     STATUS, RP0
        btfss   STATUS, C               ;Min >= MinDesde ?
        goto    ActHorarios_NextB       ;No, Min < MinDesde, ver el siguiente horario.

ActHor_7
        movf    HorarioFSR, W
        addlw   D'3'
        movwf   FSR                     ;FSR apunta a HorHasta.
        ;Ver si (Hora < HoraHasta)(7).
        movf    INDF, W
        bcf     STATUS, RP0
        subwf   ContHor, W
        bsf     STATUS, RP0
        btfss   STATUS, C               ;Hora < HorHasta ?
        goto    ActHor_Set              ;Si, Activar el horario.

ActHor_8
        ;Ver si (Hora = HoraHasta) y (Min <= MinHasta).
        movf    INDF, W                 ;Ver si (Hor = HorHasta)
        bcf     STATUS, RP0
        xorwf   ContHor, W
        bsf     STATUS, RP0
        btfss   STATUS, Z               ;Hora = HorHasta ?
        goto    ActHorarios_NextB       ;No, Hora > HoraHasta, ver el siguiente horario.        

ActHor_9
        ;Ver si (Min <= MinHasta)
        incf    FSR, F                  ;FSR apunta a MinHasta.
        bcf     STATUS, RP0
        movf    ContMin, W
        bsf     STATUS, RP0
        subwf   INDF, W
        btfss   STATUS, C               ;ContMin <= MinHasta ?
        goto    ActHorarios_NextB       ;No, ver el siguiente Horario.

ActHor_Set
        ;Activar el Horario.
        movf    HorarioFSR, W
        movwf   FSR
        bsf     INDF, DN                ;Activar DN del Horario.

ActHorarios_NextB
        ;Apuntar FSR al siguiente horario.
        movlw   D'5'                    ;HorarioFSR = HorarioFSR + 5.
        addwf   HorarioFSR, F
        movf    HorarioFSR, W
        xorlw   Hor3MinHasta + 1
        btfss   STATUS, Z               ;Ya se controlaron todos los horarios ?
        goto    ActHorarios_Next        ;No, seguir con el siguiente.
        bcf     STATUS, RP0             ;Si, volver al banco 0 de RAM.
        return


;***************************************************************************************
;       Programa principal	                                           
;***************************************************************************************
;Configuracion de los puertos y perifericos.
Config	clrf    PCLATH
        bcf	STATUS, RP0             ;Empezar en el banco 0 de RAM.
        
        ;Inicializar los latches de salida.
	movlw	B'00000000'             ;RA3(LedPgm=1)
	movwf	PORTA                   ;RA2(LedRun=1)
	movlw	B'00000000'
	movwf	PORTB
        movlw	B'01011000'             ;RC6(TX_232=1)
	movwf	PORTC
        
        ;Inicializar el Timer 1.
	clrf	T1CON                   ;Parar el Timer 1, Oscilador detenido, reloj interno, prescaler x1.
	movlw	0x80                    ;Configurar el Timer 1 para que haga overflow una
	movwf	TMR1H                   ;vez por segundo.
	clrf	TMR1L                   ;Borrar el LSB del Timer 1.

        ;Valores para poder simular la rutina.
        ifdef   Debug
        movlw   0xFF
        movwf   TMR1H
        movlw   0xFF
        movwf   TMR1L
        endif
        
        ;Configurar los puertos
	bsf	STATUS, RP0             ;Cambio al banco 1 de RAM.
	movlw   B'00000000'             ;Todo el puerto A son salidas.
	movwf	PORTA                   ;RA4 (SL_4021=Out)
	movlw   B'00000000'             ;Todo el puerto B son salidas.
	movwf   PORTB                   ;RB7 (LE_4094)
                                        ;RB6 (CK_4094)
                                        ;RB5 (DIN_4094)
	movlw   B'10100001'		;RC7 (RX_232=In)
	movwf	PORTC                   ;RC6 (TX_232=Out)
                                        ;RC5 (DIN_4021=In)
                                        ;RC4 (I2C_SDA=Out)
                                        ;RC3 (I2C_SCL=Out)
                                        ;RC2 (CK_4021=Out)
                                        ;RC0 (T1OSO=In)
	movlw 	B'10000111'             ;Prescaler x256 al Timer (PSA=0, PS2=1, PS1=1, PS0=1)
        movwf 	OPTION_REG              ;Pullups desactivados.
	movlw	B'10000110'
	movwf	ADCON1                  ;Todo el puerto A es digital.
	bcf 	STATUS, RP0             ;Volver al banco 0 de RAM.
        
        ;Inicializar el Timer 0.
	movlw	D'217'                  ;Inicializar el timer en 57.
	movwf	TMR0
	clrf	INTCON                  ;Borrar las banderas de las interrupciones.
        clrf	PIR1                    ;Borrar las banderas de las interrupciones perifericas.

        ;Configurar los periféricos.

        ;Configurar el Timer 1.
        movlw	B'00001110'
	movwf	T1CON                   ;Habilitar el Oscillator, Clock Ext, Async, prescaler x1.
	bsf	T1CON, TMR1ON           ;Activar el Timer 1.

	;Configurar la USART.
	bsf	STATUS, RP0             ;Ir al banco 1.
	movlw	D'12'                   ;Valores del generador de baudrate para cristal de 20 Mhz,
                                        ;modo alta velocidad BRGH=1
                                        ; D'12' :  19200 bps.
                                        ; D'25' :   9600 bps.
                                        ; D'103' :  2400 bps.
                                        ; D'207' :  1200 bps.				
	movwf	SPBRG
	bcf	TXSTA, SYNC             ;Modo Asincrono.
	bsf	TXSTA, BRGH             ;Modo alta velocidad.
	bcf	TXSTA, TX9              ;Transmision de 8 bits.
	bsf	TXSTA, TXEN             ;Habilitar la transmision.
	bcf	PIE1, TXIE              ;Deshabilitar la interrupcion por fin de transmision.
	bsf	PIE1, RCIE              ;Habilitar la interrupcion por fin de recepcion.
	bcf	STATUS, RP0             ;Volver al banco 0.
	bsf	RCSTA, SPEN             ;Habilitar TX/RX como pines de la USART.
	bsf	RCSTA, CREN             ;Habilitar la recepcion continua.

        ;Inicializar las variables.

	;Variables de la memoria serial.
	movlw	B'10100000'		;A2=0, A1=0, A0=0.
	movwf	slave_m			;Direccion de la memoria en el bus i2c.
	clrf	eeprom			;Borrar las banderas de la memoria.	

        ;Inicializar los registros del PLC.
	call    Init_PLC
        bsf	eeprom, First		;Empezar por la primera posicion.

	;Variables del proceso de recepcion serial.
	clrf	TXRXBand		;Borrar todas las banderas del proceso.
        clrf    RXEstado                ;Empezar por el estado 0.

        ;Variables del reloj de tiempo real.
	clrf	BandReloj	        ;Borrar todas las banderas.
	movlw	D'59'                   ;Hora 13:21:00
	movwf	ContSeg
	movlw	D'21'
	movwf	ContMin
	movlw	D'13'
	movwf	ContHor
	movlw	D'1'		        ;Dia: 1.
	movwf	ContDias
	movlw	D'1'		        ;Mes: 1.
	movwf	ContMeses
	movlw	D'198'		        ;Año: 1990.
	movwf	ContAniosL
	movlw	D'7'
	movwf	ContAniosH
        movlw   D'1'
        movwf   ContDiadeSemana         ;Dia: Lunes.
	
	;Habilitar las interrupciones.
	bsf	INTCON, T0IE		;Habilitar la interrupcion por overflow del Timer0.
        bsf     STATUS, RP0
        bsf	PIE1, TMR1IE            ;Habilitar la interrupcion por overflow del Timer1.
        bcf     STATUS, RP0
        bsf     INTCON, PEIE            ;Habilitar las interrupciones de los perifericos.
        bsf     INTCON, GIE             ;Habilitar las interrupciones.

	call	BlinkRun		;Indicar el modo Run.

	;Reconfigurar Timer para PLC.
	call	ConfigTimer10ms		;Overflow Timer cada 10 ms.

;Rutina principal.
Inicio
	call	LeerEntradas		;Actualizar las imagenes de entrada.
Next_Inst	
	call	LeerInstruc		;Leer la siguiente instruccion de la memoria eeprom.
	call	DecoInstruc		;Decodificar la instruccion, de ser necesario leer mas datos de la memoria.
	call	EjecInstruc		;Ejecutar la instruccion.
	movf	Reg_Inst, W		;Ver si se deben actualizar las salidas.
	xorlw	Inst_END		;
	btfss	STATUS, Z		;Es la ultima instruccion ?
	goto	Next_Inst		;No, Leer la proxima instruccion.
	call	EnviarSalidas		;Si, Actualizar las salidas segun sus imagenes.
	goto	Inicio			;Volver a repetir el ciclo.


;***************************************************************************************
;       Vector de Interrupcion
;***************************************************************************************
;Se guarda el contenido de los registros W, STATUS, y FSR.
Interrupt
	movwf 	wtemp			;Guardar estado del registro W en wtemp.
	swapf 	STATUS, W		;Invertir el estado de STATUS en W.
	clrf	STATUS			;Ir al banco 0, borra IRP, RP1, RP0.
	movwf 	stemp			;Guardar el estado de W en stemp.
	movf	PCLATH, W		;Guardar estado del registro PCLATCH
	movwf	pclathtemp		;en pclathtemp.
	clrf	PCLATH			;Asegurarse que estamos en la pagina 0.
	movf	FSR, W			;Guardar estado del registro FSR
	movwf	fsrtemp			;en fsrtemp.

;Verificar que causo la interrupcion.
Chk_OVF_TMR0
        btfss	INTCON, T0IF		;Interrupcion por overflow del Timer 0 ?
	goto	Chk_OVF_TMR1		;No, Ver la interrupcion por overflow del Timer 1.
	call	AjustarTimer		;Si, Demora para ajustar el timer.
	movlw	D'218'			;Inicializar el timer en 218.
	movwf	TMR0			;Para overflow cada 10 ms.
        bsf     STATUS, RP0             ;Ir al banco 1 de RAM.
        bsf     BandBase10ms            ;Indicar que occurio la int de 10 ms.
        bcf     STATUS, RP0             ;Volver al bando 0 de RAM.
	call	Inc_Timers		;Ver si se deben incrementar los timers.
	bcf	INTCON, T0IF		;Borrar la bandera de interrupcion del timer 0.
	goto	FinISR                  ;Salir de la interrupcion.

Chk_OVF_TMR1
        btfss	PIR1, TMR1IF 		;Interrupcion por overflow del Timer1 ?
        goto	Chk_RX_USARTINT		;No, Ver la interrupcion por recepcion de la USART.

        movlw   0x80
        movwf   TMR1H

        ;Valores para poder simular la rutina.
        ifdef   Debug
        movlw   0xFF
        movwf   TMR1H
        movlw   0xFF
        movwf   TMR1L
        endif

        bsf     STATUS, RP0             ;Ir al banco 1 de RAM.
        bcf     BandBase10ms            ;Indicar que occurio la int de 1 seg.
        bcf     STATUS, RP0             ;Volver al bando 0 de RAM.        
        call    Inc_Timers              ;Ver si se debe incrementar los timers.

        call	RealTime		;Actualizar la hora y la fecha.

        movf    ContSeg, W
        btfsc   STATUS, Z               ;El contador de segundos = 0 ?
        call    ActHorarios             ;Si, Actualizar los horarios.

        movlw   B'00000100'
        xorwf   PORTA, F

        ifdef   DebugState
        movf    Temp, W
        call    Enviar_232
        endif

;        bsf     STATUS, RP0
;        movf    Hor0Config, W
;        bcf     STATUS, RP0
;        call    Enviar_232
;        bsf     STATUS, RP0
;        movf    Hor1Config, W
;        bcf     STATUS, RP0
;        call    Enviar_232

        ifdef   DebugTime
        movf    ContSeg, W
        call    Enviar_232
        movf    ContMin, W
        call    Enviar_232
        movf    ContHor, W
        call    Enviar_232
        movf    ContDias, W
        call    Enviar_232
        movf    ContMeses, W
        call    Enviar_232
        movf    ContAniosH, W
        call    Enviar_232
        movf    ContAniosL, W
        call    Enviar_232
        movf    ContDiadeSemana, W
        call    Enviar_232
        endif

        bcf	PIR1, TMR1IF            ;Borrar la bandera de interrupcion del timer 1.
	goto	FinISR                  ;Salir de la interrupcion.


Chk_RX_USARTINT
        btfss   PIR1, RCIF              ;Interrupcion por recepcion de la USART ?
        goto    Chk_RB0INT              ;No, ver la interrupcion externa RB0/INT.
	call    UsartRX                 ;Ver que hacer con el byte.
        goto    FinISR                  ;Salir de la interrupcion.

Chk_RB0INT
	btfss	INTCON, INTF		;Interrupcion por flanco en RB0 ?
	goto	FinISR			;No, Fuente desconocida.
	nop

;Se restaura el contenido de los registros W, STATUS y FSR.
FinISR	
	movf	fsrtemp, W		;Restaura el contenido del
	movwf	FSR			;registro FSR.
	movf	pclathtemp, W		;Restaura el contenido del
	movwf	PCLATH			;registro PCLATH.	
	swapf	stemp, W		;Restaura el contenido invertido del
	movwf	STATUS			;registro STATUS.
	swapf	wtemp, F		;Restaura el contenido del
	swapf	wtemp, W		;registro W.
	retfie                          ;Volver al programa.

	End	
;***************************************************************************************
;	Fin del Programa
;***************************************************************************************

