Claudio Lanconelli's WEB pages - Updated 26 Aug 98

Home page ] Projects ] Useful links ] Who am I? ]


;***************************************************************************
;* IR.ASM
;*
;* Decode IR RC5 using
;* AVR MINI THREADS v.1.02
;* ---> nanoKernel for the AVR AT90S1200
;*
;* To try this code you need AvrTools from ATMEL, PonyProg and a simple
;* hardware (See schematics in PDF).
;*
;* Copyright (c) 1998 by Claudio Lanconelli
;* www.lancos.com
;*
;/ This program is free software; you can redistribute it and/or //
;/ modify it under the terms of the GNU General Public License //
;/ as published by the Free Software Foundation; either version2 of //
;/ the License, or (at your option) any later version. //
;/ //
;/ This program is distributed in the hope that it will be useful, //
;/ but WITHOUT ANY WARRANTY; without even the implied warranty of //
;/ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
;/ General Public License for more details. //
;/ //
;/ You should have received a copy of the GNU General Public License //
;/ along with this program (see COPYING); if not, write to the //
;/ Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. //
;***************************************************************************

.DEVICE AT90S1200

.include "macro.inc"
.include "1200def.inc"

;
; The biphase decoding algorithm used here need 4 bytes + 1 bit, it doesn't need any
; external interrupts, but it needs a sampling time that you can tune to your needs.
; In practice you can use the timer interrupt with the timer interval you need in
; your application. Here the timer interrupt is used for the serial thread (4800Hz) AND
; for the RC5 thread.
;
; Follows the RC5 decoder:
;-the first thread (main) wait for a signal from the RC5 thread. When it wakes up
; blink the led and send the request to transmit the 16word received to the serial thread
;-the second thread (RC5) decodes data from the IR receiver (i.e. TFMS5360) and store
; it in IrDataH + IrDataL. This 16bit data have the following format:
; bit 15: not used, always 0
; bit 14-12: RC5 start flag, always 4 for valid RC5 codes
; bit 11: RC5 control, toggle on every key stroke (is the same for repeat)
; bit 10-6: RC5 address
; bit 5-0: RC5 command
; It wait for a signal from the timer, and when it wakes up sample the IrRx and
; decode biphase RC5 signal. When the code is complete signal the main thread.
;-the third thread (Serial) wait for a signal from the timer. When it wakes up
; send data to the SerTx line at 4800N81. Most PC don't need a MAX232 to translate the
; 5V output to 12V.
;

.CSEG

;***************************************************************************
;* VARIABLE ASSIGNEMENTS
;***************************************************************************

    .def    SaveThr1Status	=     r0 ;status copy for thread 1
    .def    SaveThr2Status	=     r1    ;status copy for thread 2
    .def    SaveThr3Status	=     r2    ;status copy for thread 3
    .def    SaveStatus		=     r3    ;status copy for interrupts

    .def    SerNbit		=    r16     ;serial bit to transmit
    .def    SerData		=    r17     ;serial data byte to transmit

    .def    IrCount		=    r18
    .def    IrIdx		=    r19
    .def    IrBits		=    r20
    .def    IrDataH		=    r4
    .def    IrDataL		=    r21

    .equ    lastBit		=    7

    .def    Main1		=     r24    ;Temp variable used by main program
    .def    Temp1		=    r30
    .def    MiscFlags		=     r31    ;threads status flags + signal flags

    ;flags bit definition
    .equ    Thread1Status    =     0    ; 1 --> ready, 0 --> waiting
    .equ    Thread2Status    =     1
    .equ    Thread3Status    =     2
    .equ    Thread1Signal    =     3
    .equ    Thread2Signal    =     4
    .equ    Thread3Signal    =     5

    .equ    SerRequest	=     6
    .equ    IrLock	=     7

    .equ    THRSTATUSMASK    =     7
    .equ    SIGNALMASK =         112


