;-------------------------------------------------------------------- ; Firmware for bicolour LED bargraph USB Blikenlights module. ; ; I2C address bit 1 is masked, so two addresses are associated with ; the module, one for each LED colour. The base address is read from ; PortA on a reset. ; Data received over I2C is stored, and a flag set to indicate it is ; ready for processing. ; An I2C read sends back EEPROM data, containing a description of the ; function of the address. ; ; The main loop outputs data to the LEDs. The output polarity ; determines which colour LED is being controlled. Switching between ; the two rapidly makes them appear to be both working simultaneously. ; ; If new data is detected, then the difference between new and current ; data is calculated and divided by the time since the last data was ; received. This amount is added to the current output value each time ; period, in order to make it interpolate smoothly between data ; receptions. ; ; If no data has been received for one address for more than TIMEOUT ; periods, then the outputs are turned off and an error LED is lit. ; If no data has been received at all for TIMEOUT periods, then the ; uC goes to sleep until the next I2C event (the error LED stays on ; though). ;-------------------------------------------------------------------- ; ; The following files should be included in the MPLAB project: ; ; 16F690.asm-- Main source code file ; ; 16f690.lkr-- Linker script file ; ;--------------------------------------------------------------------- #include errorlevel -302 ;suppress message 302 from list file __CONFIG _CP_OFF & _CPD_OFF & _BOR_OFF & _MCLRE_ON & _WDT_OFF & _PWRTE_OFF & _INTRC_OSC_NOCLKOUT & _FCMEN_OFF & _IESO_OFF ;--------------------------------------------------------------------- ;Constant Definitions ;--------------------------------------------------------------------- #define NODE_ADDR 0x10 ; I2C address of this node ; Change this value to address that ; you wish to use. #define TIMEOUT 0x20 ;If this many timer periods elapse without ;data reception, an error has occurred. ;--------------------------------------------------------------------- ; Variable declarations ;--------------------------------------------------------------------- udata_shr WREGsave res 1 ;For saving registers in ISR STATUSsave res 1 PCLATHsave res 1 Arg1 res 1 ;Used by various functions Arg2 res 1 Temp res 1 #define Diff1 Arg1 ;Aliases to aid readability in function code #define Diff2 Arg2 #define Dividend Arg1 #define Divisor Arg2 #define DivResult Temp LastAddress res 1 ;Last I2C address used to send data to this device ;In the following variables: ;offset0 is for first I2C address ;offset1 is for second I2C address CurrentBits res 2 ;Current output pattern DataStatus res 2 ;Info about data #define New 0x00 ;1 if new data available, 0 otherwise #define Dir 0x01 ;1 if target > current, 0 otherwise #define TO 0x02 ;1 if timed out, 0 otherwise udata 0x20 NewData res 2 ;Data awaiting processing Current res 2 ;Current output value Target res 2 ;Target output Increment res 2 ;Amount to change Current by at each update Periods res 2 ;Number of timer periods elapsed between ;last two data receptions Count res 2 ;Number of timer periods since last ;data reception ;--------------------------------------------------------------------- ; DoIncrement - Increments (or decrements) current value, stopping ; when the target value is reached. ;--------------------------------------------------------------------- DoIncrement macro offset movf Increment+offset,W btfss DataStatus+offset,Dir ;Which direction to increment? goto Decrement#v(offset) ;Down addwf Current+offset,F ;Increment current value btfsc STATUS,C ;Exceeded maximum possible value? goto Clamp#v(offset) ;Yes movf Current+offset,W ;Current > Target? subwf Target+offset,W btfss STATUS,C goto Clamp#v(offset) ;Yes goto DoIncEnd#v(offset) Decrement#v(offset): subwf Current+offset,F btfss STATUS,C ;Decremented below 0? goto Clamp#v(offset) ;Yes movf Target+offset,W ;Target > Current? subwf Current+offset,W btfss STATUS,C call Clamp#v(offset) ;Yes goto DoIncEnd#v(offset) Clamp#v(offset): ;Set Current = Target movf Target+offset,W movwf Current+offset DoIncEnd#v(offset): movf Current+offset,W ;Determine pattern of LEDs to display call NumToBits movwf CurrentBits+offset endm ;--------------------------------------------------------------------- ; CalcInc - Calculates how much to increment the displayed value by ; at each update in order to reach the target by the time the next ; data arrives. ;--------------------------------------------------------------------- CalcInc macro offset movf NewData+offset,W movwf Target+offset ;Set new data as target movwf Diff1 ;Calculate required change in Current movf Current+offset,W movwf Diff2 call Diff movwf Dividend ;For later division... movf Count+offset,W ;Current count is number of periods movwf Periods+offset ;since last reception movwf Divisor ;For later division... clrf Count+offset ;Reset count call Div8 ;Increment = Target / Periods movwf Increment+offset bcf DataStatus+offset,Dir movf Target+offset,W ;Check if Increment should be +ve or -ve subwf Current+offset,W btfss STATUS,C bsf DataStatus+offset,Dir ;Target > Current, Increment is +ve bcf DataStatus+offset,New ;New data processed succesfully endm ;--------------------------------------------------------------------- ; Update - Updates an address: ; Checks for new data ; Checks for a timeout ; Updates values. ;--------------------------------------------------------------------- Update macro offset banksel Count btfsc DataStatus+offset,New ;New data available? goto DoCalcInc#v(offset) ;yes movlw TIMEOUT ;Timed out? subwf Count+offset,W btfss STATUS,C goto DoIncrement#v(offset) ;no clrf Current+offset ;Clean up some stuff if timed out clrf CurrentBits+offset clrf Target+offset clrf Increment+offset bsf DataStatus+offset,TO ;Set timeout flag goto End#v(offset) DoCalcInc#v(offset): bcf DataStatus+offset,TO ;Clear timeout flag CalcInc offset DoIncrement#v(offset): incf Count+offset,F DoIncrement offset End#v(offset): endm ;--------------------------------------------------------------------- ; Reinit - Initialize all display-related variables ;--------------------------------------------------------------------- Reinit macro offset banksel Current clrf Current+offset clrf Target+offset clrf Increment+offset clrf Periods+offset clrf Count+offset clrf DataStatus+offset clrf CurrentBits+offset endm ;--------------------------------------------------------------------- ; Vectors ;--------------------------------------------------------------------- STARTUP code nop goto Startup nop ; 0x0002 nop ; 0x0003 goto ISR ; 0x0004 PROG code ;--------------------------------------------------------------------- ; Main Code ;--------------------------------------------------------------------- Startup: call Setup Main: movf CurrentBits,W ;Load graph 0 movwf PORTC clrf PORTA ;Turn on graph 0 movlw 0xB0 ;Delay, to make time from setting PortA call Delay ;to setting PortC relatively small. comf CurrentBits+1,W ;Load graph 1 movwf PORTC comf PORTA,F ;Turn on graph 1 movlw 0xFF ;Slightly longer delay, to make orange call Delay ;LED brighter btfss INTCON,T0IF ;Loop until timer overflows goto Main bcf INTCON, T0IF ;Begin update cycle Update 0 Update 1 banksel PORTB bcf PORTB,5 movf DataStatus,W ;Test to see if either address has timed out iorwf DataStatus+1,W movwf Temp btfsc Temp,TO bsf PORTB,5 ;One or both have timed out, set error LED movf DataStatus,W ;Test to see if both addresses are timed out andwf DataStatus+1,W movwf Temp btfss Temp,TO goto Main ;no, go back to looping clrf PORTC ;Both timed out, may as well turn off clrf PORTA ;graphs and sleep until something sleep ;interesting happens. goto Main ;Loop forever. ;--------------------------------------------------------------------- ; Interrupt Code ;--------------------------------------------------------------------- ISR movwf WREGsave ; Save WREG movf STATUS,W ; Get STATUS register banksel STATUSsave ; Switch banks, if needed. movwf STATUSsave ; Save the STATUS register movf PCLATH,W ; movwf PCLATHsave ; Save PCLATH banksel PIR1 btfss PIR1,SSPIF ; Is this a SSP interrupt? goto $ ; No, just trap here. bcf PIR1,SSPIF call SSP_Handler ; Yes, service SSP interrupt. banksel PCLATHsave movf PCLATHsave,W movwf PCLATH ; Restore PCLATH movf STATUSsave,W; movwf STATUS ; Restore STATUS swapf WREGsave,F ; swapf WREGsave,W ; Restore WREG retfie ;--------------------------------------------------------------------- ; Setup ; Initializes program variables and peripheral registers. ;--------------------------------------------------------------------- Setup: banksel PCON bsf PCON,NOT_POR bsf PCON,NOT_BOR banksel ANSELH ;No need for analogue inputs clrf ANSELH clrf ANSEL clrf PORTA clrf PORTB clrf PORTC clrf PIR1 banksel TRISB bcf TRISA,4 ;Two outputs for LED reference voltage bcf TRISA,5 bsf TRISB,4 ;SCL bcf TRISB,5 ;Error LED bsf TRISB,6 ;SDA clrf TRISC ;PortC is main LED output clrwdt ;Clear WDT and prescaler banksel OPTION_REG movlw B'00000111' ;Set TMR0 prescale to 1:16 and movwf OPTION_REG ;turn on weak pull-ups ;--------------------------------------------------------------------- ;Work around for first I2C start-bit being ignored, as per rev. A errata ;Set SSPMSK at the same time banksel SSPCON movlw B'00111001' ;Module enable, clock movwf SSPCON ;enable, SSPMSK access banksel SSPMSK movlw B'11111101' ;Respond to two addresses movwf SSPMSK banksel SSPCON movlw B'00110110' ;Module enable, clock movwf SSPCON ;enable, 7-bit address I2C slave. ;--------------------------------------------------------------------- movlw 0x00 banksel PORTA ;Read ID bits. btfsc PORTA,2 ;Bits are in reverse order on the port iorlw b'00000100' ;Shift them up two places, past R/W bit btfsc PORTA,1 ;and the masked address bit iorlw b'00001000' btfsc PORTA,0 iorlw b'00010000' addlw NODE_ADDR ;Add minimum allowed I2C address banksel SSPADD movwf SSPADD ;Set I2C address clrf SSPSTAT banksel PIE1 ;Enable interrupts bsf PIE1,SSPIE bsf INTCON,PEIE ;Enable all peripheral interrupts bsf INTCON,GIE ;Enable global interrupts Reinit 0x0 Reinit 0x1 return ;--------------------------------------------------------------------- ; SSP_Handler ;--------------------------------------------------------------------- ; The I2C code below checks for 5 states: ;--------------------------------------------------------------------- ; State 1: I2C write operation, last byte was an address byte. ; ; SSPSTAT bits: S = 1, D_A = 0, R_W = 0, BF = 1 ; ; State 2: I2C write operation, last byte was a data byte. ; ; SSPSTAT bits: S = 1, D_A = 1, R_W = 0, BF = 1 ; ; State 3: I2C read operation, last byte was an address byte. ; ; SSPSTAT bits: S = 1, D_A = 0, R_W = 1, BF = 0 ; ; State 4: I2C read operation, last byte was a data byte. ; ; SSPSTAT bits: S = 1, D_A = 1, R_W = 1, BF = 0 ; ; State 5: Slave I2C logic reset by NACK from master. ; ; SSPSTAT bits: S = 1, D_A = 1, R_W = 0, BF = 0 ; ; For convenience, WriteI2C and ReadI2C functions have been used. ;---------------------------------------------------------------------- SSP_Handler: banksel SSPSTAT ;btfss SSPSTAT,S ;Commented out due to ;goto I2CErr ;start bit not working btfsc SSPSTAT,R_W goto I2CReadCheck I2CWriteCheck: ;Write operation (R_W = 0) banksel SSPSTAT btfss SSPSTAT,BF goto I2CNACKCheck btfss SSPSTAT,D_A goto State1 ;Last byte was address (D_A = 0) goto State2 ;Last byte was data (D_A = 1) I2CNACKCheck: btfsc SSPSTAT,D_A goto State5 ;NACK received goto I2CErr I2CReadCheck: ;Read operation (R_W = 1) btfsc SSPSTAT,BF goto I2CErr btfss SSPSTAT,D_A goto State3 ;Last byte was address (D_A = 0) goto State4 ;Last byte was data (D_A = 1) State1: ;Write operation, last byte was an address, buffer is full. call ReadI2C ;Store address for future reference movwf LastAddress return State2: ;Write operation, last byte was data, buffer is full. call ReadI2C ;Get the byte from the SSP. btfsc LastAddress, 1 ;See which address this byte is for goto NewDataFor1 NewDataFor0: banksel NewData movwf NewData ;Make new byte available bsf DataStatus,New ;Notify of new data availabilty return NewDataFor1: banksel NewData movwf NewData+1 ;Make new byte available bsf DataStatus+1,New ;Notify of new data availabilty return State3: ;Read operation, last byte was an address, buffer is empty. movlw 0x00 btfsc LastAddress, 1 ;See which address this byte is for movlw 0x80 ;Address1 starts at offset 0x80 banksel EEADR movwf EEADR banksel EECON1 bsf EECON1,RD ;Do a read banksel EEDAT movf EEDAT,W call WriteI2C ;Write the byte to SSPBUF return State4: ;Read operation, last byte was data, buffer is empty. banksel EEADR incf EEADR,F ;Next EEPROM byte banksel EECON1 bsf EECON1,RD ;Read byte banksel EEDAT movf EEDAT,W call WriteI2C ;Write to SSPBUF return State5: ;A NACK was received when transmitting. return I2CErr: ;Do nothing. return ;--------------------------------------------------------------------- ; WriteI2C ;--------------------------------------------------------------------- WriteI2C: banksel SSPSTAT btfsc SSPSTAT,BF ; Is the buffer full? goto WriteI2C ; Yes, keep waiting. banksel SSPCON ; No, continue. DoI2CWrite: bcf SSPCON,WCOL ; Clear the WCOL flag. movwf SSPBUF ; Write the byte in WREG btfsc SSPCON,WCOL ; Was there a write collision? goto DoI2CWrite bsf SSPCON,CKP ; Release the clock. return ;--------------------------------------------------------------------- ; ReadI2C ;--------------------------------------------------------------------- ReadI2C: banksel SSPBUF movf SSPBUF,W ; Get the byte and put in WREG return ;--------------------------------------------------------------------- ; Delay - Delays by W * 3 + 1 cycles ;--------------------------------------------------------------------- Delay: movwf Temp decfsz Temp,F goto $-1 ;--------------------------------------------------------------------- ; Div8 - 8bit integer division. Divides Dividend by Divisor and puts ; the result in W. Truncates result. ; The result is never allowed to be less than one (prevents display ; from becoming stuck if incoming data varies only slightly). ; The result is zero if divisor is zero. ;--------------------------------------------------------------------- Div8: clrf DivResult movf Divisor,W SubLoop: subwf Dividend,F ;Simply keep subtracting until result < 0 btfss STATUS,C goto DivDone incf DivResult,F ;Keep track of number of subtractions goto SubLoop ;Go around once more... DivDone: movf DivResult,W btfsc STATUS, Z ;Check if result is zero retlw 0x01 ;Never return less than zero return ;--------------------------------------------------------------------- ; Diff - Calculates the magnitude of Diff1 - Diff2 ;--------------------------------------------------------------------- Diff: movf Diff1,W subwf Diff2,W btfsc STATUS,C ;Result -ve? return movf Diff2,W ;Yes. Subtract the other way. subwf Diff1,W return ;--------------------------------------------------------------------- ; NumToBits - Turns an 8bit number in W into an 8bit bitmask ; Horribly massive table, but might as well fill up all that RAM! ;--------------------------------------------------------------------- NumToBits: movwf Temp movlw LOW Table ;get low 8 bits of address addwf Temp,F ;do an 8-bit add operation movlw HIGH Table ;get high 5 bits of address btfsc STATUS,C ;page crossed? addlw 0x1 ;yes then increment high address movwf PCLATH ;load high address in latch movf Temp,W ;load computed offset in w reg movwf PCL ;Table offset Table: retlw 0x00 ;0 bits set retlw 0x00 retlw 0x00 retlw 0x00 retlw 0x00 retlw 0x00 retlw 0x00 retlw 0x00 retlw 0x00 ;0 bits set retlw 0x00 retlw 0x00 retlw 0x00 retlw 0x00 retlw 0x00 retlw 0x00 retlw 0x00 retlw 0x01 ;1 bit set retlw 0x01 retlw 0x01 retlw 0x01 retlw 0x01 retlw 0x01 retlw 0x01 retlw 0x01 retlw 0x01 ;1 bit set retlw 0x01 retlw 0x01 retlw 0x01 retlw 0x01 retlw 0x01 retlw 0x01 retlw 0x01 retlw 0x01 ;1 bit set retlw 0x01 retlw 0x01 retlw 0x01 retlw 0x01 retlw 0x01 retlw 0x01 retlw 0x01 retlw 0x01 ;1 bit set retlw 0x01 retlw 0x01 retlw 0x01 retlw 0x01 retlw 0x01 retlw 0x01 retlw 0x01 retlw 0x03 ;2 bits set retlw 0x03 retlw 0x03 retlw 0x03 retlw 0x03 retlw 0x03 retlw 0x03 retlw 0x03 retlw 0x03 ;2 bits set retlw 0x03 retlw 0x03 retlw 0x03 retlw 0x03 retlw 0x03 retlw 0x03 retlw 0x03 retlw 0x03 ;2 bits set retlw 0x03 retlw 0x03 retlw 0x03 retlw 0x03 retlw 0x03 retlw 0x03 retlw 0x03 retlw 0x03 ;2 bits set retlw 0x03 retlw 0x03 retlw 0x03 retlw 0x03 retlw 0x03 retlw 0x03 retlw 0x03 retlw 0x07 ;3 bits set retlw 0x07 retlw 0x07 retlw 0x07 retlw 0x07 retlw 0x07 retlw 0x07 retlw 0x07 retlw 0x07 ;3 bits set retlw 0x07 retlw 0x07 retlw 0x07 retlw 0x07 retlw 0x07 retlw 0x07 retlw 0x07 retlw 0x07 ;3 bits set retlw 0x07 retlw 0x07 retlw 0x07 retlw 0x07 retlw 0x07 retlw 0x07 retlw 0x07 retlw 0x07 ;3 bits set retlw 0x07 retlw 0x07 retlw 0x07 retlw 0x07 retlw 0x07 retlw 0x07 retlw 0x07 retlw 0x0F ;4 bits set retlw 0x0F retlw 0x0F retlw 0x0F retlw 0x0F retlw 0x0F retlw 0x0F retlw 0x0F retlw 0x0F ;4 bits set retlw 0x0F retlw 0x0F retlw 0x0F retlw 0x0F retlw 0x0F retlw 0x0F retlw 0x0F retlw 0x0F ;4 bits set retlw 0x0F retlw 0x0F retlw 0x0F retlw 0x0F retlw 0x0F retlw 0x0F retlw 0x0F retlw 0x0F ;4 bits set retlw 0x0F retlw 0x0F retlw 0x0F retlw 0x0F retlw 0x0F retlw 0x0F retlw 0x0F retlw 0x1F ;5 bits set retlw 0x1F retlw 0x1F retlw 0x1F retlw 0x1F retlw 0x1F retlw 0x1F retlw 0x1F retlw 0x1F ;5 bits set retlw 0x1F retlw 0x1F retlw 0x1F retlw 0x1F retlw 0x1F retlw 0x1F retlw 0x1F retlw 0x1F ;5 bits set retlw 0x1F retlw 0x1F retlw 0x1F retlw 0x1F retlw 0x1F retlw 0x1F retlw 0x1F retlw 0x1F ;5 bits set retlw 0x1F retlw 0x1F retlw 0x1F retlw 0x1F retlw 0x1F retlw 0x1F retlw 0x1F retlw 0x3F ;6 bits set retlw 0x3F retlw 0x3F retlw 0x3F retlw 0x3F retlw 0x3F retlw 0x3F retlw 0x3F retlw 0x3F ;6 bits set retlw 0x3F retlw 0x3F retlw 0x3F retlw 0x3F retlw 0x3F retlw 0x3F retlw 0x3F retlw 0x3F ;6 bits set retlw 0x3F retlw 0x3F retlw 0x3F retlw 0x3F retlw 0x3F retlw 0x3F retlw 0x3F retlw 0x3F ;6 bits set retlw 0x3F retlw 0x3F retlw 0x3F retlw 0x3F retlw 0x3F retlw 0x3F retlw 0x3F retlw 0x7F ;7 bits set retlw 0x7F retlw 0x7F retlw 0x7F retlw 0x7F retlw 0x7F retlw 0x7F retlw 0x7F retlw 0x7F ;7 bits set retlw 0x7F retlw 0x7F retlw 0x7F retlw 0x7F retlw 0x7F retlw 0x7F retlw 0x7F retlw 0x7F ;7 bits set retlw 0x7F retlw 0x7F retlw 0x7F retlw 0x7F retlw 0x7F retlw 0x7F retlw 0x7F retlw 0x7F ;7 bits set retlw 0x7F retlw 0x7F retlw 0x7F retlw 0x7F retlw 0x7F retlw 0x7F retlw 0x7F retlw 0xFF ;8 bits set retlw 0xFF retlw 0xFF retlw 0xFF retlw 0xFF retlw 0xFF retlw 0xFF retlw 0xFF retlw 0xFF ;8 bits set retlw 0xFF retlw 0xFF retlw 0xFF retlw 0xFF retlw 0xFF retlw 0xFF retlw 0xFF ;Initialize eeprom. First half is for Address0, second half for Address1. EE CODE 0x2100 DE "Green LED bar graph" CODE 0x2180 DE "Orange LED bar graph" end