assemblyprojectpicreceiverinfrared

IR receiver with PIC16F887, assembly language


I have been working on a project for school. It involves PIC assembly programming. I use 44 pin demo board, PIC16F887.

I have to do an IR receiver which has a output that changes a LED brightness. I know so far I have to make a PWM control for that. However I am still struggling with decoding the buttons. I am using an emitter that has NEC coding. My input setup is digital, pulled up with internal resistor PORTB, 0. I am trying to polling the input with TMR0 interrupts.

System setup is 4MHz oscillator, TMR0 is incrementing every Osc/4. Prescaler is 1:2, So every 2μs the timer is incrementing 1. Timer0 preload is D’206’, so it is interrupting 50*2μs = 100μs. According to the NEC protocol Logic 0 has 562,5μs low, Logic 1 has 1687,5μs low signal, after a 562,5 HIGH signal.

So 1687.5μs /100μs = 16, and 562.5μs / 100 μs = 5. I was trying to subtract them out of 8. So I can check what does STATUS, ZERO bit is active or low.

I don’t know in which part I am going wrong. I will leave my code down below. This code has a LED blinking, while it supposed to set PORTD,0 LED on if volume+ button is pressed. So in this way I would know if I can detect the buttons, and work on the PWM.

Every answer is appreciated.

ISR:                        ;IF ISR GLOBAL INT 0  
    btfss   INTCON,T0IF   
    retfie                  ;if there is no interrupt
    banksel 0               ;ISR occur in Bank0
    movwf   W_save          ;save WORK register's value
    movf    STATUS,W       
    movwf   STATUS_save     ;save STATUS register's value

    call    IR              ;call IR
    goto    ISR_EXIT

ISR_EXIT:
    bcf     INTCON,T0IF     ;TMR0 interrupt flag clear
    movlw   b'01100110'     ;preload 206
    movwf   TMR0

    movf    STATUS_save,W
    movwf   STATUS          ;STATUS register original value reload
    swapf   W_save,f        ;WORK register original value reload
    swapf   W_save,W
    retfie                  ;retfie -> global int = 1

IR:
    btfss   PORTB,0         ;testing IR input
    bsf     ir_reg,0        ;button was pressed

    btfss   ir_reg,0        ;button was pressed?
    goto    NO_BUTTON

    btfsc   PORTB,0         ;HIGH signal?
    goto    HIGH_P

    btfss   ir_reg,1        ;previous was HIGH?
    goto    HIGH_TO_LOW

    incf    time,f          ;increment time
    goto    ISR_EXIT

HIGH_P:
    btfsc   ir_reg,1        ;was previous LOW?
    goto    LOW_TO_HIGH

    incf    time,f          ;increment time

    goto    ISR_EXIT

HIGH_TO_LOW:                ;transition between HIGH to LOW pulses
    bsf     PORTD,3
    movf    time,W     
    movwf   high_pulse      ;saving HIGH pulse's time
    clrf    time            ;time variable clear
    bcf     ir_reg,1        ;previous pulse was HIGH
    goto    CALC

CALC:
    movf    high_pulse,W    ;high pulse's time into Work
    bcf     STATUS,Z        ;STATUS ZERO CLEAR

    sublw   D'10'           ;LOW_P -> 5-10 = -5,HIGH_P - > 16-10 = 6
    btfsc   STATUS,Z        ;if subtraction = +
    bsf     ir_reg,3        ;subtraction ended positive -> LOGIC 1
    bcf     ir_reg,3        ;subtraction ended negative -> LOGIC 0
    goto    ADD_BITS

ADD_BITS:
    bsf     PORTD,2
    btfsc   ir_reg,3        ;if LOGIC 1
    bsf     STATUS,C        ;carry bit 1
    bcf     STATUS,C        ;carry bit 0

    goto    ROTATE

ROTATE:
    bsf     PORTD,1
    rlf     naddress        ;Carry is rotated to naddress LSB
    rlf     address         ;naddress MSB rotated to address LSB through Carry
    rlf     ncommand        ;address MSB rotated to ncommand LSB through Carry
    rlf     command         ;ncommand MSB rotated to command LSB through Carry

    incf    pulses          ;every time we have a rotation increment variable
    movf    pulses,W
    bcf     STATUS,Z        ;status zero clear
    sublw   D'32'           ;33-pulses,we have a decoded signal
    btfss   STATUS,Z        ;if Zero set
    goto    ISR_EXIT        ;goto NO_button
    goto    LED_FLASH

