Here's what I've come up with. Code below.
When the signal begins, TMR2 is loaded with the period and offset to interrupt in the middle of each half bit. The value on the pin is sampled each time TMR2 fires, with the left-hand and right-hand bits stored in respective registers. However, the receiver will not check the vaildity of received bits, leaving that for main code.
It will reclock on mid-bit edges, but only within a window, ignoring anything too whacky.
I've also taken nctnico's advice on potential IOC spam trapping the microcontroller in its ISR, so IOC is only enabled when expecting an edge and disabled once it is received / missed.
The receiver indicates completion via its state register. It's enabled again in main code by clearing the state register and enabling IOC on the pin.
The main code just transforms and spits the data out to the EUSART.
#include "p12f1572.inc"
; CONFIG1
; __config 0xFFAC
__CONFIG _CONFIG1, _FOSC_INTOSC & _WDTE_SWDTEN & _PWRTE_OFF & _MCLRE_OFF & _CP_OFF & _BOREN_ON & _CLKOUTEN_OFF
; CONFIG2
; __config 0xFEFC
__CONFIG _CONFIG2, _WRT_ALL & _PLLEN_OFF & _STVREN_ON & _BORV_LO & _LPBOREN_OFF & _LVP_ON
; ********** Common RAM working registers **********
; Main
cblock 0x70
AREG, BREG, CREG, DREG
LREG, HREG, TEMP0, TEMP1
endc
TEMP = 0x76
; ISR
cblock 0x78
ISR_AREG, ISR_BREG, ISR_CREG, ISR_DREG
ISR_LREG, ISR_HREG, ISR_TEMP0, ISR_TEMP1
endc
ISR_TEMP = 0x7E
; ********** General registers **********
; IR receiver registers on bank 0
ISR_IRRX_STATE = 0x0060 ; Receiver state
ISR_IRRX_DATL0 = 0x0061 ; First byte of left hand bits
ISR_IRRX_DATL1 = 0x0062 ; Second byte of left hand bits
ISR_IRRX_DATR0 = 0x0063 ; First byte of right hand bits
ISR_IRRX_DATR1 = 0x0064 ; Second byte of right hand bits
; ********** FIFO locations **********
RX_FIFO = 0x0140 ; Start of receiver FIFO data structure. Occupies 10 bytes.
TX_FIFO = 0x0150 ; Start of transmitter FIFO data structure. Occupies 10 bytes.
; ********** Constants **********
; PR2 values for 3.8KBd manchester
; Assume Fosc/4 = 2MHz, TMR2 prescaler = 1:4
IRRX_14BIT = d'65' ; PR2 value for 1/4 bit period
IRRX_12BIT = d'131' ; PR2 value for 1/2 bit period
IRRX_RCLK_L = d'45' ; Reclocking window lower limit
IRRX_RCLK_H = d'85' ; Reclocking window upper limit
; ********** Reset vector **********
RES_VECT CODE 0x0000
GOTO INIT
; ********** Interrupt vector **********
ISR CODE 0x0004
BANKSEL PIR1
BTFSC PIR1, TMR2IF ; Infrared receiver. TMR2 handler.
GOTO ISR_IRRX_T
BTFSC INTCON, IOCIF ; Infrared receiver. IOC handler.
GOTO ISR_IRRX_P
BTFSC PIR1, RCIF ; EUSART receiver
GOTO ISR_EURX
BTFSC PIR1, TXIF ; EUSART transmitter
GOTO ISR_EUTX
RESET ; Oops!
ISR_IRRX_T BCF PIR1, TMR2IF
BANKSEL LATA ; Pulse debugging pin
BSF LATA, LATA0
BCF LATA, LATA0
BANKSEL 0 ; Read branching state
MOVLW b'00000011'
ANDWF ISR_IRRX_STATE, W
BRW ; Branch to handler
GOTO IRRX_T_START
GOTO IRRX_T_BITL
GOTO IRRX_T_BITR
GOTO IRRX_T_FINISH
IRRX_T_START ; We shouldn't get a timeout in this state
BANKSEL T2CON
BCF T2CON, TMR2ON ; Turn TMR2 off
BANKSEL 0
BSF ISR_IRRX_STATE, 0 ; Set branching state to finish
BSF ISR_IRRX_STATE, 1
RETFIE ; Done
IRRX_T_BITL ; Receive left-hand bit
BANKSEL 0
BCF STATUS, C ; Read inverted data
BTFSS PORTA, RA2 ; PORTA is on bank 0
BSF STATUS, C
MOVF ISR_IRRX_STATE, W ; Have we received all data?
XORLW b'01000101' ; 16 bits + start bit + current branching state
BTFSC STATUS, Z ; If not, continue
GOTO IRRX_T_LAST ; If so, finish up
RLF ISR_IRRX_DATL1, F ; Rotate left bit onto registers
RLF ISR_IRRX_DATL0, F
BCF ISR_IRRX_STATE, 0 ; Set branching state to BITR
BSF ISR_IRRX_STATE, 1
BANKSEL IOCAF ; Enable IOC for RA2
BSF IOCAP, IOCAP2
BSF IOCAN, IOCAN2
RETFIE ; Done
IRRX_T_BITR ; Receive right-hand bit
BANKSEL 0
BCF STATUS, C ; Read inverted data
BTFSS PORTA, RA2 ; PORTA is on bank 0
BSF STATUS, C
RLF ISR_IRRX_DATR1, F ; Rotate right bit onto registers
RLF ISR_IRRX_DATR0, F
MOVLW b'00000100' ; Increment bit count
ADDWF ISR_IRRX_STATE, F
BSF ISR_IRRX_STATE, 0 ; Set branching state to BITL
BCF ISR_IRRX_STATE, 1
BANKSEL IOCAF
BCF IOCAP, IOCAP2 ; Disable IOC for RA2
BCF IOCAN, IOCAN2
BCF IOCAF, IOCAF2 ; Clear IOC flag for RA2
RETFIE ; Done
IRRX_T_LAST ; All bits received and last edge has passed
BSF ISR_IRRX_STATE, 0 ; Set state to FINISH
BSF ISR_IRRX_STATE, 1
BANKSEL T2CON ; Turn TMR2 off
BCF T2CON, TMR2ON
RETFIE ; Done
IRRX_T_FINISH ; We shouldn't get a timeout in this state
BANKSEL T2CON ; Turn TMR2 off
BCF T2CON, TMR2ON
RETFIE ; Done
ISR_IRRX_P
BANKSEL IOCAF
BCF IOCAP, IOCAP2 ; Disable IOC for RA2
BCF IOCAN, IOCAN2
BCF IOCAF, IOCAF2 ; Clear IOC flag for RA2
BANKSEL 0 ; Read branching state
MOVLW b'00000011'
ANDWF ISR_IRRX_STATE, W
BRW ; Branch to handler
GOTO IRRX_P_START
RETFIE
GOTO IRRX_P_RCLK
RETFIE
IRRX_P_START ; Start edge received. ISR latency (instruction cycles): 17
; When accounting for ISR latency, use:
; ( instruction cycles + 4 ) / TMR2 prescaler (4)
BANKSEL T2CON ; Configure and start TMR2
MOVLW IRRX_14BIT + 7 ; Offset TMR2 to middle of 1/2 bit, accounting for ISR latency
MOVWF TMR2
MOVLW IRRX_12BIT ; TMR2 will fire in the middle of every 1/2 bit
MOVWF PR2
BSF T2CON, TMR2ON ; ISR latency (instruction cycles): 23
BANKSEL 0
BSF ISR_IRRX_STATE, 0 ; Set branching state to BITL
BCF ISR_IRRX_STATE, 1
RETFIE ; Done
IRRX_P_RCLK ; Reclock TMR2. ISR latency (instruction cycles): 17
; When accounting for ISR latency, use:
; ( instruction cycles + 4 ) / TMR2 prescaler (4)
BANKSEL T2CON
MOVF TMR2, W ; Capture TMR2 value. ISR latency (instruction cycles): 19
ADDLW -6 ; Account for ISR latency
MOVWF ISR_TEMP
;MOVF ISR_TEMP, W
SUBLW IRRX_RCLK_H ; Determine if the edge arrived within the reclock window
MOVLW IRRX_RCLK_L ; STATUS, C will be set if within the reclock window
BTFSC STATUS, C
SUBWF ISR_TEMP, W
MOVLW IRRX_14BIT + 8 ; Offset TMR2 to middle of 1/2 bit, accounting for ISR latency
BTFSC STATUS, C ; Skip the adjustment if not within reclock window
MOVWF TMR2 ; ISR latency (instruction cycles): 28
BANKSEL LATA
BTFSC STATUS, C ; Pulse debugging pin if reclocked
BSF LATA, LATA0
BCF LATA, LATA0
RETFIE ; Done
ISR_EURX
BANKSEL RCSTA
BTFSC RCSTA, FERR ; Discard if framing error
GOTO EURX_DISCARD
MOVLW high RX_FIFO ; Select RX FIFO
MOVWF FSR0H
MOVLW low RX_FIFO
MOVWF FSR0
MOVIW 1 [FSR0] ; Check if RX FIFO is full
SUBWF INDF0, W
XORLW d'8'
BTFSC STATUS, Z ; Discard if RX FIFO is full
GOTO EURX_DISCARD
MOVF INDF0, W ; Enqueue data into RX FIFO
ANDLW d'7'
MOVWF ISR_TEMP
ADDWF FSR0L, F
MOVF RCREG, W
MOVWI 2 [FSR0]
MOVF ISR_TEMP, W
SUBWF FSR0L, F
INCF INDF0, F
RETFIE ; Done
EURX_DISCARD MOVF RCREG, W ; Discard received character
RETFIE ; Done
ISR_EUTX
BANKSEL TXREG
MOVLW high TX_FIFO ; Select TX FIFO
MOVWF FSR0H
MOVLW low TX_FIFO
MOVWF FSR0
MOVIW 1 [FSR0] ; Check if TX FIFO is empty
XORWF INDF0, W
BTFSC STATUS, Z ; If TX FIFO is empty, disable interrupts and return
GOTO EUTX_OFF
MOVIW ++ FSR0 ; Dequeue data from TX FIFO and transmit
ANDLW d'7'
MOVWF ISR_TEMP
ADDWF FSR0L, F
MOVIW 1 [FSR0]
MOVWF TXREG
MOVF ISR_TEMP, W
SUBWF FSR0L, F
INCF INDF0, F
RETFIE ; Done
EUTX_OFF ; Switch transmitter interrupts off
BANKSEL PIE1
BCF PIE1, TXIE
RETFIE ; Done
; ********** Main loop **********
MAIN_PROG CODE
INIT
BANKSEL OSCCON
MOVLW b'01110000' ; Software PLL disabled, 8MHz IRCF, clock as per FOSC<1:0> in config words
MOVWF OSCCON
BANKSEL LATA ; Pin config
CLRF LATA
BANKSEL ANSELA
BCF ANSELA, ANSA2 ; Switch off analog for RA2 (IR RX)
BCF ANSELA, ANSA4 ; Switch off analog for RA4 (UART TX)
BANKSEL TRISA
BCF TRISA, TRISA0 ; RA0 as output for LED / debug
;BANKSEL IOCAP
;BSF IOCAP, IOCAP2 ; Enable positive edge interrupts for RA2
;BSF IOCAN, IOCAN2 ; Enable negative edge interrupts for RA2
BANKSEL T2CON
MOVLW b'00000001' ; Configure TMR2. No postscaler, off, 1:4 prescaler
MOVWF T2CON
BANKSEL APFCON ; Place the EUSART on alternate pins
BSF APFCON, RXDTSEL ; RX on RA5
BSF APFCON, TXCKSEL ; TX on RA4
BANKSEL BAUDCON ; EUSART
;MOVLW b'01000000' ; Default POR value
;MOVWF BAUDCON
BCF TXSTA, SYNC ; 9600 baud rate async for 8MHz Fosc
BCF TXSTA, BRGH
BCF BAUDCON, BRG16
MOVLW d'12'
MOVWF SPBRG
CLRF SPBRGH
BSF TXSTA, TXEN ; Enable transmitter
BSF RCSTA, CREN ; Enable receiver
BSF RCSTA, SPEN ; Enable the serial port
BANKSEL PIE1 ; Interrupts
BSF PIE1, RCIE ; Enable receiver interrupt
BSF PIE1, TMR2IE ; TMR2 interrupt
BSF INTCON, PEIE ; Enable peripheral interrupts
BSF INTCON, IOCIE ; Enable interrupt on change
BSF INTCON, GIE ; Enable global interrupts
BANKSEL RX_FIFO ; Initialise the FIFOs
CLRF RX_FIFO
CLRF RX_FIFO + 1
CLRF TX_FIFO
CLRF TX_FIFO + 1
BANKSEL 0
MAIN_LOOP
CLRF ISR_IRRX_STATE ; Reset IR receiver state
BANKSEL IOCAP ; Enable IR receiver
BSF IOCAN, IOCAN2
BANKSEL 0 ; Wait until we have data from IR receiver
WAIT_IR_DATA MOVLW b'00000011'
ANDWF ISR_IRRX_STATE, W
XORLW b'00000011'
BTFSS STATUS, Z
GOTO WAIT_IR_DATA
SWAPF ISR_IRRX_STATE, W ; Convert data to hexadecimal chars and send via UART
CALL BIN2HEX
CALL SEND_CHAR
MOVF ISR_IRRX_STATE, W
CALL BIN2HEX
CALL SEND_CHAR
MOVLW a' '
CALL SEND_CHAR
SWAPF ISR_IRRX_DATL0, W
CALL BIN2HEX
CALL SEND_CHAR
MOVF ISR_IRRX_DATL0, W
CALL BIN2HEX
CALL SEND_CHAR
MOVLW a' '
CALL SEND_CHAR
SWAPF ISR_IRRX_DATL1, W
CALL BIN2HEX
CALL SEND_CHAR
MOVF ISR_IRRX_DATL1, W
CALL BIN2HEX
CALL SEND_CHAR
MOVLW a' '
CALL SEND_CHAR
SWAPF ISR_IRRX_DATR0, W
CALL BIN2HEX
CALL SEND_CHAR
MOVF ISR_IRRX_DATR0, W
CALL BIN2HEX
CALL SEND_CHAR
MOVLW a' '
CALL SEND_CHAR
SWAPF ISR_IRRX_DATR1, W
CALL BIN2HEX
CALL SEND_CHAR
MOVF ISR_IRRX_DATR1, W
CALL BIN2HEX
CALL SEND_CHAR
MOVLW d'13'
CALL SEND_CHAR
MOVLW d'10'
CALL SEND_CHAR
GOTO MAIN_LOOP
; ********** Subroutines **********
; Enqueue data in WREG for transmission via EUSART.
; If the FIFO is full, wait for room.
SEND_CHAR MOVWF TEMP0 ; Store data to send
MOVLW high TX_FIFO ; Select TX FIFO data structure
MOVWF FSR0H
MOVLW low TX_FIFO
MOVWF FSR0
SEND_CHAR_W MOVIW 1 [FSR0] ; Wait for room
SUBWF INDF0, W
XORLW d'8'
BTFSC STATUS, Z
GOTO SEND_CHAR_W
MOVF INDF0, W ; Enqueue data onto FIFO
ANDLW d'7'
MOVWF TEMP1
ADDWF FSR0L, F
MOVF TEMP0, W
MOVWI 2 [FSR0]
MOVF TEMP1, W
SUBWF FSR0L, F
INCF INDF0, F
MOVF BSR, W ; Back up BSR
BANKSEL PIE1 ; Enable the transmitter
BSF PIE1, TXIE
MOVWF BSR ; Restore BSR
RETLW d'0' ; Done
; Return the ASCII hexadecimal character
; for the least significant nibble in WREG.
BIN2HEX ANDLW b'00001111'
BRW
RETLW a'0'
RETLW a'1'
RETLW a'2'
RETLW a'3'
RETLW a'4'
RETLW a'5'
RETLW a'6'
RETLW a'7'
RETLW a'8'
RETLW a'9'
RETLW a'A'
RETLW a'B'
RETLW a'C'
RETLW a'D'
RETLW a'E'
RETLW a'F'
END