;***************************************************************************
;* Port Pin Assignements
;***************************************************************************

    ;port D bit definitions
    .equ    Led1    =    0     ;out
    .equ    Led2    =    1     ;out
    .equ    IrRx    =    2     ;in (pullup)

    ;port B bit definitions
    .equ    Key1    =    0     ;in (pullup)
    .equ    Key2    =    1     ;in (pullup)
    .equ    SerRx   =    3     ;in
    .equ    SerTx   =    4     ;out
    

;********************
; Constants
;********************
    .equ    RC5_WORDLEN    =     14

    ;the following constant is crucial for RC5 decoding. The standard bit
    ;time is 1.778 msec. RC5 thread can sample the IrRx line at any frequency
    ; from 1/1.778E-3 * 4 = 2250Hz to 1/1.778E-3 * 255 = 143KHz
    ; In this case we have the sampling frequency equal to 4800, so RC5_BITTIME
    ; is 4800 / (1/1.778E-3) = 8.53, rounded to 9.
    .equ    RC5_BITTIME    =     9

;***************************************************************************
;* VECTORS
;***************************************************************************

        rjmp RESET             ;Reset Handle
        rjmp INT0DRV             ;Ext. interrupt request 0
        rjmp TIMERDRV         ;Timer


;***************************************************************************
;*
;* MAIN PROGRAM
;*
;***************************************************************************
;* INITIALIZATION
;***************************************************************************

RESET: 
        ldi    Main1, 0b00000011     ;set port D dir
        out    DDRD, Main1
        ldi    Main1, 0b00000111     ;preset output state (activate INT0 pull-up)
        out    PortD, Main1
        ldi    Main1, 0b00010000     ;set port B dir
        out    DDRB, Main1
        ldi    Main1, 0b00000011     ;turn on pullups on key inputs
        out    PortB, Main1

        ldi    Main1, 4
        out    TCCR0, Main1         ;set prescaler to CK/256
        ldi    Main1, 32+2
        out    MCUCR, Main1         ;enable sleep idle mode (ext int on falling edge)
        ldi    Main1, 2
        out    TIMSK, Main1         ;enable timer interrupt 

        ldi    Main1, 0         ;64
        out    GIMSK, Main1         ;don't enable external interrupt

        ldi    Main1, 256-3         ;timer intr. freq. = 3.6864MHz / 256 / 3 = 4800Hz
        out    TCNT0, Main1

        ldi    MiscFlags,THRSTATUSMASK 

        sei                 ;enable interrupts


        ;Init RC-5 decoding
        set
        clr    IrIdx

        sbis    PortD,2
        nop

        in    SaveThr1Status,SREG         ;initialize status (interrupts enabled in all thread)
        mov     SaveThr2Status,SaveThr1Status
        mov     SaveThr3Status,SaveThr1Status
        
;=====================================
;THREAD 1
; main loop
THR1LOOP:
        rcall    WAIT1         ;Wait for RC5 thread signal
        cbi    PortD,Led1     ;turn led ON

        ;transmit the data from MSB first
        mov    SerData,IrDataH
        sec
        rcall    BIN2ASCII
        sbr    MiscFlags, EXP2(Thread3Signal)+EXP2(SerRequest)    ;send the request to serial thread
        rcall    WAIT1

        mov    SerData,IrDataH
        clc
        rcall    BIN2ASCII
        sbr    MiscFlags, EXP2(Thread3Signal)+EXP2(SerRequest)    ;send the request to serial thread
        rcall    WAIT1

        mov    SerData,IrDataL
        sec
        rcall    BIN2ASCII
        sbr    MiscFlags, EXP2(Thread3Signal)+EXP2(SerRequest)    ;send the request to serial thread
        rcall    WAIT1

        mov    SerData,IrDataL
        clc
        rcall    BIN2ASCII
        sbr    MiscFlags, EXP2(Thread3Signal)+EXP2(SerRequest)    ;send the request to serial thread
        rcall    WAIT1

        cbr    MiscFlags, EXP2(IrLock)         ;clear IrLock flag: we can receive another RC5 code
        sbi    PortD,Led1             ;turn led OFF
        rjmp    THR1LOOP

