;7-segment LED driver with daisy chained 2-wire serial data ;communications and pulse-width modulation (PWM) dimming. ; ;Copyright (c) 1999 Kendrick Lamont Webster (ken@webster.org, ;http://ken.webster.org). ; ; ;Target processor: ; ;PIC16C505 ;1024 x 12 EPROM ;72 x 8 RAM ;Internal RC 4MHz oscillator ;Internal Reset on power-up ; ; ;Pin assignments: ; ;Function Port Bit Pin ;---------------------------------------------------- ;Input serial port clock B 3 4 ;Input serial port data C 2 8 ;Output serial port clock B 5 2 ;Output serial port data B 4 3 ;LED segment 'a' B 1 12 ;LED segment 'b' B 0 13 ;LED segment 'c' C 5 5 ;LED segment 'd' C 4 6 ;LED segment 'e' C 3 7 ;LED segment 'f' C 0 10 ;LED segment 'g' C 1 9 ;LED decimal point B 2 11 ; ; ;All LED drivers are active high (lit when output is high) ;Segment diagram: ; ; ; aaaaaaaaaaaaaaaa ; f b ; f b ; f b ; f b ; f b ; f b ; f b ; f b ; ggggggggggggggggg ; e c ; e c ; e c ; e c ; e c ; e c ; e c ; e c ; ddddddddddddddddd ; ; ; ;------------------------------------------------------------------------ ;Serial data communications protocol: ; ;The serial port is synchronous. Data is sampled shortly after the ;high-to-low transition of the clock signal. A special start condition ;is defined as a high-to-low transition on the data line when the clock ;is low and has been low for a sufficient amount of time. This start ;condition is used to mark the beginning of a data frame. ; ;Normal data bit: ;(X = don't care) ; ; --------------------- ----------- ; | | | ; Clk | | | ; | | | ; ---------- -------------------- ; ; | t[ch] | t[cl] | ; ; | t[cycle] | ; ; ---------- --------------------- -------------------- ----------- ; |XXXXXXXXXXXXXXXXXXXXX| |XXXXXXXXXXX ; Data |XXXXXXXXXXXXXXXXXXXXX| valid databit |XXXXXXXXXXX ; |XXXXXXXXXXXXXXXXXXXXX| |XXXXXXXXXXX ; ---------- --------------------- -------------------- ----------- ; ;The data line is sampled as early as 4 microseconds and as late as 29 ;microseconds after the high-to-low clock transition. The exact delay ;depends on the state of the software at the time of the transition. ; ;Received t[cl] must thus be greater than 29 microseconds in order to ;guarantee proper reception. Due to the downstream accumulation of clock ;jitter caused by the variable delay between a clock edge and a given ;controller detecting the edge, the t[cl] as transmitted by the master ;controller would need to be n * 29 microseconds (where n is the number ;of chained controllers) in order to guarantee operation under worst-case ;conditions (all of the controllers in the chain are in the software ;state at which the maximum delay occurs on one half cycle and minimum ;delay occurs on the opposite half cycle). In actual operation, this ;situation is extremely unlikely since each microcontroller is acting ;independently and the delays will tend to average out and be distributed ;evenly between the high and low clock phases. ; ;A shorter t[cl] may therefore be used if the possibility of an ;occasional miscommunication is acceptable. In a display such as this, ;such a miscommunication would result only in a brief (probably ;imperceptable to humans) flicker. ; ;Therefore, a transmitted t[cl] of 100 microseconds should be more than ;sufficient. The t[ch] requirements are slightly more relaxed (since the ;data bit is not being sampled) but should be aproximately the same ;duration since clock jitter accumulates here too. A minimum t[cycle] of ;200 microseconds with 50% duty is thus recommended. This cycle time ;would allow a 10-digit display to be refreshed 60 times per second ;(or refreshed with brightness information included at a rate of 30 ;updates per second). ; ; ; ;Start condition: ;(X = don't care) ; ; --------- -------- ; | | ; Clk | | ; | | ; ----------------------------------------------- ; ; | t[cldl] | t[dlch] | ; ; --------- ----------------------- -------- ; XXXXXXXXX| | |XXXXXXXX ; Data XXXXXXXXX| | |XXXXXXXX ; XXXXXXXXX| | |XXXXXXXX ; --------- ----------------------- -------- ; ;The t[cldl] and t[dlch] here should be at least 100 microseconds also due ;to the same reasons given (above) for choosing 100 microseconds as the ;minimum recommended clock low and clock high times. ; ; ; ; ; ;------------------------------------------------------------------------ ;Communications protocol ;Data frame format ; ;All data is sent with the least significant bit first. The first data ;bit (following the start condition) specifies the frame type: ; ; Type bit Frame contents (described below) ; --------------------------------------------- ; 0 Segment patterns ; 1 Brightness levels ; ; ; ;Data following the type bit is grouped into 8-bit bytes and organized ;into the following types of frames (as specified by the type bit): ; ; ;Segment patterns ;---------------- ;This frame consists of n data bytes where n is the number of chained ;controllers. One byte is targetted for each controller in the chain. ;Each controller accepts the first byte in the frame for its own use, ;removes it from the frame, and passes all subsequent bytes downstream. ; ;Removal of the first byte is accomplished by supressing the clock output ;(holding it in a high state) while receiving the byte. Input data and ;clock signals are echoed to the output port at all other times. ; ;The data byte each controller receives specifies the pattern to light up ;on the LED display. Bit 0 corresponds to segment 'a', bit 1 to segment ;'b', ... bit 7 corresponds to the decimal point. For each bit, a logic ;1 specifies that the segment is lit. ; ; ;Brightness levels ;----------------- ;This frame follows the same chaining scheme as the segment patterns ;(with the target controller removing its own data byte and passing the ;remainder of the frame downstream). ; ;The byte a controller receives specifies a PWM brightness level where ;the value 0 corresponds to an almost doused (barely lit .. a 1/256 duty ;cycle) display and the value 255 corresponds to the maximum brightness ;level (100% duty cycle). ;------------------------------------------------------------------------ LIST p=16C505, r=dec INCLUDE P16C505.INC ;Application-specific general-purpose register useage: CBLOCK 0x08 APP_FLAGS ;Application-specific flags .. bits are defined below RX_STATE ;Serial data receive state-machine state PORTB_MIRROR ;Mirror for PORTB outputs for BSF, BCF, etc. PORTC_MIRROR ;Ditto for PORTC PORTB_MASK ;Mask for PORTB for PWM dimming PORTC_MASK ;Ditto for PORTC BRIGHTNESS ;<0..255> corresponds to BRIGHT1 ;buffer for receiving the bits of a new BRIGHTNESS PWM_COUNT ;counter for PWM brightness control ENDC ;Bits within the APP_FLAGS register: CBLOCK 0 TX_ECHO ;True if received data is being echoed DIMMING_PHASE ;True if LED is lit, false if doused EXIT_BITLOOP ;Set after last databit for this controller has been received ENDC ;Bits within the PORTB_MIRROR register: CBLOCK 0 PB_SEG_B PB_SEG_A PB_SEG_DP PB_INPUT_CLK PB_DATA PB_CLK ENDC ;Bits within the PORTC_MIRROR register: CBLOCK 0 PC_SEG_F PC_SEG_G PC_INPUT_DATA PC_SEG_E PC_SEG_D PC_SEG_C ENDC ;Bit masks for the LED driver outputs for PWM dimming ;(ANDed with the mirror registers for dousing the display) PB_OFF_MASK EQU (1 << PB_DATA) | (1 << PB_CLK) PC_OFF_MASK EQU 0 ;--------------------------------------------------------------- ;Start of code ORG 0 ;Reset vector ;Use the internal RC oscillator calibration factor MOVWF OSCCAL ;Watchdog timer and pre(post)scaler must be cleared prior to changing ;prescaler assignment (according to Microchip data sheet) CLRWDT ;Assign timer 0 to internal so TOCKI/RC5 pin is RC5. ;Also, assign prescaler to Timer0 and do divide by 256 ;Also disable pullups and wakeup on pin change for PortB. MOVLW 0xd7 OPTION ;Configure RB3 as input, the rest of PortB as output MOVLW (1 << PB_INPUT_CLK) TRIS PORTB ;Configure RC2 as input, the rest of PortC as output MOVLW (1 << PC_INPUT_DATA) TRIS PORTC ;Initialize application-specific registers CLRF APP_FLAGS MOVLW 0xff MOVWF PORTB_MIRROR ;initialize CLK output to high MOVWF PORTC_MIRROR ;and light up all segments MOVLW 0x40 MOVWF BRIGHTNESS ;at 1/4 brightness ; CLRF RX_STATE (implicit .. RX_STATE is cleared ;and the transmit echo is enabled when a start condition occurs) ;------------------------------------------------------ ;Main loop ;Receives input, sends output, and interprets commands ;Wait for a start condition on the data input port. ;A start condition is defined as a high-to-low transition ;on the Data input (after the sampling point and after ;enough time has elapsed for the slowest controller in ;the chain to notice the clock transition) while the ;clock input is low. WAIT_FOR_START ;Receive clock input transitioned to (or is in) the high state ;(if not, that is ok since the clock state is tested right away ;and a transition is made to WFS phase 2 if the clock is low) WFS_ENTER_PHASE1 BSF PORTB_MIRROR,PB_CLK ;Wait for the clock input to go low ... ; ;Nominal loop timing is (6 + 15 or 16) = 21 or 22 CPU clocks ;Worst-case loop timing is (6 + 21) = 27 CPU clocks ;Nominal control transfer occurs 10.5us after the clock edge WFS_PHASE1 BTFSS PORTB,PB_INPUT_CLK GOTO WFS_ENTER_PHASE2 CALL PWM GOTO WFS_PHASE1 ;Receive clock input is in the low state ... WFS_ENTER_PHASE2 BCF PORTB_MIRROR,PB_CLK ;Receive clock is low and data is low or unknown ;It is ok to enter PHASE2B without checking the state of the data line ;since it will sample the data line before echoing the clock transition ;and data state to the output port (and since it does not check for ;the start condition -- it would be wrong to enter PHASE2A without first ;checking that the data line is high because doing so would cause a ;false start if the data line was low). ; ;Nominal loop timing is (8 + 15 or 16) = 23 or 24 CPU clocks ;Worst-case loop timing is (8 + 21) = 29 CPU clocks ;Nominal control transfer occurs 11.5us after the clock edge WFS_ENTER_PHASE2B BCF PORTB_MIRROR,PB_DATA WFS_PHASE2B BTFSC PORTB,PB_INPUT_CLK GOTO WFS_ENTER_PHASE1 BTFSC PORTC,PC_INPUT_DATA GOTO WFS_ENTER_PHASE2A CALL PWM GOTO WFS_PHASE2B ;Receive clock is low and data is high ;Check for the start condition (the data line transitions ;to the low state while the clock is low) WFS_ENTER_PHASE2A BSF PORTB_MIRROR,PB_DATA WFS_PHASE2A BTFSC PORTB,PB_INPUT_CLK GOTO WFS_ENTER_PHASE1 BTFSS PORTC,PC_INPUT_DATA GOTO WFS_ENTER_PHASE2C ;Start condition CALL PWM GOTO WFS_PHASE2A ;A start condition has been received ;Wait for the input clock to go high, then enter receive phase 1 WFS_ENTER_PHASE2C BCF PORTB_MIRROR,PB_DATA WFS_PHASE2C CALL PWM BTFSS PORTB,PB_INPUT_CLK GOTO WFS_PHASE2C CLRF RX_STATE BSF APP_FLAGS,TX_ECHO BCF APP_FLAGS,EXIT_BITLOOP ;Fallthru to receive data loop ;-------------- ;Receiving data ;Clock input transitioned to the high state RX_ENTER_PHASE1 BSF PORTB_MIRROR,PB_CLK BTFSC APP_FLAGS,EXIT_BITLOOP GOTO WAIT_FOR_START ;Receiving data ;Clock input is high ... wait for it to go low RX_PHASE1 CALL PWM BTFSC PORTB,PB_INPUT_CLK GOTO RX_PHASE1 ;Fall thru to RX_ENTER_PHASE2 ;Clock input transitioned to the low state ;The transition is only echoed to the output port if it did not ;occur durring the 8 bits of data targetted for this controller RX_ENTER_PHASE2 BTFSC APP_FLAGS,TX_ECHO BCF PORTB_MIRROR,PB_CLK ;Sample the data line ... ;note that the sampling point is at least 4 microseconds after the clock edge, ;thus, sending data trasitions at the same time as the high-to-low clock ;transition is fine BCF PORTB_MIRROR,PB_DATA BTFSC PORTC,PC_INPUT_DATA BSF PORTB_MIRROR,PB_DATA CALL PWM ;echo the transition ASAP ;Handle a received data bit ;The state handler will set APP_FLAGS,EXIT_BITLOOP after the data targetted ;for this controller has been received (then the remainder of the frame ;is echoed by the WAIT_FOR_START loop) CALL RX_STATE_DISPATCH INCF RX_STATE,F ;Receiving data ;Clock input is low ... wait for it to go high RX_PHASE2 CALL PWM BTFSC PORTB,PB_INPUT_CLK GOTO RX_ENTER_PHASE1 GOTO RX_PHASE2 ;--------------------------------------------------------------- ;Received data bit state machine dispatch table ... ;the appropriate handler for the current RX_STATE is branched to ;through a lookup table. RX_STATE_DISPATCH MOVF RX_STATE,W ADDWF PCL,F GOTO RX_0 ;The frame type bit GOTO RX_1 ;Segment pattern bits GOTO RX_2 GOTO RX_3 GOTO RX_4 GOTO RX_5 GOTO RX_6 GOTO RX_7 GOTO RX_8 GOTO RX_9 ;Brightness bits GOTO RX_10 GOTO RX_11 GOTO RX_12 GOTO RX_13 GOTO RX_14 GOTO RX_15 GOTO RX_16 ;Frame type bit (0 = segment pattern, 1 = brightness) RX_0 BCF APP_FLAGS,TX_ECHO ;don't echo the upcomming byte MOVLW 8 BTFSC PORTB_MIRROR,PB_DATA MOVWF RX_STATE ;next state = 9 if receiving brightness RETURN ;or 1 if receiving a segment pattern ;Function Port Bit Pin ;---------------------------------------------------- ;LED segment 'a' B 1 12 ;LED segment 'b' B 0 13 ;LED segment 'c' C 5 5 ;LED segment 'd' C 4 6 ;LED segment 'e' C 3 7 ;LED segment 'f' C 0 10 ;LED segment 'g' C 1 9 ;LED decimal point B 2 11 RX_1 BCF PORTB_MIRROR,1 ;Seg 'a' BTFSC PORTB_MIRROR,PB_DATA BSF PORTB_MIRROR,1 RETURN RX_2 BCF PORTB_MIRROR,0 ;Seg 'b' BTFSC PORTB_MIRROR,PB_DATA BSF PORTB_MIRROR,0 RETURN RX_3 BCF PORTC_MIRROR,5 ;Seg 'c' BTFSC PORTB_MIRROR,PB_DATA BSF PORTC_MIRROR,5 RETURN RX_4 BCF PORTC_MIRROR,4 ;Seg 'd' BTFSC PORTB_MIRROR,PB_DATA BSF PORTC_MIRROR,4 RETURN RX_5 BCF PORTC_MIRROR,3 ;Seg 'e' BTFSC PORTB_MIRROR,PB_DATA BSF PORTC_MIRROR,3 RETURN RX_6 BCF PORTC_MIRROR,0 ;Seg 'f' BTFSC PORTB_MIRROR,PB_DATA BSF PORTC_MIRROR,0 RETURN RX_7 BCF PORTC_MIRROR,1 ;Seg 'g' BTFSC PORTB_MIRROR,PB_DATA BSF PORTC_MIRROR,1 RETURN RX_8 BCF PORTB_MIRROR,2 ;Decimal point BTFSC PORTB_MIRROR,PB_DATA BSF PORTB_MIRROR,2 BSF APP_FLAGS,EXIT_BITLOOP RETURN ;The brightness level byte .. all bits handled the same way ;except the last one RX_9 RX_10 RX_11 RX_12 RX_13 RX_14 RX_15 BCF STATUS,C BTFSC PORTB_MIRROR,PB_DATA BSF STATUS,C RRF BRIGHT1,F RETURN RX_16 BCF STATUS,C BTFSC PORTB_MIRROR,PB_DATA BSF STATUS,C RRF BRIGHT1,W MOVWF BRIGHTNESS BSF APP_FLAGS,EXIT_BITLOOP RETURN ;---------------------------------------------------------- ;PWM dimming and port transfer function ... ;transfers PORTB and PORTC mirrors to the actual ports and ;masks the LED driver bits based on the PWM dimming cycle. ; ;This subroutine normally returns in 15 or 16 clock cycles. ;It takes a maximum of 21 clock cycles to complete twice ;per PWM interval. PWM MOVF PORTB_MIRROR,W ;PORTB first so serial port output transitions ANDWF PORTB_MASK,W ;lag input transitions as little as possible MOVWF PORTB ;(less clk jitter accumulates downstream) MOVF PORTC_MIRROR,W ANDWF PORTC_MASK,W MOVWF PORTC CLRWDT ;this is the only CLRWDT in the main loop INCF PWM_COUNT,F MOVF PWM_COUNT,W SUBWF BRIGHTNESS,W BTFSS APP_FLAGS,DIMMING_PHASE GOTO DIM_PHASE0 ;12 CPU cycles to here ;LED is lit .. douse it if (TMR0 > BRIGHTNESS) DIM_PHASE1 BTFSC STATUS,C RETURN ;15 CPU cycles to caller MOVLW PB_OFF_MASK MOVWF PORTB_MASK MOVLW PC_OFF_MASK MOVWF PORTC_MASK BCF APP_FLAGS,DIMMING_PHASE RETURN ;21 CPU cycles to caller ;13 CPU cycles to here ;LED is doused .. light it if (TMR0 <= BRIGHTNESS) DIM_PHASE0 BTFSS STATUS,C RETURN ;16 CPU cycles to caller MOVLW 0xFF MOVWF PORTB_MASK MOVWF PORTC_MASK BSF APP_FLAGS,DIMMING_PHASE RETURN ;21 CPU cycles to caller END