; ---------------------------------------------------- ; ; RDS decoder -- pic code ; by AHDL ; ; Config : PIC 16F84 / 4.332 / PUT enabled / XT ; ; TDA7330 demodulator ; ; (c) 2000 Andy Dewilde (xxx@xxx.xx) ; and Lieven Hollevoet (picmicro@hollie.tk) ; ; ; Re-use and modification of this code is hereby ; permitted, as long as the names and e-mail ; addresses of both authors are mentioned in the new ; version. ; ---------------------------------------------------- #include "P16F84.INC" LIST p=PIC16F84 __CONFIG _CP_OFF & _PWRTE_ON & _WDT_OFF & _XT_OSC ; -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- ; definitions ; -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- ; *** PORTS *** #DEFINE rdsClock 0x00 ; RB0 = rds clock, input #DEFINE rdsData 0x01 ; RB1 = rds data, input #DEFINE TxD 0x02 ; RB2 = transmit line ; *** MACROS *** #DEFINE bank0 bcf STATUS, RP0 #DEFINE bank1 bsf STATUS, RP0 ; *** CONSTANTS *** #DEFINE dump 0x04 ; *** REGISTERS *** ; used by rds routines Reg1A EQU 0x10 Reg1B EQU 0x11 Reg2A EQU 0x12 Reg2B EQU 0x13 Reg3A EQU 0x14 Reg3B EQU 0x15 Reg4A EQU 0x16 Reg4B EQU 0x17 StoReg1 EQU 0x18 StoReg2 EQU 0x19 StoReg3 EQU 0x1A StoReg4 EQU 0x1B StoReg1X EQU 0x1C StoReg2X EQU 0x1D StoReg3X EQU 0x1E StoReg4X EQU 0x1F HiReg EQU 0x20 LoReg EQU 0x21 Loper EQU 0x23 CountReg EQU 0x24 DelayReg EQU 0x25 Counter EQU 0x2E ; used by serial routines Locatie EQU 0x26 LoopCount EQU 0x27 TmrVal EQU 0x28 TxChar EQU 0x29 ; used by the isr W_Temp EQU 0x2A Status_Temp EQU 0x2B ; used to store the PS information ; WARNING: DO NOT change these values, ; these locations are used in calcs! Char0 EQU 0x30 Char1 EQU 0x31 Char2 EQU 0x32 Char3 EQU 0x33 Char4 EQU 0x34 Char5 EQU 0x35 Char6 EQU 0x36 Char7 EQU 0x37 Flags EQU 0x38 ; For PTY, TA (traffic announcement), TP (traffic programma) ; -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- ; This is the begin of the main loop code ; -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- ORG 0x00 ; reset vector goto _main ORG 0X04 ; interrupt vector goto _isr ; ---------------- _main ---------------------------------------- ; ; Initialisation of the I/O ports and several variables that are ; used throughout the program, also clears ram locations ; ; --------------------------------------------------------------- _main bcf STATUS, RP0 ; Set up I/O ports A & B clrf PORTA bsf STATUS, RP0 movlw 0x00 movwf TRISA ; A = unused (set all as output) bcf STATUS, RP0 ; PORTB is used as: clrf PORTB ; RB0 = rds clock, input bsf STATUS, RP0 ; RB1 = rds data, input movlw 0x03 ; RB2 = serial data, output movwf TRISB ; movlw dump ; Determines after how many blocks a dump will be triggered movwf LoopCount clrwdt ; Change prescaler assignment (in case we use TMR0) bsf STATUS, RP0 ; Enable pull-up movlw b'01010000' ; Enable TMR0 movwf OPTION_REG bcf STATUS, RP0 bsf PORTB, TxD ; pull TxD line high movlw 0x30 ; Clear RAM locations for PS info movwf FSR ; initialize pointer _nloc clrf INDF ; clear INDF register incf FSR, F ; increase pointer btfss FSR, 3 ; stop on 0x38 goto _nloc call _send ; -------------------------------------------------------------------- ; *************** Start the detection of RDS blocks ****************** ; -------------------------------------------------------------------- ; ----- _block1 ----------------------------------------------------- ; ; FUNCTION: searches for an A-datablock in the incoming datastream ; using the 'bit-slip'(tm) method. It shift all the previous ; bits and inserts the newly read bit as LSB ; Then it calculates the syndrome and checks for block A ; This subroutine actually synchronizes the algorithm ; with the incoming bitstream ; ; USES: _getBit ; ; FALLS INTO: _block2 ; ; -------------------------------------------------------------------- _block1 ; Search for syndrome A (using the bit-slip(tm) method) call _getBit ; wait for new databit, get syndrome movlw 0xF6 ; syndrome A? xorwf HiReg, W ; check 8 MSB's btfss STATUS, Z goto _block1 ; again clrw xorwf LoReg, W ; check the LSB's btfss STATUS, Z goto _block1 ; again movf StoReg1, W ; store *** block 1 data *** movwf Reg1A movf StoReg2, W movwf Reg1B ; ----- _block2 ----------------------------------------------------- ; ; FUNCTION: reads the next 26 incoming bits and checks the syndrome ; ; USES: _get26Bits, interrupt service routine, _syndrome, _block1 ; ; FALLS INTO: _block3 ; ; ------------------------------------------------------------------- _block2 call _get26Bits ; read the next 26 bits movlw 0xF5 ; syndrome B? xorwf HiReg, W btfss STATUS, Z goto _block1 ; again clrw xorwf LoReg, W btfss STATUS, Z goto _block1 ; again movf StoReg1, W ; store *block 2 data* movwf Reg2A movf StoReg2, W movwf Reg2B ; ----- _block3 ----------------------------------------------------- ; ; FUNCTION: reads the next 26 incoming bits and checks the syndrome ; this routine handles blocks of type C and C' ; ; USES: _get26Bits, interrupt service routine, _syndrome, _block1 ; ; FALLS INTO: _block4 ; ; ------------------------------------------------------------------- _block3 call _get26Bits ; read the next 26 bits movlw 0x97 ; syndrome C? xorwf HiReg, W btfsc STATUS, Z goto _block3_lo movlw 0xF3 ; syndrome C'? xorwf HiReg, W btfss STATUS, Z goto _block1 ; again _block3_lo clrw xorwf LoReg, W btfss STATUS, Z goto _block1 ; again movf StoReg1, W ; store *block 3 data* movwf Reg3A movf StoReg2, W movwf Reg3B ; ----- _block4 ----------------------------------------------------- ; ; FUNCTION: reads the next 26 incoming bits and checks the syndrome ; ; USES: interrupt service routine, _syndrome, _block1 ; ; FALLS INTO: _getps ; ; ------------------------------------------------------------------- _block4 call _get26Bits ; read the next 26 bits movlw 0x96 ; syndrome D? xorwf HiReg, W btfss STATUS, Z goto _block1 ; again clrw xorwf LoReg, W btfss STATUS, Z goto _block1 ; again movf StoReg1, W ; store *block 4 data* movwf Reg4A movf StoReg2, W movwf Reg4B ; -------------------------------------------------------------------- ; *************** Process the data & extract info ****************** ; -------------------------------------------------------------------- ; ----- _getdata ---------------------------------------------------- ; ; FUNCTION: Extract the PS/PTY/TP/TA from the group data (group 0A/0B) ; ; USES: _block1 ; ; FALLS INTO: nothing ; ; ------------------------------------------------------------------- _getdata movf Reg2A, W andlw 0xF0 ; Check for group 0A/B xorlw 0x00 btfss STATUS, Z goto _block1 ; start all over again ; REG4A & REG4B now contain 2 chars of PS movf Reg2B, W ; read char position andlw 0x03 ; mask bits movwf Locatie ; save position rlf Locatie, F ; x 2 movlw 0x30 ; calculate mempos addwf Locatie, W movwf FSR ; write char using indirect adressing movf Reg4A, W movwf INDF incf FSR, F movf Reg4B, W movwf INDF movlw 0x38 ; Now get TP(1)/PTY(5)/TA(1) code (Block B, bit 10-4) movwf FSR rlf Reg2B, F ; ! Reg2B & REG2A are changed here (to save clock ticks) rlf Reg2A, F rlf Reg2B, F rlf Reg2A, F rlf Reg2B, F rlf Reg2A, F rlf Reg2B, F rlf Reg2A, W movwf INDF decfsz LoopCount, F ; decrease loop counter, if 0 => dump goto _block1 movlw dump movwf LoopCount call _dump goto _block1 ; -------------------------------------------------------------------- ; ************************* Subroutines ****************************** ; -------------------------------------------------------------------- ; ----- _getBit ----------------------------------------------------- ; ; FUNCTION: used to sync the algortihm with the incoming bitstream ; ; USES: _shuffle, _syndrome ; ; FALLS INTO: nothing ; ; ------------------------------------------------------------------- _getBit btfss PORTB, rdsClock goto _getBit ; wait for RDCL to go high call _shuffle ; shuffle new bit into stack call _syndrome ; get syndrome return ; ----- _get26Bits -------------------------------------------------- ; ; FUNCTION: read next 26 bits from the incoming bitstream ; ; USES: _syndrome ; ; FALLS INTO: nothing ; ; ------------------------------------------------------------------- _get26Bits movlw 0x1A ; read 26 bits movwf Loper ; save value in Loper movlw 0x90 ; enable interrupt (INT/RB0) movwf INTCON _get26Lus movf Loper, F ; just to check if Loper = zero btfss STATUS, Z goto _get26Lus movlw 0x00 ; Now, the 26 bits are read, disable interrupts movwf INTCON call _syndrome return ; ----- _shuffle ---------------------------------------------------- ; ; FUNCTION: shuffle the working registers containing the incoming data ; ; USES: nothing ; ; FALLS INTO: nothing ; ; ------------------------------------------------------------------- _shuffle bcf STATUS, C btfsc PORTB, rdsData ; incoming bit bsf STATUS, C rlf StoReg4, F ; shuffle rlf StoReg3, F rlf StoReg2, F rlf StoReg1, F return ; ----- _syndrome --------------------------------------------------- ; ; FUNCTION: determine the syndrome, using the data from the working ; registers and the check-matrix in program memory ; ; USES: _matrixHi, _matrixLo, _delay ; ; FALLS INTO: nothing ; ; ------------------------------------------------------------------- _syndrome ; Multiply the 26 bits by the 26x10 movlw 0x1A ; check-matrix H to produce a 10-bit movwf CountReg ; syndrome... clrf HiReg ; Hireg/Loreg will eventually contain clrf LoReg ; the syndrome movf StoReg4, W movwf StoReg4X movf StoReg3, W movwf StoReg3X movf StoReg2, W movwf StoReg2X movf StoReg1, W movwf StoReg1X _loops rlf StoReg4X, F ; shuffle stack by 1 bit rlf StoReg3X, F rlf StoReg2X, F rlf StoReg1X, F btfss STATUS, C ; was it a `1'? goto _loopx ; for a `0' movf CountReg, W call _matrixHi ; get appropriate row from matrix xorwf HiReg, F ; and exclusive OR it movf CountReg, W call _matrixLo ; do same for last 2 bits of matrix row xorwf LoReg, F decfsz CountReg, F goto _loops ; repeat this 26 times, in less than 842uS return _loopx ; this is trap for `0', no exclusive OR movlw 0x03 ; just padding to avoid reading the call _delay ; same bit twice decfsz CountReg, F goto _loops return ; ----- _delay ------------------------------------------------------ ; ; FUNCTION: causes a delay :-) ; ; USES: the present value of the W register ; ; FALLS INTO: nothing ; ; ------------------------------------------------------------------- _delay ; delay movwf DelayReg _del decfsz DelayReg, F goto _del return ; ----- _MatrixHi --------------------------------------------------- ; (info on the checkmatrix extracted from Wireless World, '89) ; ; FUNCTION: contains the 8 upper-most bytes of the checkmatrix ; ; USES: the present value of W register ; ; RETURNS: the requested data from the checkmatrix ; ; FALLS INTO: nothing ; ; ------------------------------------------------------------------- _matrixHi addwf PCL, F nop ; nop needed because adding to PC causes 2 cycles retlw 0xC6 retlw 0xE3 retlw 0xA9 retlw 0x3D retlw 0x7B retlw 0xF7 retlw 0x80 retlw 0x6E retlw 0xDD retlw 0xD5 retlw 0xC4 retlw 0xE7 retlw 0xA1 retlw 0x2D retlw 0x5B retlw 0xB7 retlw 0x00 retlw 0x00 retlw 0x01 retlw 0x02 retlw 0x04 retlw 0x08 retlw 0x10 retlw 0x20 retlw 0x40 retlw 0x80 ; ----- _MatrixLo --------------------------------------------------- ; (info on the checkmatrix extracted from Wireless World, '89) ; ; FUNCTION: contains the 2 lower-most bytes of the checkmatrix ; ; USES: the present value of W register ; ; RETURNS: the requested data from the checkmatrix ; ; FALLS INTO: nothing ; ; ------------------------------------------------------------------- _matrixLo addwf PCL, F nop ; needed because adding 1 to PC jumps 2 steps retlw 0xC0 retlw 0xC0 retlw 0xC0 retlw 0xC0 retlw 0x80 retlw 0x00 retlw 0x40 retlw 0xC0 retlw 0x80 retlw 0x40 retlw 0xC0 retlw 0xC0 retlw 0xC0 retlw 0xC0 retlw 0x80 retlw 0x00 retlw 0x40 retlw 0x80 retlw 0x00 retlw 0x00 retlw 0x00 retlw 0x00 retlw 0x00 retlw 0x00 retlw 0x00 retlw 0x00 ; ----- _dump ------------------------------------------------------- ; ; FUNCTION: Dumps the memory contents of locations 30h - 37h ; to the serial port ; ; USES: nothing ; ; FALLS INTO: nothing ; ; ------------------------------------------------------------------- _dump movf Char0, W call _send movf Char1, W call _send movf Char2, W call _send movf Char3, W call _send movf Char4, W call _send movf Char5, W call _send movf Char6, W call _send movf Char7, W call _send movf Flags, W call _send return ; ----- _send ------------------------------------------------------ ; ; FUNCTION: Transmits the byte that is in W register 9600, 8N1 ; ; USES: _sendDelay ; ; FALLS INTO: _sendDelay ; ---------------------------------- _send movwf TxChar movlw 8 movwf Counter bcf PORTB, TxD ; send the startbit call _sendDelay ; delay _loop bcf STATUS, C rrf TxChar, F btfss STATUS, C goto _zendNul bsf PORTB, TxD goto _wait _zendNul bcf PORTB, TxD _wait call _sendDelay decfsz Counter, F goto _loop _eos bsf PORTB, TxD ; send the stopbit ; hier geen return, er is nog een senddelay nodig voor stopbit ! ; --------------------- ; Serial send delay ; --------------------- _sendDelay movlw d'33' movwf DelayReg _serlus decfsz DelayReg, F goto _serlus return ; ----- _isr ------------------------------------------------------- ; ; FUNCTION: Takes care of interrupt calls. ; ; In case of a RB0/INT change interrupt -> reads the next bit ; ; USES: _shuffle ; ; FALLS INTO: nothing ; ; ------------------------------------------------------------------- _isr ; Interrupt service routine _push movwf W_Temp ; save W swapf STATUS, W ; save status movwf Status_Temp ; without changing flags call _shuffle ; it was the bit-read routine decf Loper, F bcf INTCON, INTF _pop swapf Status_Temp, W ; get original status back movwf STATUS ; into status register swapf Status_Temp, F ; old no flags trick again swapf Status_Temp, W ; to restore W retfie ; finished END