BlinkenArea - GitList
Repositories
Blog
Wiki
bluebox
Code
Commits
Branches
Tags
Search
Tree:
fd252ce
Branches
Tags
master
bluebox
BlueBrightnessControl4
firmware
BlueBrightnessControl4.asm
initial commit of files from bluebox project
Stefan Schuermans
commited
fd252ce
at 2015-12-19 20:16:38
BlueBrightnessControl4.asm
Blame
History
Raw
; BlueBrightnessControl4 - brightness control module for 4 230V AC lamps ; version 0.6.2 date 2007-03-23 ; Copyright (C) 2006 Stefan Schuermans <stefan@blinkenarea.org> ; a BlinkenArea project - http://www.blinkenarea.org/ ; clock frequency: 8 MHz (calibrated internal RC oscillator) ; PA0: unused (output, low) ; PA1: unused (output, low) ; PA2: reset ; PB0: input from button (input, pull-up enabled, active low) ; PB1: output to status LED (output, active high) ; PB2: output to lamp 1 (output, active low) ; PB3: output to lamp 3 (output, active low) ; PB4: output to lamp 4 (output, active low) ; PB5: programming clock (input, pull-up enabled) ; PB6: programming data output (output, low) ; PB7: programming data input (input, pull-up enabled) ; PD0: serial data input (input, pull-up disabled) ; PD1: unused (output, low) ; PD2: unused (output, low) ; PD3: unused (output, low) ; PD4: unused (output, low) ; PD5: output to lamp 2 (output, active low) ; PD6: unused (output, low) ; serial input: 4800,8,N,1 ; <message> := <control> [<value 1> <value 2> <value 3> <value4>] ; <control> := control byte (0x80..0xFF) ; 0x80 all lamps off (no values must be present) ; 0x81 linear mapping from grayscale to PWM values ; 0x82 table based mapping from grayscale to PWM values ; <value N> := grayscale value for lamp N (0x00..0x7F) ; output is done using the 4 PWM channels and using 8 bit fast PWM mode ; the first PWM output of each timer is inverted and is filled with 0x00 for off and 0xFF for on (outputs are active low) ; the second PWM output of each timer is not inverted and is filled with 0xFF for off and 0x00 for on (outputs are active low) ; this results in a phase shift of 1/2 period between the two output of each timer ; the timers are set up to be phase shifted 1/4 period ; this results in a phase shift of 1/4 between two outputs (in this order OC0A, OC1A, OC0B, OC1B) .INCLUDE "tn2313def.inc" ; IO pins .equ PIN_N_BUTTON = PINB ; input from button (active low) .equ BIT_N_BUTTON = 0 .equ PORT_STATUS = PORTB ; output to status LED (active high) .equ BIT_STATUS = 1 .equ PORT_N_PWM0 = PORTB ; 1st PWM output (active low) .equ BIT_N_PWM0 = 2 .equ PORT_N_PWM1 = PORTD ; 2nd PWM output (active low) .equ BIT_N_PWM1 = 5 .equ PORT_N_PWM2 = PORTB ; 3rd PWM output (active low) .equ BIT_N_PWM2 = 3 .equ PORT_N_PWM3 = PORTB ; 4th PWM output (active low) .equ BIT_N_PWM3 = 4 .equ PIN_SER_IN = PIND ; serial input .equ BIT_SER_IN = 0 ; constants ; maximum message length .equ MSG_LEN_MAX = 5 ; theshold value for button state (debouncing time in milliseconds) .equ BUTTON_THR = 80 ; time the button has priority over the serial data after a press (in 100 milliseconds) .equ BUTTON_TIME = 50 ; time of idle serial port that is recognized as a timeout (in 100 milliseconds) .equ SER_TIMEOUT = 50 ; synchronization trend limit (value the sync trend must reach before adapting RC oscillator, 20..120) .equ SYNC_TREND_LIMIT = 120 ; number of byte to receive next on serial port .def RECV_CNT = r0 ; received data byte .def RECV_DATA = r16 ; counter for detecting milliseconds .def MS_CNT = r1 ; counter for detecting 100 milliseconds .def MS100_CNT = r2 ; state of button (0=released ... 2*BUTTON_THR=full pressed) .def BUTTON_STATE = r17 ; number of lighting mode that will be enabled at next press of button .def BUTTON_MODE = r18 ; time in 100 milliseconds the button has priority over the serial port .def BUTTON_PRIO = r3 ; time in 100 milliseconds the serial port has been idle .def SER_IDLE = r4 ; number of times to blink in current status output cycle .def STATUS_BLINK = r5 ; counter for status output intervals .def STATUS_CNT = r6 ; tick counter for animation mode .def ANIM_TICKS = r7 ; frame counter for animation mode .def ANIM_FRAME = r8 ; last state of serial port (0x00 for LOW, 0xFF for HIGH) (used for sync) .def SER_LAST_STATE = r9 ; number of times serial port state did not change (used for sync) .def SER_UNCHANGED_CNT = r10 ; trend if running too slow or too fast (compared to speed of serial signal) .def SER_SYNC_TREND = r11 ; variable for bits .def BITS = r19 ; animation mode enabled .equ BIT_ANIM_ENAB = 0 ; general purpose registers ; NOT for usage in interrupt .def TMP = r20 ; registers for interrupts (or when interrupts are disabled) .def I_SREG = r12 ; interrupt backup register for SREG .def I_TMP = r21 ; general purpose registers for interrupt .def I_CNT = r22 .DSEG .ORG 0x060 ; buffer for data bytes received on serial port RECV_BUFFER: .BYTE MSG_LEN_MAX ; buffer for current PWM values (0x00..0xFF) PWM_CUR: .BYTE 4 .CSEG .ORG 0x000 rjmp ENTRY ; RESET reti ; INT0 reti ; INT1 reti ; TIMER1_CAPT reti ; TIMER1_COMPA reti ; TIMER1_OVF reti ; TIMER0_OVF rjmp SER_RECV ; USART0_RX reti ; USART0_UDRE reti ; USART0_TX reti ; ANALOG_COMP reti ; PC_INT reti ; TIMER1_COMPB reti ; TIMER0_COMPA reti ; TIMER0_COMPB reti ; USI_START reti ; USI_OVERFLOW reti ; EE_READY reti ; WDT ; code entry point ENTRY: ; set system clock prescaler to 1:1 ldi TMP,1<<CLKPCE out CLKPR,TMP ldi TMP,0 out CLKPR,TMP ; initialize output ports ldi TMP,0x00 ; PA[01] to output, low out PORTA,TMP ldi TMP,0x02 out DDRA,TMP ldi TMP,0xBD ; PB[16] to output, low - PB[2-4] to output, high - PB[057] to input, pull-up enabled out PORTB,TMP ldi TMP,0x5E out DDRB,TMP ldi TMP,0x20 ; PD[1-46] to output, low - PD5 to output, high - PD0 to input, pull-up disabled out PORTD,TMP ldi TMP,0x7E out DDRD,TMP ; initialize stack pointer ldi TMP,RAMEND out SPL,TMP ; enable watchdog (64ms) wdr ldi TMP,1<<WDCE|1<<WDE out WDTCR,TMP ldi TMP,1<<WDE|1<<WDP1 out WDTCR,TMP wdr ; disable analog comparator ldi TMP,1<<ACD out ACSR,TMP ; set up timer 0 ldi TMP,1<<WGM01|1<<WGM00 ; timer 0: fast PWM mode PWM out TCCR0A,TMP ldi TMP,1<<CS00 ; timer 0: fast PWM mode, system clock, prescaler 1:1 out TCCR0B,TMP ; set up timer 1 ldi TMP,1<<WGM10 ; timer 1: fast 8 bit PWM mode PWM out TCCR1A,TMP ldi TMP,1<<WGM12|1<<CS10 ; timer 1: fast 8 bit PWM mode, system clock, prescaler 1:1 out TCCR1B,TMP ldi TMP,0 out TCCR1C,TMP ; disable timer interrupts ldi TMP,0 ; no timer interrupts (for timer 0 and timer 1) out TIMSK,TMP ; sync timers to be separated by 1/4 period ldi TMP,0 out TCNT0,TMP out TCNT1H,TMP ldi TMP,0x43 ; 3 cycles already elapsed since write to TCNT0 out TCNT1L,TMP ; set up usart ; transmitter disabled ; receiver enabled ; receive interrupt enabled ; 4800 baud ; 8N1 ; enable usart ldi TMP,0 ; single speed out UCSRA,TMP ldi TMP,1<<RXCIE|1<<RXEN ; only receiver enabled, receive interrupt enabled out UCSRB,TMP ldi TMP,1<<UCSZ1|1<<UCSZ0 ; asynchronous, 8N1: 8 data bits, no parity, 1 stop bit out UCSRC,TMP ldi TMP,0 ; speed = Fosc / (16 * (UBRR + 1)) out UBRRH,TMP ; = 8MHz / (16 * (103 + 1)) ldi TMP,103 ; = 4808bps (4800bps, 0.16% deviation) out UBRRL,TMP ; jump to main program rjmp MAIN ; table for mapping from grayscale values to PWM values .include "mapping.gen.inc" ; turn on/off PWM machinery and write current PWM values to PWM registers ; - must be called with interrupts disabled ; - changes: Z, I_TMP PWM_OUT: ; set outputs ; - needed in case pin is disconnected from PWM in next step ldi ZH,HIGH(PWM_CUR) ; base address ldi ZL,LOW(PWM_CUR) ld I_TMP,Z+ ; 1st sbrs I_TMP,7 sbi PORT_N_PWM0,BIT_N_PWM0 sbrc I_TMP,7 cbi PORT_N_PWM0,BIT_N_PWM0 ld I_TMP,Z+ ; 2nd sbrs I_TMP,7 sbi PORT_N_PWM1,BIT_N_PWM1 sbrc I_TMP,7 cbi PORT_N_PWM1,BIT_N_PWM1 ld I_TMP,Z+ ; 3rd sbrs I_TMP,7 sbi PORT_N_PWM2,BIT_N_PWM2 sbrc I_TMP,7 cbi PORT_N_PWM2,BIT_N_PWM2 ld I_TMP,Z+ ; 4th sbrs I_TMP,7 sbi PORT_N_PWM3,BIT_N_PWM3 sbrc I_TMP,7 cbi PORT_N_PWM3,BIT_N_PWM3 ; output PWM values to PWM registers ldi ZH,HIGH(PWM_CUR) ; base address ldi ZL,LOW(PWM_CUR) ld I_TMP,Z+ ; 1st out OCR0A,I_TMP ld I_TMP,Z+ ; 2nd (invert: 0xFF -> 0x00, other -> 0xFE - other) com I_TMP breq PWM_OUT_2INV dec I_TMP PWM_OUT_2INV: out OCR0B,I_TMP ld I_TMP,Z+ ; 3rd out OCR1AL,I_TMP ld I_TMP,Z+ ; 4th (invert: 0xFF -> 0x00, other -> 0xFE - other) com I_TMP breq PWM_OUT_4INV dec I_TMP PWM_OUT_4INV: out OCR1BL,I_TMP ; (dis)connect pins from/to PWM ldi ZH,HIGH(PWM_CUR) ; base address ldi ZL,LOW(PWM_CUR) ld I_TMP,Z+ ; 1st cpi I_TMP,0x00 breq PWM_OUT_0 cpi I_TMP,0xFF breq PWM_OUT_0 in I_TMP,TCCR0A ; 1st: (inverted) PWM sbr I_TMP,1<<COM0A1|1<<COM0A0 out TCCR0A,I_TMP rjmp PWM_OUT_0_END PWM_OUT_0: in I_TMP,TCCR0A ; 1st: no (inverted) PWM cbr I_TMP,1<<COM0A1|1<<COM0A0 out TCCR0A,I_TMP PWM_OUT_0_END: ld I_TMP,Z+ ; 2nd cpi I_TMP,0x00 breq PWM_OUT_1 cpi I_TMP,0xFF breq PWM_OUT_1 in I_TMP,TCCR0A ; 2nd: PWM sbr I_TMP,1<<COM0B1 out TCCR0A,I_TMP rjmp PWM_OUT_1_END PWM_OUT_1: in I_TMP,TCCR0A ; 2nd: no PWM cbr I_TMP,1<<COM0B1 out TCCR0A,I_TMP PWM_OUT_1_END: ld I_TMP,Z+ ; 3rd cpi I_TMP,0x00 breq PWM_OUT_2 cpi I_TMP,0xFF breq PWM_OUT_2 in I_TMP,TCCR1A ; 3rd: (inverted) PWM sbr I_TMP,1<<COM1A1|1<<COM1A0 out TCCR1A,I_TMP rjmp PWM_OUT_2_END PWM_OUT_2: in I_TMP,TCCR1A ; 3rd: no (inverted) PWM cbr I_TMP,1<<COM1A1|1<<COM1A0 out TCCR1A,I_TMP PWM_OUT_2_END: ld I_TMP,Z+ ; 4th cpi I_TMP,0x00 breq PWM_OUT_3 cpi I_TMP,0xFF breq PWM_OUT_3 in I_TMP,TCCR1A ; 4th: PWM sbr I_TMP,1<<COM1B1 out TCCR1A,I_TMP rjmp PWM_OUT_3_END PWM_OUT_3: in I_TMP,TCCR1A ; 4th: no PWM cbr I_TMP,1<<COM1B1 out TCCR1A,I_TMP PWM_OUT_3_END: ; done ret ; reception (or receive error) on serial port ; - serial receive interrupt SER_RECV: ; interrupt entry in I_SREG,SREG push XH push XL push YH push YL push ZH push ZL ; check for reception sbis UCSRA,RXC rjmp SER_RECV_END ; nothing received ; check for errors sbic UCSRA,FE rjmp SER_RECV_ERR ; frame error sbic UCSRA,DOR rjmp SER_RECV_ERR ; data overrun ; fetch received byte in RECV_DATA,UDR ; uppermost bit set -> first byte of message (control byte) sbrc RECV_DATA,7 clr RECV_CNT ; do nothing if behind maximum message length ldi I_TMP,MSG_LEN_MAX cp RECV_CNT,I_TMP brsh SER_RECV_END ; store received message byte ldi ZH,HIGH(RECV_BUFFER) ; base address of message buffer ldi ZL,LOW(RECV_BUFFER) clr I_TMP ; add grayscale number as offset add ZL,RECV_CNT adc ZH,I_TMP st Z,RECV_DATA ; store received message byte ; next byte inc RECV_CNT ; check for complete message lds RECV_DATA,RECV_BUFFER+0 ; get control byte cpi RECV_DATA,0x80 ; turn off all lamps brne SER_RECV_NOT_80 ldi I_TMP,1 ; check if complete cp RECV_CNT,I_TMP brlo SER_RECV_END ldi I_TMP,0xFF ; message completed mov RECV_CNT,I_TMP rjmp SER_RECV_OFF ; process off command SER_RECV_NOT_80: cpi RECV_DATA,0x81 ; linear mapped grayscales brne SER_RECV_NOT_81 ldi I_TMP,5 ; check if complete cp RECV_CNT,I_TMP brlo SER_RECV_END ldi I_TMP,0xFF ; message completed mov RECV_CNT,I_TMP rjmp SER_RECV_LINEAR ; process linear greyscales SER_RECV_NOT_81: cpi RECV_DATA,0x82 ; table based mapped grayscales brne SER_RECV_NOT_82 ldi I_TMP,5 ; check if complete cp RECV_CNT,I_TMP brlo SER_RECV_END ldi I_TMP,0xFF ; message completed mov RECV_CNT,I_TMP rjmp SER_RECV_TABLE ; process table based mapped greyscales SER_RECV_NOT_82: ldi I_TMP,0xFF ; message completed mov RECV_CNT,I_TMP rjmp SER_RECV_END ; receive error SER_RECV_ERR: in I_TMP,UDR ; read data from USART to clear error SER_RECV_END: ; interrupt exit pop ZL pop ZH pop YL pop YH pop XL pop XH out SREG,I_SREG reti ; turn off all lamps SER_RECV_OFF: clr SER_IDLE ; serial port is not idle cbr BITS,1<<BIT_ANIM_ENAB ; diable animation mode (on serial timeout) ldi YH,HIGH(PWM_CUR) ; base address current PWM values ldi YL,LOW(PWM_CUR) clr I_TMP ; set all to 0x00 st Y+,I_TMP st Y+,I_TMP st Y+,I_TMP st Y+,I_TMP rjmp SER_RECV_OUTPUT ; output new PWM values ; linear mapped grayscales SER_RECV_LINEAR: clr SER_IDLE ; serial port is not idle sbr BITS,1<<BIT_ANIM_ENAB ; enable animation mode (on serial timeout) ldi ZH,HIGH(RECV_BUFFER+1) ; base address received grayscales ldi ZL,LOW(RECV_BUFFER+1) ldi YH,HIGH(PWM_CUR) ; base address current PWM values ldi YL,LOW(PWM_CUR) ldi I_CNT,4 ; process 4 lamps SER_RECV_LINEAR_LOOP: ld I_TMP,Z+ ; expand grayscale value from 7 to 8 bits lsl I_TMP sbrc I_TMP,7 sbr I_TMP,1<<0 st Y+,I_TMP dec I_CNT ; loop brne SER_RECV_LINEAR_LOOP rjmp SER_RECV_OUTPUT ; output new PWM values ; table based mapped grayscales SER_RECV_TABLE: clr SER_IDLE ; serial port is not idle sbr BITS,1<<BIT_ANIM_ENAB ; enable animation mode (on serial timeout) ldi YH,HIGH(RECV_BUFFER+1) ; base address received grayscales ldi YL,LOW(RECV_BUFFER+1) ldi XH,HIGH(PWM_CUR) ; base address current PWM values ldi XL,LOW(PWM_CUR) ldi I_CNT,4 ; process 4 lamps SER_RECV_TABLE_LOOP: ld I_TMP,Y+ ; translate grayscale value using mapping table ldi ZH,HIGH(MAPPING_TABLE*2) ldi ZL,LOW(MAPPING_TABLE*2) add ZL,I_TMP ldi I_TMP,0 adc ZH,I_TMP lpm I_TMP,Z st X+,I_TMP dec I_CNT ; loop brne SER_RECV_TABLE_LOOP rjmp SER_RECV_OUTPUT ; output new PWM values ; output new grayscale value SER_RECV_OUTPUT: ; ignore received grayscales if button has got priority mov I_TMP,BUTTON_PRIO ; branch if not 0 cpi I_TMP,0 brne SER_RECV_END ; write current PWM values to PWM registers rcall PWM_OUT rjmp SER_RECV_END ; lighting modes to be activated on button press ; - format: <PWM lamp 1> <PWM lamp 2> <PWM lamp 3> <PWM lamp 4> BUTTON_MODES: .DB 0x1F, 0x1F, 0x1F, 0x1F, 0x01, 0x00 ; all a little bit on, animation mode enabled .DB 0x7F, 0x7F, 0x7F, 0x7F, 0x01, 0x00 ; all half on, animation mode enabled .DB 0xDF, 0xDF, 0xDF, 0xDF, 0x01, 0x00 ; all almost full on, animation mode enabled .DB 0x7F, 0x00, 0x00, 0x00, 0x01, 0x00 ; 1st half on, animation mode enabled .DB 0x00, 0x7F, 0x00, 0x00, 0x01, 0x00 ; 2nd half on, animation mode enabled .DB 0x00, 0x00, 0x7F, 0x00, 0x01, 0x00 ; 3st half on, animation mode enabled .DB 0x00, 0x00, 0x00, 0x7F, 0x01, 0x00 ; 4nd half on, animation mode enabled .DB 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ; full off, animation mode disabled BUTTON_MODES_END: ; button has just been pressed ; - must be called with interrupts enabled ; - changes: BUTTON_MODE, BUTTON_PRIO, BIT_ANIM_ENAB, Y, Z, TMP BUTTON_PRESS: ; disable interrupts cli ; get address of lighting mode data ldi ZH,HIGH(BUTTON_MODES*2) ; base address of lighting mode table ldi ZL,LOW(BUTTON_MODES*2) mov TMP,BUTTON_MODE ; multiply mode with 6 to get offset lsl TMP add TMP,BUTTON_MODE lsl TMP add ZL,TMP ; add offset ldi TMP,0 adc ZH,TMP ; next lighting mode inc BUTTON_MODE cpi BUTTON_MODE,(BUTTON_MODES_END*2-BUTTON_MODES*2)/6 brlo BUTTON_MODE_NEXT_DONE clr BUTTON_MODE BUTTON_MODE_NEXT_DONE: ; set new PWM values ldi YH,HIGH(PWM_CUR) ; base address of current PWM values ldi YL,LOW(PWM_CUR) lpm TMP,Z+ ; use lighting mode data as current PWM values st Y+,TMP lpm TMP,Z+ st Y+,TMP lpm TMP,Z+ st Y+,TMP lpm TMP,Z+ st Y+,TMP ; enable or disable animation mode lpm TMP,Z+ sbrs TMP,0 ; flag bit 0 cleared -> disable animation mode cbr BITS,1<<BIT_ANIM_ENAB sbrc TMP,0 ; flag bit 0 set -> enable animation mode sbr BITS,1<<BIT_ANIM_ENAB ; write current PWM values to PWM registers rcall PWM_OUT ; give button priority over serial data for configured time ldi TMP,BUTTON_TIME mov BUTTON_PRIO,TMP ; enable interrupts sei ret ; button processing ; - must be called with interrupts enabled ; - changes: BUTTON_STATE, BUTTON_MODE, BUTTON_PRIO, BIT_ANIM_ENAB, Y, Z, TMP BUTTON_PROC: ; check if button input is active sbis PIN_N_BUTTON,BIT_N_BUTTON rjmp BUTTON_IN_ACTIVE ; jump if button input active ; if button state is below BUTTON_THR, keep it at "full released" cpi BUTTON_STATE,BUTTON_THR brsh BUTTON_RELEASED_DEC ldi BUTTON_STATE,0 ret BUTTON_RELEASED_DEC: ; decrement button state dec BUTTON_STATE ; if button state is now BUTTON_THR-1 the button has been released cpi BUTTON_STATE,BUTTON_THR-1 breq BUTTON_RELEASED_NOW ret BUTTON_RELEASED_NOW: ; set button state to "full released" ldi BUTTON_STATE,0 ; do actions to do on button release ret BUTTON_IN_ACTIVE: ; if button state is already at least BUTTON_THR, keep it at "full pressed" cpi BUTTON_STATE,BUTTON_THR brlo BUTTON_PRESSED_INC ldi BUTTON_STATE,2*BUTTON_THR ret BUTTON_PRESSED_INC: ; increment button state inc BUTTON_STATE ; if button state is now BUTTON_THR the button has been pressed cpi BUTTON_STATE,BUTTON_THR breq BUTTON_PRESSED_NOW ret BUTTON_PRESSED_NOW: ; set button state to "full pressed" ldi BUTTON_STATE,2*BUTTON_THR ; do actions to do on button press rcall BUTTON_PRESS ret ; button priority processing ; - must be called with interrupts enabled ; - changes: BUTTON_PRIO, TMP BUTTON_PRIO_PROC: ; decrease BUTTON_PRIO if greater than 0 cli ; disable interrupts mov TMP,BUTTON_PRIO ; branch if 0 cpi TMP,0 breq BUTTON_PRIO_PROC_NO_DEC dec BUTTON_PRIO ; decrement BUTTON_PRIO_PROC_NO_DEC: sei ; enable interrupts ; done ret ; serial port timeout detection ; - must be called with interrupts enabled ; - changes: SER_IDLE, TMP SER_TIMEOUT_DETECT: ldi TMP,SER_TIMEOUT ; check if timeout cp SER_IDLE,TMP brsh SER_TIMEOUT_DETECT_NO_INC ; branch if timeout already detected inc SER_IDLE ; increment serial port idle time SER_TIMEOUT_DETECT_NO_INC: ; done ret ; sync to timing of serial line ; - must be called every 32 microseconds ; - must be called with interrupts enabled SER_SYNC: ; sample serial input clr TMP sbic PIN_SER_IN,BIT_SER_IN dec TMP ; check if state of serial input changed cp TMP,SER_LAST_STATE brne SER_SYNC_CHANGE ; state changed ; state of serial input did not change sbrs SER_UNCHANGED_CNT,7 ; count number of times the state did not change (limit count at 0x80) inc SER_UNCHANGED_CNT ret ; done SER_SYNC_CHANGE: ; state of serial input changed ; - 4800 bps -> 208.33 us per bit -> 6.51 samples per bit (sampled every 32 us) ; - state unchanged 5 or 6 times in a row (both with about 50% probability) for single bits on serial line ; - conclusion: ; - <= 3 unchanged samples -> wrong measurement ; - 4 or 5 unchanged samples -> running too slow ; - 6 or 7 unchanged samples -> running too fast ; - >= 8 unchanged samples -> wrong measurement mov SER_LAST_STATE,TMP ; save new state mov TMP,SER_UNCHANGED_CNT ; save unchanged count clr SER_UNCHANGED_CNT ; reset unchanged count cpi TMP,5 breq SER_SYNC_SLOW ; running too slow cpi TMP,6 breq SER_SYNC_FAST ; running too fast cpi TMP,4 breq SER_SYNC_SLOW ; running too slow cpi TMP,7 breq SER_SYNC_FAST ; running too fast ret ; wrong measurement -> done ; accumulate trend if running too slow or too fast SER_SYNC_SLOW: dec SER_SYNC_TREND rjmp SER_SYNC_TREND_REACT SER_SYNC_FAST: inc SER_SYNC_TREND ; react if trend gets too low or too high SER_SYNC_TREND_REACT: mov TMP,SER_SYNC_TREND cpi TMP,SYNC_TREND_LIMIT+1 brge SER_SYNC_RC_SLOWER ; configure to run slower (running too fast at the moment) mov TMP,SER_SYNC_TREND cpi TMP,-SYNC_TREND_LIMIT brlt SER_SYNC_RC_FASTER ; configure to run faster (running too slow at the moment) ret ; done ; configure RC oscillator to run slower SER_SYNC_RC_SLOWER: clr SER_SYNC_TREND ; reset trend cli ; disable interrupts in TMP,OSCCAL ; read calibration value cpi TMP,0x00+1 ; decrement if not yet at minimum brlo SER_SYNC_RC_SLOWER_MIN dec TMP out OSCCAL,TMP ; write new calibration value SER_SYNC_RC_SLOWER_MIN: sei ; enable interrupts ret ; done ; configure RC oscillator to run faster SER_SYNC_RC_FASTER: clr SER_SYNC_TREND ; reset trend cli ; disable interrupts in TMP,OSCCAL ; read calibration value cpi TMP,0x7F ; increment if not yet at maximum brsh SER_SYNC_RC_FASTER_MAX inc TMP out OSCCAL,TMP ; write new calibration value SER_SYNC_RC_FASTER_MAX: sei ; enable interrupts ret ; done ; get number of times to blink in current status output cycle ; - must be called with interrupts enabled ; - changes: STATUS_BLINK, TMP STATUS_GET_BLINK: ; blink 1 time by default ldi TMP,1 mov STATUS_BLINK,TMP ; blink 2 times in case of serial timeout ldi TMP,SER_TIMEOUT cp SER_IDLE,TMP brlo STATUS_GET_BLINK_NO_SER_TIMEOUT ldi TMP,2 mov STATUS_BLINK,TMP STATUS_GET_BLINK_NO_SER_TIMEOUT: ; blink 3 times if button has got priority ldi TMP,0 cp BUTTON_PRIO,TMP breq STATUS_GET_BLINK_NO_BUTTON_PRIO ldi TMP,3 mov STATUS_BLINK,TMP STATUS_GET_BLINK_NO_BUTTON_PRIO: ; done ret ; status output ; - must be called with interrupts enabled ; - changes: STATUS_BLINK, STATUS_CNT, TMP STATUS_OUT: ; count status output intervals inc STATUS_CNT ; count from 0 to 15 ldi TMP,16 cp STATUS_CNT,TMP brlo STATUS_OUT_NO_CLR clr STATUS_CNT rcall STATUS_GET_BLINK ; get number of times to blink in current status output cycle STATUS_OUT_NO_CLR: ; turn status LED on or off cbi PORT_STATUS,BIT_STATUS ; turn off status LED sbrc STATUS_CNT,1 ; status LED can only be turned on in steps 0, 1, 4, 5, ... rjmp STATUS_OUT_OFF mov TMP,STATUS_CNT ; get number of step divided by 4 lsr TMP lsr TMP cp TMP,STATUS_BLINK ; status LED on if smaller than the number of times to blink brsh STATUS_OUT_OFF sbi PORT_STATUS,BIT_STATUS ; turn on status LED STATUS_OUT_OFF: ; done ret ; animation mode data ; - format: <PWM lamp 1> <PWM lamp 2> <PWM lamp 3> <PWM lamp 4> <ticks (100ms)> <reserved> ANIM_DATA: ; off ; .DB 0x00, 0x00, 0x00, 0x00, 0x1E, 0x00 ; off, 3.0s ; 1 - 4 ; .DB 0x7F, 0x00, 0x00, 0x00, 0x05, 0x00 ; 1st half on, 0.5s ; .DB 0x00, 0x00, 0x00, 0x00, 0x1E, 0x00 ; off, 3.0s ; .DB 0x00, 0x7F, 0x00, 0x00, 0x05, 0x00 ; 2nd half on, 0.5s ; .DB 0x00, 0x00, 0x00, 0x00, 0x1E, 0x00 ; off, 3.0s ; .DB 0x00, 0x00, 0x7F, 0x00, 0x05, 0x00 ; 3st half on, 0.5s ; .DB 0x00, 0x00, 0x00, 0x00, 0x1E, 0x00 ; off, 3.0s ; .DB 0x00, 0x00, 0x00, 0x7F, 0x05, 0x00 ; 4nd half on, 0.5s ; .DB 0x00, 0x00, 0x00, 0x00, 0x1E, 0x00 ; off, 3.0s ; KITT ; .DB 0xFF, 0x5F, 0x3F, 0x00, 0x02, 0x00 ; .DB 0x5F, 0xFF, 0x00, 0x00, 0x02, 0x00 ; .DB 0x1F, 0x5F, 0xFF, 0x00, 0x02, 0x00 ; .DB 0x00, 0x1F, 0x5F, 0xFF, 0x02, 0x00 ; .DB 0x00, 0x00, 0xFF, 0x5F, 0x02, 0x00 ; .DB 0x00, 0xFF, 0x5F, 0x1F, 0x02, 0x00 ; round running light .DB 0xFF, 0x00, 0x3F, 0x5F, 0x03, 0x00 .DB 0x5F, 0xFF, 0x00, 0x3F, 0x03, 0x00 .DB 0x3F, 0x5F, 0xFF, 0x00, 0x03, 0x00 .DB 0x00, 0x3F, 0x5F, 0xFF, 0x03, 0x00 ANIM_DATA_END: ; animation mode ; - must be called with interrupts enabled ; - changes: ANIM_TICKS, ANIM_FRAME, Y, Z, TMP ANIM_MODE: ; exit if no serial timeout (i.e. no animation mode) ldi TMP,SER_TIMEOUT cp SER_IDLE,TMP brsh ANIM_MODE_SER_TIMEOUT ret ANIM_MODE_SER_TIMEOUT: ; exit if button has got priority mov TMP,BUTTON_PRIO cpi TMP,0 breq ANIM_MODE_BUTTON_NO_PRIO ret ANIM_MODE_BUTTON_NO_PRIO: ; check if anim mode is enabled sbrs BITS,BIT_ANIM_ENAB rjmp ANIM_MODE_DISABLED ; decrement ticks and exit (if not zero yet) mov TMP,ANIM_TICKS cpi TMP,0 breq ANIM_MODE_NEXT_FRAME dec ANIM_TICKS ret ANIM_MODE_NEXT_FRAME: ; show next frame inc ANIM_FRAME ldi TMP,(ANIM_DATA_END*2-ANIM_DATA*2)/6 cp ANIM_FRAME,TMP brlo ANIM_MODE_SHOW_FRAME clr ANIM_FRAME ANIM_MODE_SHOW_FRAME: ; disable interrupts cli ; get address of animation mode data ldi ZH,HIGH(ANIM_DATA*2) ; base address of lighting mode table ldi ZL,LOW(ANIM_DATA*2) clr TMP ; add offset: 6 times the frame number add ZL,ANIM_FRAME adc ZH,TMP add ZL,ANIM_FRAME adc ZH,TMP add ZL,ANIM_FRAME adc ZH,TMP add ZL,ANIM_FRAME adc ZH,TMP add ZL,ANIM_FRAME adc ZH,TMP add ZL,ANIM_FRAME adc ZH,TMP ; set new PWM values ldi YH,HIGH(PWM_CUR) ; base address of current PWM values ldi YL,LOW(PWM_CUR) lpm TMP,Z+ ; use lighting mode data as current PWM values st Y+,TMP lpm TMP,Z+ st Y+,TMP lpm TMP,Z+ st Y+,TMP lpm TMP,Z+ st Y+,TMP ; load tick counter lpm ANIM_TICKS,Z+ ; write current PWM values to PWM registers rcall PWM_OUT ; enable interrupts sei ; done ret ; animation mode disabled - turn off lamps ANIM_MODE_DISABLED: ; disable interrupts cli ; set PWM values to 0 ldi YH,HIGH(PWM_CUR) ; base address of current PWM values ldi YL,LOW(PWM_CUR) clr TMP ; turn off lights st Y+,TMP st Y+,TMP st Y+,TMP st Y+,TMP ; write current PWM values to PWM registers rcall PWM_OUT ; enable interrupts sei ; done ret ; detect when 100 milliseconds have elapsed and then do some stuff ; - must be called every millisecond ; - must be called with interrupts enabled MS100: ; exit if 100 milliseconds have not yet elapsed inc MS100_CNT ; milliseconds counter ldi TMP,100 ; divibe by 100 cp MS100_CNT,TMP brsh MS100_ELAPSED ret MS100_ELAPSED: clr MS100_CNT ; clear milliseconds counter ; button priority processing rcall BUTTON_PRIO_PROC ; serial port timeout detection rcall SER_TIMEOUT_DETECT ; status output rcall STATUS_OUT ; animation mode rcall ANIM_MODE ; done ret ; detect when a milliseconds has elapsed and then do some stuff ; - must be called every 32 microseconds ; - must be called with interrupts enabled MS: ; exit if millisecond has not yet elapsed inc MS_CNT ; 32kHz counter ldi TMP,32 ; divibe by 32 cp MS_CNT,TMP brsh MS_ELAPSED ret MS_ELAPSED: clr MS_CNT ; clear 32kHz counter ; button processing rcall BUTTON_PROC ; 100 ms stuff rcall MS100 ; done ret ; main program MAIN: ; initialization clr BITS ldi TMP,0xFF ; no data bytes received yet (i.e. behind a message) mov RECV_CNT,TMP sts PWM_CUR+0,TMP ; all current PWM values to zero sts PWM_CUR+1,TMP sts PWM_CUR+2,TMP sts PWM_CUR+3,TMP clr MS_CNT ; initialize detection of milliseconds clr MS100_CNT ; initialize detection of 100 milliseconds clr BUTTON_STATE ; button is not pressed clr BUTTON_MODE ; begin with first lighting mode on first button press clr BUTTON_PRIO ; button does not have priority ldi TMP,SER_TIMEOUT ; serial port has had timeout mov SER_IDLE,TMP clr STATUS_BLINK ; do not blink in first status output cycle clr STATUS_CNT cbr BITS,1<<BIT_ANIM_ENAB ; animation mode disabled clr ANIM_TICKS ; start with first frame in animation mode clr ANIM_FRAME dec ANIM_FRAME ldi TMP,0x80 ; nothing known about last state and number of unchanged samples of serial port mov SER_LAST_STATE,TMP mov SER_UNCHANGED_CNT,TMP clr SER_SYNC_TREND ; no trend if running too slow or too fast ; enable interrupts sei MAIN_LOOP: wdr ; jump if timer 0 did not overflow yet in TMP,TIFR sbrs TMP,TOV0 rjmp MAIN_US32_END ldi TMP,1<<TOV0 ; clear overflow flag out TIFR,TMP ; sync to timing of serial line rcall SER_SYNC ; 1 ms stuff rcall MS MAIN_US32_END: ; bottom of main loop rjmp MAIN_LOOP