; ---------------------------------------------------- ; soundsensor.asm ; By Joel Jordan ; University of Illinois at Urbana-Champaign ; ; Simple wireless sound-level sensor ; ; ---------------------------------------------------- ; ---------------------------------------------------- ; 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 #define NodeID 0x0012 ; ---------------------------------------------------- ; 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 BOOST_CONVERTER_EN PORTA, 3 #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 Temp DisplayByte ADCSample Time0 Time1 Time2 Time3 Checksum SoundLevel TempC TempD Bargraph SolarLevel endc ; RF Packet structure cblock PacketLength ; length of TX packet RFPacket:0 ; transmit packet buffer PacketDest:1 ; destination node (-1 = broadcast) PacketData:0xF ; packet data (up to 15 bytes) endc ; RF Transmit Variables cblock TXByte ; byte to send TXFlags ; flags TXBufLen ; length of transmit buffer TXBufCnt ; counter for transmission EncodeNybble ; endc cblock 0x100 TXBuf:0x10 endc #define TXNybble TXFlags, 0 ; 1 if high nybble has been sent #define TXDoneFlag TXFlags, 1 #define HighPowerModeFlag TXFlags, 2 StartCode1 equ 0x01 StartCode2 equ 0x37 ; ---------------------------------------------------- ; program code ; ---------------------------------------------------- org 0x00 ResetVector: goto Initialize org 0x08 HighPriorityInt: bra HPInt retfie org 0x18 LowPriorityInt: movwf WSave movff STATUS, StatusSave movff BSR, BSRSave btfsc PIR1, ADIF ; ADC interrupt? bra ADCInt LPIntDone: movff BSRSave, BSR movff StatusSave, STATUS movf WSave, w retfie ADCInt: bcf PIR1, ADIF ; clear int flag ; display sample on LEDs movf ADRESH, W call LEDDisplayByte bra LPIntDone HPInt: btfss HighPowerModeFlag ; are we in high-power mode? retfie FAST ; if not, return from interrupt SerialInterrupt: btfsc TXDoneFlag, ACCESS bra TXDisable btfss TXNybble, ACCESS bra TXNewByte movf TXByte, w, ACCESS rcall EncodeFourToEight movwf TXREG, ACCESS bcf TXNybble, ACCESS dcfsnz TXBufCnt, F, ACCESS bra TXSetDone bra SerialInterruptDone TXNewByte: movff POSTINC2, TXByte swapf TXByte, w, ACCESS rcall EncodeFourToEight movwf TXREG, ACCESS bsf TXNybble, ACCESS SerialInterruptDone: retfie FAST TXSetDone bsf TXDoneFlag ; set done flag bra SerialInterruptDone TXDisable bcf TXSTA, TXEN ; disable transmitter bra SerialInterruptDone EncodeFourToEight: movwf EncodeNybble, ACCESS movlw UPPER(FourToEightTable) movwf TBLPTRU movlw HIGH(FourToEightTable) movwf TBLPTRH movlw LOW(FourToEightTable) movwf TBLPTRL movf EncodeNybble, W, ACCESS andlw 0xF DoLookup: addwf TBLPTRL, F movlw 0 addwfc TBLPTRH, F addwfc TBLPTRU, F tblrd* movf TABLAT, W return ; --------------------------------------------------------- ; main routine ; --------------------------------------------------------- Initialize: call SetupIOPorts call DisableBoostConverter bsf LATB, 0 clrf Time0 clrf Time1 clrf Time2 clrf Time3 ; turn off unnecessary peripherals call DisableTimers call DisableCCP ; most other stuff should be disabled at power-on ; default to 31.25KHz oscillator speed call SetOSC31 ; turn on the radio module, then put it to sleep ; this uses a lot of current for about 15ms bsf RF_CTR1 bsf RF_CTR0 clrf LoopCounter radio_on_delay decfsz LoopCounter goto radio_on_delay ; go into sleep mode bcf RF_CTR1 bcf RF_CTR0 ; sip power slowly from battery source LowPowerMode: call SetupADCSolar ; set up ADC call SetOSC31 call DisableBoostConverter bcf OSCCON, IDLEN ; disable IDLE mode SlowLoop: bsf WDTCON, SWDTEN ; enable WDT sleep ; wait for WDT to wake us up bcf WDTCON, SWDTEN ; disable WDT ; increment time call IncrementTimer ; has the solar panel voltage gotten high enough? call GetADCSample ; returns in ADCSample movf ADCSample, W movwf SolarLevel movlw 0x80 cpfslt ADCSample ; less than 1.67 V, stay inactive bra HighPowerMode goto SlowLoop ; burn power from secondary source HighPowerMode: call EnableBoostConverter ; read sound level from ADC, returns 8-bit number call GetSoundLevel movwf SoundLevel call SetOSC4000 ; set 4MHz oscillator speed ; call SoundLevelBargraph ; uncomment this for debugging call BuildPacket ; make a packet from the time & sound level call SetupUART bsf RF_CTR1 ; place radio in receive mode at first bsf RF_CTR0 bsf HighPowerModeFlag bsf INTCON, GIEL ; enable interrupts bsf INTCON, GIEH call TransmitPacket ; send the packet call TransmitPacket ; send the packet call TransmitPacket ; send the packet (3rd time's a charm!) call DisableUART bcf INTCON, GIEH ; disable interrupts bcf INTCON, GIEL bcf RF_CTR0 ; radio back to sleep bcf RF_CTR1 goto LowPowerMode ; done sending message, back to low-power mode IncrementTimer: movlw 0x0 incf Time0, F addwfc Time1, F addwfc Time2, F addwfc Time3, F return BuildPacket: lfsr FSR0, TXBuf clrf Checksum movlw 0x00 movwf POSTINC0 movlw 0x00 movwf POSTINC0 movlw 0x00 movwf POSTINC0 movlw 0x00 movwf POSTINC0 movlw 0x55 movwf POSTINC0 movlw StartCode1 addwf Checksum, F movwf POSTINC0 movlw StartCode2 addwf Checksum, F movwf POSTINC0 movlw LOW(NodeID) addwf Checksum, F movwf POSTINC0 movlw HIGH(NodeID) addwf Checksum, F movwf POSTINC0 movf Time0, W addwf Checksum, F movwf POSTINC0 movf Time1, W addwf Checksum, F movwf POSTINC0 movf Time2, W addwf Checksum, F movwf POSTINC0 movf Time3, W addwf Checksum, F movwf POSTINC0 movf SolarLevel, W movwf POSTINC0 movf SoundLevel, W addwf Checksum, F movwf POSTINC0 movff Checksum, POSTINC0 clrf POSTINC0 ; Pad with trailing zero due to bug in serial port code movlw 0x11 movwf TXBufLen return EnableBoostConverter: bsf BOOST_CONVERTER_EN return DisableBoostConverter: bcf BOOST_CONVERTER_EN 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 ; ------------------------------------------------------ ; configure the adc for solar voltage sampling ; ------------------------------------------------------ SetupADCSolar: ; set up analog ports, references movlw b'00001101' ; only AN0-AN1 enabled movwf ADCON1 ; also, use Vdd and Vss for +/- ref ; select input channel movlw b'11000011' andwf ADCON0, F movlw b'00000100' iorwf ADCON0, F ; sample from AD1 (V_SOLAR) ; set acquisition time = 0 * TAD movlw b'01000000' ; clear bits we're gonna change andwf ADCON2, F ; set A/D conversion clock movlw b'00000111' ; internal RC oscillator iorwf ADCON2, F ; left-justify A/D result bcf ADCON2, ADFM ; set A/D interrupt priority low bcf IPR1, ADIP return SetupADCMic: ; set up analog ports, references movlw b'00001101' ; only AN0-AN1 enabled movwf ADCON1 ; also, use Vdd and Vss for +/- ref ; select input channel movlw b'11000011' andwf ADCON0, F ; sample from AD0 (Mic) ; set acquisition time = 0 * TAD movlw b'01000000' ; clear bits we're gonna change andwf ADCON2, F ; set A/D conversion clock movlw b'00000111' ; internal RC oscillator iorwf ADCON2, F ; left-justify A/D result bcf ADCON2, ADFM ; set A/D interrupt priority low bcf IPR1, ADIP return EnableADC: bsf ADCON0, ADON ; turn on AD module bcf PIR1, ADIF ; clear AD interrupt flag ; bsf PIE1, ADIE ; enable AD interrupt return DisableADC: bcf ADCON0, ADON ; turn off AD module ; bcf PIE1, ADIE ; disable AD interrupt return GetADCSample: rcall EnableADC bsf ADCON0, GO GetADCSampleLoop: btfsc ADCON0, GO bra GetADCSampleLoop movff ADRESH, ADCSample rcall DisableADC return ; ------------------------------------------------------- ; Take a sample from the Microphone ; return top 8 bits of absolute value ; ------------------------------------------------------- GetSoundLevel: bsf MIC_POWER call SetupADCMic ; wait ~500ms for microphone/amplifier to stabilize ; note: how can this be fixed? I think it's a bug movlw b'11010011' ; configure 1:16 prescale for TMR0 movwf T0CON bsf OSCCON, IDLEN ; enable IDLE mode bcf INTCON, GIEH ; disable int handlers bcf INTCON, TMR0IF ; clear TMR0 int flag bsf INTCON, TMR0IE ; enable TMR0 interrupt to wake from sleep clrf TMR0L ; gives us lots of sleep time sleep bcf INTCON, TMR0IE bcf T0CON, TMR0ON ; disable TMR0 ; select input channel call GetADCSample bcf MIC_POWER ; sample is centered about 1.5V ; take absolute value of sample movlw 0x80 subwf ADRESH, W movwf Temp btfss Temp, 7 bra SoundLevelPositive comf Temp, F bcf STATUS, C ; load !(ADRESL<7>) into carry btfss ADRESL, 7 bsf STATUS, C rlcf Temp, F ; rotate it in incf Temp, W ; increment Temp to get 2's complement return SoundLevelPositive: bsf STATUS, C ; get ADRESL<7> into carry btfss ADRESL, 7 bcf STATUS, C rlcf Temp, W ; rotate it in return SoundLevelBargraph: movff SoundLevel, Temp ; only look at bits 5-7 swapf Temp, F rrncf Temp, F movlw 0x07 andwf Temp, F incf Temp, F clrf Bargraph MicMakeBargraph: bsf STATUS, C dcfsnz Temp, F bra MicBargraphDone rlcf Bargraph, F bra MicMakeBargraph MicBargraphDone: movf Bargraph, W call LEDDisplayByte 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'00010011' movwf TRISA ; PORTA<0: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 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 ; ------------------------------------------------------ ; set up serial port, 19200 baud ; Assumes 4MHz oscillator ; ------------------------------------------------------ SetupUART bsf IPR1, RCIP ; set high interrupt priority bsf IPR1, TXIP ; set high interrupt priority bcf TRISC, 6 ; TX output movlw d'12' ; ~19200 bps @ 4MHz movwf SPBRG ; movlw b'00000100' ; 8-bit, async high speed, initially disabled movwf TXSTA movlw b'10000000' ; serial port enabled, 8-bit, receiver disabled movwf RCSTA bsf TXDoneFlag ; set up xmitter free flag bcf TXNybble return DisableUART bcf RCSTA, SPEN ; disable serial port return ; ---------------------------------------------------- ; TransmitPacket: Wait for a full packet to be transmitted ; ---------------------------------------------------- ; first send preamble TransmitPacket: bcf RF_CTR1 ; it takes 12us to switch TR1000 from bsf RF_CTR0 ; RX to TX mode, but I'm ignoring that for now bcf TXDoneFlag movff TXBufLen, TXBufCnt ; set up packet transfer lfsr FSR2, TXBuf BSF TXSTA, TXEN ; enable xmitter bsf PIE1, TXIE ; enable tx interrupt bsf OSCCON, IDLEN ; enable IDLE sleep mode ; allows UART to function in sleep mode Transmit_WaitLoop: ; sleep ; sleep until interrupt btfsc TXSTA, TXEN ; wait for packet to be sent goto Transmit_WaitLoop bcf OSCCON, IDLEN bcf PORTC, 6 ; clear TX pin ; bcf TXSTA, TXEN ; disable transmitter bcf PIE1, TXIE ; disable tx interrupt return ; --------------------------------------- ; LED Debugging stuff used when connected ; to the debug board ; --------------------------------------- LEDDisplayByte ; Byte to show: 0 1 2 3 4567 ; Display is PORTC<1:2>, PORTD<0:1,4:7> xorlw 0xFF ; negate w movff LATC, TempC movwf DisplayByte movlw 0xF9 andwf TempC, F ; mask out PORTC<1:2> rlncf DisplayByte, W ; shift the last two bits andlw 0x06 ; mask everything else out iorwf TempC, F ; or it into the right place movff TempC, LATC movff LATD, TempD movlw 0x0C andwf TempD, F ; mask out PORTD<0:1,4:7> movlw 0xF0 ; handle PORTD<4:7> andwf DisplayByte, W iorwf TempD, F rrncf DisplayByte, F ; finally, PORTD<0:1> rrncf DisplayByte, W andlw 0x03 iorwf TempD, F movff TempD, LATD 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 FourToEightTable: ; Manchester-encoded nybbles ; where for each bit, 0 -> 01, 1 -> 10 ; 0 1 2 3 db b'01010101', b'01010110', b'01011001', b'01011010' ; 4 5 6 7 db b'01100101', b'01100110', b'01101001', b'01101010' ; 8 9 A B db b'10010101', b'10010110', b'10011001', b'10011010' ; C D E F db b'10100101', b'10100110', b'10101001', b'10101010' end