;-----------------
WAIT1:
        ;save status before switch to another thread
        in    SaveThr1Status,SREG
        sbrs    MiscFlags, Thread1Signal         ;check for reeceived signal
        cbr    MiscFlags, EXP2(Thread1Status)        ;thread1 in wait state
        cbr    MiscFlags, EXP2(Thread1Signal)        ;reset signal
        rjmp    SCHEDULER2
THR1WAKEUP:
        ret


;-----------------
;converts a binary byte to ASCII char
;
;if Carry == 1, most significant nibble
;altrimenti less significant nibble
;-----------------
BIN2ASCII:
        brcc    DIGIT0
        swap    SerData
DIGIT0:
        andi    SerData,15
        cpi    SerData,10
        brlo    DIGDEC
        addi    SerData,(65-10)     ;'A'
        ret
DIGDEC:
        addi    SerData,48     ;'0'
        ret


;=================================
;THREAD 2    IrReceiver thread (decode RC-5 codes)
; main loop
THR2LOOP:
        ;thread 2 WAIT
        ;we can't do a separate Wait subroutine for every thread. Note that
        ;the stack is hardware, and not accessible.
        ;save status before switch to another thread
        in    SaveThr2Status,SREG
        sbrs    MiscFlags, Thread2Signal         ;check for received signal, if we have already
                                ;received the signal don't go sleep
        cbr    MiscFlags, EXP2(Thread2Status)        ;thread2 in wait state
        cbr    MiscFlags, EXP2(Thread2Signal)
        rjmp    SCHEDULER3
THR2WAKEUP:
        sbrc    MiscFlags, IrLock
        rjmp    THR2LOOP

        ;compare last sampled status with current one
        cpeqPortBit    PinD, IrRx, RC5_NOTEQ

        tst    IrIdx         ;IrIdx zero mean "wait start"
        breq    THR2LOOP

        inc    IrCount
        cpi    IrCount,(RC5_BITTIME*3)/2
        brlo    RC5_NOTIMEOUT
        ;test if timeout was caused by stop bit (last bit was 1)
        cpi    IrIdx,RC5_WORDLEN
        breq    RC5_LONG
RC5_TIMEOUT:
        clr    IrIdx         ;discard data and go to "wait start" state
        set
RC5_NOTIMEOUT:
        rjmp    THR2LOOP

RC5_NOTEQ:
        tst    IrIdx
        brne    RC5_NOWAIT
        sbr    IrBits, EXP2(lastBit)
        ldi    IrCount,RC5_BITTIME
        clr    IrDataL
RC5_NOWAIT:
        cpi    IrCount,(RC5_BITTIME*2)/3         ;short or long status?
        brsh    RC5_LONG

        cpeqRegBit     IrBits,lastBit,RC5_L1
        lsl    IrDataL
        rol    IrDataH
        sbrc    IrBits, lastBit
        sbr    IrDataL, 1
        inc    IrIdx
RC5_L1:
        rjmp    RC5_L2
RC5_LONG:                         ;long status
        lsl    IrDataL
        rol    IrDataH
        sbrc    IrBits, lastBit
        sbr    IrDataL, 1
        inc    IrIdx

        ldi    Temp1,EXP2(lastBit)         ;one's complement of lastBit
        eor    IrBits,Temp1
RC5_L2:
        brts    RC5_L3                 ;one's complement of T
        set
        skipnext
RC5_L3:
        clt

        clr    IrCount
        cpi    IrIdx,RC5_WORDLEN+1
        brlo    THR2LOOP
RC5_RECVOK:
        clr    IrIdx
        ;transmit IrData...
        sbr    MiscFlags, EXP2(IrLock)+EXP2(Thread1Signal)

        rjmp    THR2LOOP


;===================================
;SERIAL DRIVER THREAD
; main loop
THR3LOOP:
        ;thread 3 WAIT
        ;save status before switch to another thread
        in    SaveThr3Status,SREG
        sbrs    MiscFlags, Thread3Signal         ;check for received signal
        cbr    MiscFlags, EXP2(Thread3Status)        ;thread3 in wait state
        cbr    MiscFlags, EXP2(Thread3Signal)
        rjmp    SCHEDULER1