LED_FLASH:
    movf    command,W
    bcf     STATUS,Z
    sublw   b'10101000'     ;+ button command: b'10101000'
    btfss   STATUS,Z
    goto    NO_BUTTON
    bsf     PORTD,0
    goto    ISR_EXIT

LOW_TO_HIGH:                ;transition between LOW to HIGH pulses
    movf    time,W
    movwf   low_pulse       ;saving LOW pulse's time
    clrf    time            ;time variable clear
    bsf     ir_reg,1        ;previous pulse was LOW
    goto    ISR_EXIT

NO_BUTTON:
    btfsc   PORTB,0
    goto    ISR_EXIT

    clrf    pulses          ;clearing variables
    clrf    ir_reg
    clrf    time
    clrf    address
    clrf    naddress
    clrf    address
    clrf    ncommand
    clrf    command
    goto    ISR_EXIT

INIT:
;OSCCON INIT
    banksel OSCCON
    movlw   b'01100000'     ;4Mhz oscillator
    movwf   OSCCON

;OUTPUT INIT
    banksel TRISD
    clrf    TRISD           ;TRISD OUTPUT
    banksel PORTD
    clrf    PORTD           ;PORTD LOW

;INPUT INIT
    banksel TRISB
    bsf     TRISB,RB0       ;RB0 INPUT
    bsf     WPUB,RB0

    movlw   0x00
    banksel ANSELH
    movwf   ANSELH          ;RB0 DIGITAL

    call Delay

;OPTION REG INIT / TMR0
    banksel OPTION_REG
    movlw   b'00000000'     ;TMR0 prescale 1:2 increment every 2us
    movwf   OPTION_REG
    movlw   b'01100110'     ;preload 206
    movwf   TMR0            ;50 tick until overflow 50*2us = 100us

;INTCON INIT
    banksel INTCON
    bcf     INTCON,T0IF     ;TMR0 overflow flag clear
    bsf     INTCON,T0IE     ;TMR0 overflow enable
    bsf     INTCON,GIE      ;global interrupt enable

    return

MAIN:

    call INIT
    call FLASH              ;LED FLASH
    goto $-1
    END

