; ---------------------------------------------------- ; soundbasestation.asm ; By Joel Jordan ; University of Illinois at Urbana-Champaign ; ; Receives sound level packets over radio ; intelligent ; Debug board polls periodically over I2C ; when polled, send last packet received ; ; ---------------------------------------------------- ; ---------------------------------------------------- ; select target device ; ---------------------------------------------------- #include "p18f4320.inc" LIST P=18f4320, R=HEX ; ---------------------------------------------------- ; select configuration bits ; ---------------------------------------------------- __CONFIG _CONFIG1H, _IESO_OFF_1H & _FSCM_OFF_1H & _INTIO1_OSC_1H __CONFIG _CONFIG2L, _PWRT_ON_2L & _BOR_OFF_2L & _BORV_20_2L __CONFIG _CONFIG2H, _WDT_OFF_2H & _WDTPS_256_2H __CONFIG _CONFIG3H, _MCLRE_ON_3H & _PBAD_DIG_3H & _CCP2MX_C1_3H __CONFIG _CONFIG4L, _DEBUG_OFF_4L & _LVP_OFF_4L & _STVR_OFF_4L __CONFIG _CONFIG5L, _CP0_OFF_5L & _CP1_OFF_5L & _CP2_OFF_5L & _CP3_OFF_5L __CONFIG _CONFIG5H, _CPB_OFF_5H & _CPD_OFF_5H __CONFIG _CONFIG6L, _WRT0_OFF_6L & _WRT1_OFF_6L & _WRT2_OFF_6L & _WRT3_OFF_6L __CONFIG _CONFIG6H, _WRTC_OFF_6H & _WRTB_OFF_6H & _WRTD_OFF_6H __CONFIG _CONFIG7L, _EBTR0_OFF_7L & _EBTR1_OFF_7L & _EBTR2_OFF_7L & _EBTR3_OFF_7L __CONFIG _CONFIG7H, _EBTRB_OFF_7H ; ---------------------------------------------------- ; macros for I/O pins ; changing these does not change pin assignments ; everywhere in the code! ; these are just here for convenience when doing ; single-bit operations ; ---------------------------------------------------- #define RF_RX PORTC, 7 #define MIC_POWER PORTB, 4 #define RF_CTR1 PORTD, 3 #define RF_CTR0 PORTD, 2 #define RF_CTR1_TRIS TRISD, 3 #define RF_CTR0_TRIS TRISD, 2 ; ---------------------------------------------------- ; declare global variables ; ---------------------------------------------------- cblock 0x0 WSave BSRSave StatusSave LoopCounter ; generic loop counter, free to use Flags StartByteHigh StartByteLow DisplayByte DecodeNybble PacketCount endc ; RF Packet structure cblock RXPacketDest RXPacketSrc endc ; RF Receive Variables cblock RXFlags RXByte RXByteCounter RXTemp endc #define NextTestFlag Flags, 0 #define RXByteReady RXFlags, 0 #define RXNybble RXFlags, 1 #define RXError RXFlags, 2 ; High byte includes stop bit, start bit ; which are mandated by UART #define StartCodeHigh b'10011010' #define StartCodeLow b'01100110' ; all valid packets begin with 0x0137 StartCode1 equ 0x01 StartCode2 equ 0x37 ; I2C Variables cblock I2CAddr ; address associated with last I2C event I2CRXBuf:0x10 ; RX buffer for I2C I2CRXLength I2CTXLength ; user counter I2CTXBufAddrL I2CTXBufAddrH I2CTXBufAddrU I2CTXIndex ; used from ISR I2CState ; status I2CTXFill ; flag ready to fill TX buffer I2CTXFull ; flag whether TX buf full Temp endc ; some stuff needed for I2C state machine #define IDLE 0x00 #define RECV 0x01 #define XMIT 0x02 #define WAIT_BUF 0x03 #define I2CAddress 0x02 ; buffer for received packet data cblock RFPacket:0x0 RFPacketLength:0x1 RFPacketData:0x40 endc ; ---------------------------------------------------- ; program code ; ---------------------------------------------------- org 0x00 ResetVector: goto Initialize org 0x08 HighPriorityInt: goto HPInt org 0x18 LowPriorityInt: ; save status movwf WSave movff STATUS, StatusSave movff BSR, BSRSave ; see what caused the interrupt btfsc PIR1, RCIF ; recv interrupt? bra RecvInterrupt LPIntDone: movff BSRSave, BSR movff StatusSave, STATUS movf WSave, w retfie RecvInterrupt: movff RCREG, RXTemp bcf PIR1, RCIF ; check for framing and overrun errors movlw b'00000110' andwf RCSTA, w bnz RecvError movf RXTemp, W btfss RXNybble, ACCESS bra RXNewByte ; low nybble rcall DecodeEightToFour iorwf RXByte, F, ACCESS xorlw 0xFF bz RecvError bcf RXNybble, ACCESS bsf RXByteReady, ACCESS goto RecvIntDone RXNewByte: ; high nybble first rcall DecodeEightToFour movwf RXByte, ACCESS xorlw 0xFF bz RecvError swapf RXByte, F, ACCESS bsf RXNybble, ACCESS RecvIntDone: goto LPIntDone RecvError: bcf RCSTA, CREN bsf RCSTA, CREN bsf RXError bcf RXNybble ; next time, read new byte goto LPIntDone ; High Priority Interrupt Handler ; used by I2C HPInt: btfss PIR1, SSPIF, 0 ; interrupt on I2C event? retfie FAST ; break out if not call service_I2C ; and service I2C event retfie FAST ; restore WREG, STATUS and BSR from fast return stack ; ------------------------------------------------------ ; Service routine for I2C interrupts ; adapted from Microchip App note AN734 ; ------------------------------------------------------ service_I2C movf SSPSTAT, w, 0 ; get SSP status andlw b'00101101' ; and mask out bits not used to determine state movwf Temp State1 ; Receiving, last byte was an address, movlw b'00001001' ; buffer full xorwf Temp,w bnz State2 movlw RECV ; mark that we're in the recv state movwf I2CState clrf I2CRXLength movf SSPBUF,w,0 ; dummy read to get address bsf SSPCON1,CKP,0 ; release clock bcf PIR1, SSPIF, 0 ; we're done, clear SSPIF return State2 movlw b'00101001' ; Receiving, last byte was data, xorwf Temp,w ; buffer is full bnz State3 ; handle received data here ; right now, we're just ignoring it bsf SSPCON1,CKP,0 ; release clock bcf PIR1, SSPIF, 0 ; we're done, clear SSPIF return State3 ; Transmitting, last byte was an movlw b'00001100' ; address, buffer is empty xorwf Temp,w bnz State4 movff RFPacketLength, I2CTXLength movlw XMIT ; mark that we're in the xmit state movwf I2CState clrf I2CTXIndex lfsr FSR2, RFPacket movlw 0x00 btfsc I2CTXFull, 0 movf INDF2, w, 0 call WriteI2C incf I2CTXIndex,f,0 bcf PIR1, SSPIF, 0 ; we're done, clear SSPIF return State4 ; Transmitting, last byte was data, movlw b'00101100' ; buffer is empty xorwf Temp,w bnz State5 lfsr 2, RFPacket ; load byte currently being transmitted movf I2CTXIndex, w addwf FSR2L, f btfsc STATUS, C incf FSR2H, f movlw 0x00 btfsc I2CTXFull, 0 movf INDF2,w,0 call WriteI2C incf I2CTXIndex,F movf I2CTXIndex,w andlw 0x3f ; test buffer overflow btfsc STATUS,Z clrf I2CTXIndex bcf PIR1, SSPIF, 0 ; we're done, clear SSPIF return State5 ; NACK received movlw b'00101000' xorwf Temp,w bnz I2CError movlw IDLE ; mark that we're in the idle state movwf I2CState bcf I2CTXFull, 0 bcf PIR1, SSPIF, 0 ; we're done, clear SSPIF return I2CError ; otherwise, hang and wait for WDT return ; ------------------------------------------------------ ; WriteI2C ; ------------------------------------------------------ WriteI2C btfsc SSPSTAT,BF,0 ; buffer full? goto $-2 ; wait for it to empty write bcf SSPCON1,WCOL,0 movwf SSPBUF,0 btfsc SSPCON1,WCOL,0 ; write collision? goto write bsf SSPCON1,CKP,0 ; release clock return ; --------------------------------------------------------- ; main routine ; --------------------------------------------------------- Initialize: call SetupIOPorts clrf PacketCount bsf LATB, 0 bsf RCON, IPEN ; enable interrupt priorities ; default to 8MHz oscillator speed call SetOSC8000 call LEDDemo ; test LEDs ; turn off unnecessary peripherals call DisableTimers call DisableCCP movlw b'11000111' ; enable TMR0 movwf T0CON ; with 1:256 prescale ; most other stuff should be disabled at power-on call SetupI2C call SetupUART ; make a fun initial packet call LoadTestString bsf I2CTXFull,0 ; have to wait about 15ms before putting radio into ; sleep mode clrf LoopCounter radio_on_delay decfsz LoopCounter goto radio_on_delay ; put radio into sleep mode bcf RF_CTR1 bcf RF_CTR0 MainLoop: call ReceivePacket movf PacketCount, W call LEDDisplayByte incf PacketCount, F ; wait for the packet to get read ; btfsc I2CTXFull, 0 ; bra $-2 bcf INTCON, TMR0IF clrf TMR0L ; clear timer0 TimeoutLoop btfss INTCON, TMR0IF bra TimeoutLoop bra MainLoop ; ---------------------------------------------------- ; ReceivePacket: Wait for a full packet to be received ; ---------------------------------------------------- ReceivePacket: bsf RF_CTR1 bsf RF_CTR0 Receive_WaitForStartCode: lfsr FSR0, RFPacket rcall DisableUART rcall SetOSC1000 rcall WaitForStartCode ; sets back to 8MHz before return rcall EnableUART rcall Receive_Byte btfsc RXError bra Receive_HandleError xorlw StartCode1 bnz Receive_HandleError rcall Receive_Byte btfsc RXError bra Receive_HandleError xorlw StartCode2 bnz Receive_HandleError movlw d'11' ; sound-level packets are 9 bytes long movwf LoopCounter movwf INDF0 movlw 0x5 ; add serial start code length + 1 addwf POSTINC0, F ; since I2C wants length + 1 for some reason movlw 0xDE movwf POSTINC0 movlw 0xAD movwf POSTINC0 movlw 0xBE movwf POSTINC0 movlw 0xEF movwf POSTINC0 ReceiveLoop1: rcall Receive_Byte btfsc RXError bra Receive_HandleError movwf POSTINC0 decfsz LoopCounter bra ReceiveLoop1 ; ignore end of message markers for now Receive_Done: call DisableUART ; just send everything over I2C bsf I2CTXFull, 0 bsf PIE1, SSPIE, 0 ; re-enable I2C interrupts return Receive_HandleError: bcf RXError bsf PIE1, SSPIE, 0 ; re-enable I2C interrupts bra Receive_WaitForStartCode ; ------------------------------------------------------ ; Waits to receive one byte. ; Returns result in W ; ------------------------------------------------------ Receive_Byte: btfsc RXError return btfss RXByteReady bra Receive_Byte movf RXByte, W bcf RXByteReady return DecodeEightToFour: ; return -1 if the input is invalid ; table lookup used because speed is more important ; than code size here. movwf DecodeNybble movlw UPPER(EightToFourTable) movwf TBLPTRU movlw HIGH(EightToFourTable) movwf TBLPTRH movlw LOW(EightToFourTable) movwf TBLPTRL movf DecodeNybble, W bra DoLookup DoLookup: addwf TBLPTRL, F movlw 0 addwfc TBLPTRH, F addwfc TBLPTRU, F tblrd* movf TABLAT, W return ; Poll RX pin waiting for a start code ; Runs at 1MHz ; ------------------------------------ WaitForStartCode: ; 1MHz at 19200 baud means is 13 cycles per bit ; wait for pending I2C transfers to end ; since they mess up our timing StartCodeWaitI2C: movf I2CState, F bnz StartCodeWaitI2C clrf StartByteHigh clrf StartByteLow ; first bit of start code is 1, so all 8 ; zeros must be shifted out before compare ; can find valid start code movlw b'00001100' ; put TR1000 in receive mode iorwf LATD, F ; bsf INTCON, GIEL bsf INTCON, GIEH ; enable interrupts StartCode_Loop bsf STATUS, C ; 11 btfss RF_RX ; 12 bcf STATUS, C ; 0 Grab the latest bit rlcf StartByteLow, F ; 1 rlcf StartByteHigh, F ; 2 movlw StartCodeHigh ; 3 check against the start code xorwf StartByteHigh, W ; 4 bz StartCode_CheckHighByte ; 5 keep going until start code found movf I2CState, F ; 6 sets zero flag if I2C is idle bnz StartCodeWaitI2C ; 7 nop ; 8 bra StartCode_Loop ; 9,10 ; check the high byte StartCode_CheckHighByte: movlw StartCodeLow ; 7 xorwf StartByteLow, W ; 8 bnz StartCode_Loop ; 9,10 StartCode_End: rcall SetOSC8000 movlw d'25' ; 19200 bps 8MHz movwf SPBRG ; restarts baud rate generator ; disable I2C while receiving a packet ; since the buffer is being overwritten bcf PIE1, SSPIE return DisableTimers: bcf T0CON, TMR0ON bcf T1CON, T1OSCEN bcf T1CON, TMR1ON bcf T2CON, TMR2ON bcf T3CON, TMR3ON return DisableCCP: movlw 0xf0 andwf CCP1CON, F andwf CCP2CON, F return ; ------------------------------------------------------ ; set up serial port ; ------------------------------------------------------ SetupUART: bcf IPR1, RCIP ; set low interrupt priority bcf IPR1, TXIP ; set low interrupt priority bcf TRISC, 6 ; TX output bsf TRISC, 7 ; RX input movlw d'25' ; 19200 bps 8MHz movwf SPBRG ; movlw b'00000100' ; 8-bit, async high speed, initially disabled movwf TXSTA movlw b'00000000' ; serial port disabled, 8-bit, receiver disabled movwf RCSTA return EnableUART: bsf RCSTA, SPEN bsf RCSTA, CREN bsf PIE1, RCIE bcf RXByteReady return DisableUART: bcf RCSTA, CREN bcf RCSTA, SPEN bcf PIE1, RCIE return SetupIOPorts: ; set all pins as digital outputs for lowest power consumption movlw b'00001101' movwf ADCON1 ; PORTA<0:1> analog, the rest digital movlw b'00010010' movwf TRISA ; PORTA<1,4> inputs, the rest outputs clrf TRISB ; PORTB outputs (saves power) movlw b'10000000' ; PORTC<7> input, the rest outputs movwf TRISC clrf TRISD ; PORTD all outputs ; set everything low but LEDs PORTD<0:1,4:7> and PORTC<1:2> (active low) ; and radio module PORTD<2:3> clrf LATA ; all values initially 0 (saves power) clrf LATB clrf LATC movlw b'00001100' ; radio must be started in receive mode movwf LATD return SetupI2C: clrf I2CState clrf I2CTXFill movlw b'00011000' ; set TRISC<3:4> to be inputs iorwf TRISC, f, 0 movlw b'00110110' ; set slave mode, enable MSSP, movwf SSPCON1, 0 bsf SSPCON2, SEN, 0 ; enable clock stretching on send/recv movlw I2CAddress movwf I2CAddr movff I2CAddr, SSPADD ; set address clrf SSPSTAT, 0 clrf PIR1, 0 ; clear current interrupt status bsf PIE1, SSPIE, 0 ; enable I2C interrupts bsf INTCON, GIEL, 0 ; enable global interrupts bsf INTCON, GIEH, 0 ; (i2c is high priority by default) return SetOSC31: movf OSCCON, W andlw b'10001111' movwf OSCCON return SetOSC1000: movf OSCCON, W andlw b'10001111' iorlw b'01000000' movwf OSCCON return SetOSC2000: movf OSCCON, W andlw b'10001111' iorlw b'01010000' movwf OSCCON return SetOSC4000: movf OSCCON, W andlw b'10001111' iorlw b'01100000' movwf OSCCON return SetOSC8000: ; setup internal oscillator to run at 8MHz movf OSCCON, W iorlw b'01110000' movwf OSCCON return LEDDisplayByte ; Byte to show: 0 1 2 3 4567 ; Display is PORTC<1:2>, PORTD<0:1,4:7> xorlw 0xFF ; negate w movwf DisplayByte movlw 0xF9 andwf LATC, F ; mask out PORTC<1:2> rlncf DisplayByte, W ; shift the last two bits andlw 0x06 ; mask everything else out iorwf LATC, F ; or it into the right place movlw 0x0C andwf LATD, F ; mask out PORTD<0:1,4:7> movlw 0xF0 ; handle PORTD<4:7> andwf DisplayByte, W iorwf LATD, F rrncf DisplayByte, F ; finally, PORTD<0:1> rrncf DisplayByte, W andlw 0x03 iorwf LATD, F return LEDDemo: clrf Temp movlw 0x00 bsf STATUS, C movlw b'11010111' ; configure 1:256 prescale for TMR0 movwf T0CON DemoLeftLoop: rlcf Temp, F btfsc STATUS, C bra DemoRightLoop movf Temp, W rcall LEDDisplayByte rcall DemoWait bra DemoLeftLoop DemoRightLoop: rrcf Temp, F btfsc STATUS, C bra DemoEnd movf Temp, W rcall LEDDisplayByte rcall DemoWait bra DemoRightLoop DemoEnd: bcf T0CON, TMR0ON return DemoWait: bcf INTCON, TMR0IF DemoWaitLoop: btfss INTCON, TMR0IF bra $-2 return ; ---------------------------------------------------- ; LoadTestString: Loads a sample string to test ; the I2C interface ; ---------------------------------------------------- LoadTestString: movlw UPPER(TestString) movwf TBLPTRU movlw HIGH(TestString) movwf TBLPTRH movlw LOW(TestString) movwf TBLPTRL movlw TestStringLength movwf Temp movwf RFPacketLength lfsr FSR0, RFPacketData LoadTestString_Loop: tblrd*+ movff TABLAT, POSTINC0 decfsz Temp goto LoadTestString_Loop return EightToFourTable: ; 0 1 2 3 4 5 6 7 8 9 A B C D E F db -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 ; 0 db -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 ; 1 db -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 ; 2 db -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 ; 3 db -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 ; 4 db -1, -1, -1, -1, -1,0x0,0x1, -1, -1,0x2,0x3, -1, -1, -1, -1, -1 ; 5 db -1, -1, -1, -1, -1,0x4,0x5, -1, -1,0x6,0x7, -1, -1, -1, -1, -1 ; 6 db -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 ; 7 db -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 ; 8 db -1, -1, -1, -1, -1,0x8,0x9, -1, -1,0xA,0xB, -1, -1, -1, -1, -1 ; 9 db -1, -1, -1, -1, -1,0xC,0xD, -1, -1,0xE,0xF, -1, -1, -1, -1, -1 ; a db -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 ; b db -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 ; c db -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 ; d db -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 ; e db -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 ; f TestString db "Base Station Online", 0xD, 0xA TestStringEnd db 0x00 TestStringLength equ TestStringEnd - TestString end