THR3WAKEUP:
        ;test SerRequest to know who have sent the signal (Thr1 or Timer)
        sbrc    MiscFlags, SerRequest
        rjmp    THR3_L1

        ;waked up from Timer, are some data bits to transmit?
        tst    SerNBit                     ;if zero do nothing
        breq    THR3LOOP

        dec    SerNBit
        breq    THR3_TXEND                 ;if zero tx stop and finish

        cpi    SerNBit,9                 ;tx start
        breq    THR3_TX0
THR3_TXDATA:
        lsr    SerData                     ;tx next bit (stored in the carry)
        brcc    THR3_TX0
THR3_TX1:
        cbi    PortB,SerTx
        rjmp    THR3LOOP
THR3_TX0:
        sbi    PortB,SerTx
        rjmp    THR3LOOP
THR3_L1:
        ;new request from Thr1
        cbr    MiscFlags, EXP2(SerRequest)
        ldi    SerNBit,10                 ;tx 10 bits: 1start + 8dati + 1stop
        rjmp    THR3LOOP
THR3_TXEND:
        sbr    MiscFlags, EXP2(Thread1Signal)        ;wakeup thread 1
        rjmp    THR3_TX1


;---------------
;Interrupts routine just send a signal to a thread. If you need a VERY short interrupt
;response you can put some instructions here, then send the signal to the thread
;---------------
TIMERDRV:
        in    SaveStatus,SREG

        ldi    Main1, 256-3
        out    TCNT0, Main1
        sbr    MiscFlags, EXP2(Thread3Signal)+EXP2(Thread2Signal) ;wakeup thread 3 and 2

        out    SREG,SaveStatus
        reti


;---------------
INT0DRV:
        in    SaveStatus,SREG
        sbr    MiscFlags, EXP2(Thread2Signal)        ;wakeup thread 2
        out    SREG,SaveStatus
        reti


;-----------------
;This is the scheduler, the core of the nanoKernel. You can decide to go sleep
; when there's nothing to do. You can also put there the watchdog reset instruction.
;-----------------
SCHEDULER:
        sei                         ;be sure interrupts on here

        ;if you don't want to go sleep remove next 3 instr
        tst    MiscFlags
        brne    SCHEDULER1
        sleep
SCHEDULER1:
        sbrc    MiscFlags, Thread1Status         ;test if thread1 is ready
        rjmp    SCHEDRDY1
        ;check for wakeup
        sbrs    MiscFlags, Thread1Signal         ;test if thread1 received a signal
        rjmp    SCHEDULER2
        cbr    MiscFlags, EXP2(Thread1Signal)        ;clear signal
SCHEDRDY1:
        ;switch to thread 1
        sbr    MiscFlags, EXP2(Thread1Status)
        out    SREG,SaveThr1Status
        rjmp    THR1WAKEUP

SCHEDULER2:
        sbrc    MiscFlags, Thread2Status         ;test if thread2 is ready
        rjmp    SCHEDRDY2
        ;check for wakeup
        sbrs    MiscFlags, Thread2Signal         ;test if thread1 received a signal
        rjmp    SCHEDULER3
        cbr    MiscFlags, EXP2(Thread2Signal)        ;clear signal
SCHEDRDY2:
        ;switch to thread 2
        sbr    MiscFlags, EXP2(Thread2Status)
        out    SREG,SaveThr2Status
        rjmp    THR2WAKEUP
SCHEDULER3:
        sbrc    MiscFlags, Thread3Status         ;test if thread3 is ready
        rjmp    SCHEDRDY3
        ;check for wakeup
        sbrs    MiscFlags, Thread3Signal         ;test if thread1 received a signal
        rjmp    SCHEDULER
        cbr    MiscFlags, EXP2(Thread3Signal)        ;clear signal
SCHEDRDY3:
        ;switch to thread 3
        sbr    MiscFlags, EXP2(Thread3Status)
        out    SREG,SaveThr3Status
        rjmp    THR3WAKEUP