title "4-digit Serial Nixie Clock For Leigh" ;#define Debug ; ; The clock uses a watch tuningfork crystal that ; clocks at 32kHz along with an RTC chip from TI. ; ; Hardware Notes: ; This application runs on a 16F87 executing at 4 MHz with the internal osc. ; _MCLR is tied through a 10K Resistor to Vcc and PWRT is Enabled. ; Port B is used as a general purpose data output bus. The data are ; sent out in 32-bit chunks, made up of 4 8-bit registers. Separate ; functions build the data block so that multiplexing is done in ; software as opposed to hardware. Only two nixies' cathodes are tied ; low at a time in this manner... Time from the RTC comes in BCD format, so I will ; be converting BCD to decimal in EEPROM this time as usual. ; LIST R=DEC, P=16F87 #INCLUDE "p16f87.inc" LIST CLK EQU 7; DAT EQU 6; OE EQU 5; SCL EQU 4; SDA EQU 1; ; Registers CBLOCK 0x020 ;;registers! _w, _status, _pclath, _fsr ; Context Register Save Values ByteA ; ByteB ; ByteC ; ByteD ; ; second ; correct_hours; correct_minutes; minutes_high; minutes_low; minutes ; hours_high; hours_low; hours ; minutes_old; hours_old; PWM ; ;bit 5 = spin flag ;bit 0 = PWM hi-low state ;bit 1 = time update flag PWMOn ; brigntness; A_or_B; keys ; temp_w; Data_in; Data_out; i2c_count; serial_count; display_out; show_timer; which_tube; spinner; spin_timer; spin_timer2; slave; slave_temp; data_container; wr_address; re_address; msd_temp lsd_temp; ENDC PAGE ;Program Configuration Register 1 __CONFIG _CONFIG1, _CP_OFF & _CCP1_RB0 & _DEBUG_OFF & _WRT_PROTECT_OFF & _CPD_OFF & _LVP_OFF & _BODEN_OFF & _MCLR_OFF & _PWRTE_OFF & _WDT_OFF & _INTRC_CLKOUT ;Program Configuration Register 2 __CONFIG _CONFIG2, _IESO_OFF & _FCMEN_OFF ; Mainline of Timer org 0x0 ;reset vector goto Mainline org 0x4 ;interrupt vector goto Int org 0x10 Mainline movlw 0x4F; ; Register initialization movwf PWMOn; movlw 0xFF; movwf ByteA; movwf A_or_B; movwf ByteB; movwf ByteC; movwf ByteD; movwf minutes movwf hours movwf data_container; movwf hours_low; movwf hours_high; movwf minutes_low; movwf minutes_high; movwf Data_in; movwf Data_out; movwf temp_w; movwf lsd_temp; movwf msd_temp; movwf serial_count; movlw b'01010101' movwf display_out; movlw 0x01; movwf spinner; movwf spin_timer; movwf spin_timer2; movwf minutes_old; movwf hours_old; movwf PWM; ;Special register initialization clrf Data_in; clrf Data_out; bsf STATUS, RP0; movlw 0x11; movwf TRISB; movlw 0x1F; movwf TRISA ; PORTA is input movlw 0x0C5 ; 0x0C0 - Minimum Timer Delay ; movlw 0x0C4 ; 0x0C0 - Minimum Timer Delay ; movlw 0xC0; movwf OPTION_REG movlw 0x070 movwf OSCCON clrf STATUS ;bank 0 CLRF SSPSTAT ; SMP = 0, CKE = 0, and clear status bits MOVLW 0x00 ; Set up SPI port, Master mode, CLK/16, MOVWF SSPCON ; Data xmit on falling edge (CKE=1 & CKP=1) bcf STATUS, RP0 ; initialize the RTC chip ; should be like 1 or 2 writes. ; might also set the starting ; time to something specific ; so another 2 writes setup_script movlw 0xF0; movwf temp_w; call Delay; movlw 0xD0; movwf slave; movlw 0x0C; ;halt time movwf wr_address; movlw 0x00; movwf data_container; movlw 0xF0; movwf temp_w; call Delay; call write_a_byte; movlw 0x0C; movwf re_address; call read_a_byte; movfw Data_in; btfsc Data_in, 6; goto setup_script; nop; nop; movlw 0xD0; movwf slave; movlw 0x02; minutes movwf re_address; call read_a_byte; movfw Data_in; movwf correct_minutes; movwf minutes_old; movlw 0x03; hours movwf re_address; call read_a_byte; movfw Data_in; movwf correct_hours; movwf hours_old; nop; nop; movlw 0x01 ;control, seconds movwf re_address; call read_a_byte; movfw Data_in; btfsc Data_in, 7; goto set_start; goto Enable_Interrupts; set_start ;also set time regs movlw 0x01; movwf wr_address; movlw 0x00 ;start oscillator, fool! seconds = 30; movwf data_container; movlw 0x0F; movwf temp_w; call Delay; call write_a_byte; movlw 0x03; ;hours movwf wr_address; movlw 0x08; movwf data_container; movlw 0x0F; movwf temp_w; call Delay; call write_a_byte; movlw 0x02; movwf wr_address; ;minutes; movlw 0x17; movwf data_container; movlw 0x0F; movwf temp_w; call Delay; call write_a_byte; ; goto setup_script Enable_Interrupts clrf TMR0 ; Start the Timer from Scratch movlw b'11100000' ;movlw (1 << GIE) + (1 << T0IE) movwf INTCON ; Enable Interrupts Loop ; Loop Here btfss PWM,1; ;bit "1" is the display update bit goto Skip_update; (remember to set it in INT_PWM when PWM goes low) call download_time; call load_display; call display_update; bcf PWM,1; ;reset update flag Skip_update ; movlw 0x01; movf PORTA, w; xorwf keys, w; andlw 0x0F ;only check the 4 button pins btfsc STATUS,Z ;test, if Z=0, no buttons goto Loop ;no, loop again xorwf keys,f movlw 0xFF; ;long,short delay ; movlw 0x04; movwf temp_w; call Delay Debounce ;movlw 0xA0 movlw 0xFF; ; movlw 0x04; movwf temp_w; call Delay movf PORTA, w; movwf keys; test_hours btfsc keys, 0 ;hours goto test_minutes; call Hours; goto Loop; test_minutes btfsc keys, 1 ;minutes goto test_brightness; call Minutes; goto Loop; test_brightness btfsc keys, 2 ;brightness goto test_spinner; call Dimmer; goto Loop; test_spinner btfsc keys, 3 goto Loop; call set_spinner; goto Loop; set_spinner incf spinner; movlw 0x05; subwf spinner,w; btfss STATUS, C; goto end_set clrf spinner; end_set return; Hours ; will be add one hour movlw 0xD0; movwf slave; movlw 0x03; hours movwf re_address; call read_a_byte; movfw Data_in; movwf hours; andlw 0xF0; movwf hours_high; ; swapf hours_high; movfw hours; andlw 0x0F; movwf hours_low; incf hours_low; movfw hours_high; iorwf hours_low, w; movwf hours; movlw 0x24; subwf hours, w btfss STATUS, C goto Hours_adv Hours_clear clrf hours_high; clrf hours_low; goto Hours_end; Hours_adv ;low movlw 0xA; subwf hours_low,w; btfss STATUS, C; goto Hours_end; swapf hours_high; incf hours_high; swapf hours_high; movlw 0x00; movwf hours_low; Hours_end ;high movfw hours_high; iorwf hours_low, w; movwf hours; swapf hours_high; movwf data_container; movlw 0x03; minutes movwf wr_address; movlw 0xD0; movwf slave; call write_a_byte; return; Minutes ; will be add one minute movlw 0xD0; movwf slave; movlw 0x02; minutes movwf re_address; call read_a_byte; movfw Data_in; movwf minutes; andlw 0xF0; movwf minutes_high; swapf minutes_high; movfw minutes; andlw 0x0F; movwf minutes_low; incf minutes_low; movlw 0xA; subwf minutes_low, w btfss STATUS, C goto Minutes_end movlw 0x00; movwf minutes_low; incf minutes_high; movlw 0x06; subwf minutes_high, w; btfss STATUS, C goto Minutes_end movlw 0x00; movwf minutes_high; movwf minutes_low; Minutes_end swapf minutes_high; movfw minutes_low; iorwf minutes_high, w; movwf minutes; swapf minutes_high; movwf data_container; movlw 0x02; minutes movwf wr_address; movlw 0xD0; movwf slave; call write_a_byte; return; Dimmer ;adds 0x20 to PWM movfw PWMOn; addlw 0x10; movwf PWMOn; movlw 0x9F; subwf PWMOn, w; btfss STATUS, C goto Dimmer_end; movlw 0x4F; movwf PWMOn; Dimmer_end return; Int ; Interrupt Handler movwf _w ; Save Context Registers movf STATUS, w ; - Assume TMR0 is the only enabled bcf STATUS, RP0 bcf STATUS, RP1 movwf _status; movf PCLATH, w movwf _pclath clrf PCLATH btfsc INTCON, TMR0IF goto TMR0_INT goto INT_end; TMR0_INT ;TMR0 Interrupt: PWM-ing in the interrupter btfsc PWM,0; goto PWM_ON; bsf PWM,0; bcf PWM,1; btfss A_or_B, 0; goto B_set; A_set bsf PORTB, 3; bcf PORTB, 0; goto skipped; B_set bcf PORTB, 3; bsf PORTB, 0; skipped movlw 6 + 6 ; to be re-calculated... subwf PWMOn, w; sublw 0 ;get value in timer movwf TMR0; goto PWM_Done PWM_ON incf A_or_B; ; might not need, see p283 ; now turn off the HV bcf PWM,0; bcf PORTB, 3; bcf PORTB, 0; bsf PWM,1; ; flag to tell the register to update the time ; set the A,B pins to toggle between tube pairs movf PWMOn, w; sublw 6 + 6; sublw 0 ;get value in timer movwf TMR0; goto PWM_Done PWM_Done bcf INTCON, TMR0IF ; Reset the Interrupt Flag INT_end movf _pclath, w movwf PCLATH movf _status, w ; Restore the Context Registers movwf STATUS swapf _w, f swapf _w, w retfie ; bcd2dec ;eeprom binary to decimal hack banksel EEADR; movwf EEADR; banksel EECON1; bcf EECON1, EEPGD ; EE Point bsf EECON1, RD ; EE Read banksel EEDATA; movf EEDATA, w; banksel ByteA; return; download_time movlw 0xD0; movwf slave; movlw 0x02; minutes movwf re_address; call read_a_byte; movfw Data_in; movwf correct_minutes; movlw 0x03; hours movwf re_address; call read_a_byte; movfw Data_in; movwf correct_hours; call make_12_hr_time; ;translate to 12-hr time, then... ;check if spinner state; Spinner_zero movlw 0x04; subwf spinner,w; btfsc STATUS, C; goto spin_12_hour; movlw 0x03; subwf spinner,w; btfsc STATUS, C; goto spin_1_hour; movlw 0x02; subwf spinner,w; btfsc STATUS, C; goto spin_10_minute; movlw 0x01; subwf spinner,w; btfsc STATUS, C; goto spin_1_minute; movlw 0x00; subwf spinner,w; btfsc STATUS, C; goto HM_hi_low; goto HM_hi_low; spin_1_minute movfw correct_minutes; xorwf minutes_old,w; ;minutes changed? btfss STATUS, Z; ;skip if didn't change bsf PWM,5; ;spin_flag goto spin_spin_sugar spin_10_minute movfw correct_minutes; xorwf minutes_old,w; iorlw 0x0F; xorlw 0x0F; btfss STATUS, Z; bsf PWM,5; goto spin_spin_sugar spin_1_hour movfw correct_hours; xorwf hours_old,w; btfss STATUS, Z; bsf PWM, 5; goto spin_spin_sugar spin_12_hour movfw correct_hours; xorwf hours_old,w; iorlw 0x0F; xorlw 0x0F; btfss STATUS, Z; bsf PWM, 5; spin_spin_sugar btfss PWM, 5; ;if flag not set, skip goto HM_hi_low; incf spin_timer; movlw 0x0A; xorwf spin_timer,w; btfss STATUS, Z; goto download_time_end; clrf spin_timer; incf spin_timer2; movlw 0x29; subwf spin_timer2,w; btfsc STATUS, C; goto HM_hi_low; movlw 0x1F; subwf spin_timer2,w; btfsc STATUS, C; goto min_low; movlw 0x15; subwf spin_timer2,w; btfsc STATUS, C; goto min_high; movlw 0x0B; subwf spin_timer2,w; btfsc STATUS, C; goto hour_low; goto hour_high; hour_high incf hours_high movlw 0x0A; subwf hours_high, w; btfss STATUS, C; goto hour_low movlw 0x00; movwf hours_high; hour_low incf hours_low; movlw 0x0A; subwf hours_low, w; btfss STATUS, C; goto min_high; movlw 0x00; movwf hours_low; min_high incf minutes_high; movlw 0x0A; subwf minutes_high, w; btfss STATUS, C; goto min_low; movlw 0x00; movwf minutes_high; min_low incf minutes_low; movlw 0x0A; subwf minutes_low, w; btfss STATUS, C; goto assemble_time; movlw 0x00; movwf minutes_low; assemble_time movfw minutes_high; swapf minutes_high,w; iorwf minutes_low,w; movwf minutes; movfw hours_high; swapf hours_high,w; iorwf hours_low,w; movwf hours; goto download_time_end; HM_hi_low bcf PWM, 5; clrf spin_timer2; clrf spin_timer; movfw correct_minutes; andlw 0xF0; movwf minutes_high; swapf minutes_high; movfw correct_minutes; andlw 0x0F; movwf minutes_low; movfw correct_hours andlw 0xF0; movwf hours_high; swapf hours_high; movfw correct_hours; andlw 0x0F; movwf hours_low; movfw correct_minutes; movwf minutes_old; movfw correct_hours; movwf hours_old; goto assemble_time; download_time_end; return; make_12_hr_time movlw 0x23 subwf correct_hours, w; btfss STATUS, C; goto check_10 movlw 0x11 movwf correct_hours; goto make_end check_10 movlw 0x22 subwf correct_hours, w; btfss STATUS, C; goto check_9 movlw 0x10; movwf correct_hours; goto make_end; check_9 movlw 0x21 subwf correct_hours,w; btfss STATUS, C; goto check_8 movlw 0x09; movwf correct_hours; goto make_end; check_8 movlw 0x20; subwf correct_hours, w; btfss STATUS, C; goto check_12; movlw 0x08; movwf correct_hours; goto make_end; check_12 movfw correct_hours; xorlw 0x00; btfss STATUS, Z goto over_13; movlw 0x12; movwf correct_hours; goto make_end over_13 movlw 0x013; subwf correct_hours,w; btfss STATUS, C; goto make_end; movfw correct_hours; andlw 0x0F; movwf correct_hours; decf correct_hours; decf correct_hours; make_end return; load_display AorB ;;LOAD DISPLAY btfsc A_or_B,0; ;;LOAD DISPLAY goto group_A ;;LOAD DISPLAY group_B ;2,4 ;;LOAD DISPLAY swapf hours,w; andlw 0x0F; movwf msd_temp; swapf minutes, w; andlw 0x0F; movwf lsd_temp; ; goto lookup; btfsc PWM, 5; ;Spinner Flag goto lookup; movfw msd_temp; xorlw 0x00; btfss STATUS, Z; ;high bit zero? goto lookup; movlw 0x14 ;yep. 0x14 is a location to "zero" in eeprom no matter what movwf msd_temp; goto lookup group_A ;1,3 movfw hours; andlw 0x0F; movwf msd_temp; movfw minutes; andlw 0x0F; movwf lsd_temp; goto lookup; lookup ;check if overflow first ; movlw 0x0A; ; subwf lsd_temp,w; ; btfss STATUS, C; if overflow, skip ; goto check_msd ; movlw 0x09; ;error code "9" ; movwf lsd_temp; ;check_msd ; movlw 0x0A; ; subwf msd_temp,w; ; btfss STATUS, C; if overflow, skip ; goto look_it_up ; movlw 0x09; ;error code "9" ; movwf msd_temp; look_it_up movfw lsd_temp; call bcd2dec; movwf ByteD; movfw lsd_temp; addlw 10; call bcd2dec; movwf ByteC; movfw msd_temp; addlw 20; call bcd2dec; movwf ByteB; movfw msd_temp; addlw 30; call bcd2dec; movwf ByteA; return; Delay ;delays by amount in w movf temp_w, w ; subroutine, not goto! Delay_loop decfsz temp_w,f goto Delay_loop return org 0x0300 send_start ;i2c start condition, no error movlw 0x00; ;setup the all-output state bsf STATUS, RP0; movwf TRISB; bcf STATUS, RP0; bsf PORTB, SDA; bsf PORTB, SCL; nop; nop; nop; bcf PORTB, SDA; ;sda hi to low while clock high nop; nop; nop; bcf PORTB, SCL; ;start clock train! ; banksel Data_in; return; send_stop ;12c stop condigion, no error bcf PORTB, SDA; nop; nop; nop; bsf PORTB, SCL; ;Clock High nop; nop; nop; bsf PORTB, SDA; ;Sda goes low to high while clock hi nop; ;necessary? return; send_a_bit ;i2c send a bit (only one bit, though) ;have to shift left or right by hand before single bit xmit movlw 0x00; setup all output state bsf STATUS, RP0 movwf TRISB; bcf STATUS, RP0 btfss Data_out, 7; goto send_zero; bsf PORTB, SDA; ;send "1" nop; goto clk1; send_zero bcf PORTB, SDA; ;send "0" nop; nop; nop; clk1 bsf PORTB, SCL; ;toggle clock nop; nop; nop; nop; bcf PORTB, SCL; ; return; send_byte ;i2c send byte command (shifts so send_a_bit works) ;assumes Data_out already loaded movlw 0x08 ;8 bits movwf i2c_count; sender ;loop part call send_a_bit; rlf Data_out; decfsz i2c_count; goto sender ; one more clock for the ack ; call get_a_bit; return; get_a_bit ;i2c receive a bit ;have to shift left or right after single bit reception bsf STATUS, RP0 bsf TRISB, SDA; ;Force SDA input bsf PORTB, SDA; ;input bcf STATUS, RP0 bcf Data_in, 0 ;set to zero as an assumption nop; bsf PORTB, SCL; ;clock hi nop; nop; btfsc PORTB, SDA; bsf Data_in, 0; ;oh- I guess it was a "1" nop; bcf PORTB, SCL; ;clear clock return; receive_byte ;i2c recieve byte command (shifts so get_a_bit works) movlw 0x08; movwf i2c_count; receiver ;loop part rlf Data_in; call get_a_bit decfsz i2c_count; goto receiver; bsf PORTB, SDA; bsf Data_out,7; call send_a_bit; ;nack return; write_a_byte ;i2c routine to write a byte to the addres "wr_address" ;assuming the data is stored in "data_container," and the slave address ;is in "slave" movfw slave; movwf Data_out; call send_start; nop; nop; call send_byte; ;send slave address first (with r/w = "1" bit set) movfw wr_address; movwf Data_out; nop; nop; call send_byte; movfw data_container; movwf Data_out; nop; nop; call send_byte; nop; nop; call send_stop; return; read_a_byte ;i2c routine to read a byte from address "re_address" ;assuming the data goes into "Data_in," and the slave ;address lives in "slave." movfw slave; movwf Data_out; call send_start; nop; nop; call send_byte; ;send slave address first (with r/w = "0" bit set) movfw re_address; movwf Data_out; nop; nop; call send_byte; send read_address; nop; nop; call send_start; movfw slave; iorlw 0x01; ;set r/w high movwf Data_out; nop; nop; call send_byte; send slave again; nop; nop; call receive_byte; nop; nop; call send_stop; return org 0x0400 display_update bsf PORTB, OE; ;disable device movf ByteD, w movwf display_out; call register_director; movf ByteC, w movwf display_out; call register_director; movf ByteB, w movwf display_out; call register_director; movf ByteA, w movwf display_out; call register_director; ;done updating the values in HV5122 bcf PORTB, OE; ;enable device return; register_director movlw 0x08; movwf serial_count; ones_zeros btfsc display_out, 0; lowest bit set goto one_out; zero_out bsf PORTB,CLK; ;clock bsf PORTB,DAT; (just the data bit);data movlw 0x0F; movwf temp_w; call Delay; rrf display_out,f; goto serial_loop one_out bsf PORTB,CLK; bcf PORTB,DAT; movlw 0x0F; movwf temp_w; call Delay; rrf display_out,f; serial_loop bcf PORTB,CLK; movlw 0x0F; movwf temp_w; call Delay; decfsz serial_count; goto ones_zeros; return; org 0x02100 ;EEProm data with pattern of digits in HV5122 de 0x00, 0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 de 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 de 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x40, 0x20 de 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01, 0x00, 0x00, 0x00 de 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 de 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00; ;; 0 1 2 3 4 5 6 7 8 9 end