Solution

  • In my arrogance I thought that your assignment was simple and straightforward.

    I could not have been more wrong. It has taken me three days to code and test an application that can decode the NEC Infrared Remote control protocol.

    There are significant challenges just trying to understand the alleged documentation found in various web sites. All of it is misleading and incomplete. It consumed a full day just sorting through that mess.

    This is what I got working:

        list n=0,c=255,r=dec    ; Make .LST file look nice
        errorlevel -302         ; Suppress Register in operand not in bank 0 warning.
    #define MAIN_ASM
    ;
    ; File:     main.asm
    ; Date:     2020-05-23
    ; Target:   PIC16F887
    ; Author:   dan1138
    ;
    ; Description:
    ;   Decoder for NEC Infrared Remote control protocol.
    ;
    ;   Physical transport:
    ;       Long flash  (> 8ms)
    ;       Pause       (COMMAND when pause is more than 4ms), 
    ;                   (REPEAT when pause is less than 4ms but greater than 2ms)
    ;       Short flash (0.5 to 0.6ms)
    ;     Repeats 32 times:
    ;       Pause       DATA is one when pause is more than 1ms, else DATA is zero.
    ;       Short flash (0.5 to 0.6ms)
    ;
    ;
    ;                         PIC16F887
    ;                 +----------:_:----------+
    ;       VPP ->  1 : RE3/MCLR/VPP  PGD/RB7 : 40 <> PGD
    ;           <>  2 : RA0/AN0       PGC/RB6 : 39 <> PGC
    ;           <>  3 : RA1/AN1      AN13/RB5 : 38 <>
    ;           <>  4 : RA2/AN2      AN11/RB4 : 37 <>
    ;           <>  5 : RA3/AN3   PGM/AN9/RB3 : 36 <> 
    ;           <>  6 : RA4/T0CKI     AN8/RB2 : 35 <> 
    ;           <>  7 : RA5/AN4      AN10/RB1 : 34 <> 
    ;           <>  8 : RE0/AN5  INT/AN12/RB0 : 33 <> IR_RECEIVERn
    ;           <>  9 : RE1/AN6           VDD : 32 <- 5v0
    ;           <> 10 : RE2/AN7           VSS : 31 <- GND
    ;       PWR -> 11 : VDD               RD7 : 30 -> LCD_ON
    ;       GND -> 12 : VSS               RD6 : 29 -> LCD_E
    ;           -> 13 : RA7/OSC1          RD5 : 28 -> LCD_RW
    ;           <- 14 : RA6/OSC2          RD4 : 27 -> LCD_RS
    ;           <> 15 : RC0/SOSCO   RX/DT/RC7 : 26 <>    
    ;           <> 16 : RC1/SOSCI   TX/CK/RC6 : 25 <>    
    ;           <> 17 : RC2/CCP1          RC5 : 24 <>
    ;           <> 18 : RC3/SCL       SDA/RC4 : 23 <>    
    ;    LCD_D4 <> 19 : RD0               RD3 : 22 <> LCD_D7
    ;    LCD_D5 <> 20 : RD1               RD2 : 21 <> LCD_D6
    ;                 +-----------------------:
    ;                          DIP-40
    ;
    ; Include Special Function Register definitions
    ;
    #include "p16f887.inc"
    #include "main.inc"
    #include "lcd.inc"
    ;
    ; PIC16F887 Configuration Bit Settings
    ; Assembly source line config statements
    ;
     __CONFIG _CONFIG1, _FOSC_INTRC_NOCLKOUT & _WDTE_OFF & _PWRTE_OFF & _MCLRE_ON & _CP_OFF & _CPD_OFF & _BOREN_OFF & _IESO_ON & _FCMEN_OFF & _LVP_OFF
     __CONFIG _CONFIG2, _BOR4V_BOR21V & _WRT_OFF
    ;
    ; Power on reset vector
    ;
    RES_VECT    CODE    0x0000      ; processor reset vector
        pagesel START
        GOTO    START               ; go to beginning of program
    ;
    ; Interrupt context save area
    ;
    ISR_DATA    UDATA_SHR
    WREG_SAVE   res     1
    STATUS_SAVE res     1
    PCLATH_SAVE res     1
    NEC_IR_State        res 1
    NEC_IR_StartFlash   res 1
    NEC_IR_CdPause      res 1
    ;
    ; Data area for protocol decoder
    ;
    NEC_IR_DATA   UDATA
    NEC_IR_RawData      res 4
    NEC_IR_Address      res 1
    NEC_IR_Command      res 1
    NEC_IR_Flags        res 1
    #define BIT_NEC_IR_Flags_COMMAND NEC_IR_Flags,0
    #define BIT_NEC_IR_Flags_REPEAT  NEC_IR_Flags,1
    ;
    ; Interrupt Service Routine
    ;
    ISR_VECT    CODE    0x0004      ; interrgot vector
    ISR:
        movwf   WREG_SAVE           ; 
        movf    STATUS,W            ; These register: WREG, STATUS, PCLATH
        movwf   STATUS_SAVE         ; are what, at the minimum, must be saved 
        movf    PCLATH,W            ; and restored on an interrupt.
        movwf   PCLATH_SAVE         ;
        clrf    STATUS              ; Force to memory bank 0
        clrf    PCLATH              ; Force to code page 0
    ;
    ; Handle external INT interrupt request
    ;
        btfsc   INTCON,INTE
        btfss   INTCON,INTF
        goto    INT_End
        bcf     INTCON,INTF
    ;
    ; Block flash detection until application loop is done
    ;
        btfss   BIT_NEC_IR_Flags_COMMAND
        btfsc   BIT_NEC_IR_Flags_REPEAT
        goto    INT_End
    
        movf    NEC_IR_State,F
        skpz    
        goto    NEC_IR_NextState
    ;
    ; Look for initial long flash
    ;
        clrf    NEC_IR_StartFlash
        clrf    TMR0
        bcf     INTCON,T0IF
    MeasureStartFlash:
        btfsc   PORTB,0             ; Skip if flash still on
        goto    EndOfFlash
        btfss   INTCON,T0IF
        goto    MeasureStartFlash
        bcf     INTCON,T0IF
        incfsz  NEC_IR_StartFlash,W
        movwf   NEC_IR_StartFlash
        goto    MeasureStartFlash
    EndOfFlash:
        clrf    TMR0
        bcf     INTCON,T0IF
        clrf    NEC_IR_CdPause
        movlw   8
        subwf   NEC_IR_StartFlash,W
        btfss   STATUS,C            ; Skip if count equal or greater than 8 T0IF ticks
        goto    INT_End
    ;
    ; Measure pause after flash
    ;
    MeasurePause:
        btfss   PORTB,0             ; Skip if flash still off
        goto    EndOfPause
        btfss   INTCON,T0IF
        goto    MeasurePause
        bcf     INTCON,T0IF
        incfsz  NEC_IR_CdPause,W
        movwf   NEC_IR_CdPause
        goto    MeasurePause
    EndOfPause:
        btfss   PORTB,0             ; Skip when flash goes off
        goto    EndOfPause
        clrf    TMR0
        bcf     INTCON,T0IF
        bcf     INTCON,INTF
        movlw   4
        subwf   NEC_IR_CdPause,W
        btfsc   STATUS,C            ; Skip if count less than 4 T0IF ticks
        goto    ReceiveCommandState
        banksel NEC_IR_Flags
        bsf     BIT_NEC_IR_Flags_REPEAT ; Assert this is a REPEAT event
        goto    INT_End
    ReceiveCommandState:
        movlw   d'32'
        movwf   NEC_IR_State      ; Advnace to state 32 when we expect ADDRESS/COMMAND data
    INT_End:
    ;
        movf    PCLATH_SAVE,W       ;
        movwf   PCLATH              ; Restore the saved context of the
        movf    STATUS_SAVE,W       ; interrupted execution.
        movwf   STATUS              ;
        swapf   WREG_SAVE,F         ;
        swapf   WREG_SAVE,W         ;
        retfie                      ; Exit ISR and enable the interrupts.
    ;
    ; Receive COMMAND or REPEAT event
    ;
    NEC_IR_NextState:
        banksel PORTB
        bcf     STATUS,C
        btfsc   INTCON,T0IF
        bsf     STATUS,C
        banksel NEC_IR_RawData
        rlf     NEC_IR_RawData,F
        rlf     NEC_IR_RawData+1,F
        rlf     NEC_IR_RawData+2,F
        rlf     NEC_IR_RawData+3,F
    EndOfBit:
        banksel PORTB
        btfss   PORTB,0             ; Skip when flash goes off
        goto    EndOfBit
        clrf    TMR0
        bcf     INTCON,T0IF
        decf    NEC_IR_State,F
        btfss   STATUS,Z
        goto    INT_End
    ;
    ; Validate ADDRESS and COMMAND
    ;
        banksel NEC_IR_RawData
        comf    NEC_IR_RawData,W
        xorwf   NEC_IR_RawData+1,W
        btfss   STATUS,Z
        goto    INT_End
        comf    NEC_IR_RawData+2,W
        xorwf   NEC_IR_RawData+3,W
        btfss   STATUS,Z
        goto    INT_End
    
        movf    NEC_IR_RawData+1,W
        movwf   NEC_IR_Command
        movf    NEC_IR_RawData+3,W
        movwf   NEC_IR_Address
        bsf     BIT_NEC_IR_Flags_COMMAND
        goto    INT_End
    ;
    ; Initialize the PIC hardware
    ;
    START:
        clrf    INTCON              ; Disable all interrupt sources
        banksel BANK1
        clrf    PIE1
        clrf    PIE2
    
        movlw   b'01100000'
        movwf   OSCCON              ; Set internal oscillator at 4MHz
    
        movlw   b'10000001'         ; Pull-ups off, INT edge high to low, WDT prescale 1:1
        movwf   OPTION_REG          ; TMR0 clock edge low to high, TMR0 clock = FCY, TMR0 prescale 1:4
                                    ; TIMER0 will assert the overflow flag every 256*4 (1024)
                                    ; instruction cycles, with a 4MHz oscilator this ia 1.024 milliseconds.
    
        movlw   b'11111111'         ;
        movwf   TRISA
    
        movlw   b'11111111'         ;
        movwf   TRISB
    
        movlw   b'11111111'         ;
        movwf   TRISC
    
        movlw   b'11111111'         ;
        movwf   TRISD
    
        ; Set all ADC inputs for digital I/O
        banksel BANK3
        movlw   b'00000000'
        movwf   ANSEL
        movlw   b'00000000'
        movwf   ANSELH
        banksel BANK2
        clrf    CM1CON0             ; turn off comparator
        clrf    CM2CON0             ; turn off comparator
        banksel BANK1
        movlw   b'00000000'
        movwf   ADCON1
        clrf    VRCON               ; turn off voltage reference
        banksel BANK0
        movlw   b'10000000'
        movwf   ADCON0
    
        pagesel main
        goto    main
    ;
    ; Main data
    ;
    MAIN_DATA   UDATA
    RepeatCount res     1
    ;
    ; Main application code
    ;
    MAIN_PROG   CODE
    ; 
    ; Main application initialization
    ;
    main:
        lcall   OpenXLCD
    
        movlw   LINE_ONE
        lcall   SetDDRamAddr
    
        movlw   LOW(LCD_message1)
        movwf   pszLCD_RomStr
        movlw   HIGH(LCD_message1)
        movwf   pszLCD_RomStr+1
        lcall   putrsXLCD
    
        banksel NEC_IR_Flags
        clrf    NEC_IR_Flags
        clrf    NEC_IR_State
        bcf     BIT_NEC_IR_Flags_COMMAND
        bcf     BIT_NEC_IR_Flags_REPEAT
        bcf     INTCON,INTF
        bsf     INTCON,INTE
        bsf     INTCON,GIE
    ;
    ; Application process loop
    ;
    AppLoop:
        movf    NEC_IR_Flags,F      ; Check for event
        btfsc   STATUS,Z            ; Skip if any event bit set
        GOTO    AppLoop             ;
    
        banksel NEC_IR_Flags
        btfsc   BIT_NEC_IR_Flags_REPEAT ; skip of not a REPEAT event
        goto    IncrementCount
        banksel RepeatCount
        clrf    RepeatCount
    ;
    ; Increment repeat count
    ;
    IncrementCount:
        banksel RepeatCount
        incfsz  RepeatCount,W
        movwf   RepeatCount
    
    ;
    ; Show measurement for Start Of Transmission (SOT) flash
    ;
        movlw   LINE_TWO
        lcall   SetDDRamAddr
        movf    NEC_IR_StartFlash,W
        lcall   PutDecXLCD
    ;
    ; Show measurement for pause after SOT flash
    ;
        movlw   ' '
        lcall   WriteDataXLCD
        movf    NEC_IR_CdPause,W
        lcall   PutDecXLCD
    ;
    ; Show decoded ADDRESS and COMMAND
    ;
        movlw   ' '
        lcall   WriteDataXLCD
        banksel NEC_IR_Address
        movf    NEC_IR_Address,W
        lcall   PutHexXLCD
        banksel NEC_IR_Command
        movf    NEC_IR_Command,W
        lcall   PutHexXLCD
    ;
    ; Show REPEAT count
    ;
        movlw   ' '
        lcall   WriteDataXLCD
        banksel RepeatCount
        movf    RepeatCount,W
        lcall   PutHexXLCD
    ;
    ; Clear event flags to enable capture of next event
    ;
        banksel NEC_IR_Flags
        clrf    NEC_IR_Flags
    
        lgoto   AppLoop
    ;
    ; LCD messages
    ;
    MAIN_CONST   code
    LCD_message1:
        dt  "NEC IR Decode v0",0
        END
    

    I have written a lot of code in assembly language for 8-bit PIC controllers. This application is dense with some very subtle tricks. There are not enough comments in it to explain how and why it works. You are just going to have to analyze it for yourself.

    The trick with the NEC IR protocol is that it's all about the pauses between the flashes of IR. If there is an accurate, complete and understandable description of the NEC IR control protocol on the Internet I could not find it.

    I was thinking it would be possible to teach you how to code this but I do not think I am good enough to pull that off. I can only hope this can work as an example for you.