; Verbose clock ; ATMEGA88, 168, 328 ; ; Display is a 24-digit 14-segment LED, driven by a 64-bit serial shift register ; connected to SPI port, loading leftmost digit first, high bit of high byte unused, ; send order is a,...,p,DP ; ; Digit select lines (to power transistors): ; Digit 0 = PC0 ; Digit 1 = PC1 ; Digit 2 = PC2 ; Digit 3 = PD5 ; Digit 4 = PD6 ; Digit 5 = PD7 ; ; PC3 = SELECT ; PC4 = UP ; PC5 = DOWN .include "m328pdef.inc" ; ; a ; ------------- ; |\ | /| ; f| \g h| j/ |b ; | \ | / | ; | \ | / | ; p------ ------k ; | / | \ | ; | / | \ | ; e| /n m| l\ |c ; |/ | \| ; ------------- ; d * Decimal Point #define LED_COMMON_ANODE 1 ;#define ATMEGA48OR88 1 .def REG_ZERO = r2 ; register always zero; saves a lot of clr instructions .def INTERRUPT_SREG = r3 ; Storage for Status flags during interrupt .def KEY_REPEAT_DELAY = r4 ; countdown for key repeat .def REFRESH_PHASE = r5 ; display refresh phase from 0 to 5 (0,2,4,6,8,10) .def SCRATCH0 = r16 .def SCRATCH1 = r17 .def SCRATCH2 = r18 .def SCRATCH3 = r19 .def SCRATCH4 = r20 .def FLAG1_REGISTER = r21 ; r26, r27, r28, r29, r30, r31 are used as pointers .equ FLAG1_TIME_CHANGED = 0 .equ FLAG1_TIME_UPDATE = 1 .equ FLAG1_BUTTON_SELECT_PRESSED = 2 .equ FLAG1_BUTTON_UP_PRESSED = 3 .equ FLAG1_BUTTON_DOWN_PRESSED = 4 .equ FLAG1_BUTTON_HELD = 5 .equ FLAG1_MODE_SET_HOURS = 6 .equ FLAG1_MODE_SET_MINUTES = 7 ; FLAG2 register is GPIOR0 .equ FLAG2_SERIAL_DATA_PENDING = 0 .equ FLAG2_SERIAL_DISPLAY_MODE = 1 .equ FLAG2_SERIAL_LEFT_JUSTIFIED = 2 .equ FLAG2_MODE_DISPLAY_OFF = 3 .equ FLAG2_DISPLAY_OFF = 4 .equ FLAG2_INTS_OK_1 = 5 ; Beginning of code: interrupt vectors ; for the ATMEGA48/88/168/328P .cseg #ifdef ATMEGA48OR88 rjmp V_RESET rjmp V_INT0 rjmp V_INT1 rjmp V_PCINT0 rjmp V_PCINT1 rjmp V_PCINT2 rjmp V_WDT rjmp V_TIM2_COMPA rjmp V_TIM2_COMPB rjmp V_TIM2_OVF rjmp V_TIM1_CAPT rjmp V_TIM1_COMPA rjmp V_TIM1_COMPB rjmp V_TIM1_OVF rjmp V_TIM0_COMPA rjmp V_TIM0_COMPB rjmp V_TIM0_OVF rjmp V_SPI_STC rjmp V_USART0_RXC rjmp V_USART0_UDRE rjmp V_USART0_TXC rjmp V_ADC rjmp V_EE_READY rjmp V_ANALOG_COMP rjmp V_TWI rjmp V_SPM_READY #else jmp V_RESET jmp V_INT0 jmp V_INT1 jmp V_PCINT0 jmp V_PCINT1 jmp V_PCINT2 jmp V_WDT jmp V_TIM2_COMPA jmp V_TIM2_COMPB jmp V_TIM2_OVF jmp V_TIM1_CAPT jmp V_TIM1_COMPA jmp V_TIM1_COMPB jmp V_TIM1_OVF jmp V_TIM0_COMPA jmp V_TIM0_COMPB jmp V_TIM0_OVF jmp V_SPI_STC jmp V_USART0_RXC jmp V_USART0_UDRE jmp V_USART0_TXC jmp V_ADC jmp V_EE_READY jmp V_ANALOG_COMP jmp V_TWI jmp V_SPM_READY #endif ; ; !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ ; FONT_START: FONT_SYMBOL_1: ;.db 0b0abcdefg,0bhjklmnpD ; Format example .db 0b00000000,0b00000000 ; space = 0 .db 0b00100000,0b00000001 ; ! .db 0b00100000,0b10000000 ; " .db 0b01001110,0b10001000 ; # .db 0b01011010,0b10101010 ; $ = 4 .db 0b00010010,0b01000100 ; % .db 0b01001001,0b01010100 ; & .db 0b00000000,0b01000000 ; ' .db 0b00000000,0b01010000 ; ( = 8 .db 0b00000001,0b00000100 ; ) .db 0b00000001,0b11111110 ; * .db 0b00000000,0b10101010 ; + .db 0b00000000,0b00000100 ; , = 12 .db 0b00000000,0b00100010 ; - .db 0b00000000,0b00000001 ; . .db 0b00000000,0b01000100 ; / FONT_NUMERIC: ;.db 0b0abcdefg,0bhjklmnpD ; Format example .db 0b01111110,0b01000100 ; 0 = 16 .db 0b00110000,0b00000000 ; 1 .db 0b01101100,0b00100010 ; 2 .db 0b01111000,0b00100010 ; 3 .db 0b00110010,0b00100010 ; 4 = 20 .db 0b01011010,0b00100010 ; 5 .db 0b01011110,0b00100010 ; 6 .db 0b01110000,0b00000000 ; 7 .db 0b01111110,0b00100010 ; 8 = 24 .db 0b01111010,0b00100010 ; 9 FONT_SYMBOL_2: ;.db 0b0abcdefg,0bhjklmnpD ; Format example .db 0b00000000,0b10001000 ; : .db 0b00000000,0b10000100 ; ; .db 0b00000000,0b01010000 ; < = 28 .db 0b00001000,0b00100010 ; = .db 0b00000001,0b00000100 ; > .db 0b01100000,0b00101000 ; ? .db 0b01111110,0b00101000 ; @ = 32 FONT_ALPHABET: ;.db 0b0abcdefg,0bhjklmnpD ; Format example .db 0b01110110,0b00100010 ; A .db 0b01111000,0b10101000 ; B .db 0b01001110,0b00000000 ; C .db 0b01111000,0b10001000 ; D = 36 .db 0b01001110,0b00100010 ; E .db 0b01000110,0b00100010 ; F .db 0b01001110,0b00110000 ; G .db 0b00110110,0b00100010 ; H = 40 .db 0b01001000,0b10001000 ; I .db 0b00111100,0b00000000 ; J .db 0b00000110,0b01010010 ; K .db 0b00001110,0b00000000 ; L = 44 .db 0b00110111,0b01000000 ; M .db 0b00110111,0b00010000 ; N .db 0b01111110,0b00000000 ; O .db 0b01100110,0b00100010 ; P = 48 .db 0b01111110,0b00010000 ; Q .db 0b01100110,0b00110010 ; R .db 0b01011010,0b00100010 ; S .db 0b01000000,0b10001000 ; T = 52 .db 0b00111110,0b00000000 ; U .db 0b00000110,0b01000100 ; V .db 0b00110110,0b00010100 ; W .db 0b00000001,0b01010100 ; X = 56 .db 0b00000001,0b01001000 ; Y .db 0b01001000,0b01000100 ; Z ; .db 0b00110001,0b00010000 ; V (backward) FONT_SYMBOL_3: ;.db 0b0abcdefg,0bhjklmnpD ; Format example .db 0b01001110,0b00000000 ; [ .db 0b00000001,0b00010000 ; \ = 60 .db 0b01111000,0b00000000 ; ] .db 0b00000000,0b00010100 ; ^ .db 0b00001000,0b00000000 ; _ .db 0b00000001,0b00000000 ; ` = 64 FONT_SYMBOL_4: ;.db 0b0abcdefg,0bhjklmnpD ; Format example .db 0b00000000,0b01010010 ; { .db 0b00000000,0b10001000 ; | .db 0b00000001,0b00100100 ; } .db 0b00000001,0b01000000 ; ~ = 68 .db 0b01110110,0b00100011 ; A. .db 0b01100110,0b00100011 ; P. .db 0b00110111,0b01000001 ; M. NUMBER_TEXT_MAP: ; offsets, 1-20 .db 0,3,6,11,15,19,22,27,32,36,39,45,51,55,59,19,22,62,32,66,72,78,83,88, 92, 96, 98, 100 ; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 30 40 50 TEEN PAST A.M. P.M. HALF .db 104, 111, 118, 127, 138, 152, 164 ; EXACTLY QUARTER SET HOURS SET MINUTES SET BRIGHTNESS TIME NOT SET DISPLAY OFF ; Each line must be an even number of bytes, otherwise you get an extra 0 byte TEXT_STRINGS: .db 47,46,37+128, 52,55,47+128, 52,40,50,37,37+128, 38,47,53,50+128, 38,41,54,37+128, 51,41,56+128 ; ONE TWO THREE FOUR FIVE SIX .db 51,37,54,37,46+128, 37,41,39,40,52+128, 46,41,46,37+128 ; SEVEN EIGHT NINE .db 52,37,46+128, 37,44,37,54,37,46+128, 52,55,37,44,54,37+128, 52,40,41,50+128, 38,47,53,50+128, 38,41,38+128 ; TEN ELEVEN TWELVE THIR FOUR FIF .db 37,41,39,40+128, 52,55,37,46,52,57+128, 52,40,41,50,52,57+128, 38,47,50,52,57+128, 38,41,38,52,57+128 ; EIGH TWENTY THIRTY FORTY FIFTY .db 52,37,37,46+128, 48,33,51,52+128, 69,71+128, 70,71+128, 40,33,44,38+128 ; TEEN PAST A.M. P.M. HALF .db 37,56,33,35,52,44,57+128, 49,53,33,50,52,37,50+128 ; EXACTLY QUARTER .db 51,37,52,00,40,47,53,50,51+128, 51,37,52,00,45,41,46,53,52,37,51+128 ; SET HOURS SET MINUTES .db 51,37,52,00,34,50,41,39,40,52,46,37,51,51+128, 52,41,45,37,00,46,47,52,00,51,37,52+128 ; SET BRIGHTNESS TIME NOT SET .db 36,41,51,48,44,33,57,0,47,38,38+128 ; DISPLAY OFF ; A=33 B=34 C=35 D=36 E=37 F=38 G=39 H=40 I=41 J=42 K=43 L=44 M=45 ; N=46 O=47 P=48 Q=49 R=50 S=51 T=52 U=53 V=54 W=55 X=56 Y=57 Z=58 .equ NTM_OFFSET_TEEN = 24 ; these are 1-based not 0-based .equ NTM_OFFSET_PAST = 25 .equ NTM_OFFSET_AM = 26 .equ NTM_OFFSET_PM = 27 .equ NTM_OFFSET_HALF = 28 .equ NTM_OFFSET_EXACTLY = 29 .equ NTM_OFFSET_QUARTER = 30 .equ NTM_OFFSET_SET_HOURS = 31 .equ NTM_OFFSET_SET_MINUTES = 32 .equ NTM_OFFSET_SET_BRIGHTNESS = 33 .equ NTM_OFFSET_TIME_NOT_SET = 34 .equ NTM_OFFSET_DISPLAY_OFF = 35 .equ FA_OFFSET_DASH = 13 .equ FA_OFFSET_BLANK = 00 .equ BAUD_DIVISOR = 51 ; 9600 baud at 8 MHz .equ BRIGHTNESS_DEFAULT = 250 ; These are the hardware initialization values which will be written into I/O registers ; at startup. Format is address, value. All registers which are accessible by in/out ; must have 0x20 added to provide the correct data space address. CONFIGURATION_TABLE: ; Stack pointer .db SPH+0x20,high(RAMEND) .db SPL+0x20,low(RAMEND) ; Registers .db GPIOR0+0x20,0 ; FLAGS2 .db GPIOR1+0x20,low(SERIAL_INPUT_BUFFER) ; serial read pointer .db GPIOR2+0x20,low(SERIAL_INPUT_BUFFER) ; serial write pointer .db OCR0A+0x20,BRIGHTNESS_DEFAULT ; default brightness value ; Timer0 .db TIMSK0,(1< DISPLAY_BUFFER UPDATE_DISPLAY_LEFT_JUSTIFIED: clr SCRATCH0 mov SCRATCH1,XL ; end marker rjmp UPDATE_DISPLAY_COMMON ; UPDATE_DISPLAY_CENTERED: ; Calculate number of characters to indent mov SCRATCH1,XL ; end marker ldi SCRATCH0,low(TEXT_BUFFER+24) ; pointer to end of text buffer sub SCRATCH0,SCRATCH1 ; SCRATCH0 = number of unused characters lsr SCRATCH0 ; halve it = number to leave blank on left site ; Load registers UPDATE_DISPLAY_COMMON: cbi GPIOR0,FLAG2_SERIAL_DISPLAY_MODE ldi YH,high(DISPLAY_BUFFER) ldi YL,low(DISPLAY_BUFFER) ldi ZH,high(FONT_START<<1) #ifdef LED_COMMON_ANODE ldi SCRATCH2,0xff ; blank digit #else ldi SCRATCH2,0x00 ; blank digit #endif ; Clear first n characters to center the text UDC_LOOP1: tst SCRATCH0 breq UDC_OUT1 st Y+,SCRATCH2 st Y+,SCRATCH2 ; blank dec SCRATCH0 rjmp UDC_LOOP1 UDC_OUT1: ; Decode text to patterns and write it onto the display ldi XL,low(TEXT_BUFFER) UDC_LOOP2: cp XL,SCRATCH1 brsh UDC_OUT2 ; done ld ZL,X+ ; get character lsl ZL ; double address and put decimal point in C flag clr SCRATCH3 ; must be a better way adc SCRATCH3,REG_ZERO ; C flag into bit 0 subi ZL,0-low(FONT_START<<1) ; same page assumption (barely!) lpm SCRATCH0,Z+ #ifdef LED_COMMON_ANODE com SCRATCH0 #endif st Y+,SCRATCH0 lpm SCRATCH0,Z+ or SCRATCH0,SCRATCH3 ; merge in decimal point #ifdef LED_COMMON_ANODE com SCRATCH0 #endif st Y+,SCRATCH0 rjmp UDC_LOOP2 UDC_OUT2: ; Blank the rightmost digits UDC_LOOP3: cpi YL,low(DISPLAY_BUFFER+48) brsh UDC_OUT3 ; done st Y+,SCRATCH2 rjmp UDC_LOOP3 UDC_OUT3: ret ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Brightness control interrupt, turn off display ; These instructions do not affect flags ; OCR0A register controls duty cycle and therefore brightness V_TIM0_COMPA: #ifdef LED_COMMON_ANODE sbi PORTC,PORTC0 sbi PORTC,PORTC1 sbi PORTC,PORTC2 sbi PORTD,PORTD5 sbi PORTD,PORTD6 sbi PORTD,PORTD7 #else cbi PORTC,PORTC0 cbi PORTC,PORTC1 cbi PORTC,PORTC2 cbi PORTD,PORTD5 cbi PORTD,PORTD6 cbi PORTD,PORTD7 #endif sbi GPIOR0,FLAG2_INTS_OK_1 reti ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Main periodic interrupt, refresh display, increment time, check buttons V_TIM0_OVF: TIMER_INTERRUPT: in INTERRUPT_SREG,SREG push SCRATCH0 push SCRATCH1 push SCRATCH2 push ZL push ZH ; sbic GPIOR0,FLAG2_INTS_OK_1 wdr cbi GPIOR0,FLAG2_INTS_OK_1 ; sbic GPIOR0,FLAG2_DISPLAY_OFF ; if display is blank... rjmp TI_KEY_SCAN ; proceed to key scan ; ; Send the display data to the shift register ldi ZH,high(DISPLAY_BUFFER) ldi ZL,low(DISPLAY_BUFFER) add ZL,REFRESH_PHASE ; same page assumption TI_REFRESH_COMMON_ITER: ld SCRATCH2,Z+ ; get high byte of digit out SPDR,SCRATCH2 ; send high byte ld SCRATCH2,Z ; get low byte of digit adiw ZH:ZL,11 ; step to high byte of next digit TI_REF_WAITSPI0: in SCRATCH1,SPSR sbrs SCRATCH1,SPIF rjmp TI_REF_WAITSPI0 out SPDR,SCRATCH2 ; send low byte TI_REF_WAITSPI1: in SCRATCH1,SPSR sbrs SCRATCH1,SPIF rjmp TI_REF_WAITSPI1 cpi ZL,(low(DISPLAY_BUFFER)+48) ; same page assumption brlo TI_REFRESH_COMMON_ITER ; done if past end of buffer, else iterate ; ; Turn on the digits ldi ZH,high(TI_REFRESH_PHASE) ldi ZL,low(TI_REFRESH_PHASE) add ZL,REFRESH_PHASE ; dangerous same page assumption ijmp ; Z TI_REFRESH_PHASE: #ifdef LED_COMMON_ANODE cbi PORTC,PORTC0 rjmp TI_REFRESH_PHASE_OUT cbi PORTC,PORTC1 rjmp TI_REFRESH_PHASE_OUT cbi PORTC,PORTC2 rjmp TI_REFRESH_PHASE_OUT cbi PORTD,PORTD5 rjmp TI_REFRESH_PHASE_OUT cbi PORTD,PORTD6 rjmp TI_REFRESH_PHASE_OUT cbi PORTD,PORTD7 ldi SCRATCH0,0xfe mov REFRESH_PHASE,SCRATCH0 ; rolls over to 0 when it gets +2 below #else sbi PORTC,PORTC0 rjmp TI_REFRESH_PHASE_OUT sbi PORTC,PORTC1 rjmp TI_REFRESH_PHASE_OUT sbi PORTC,PORTC2 rjmp TI_REFRESH_PHASE_OUT sbi PORTD,PORTD5 rjmp TI_REFRESH_PHASE_OUT sbi PORTD,PORTD6 rjmp TI_REFRESH_PHASE_OUT sbi PORTD,PORTD7 ldi SCRATCH0,0xfe mov REFRESH_PHASE,SCRATCH0 ; rolls over to 0 when it gets +2 below #endif TI_REFRESH_PHASE_OUT: inc REFRESH_PHASE inc REFRESH_PHASE ; note flags used below ; ; Scan the keys (every sixth interrupt) brne TI_NO_KEY_SCAN ; this skips on all except phase 0 ; TI_KEY_SCAN: ldi SCRATCH1,1 ; fast repeat interval sbrc FLAG1_REGISTER,FLAG1_MODE_SET_HOURS ldi SCRATCH1,20 ; slow repeat interval sbrc FLAG1_REGISTER,FLAG1_MODE_SET_MINUTES ldi SCRATCH1,20 ; slow repeat interval sbrs FLAG1_REGISTER,FLAG1_BUTTON_HELD ldi SCRATCH1,128 ; repeat delay ; in SCRATCH0,PINC com SCRATCH0 andi SCRATCH0,(1<