;Atmel AVR Design Contest 2006 Registration Number AT3223



; Source: stationMaster.asm

; Application: train stop alarm.

; Date: 07/05/06.

; Version: 0.1

; Assembler: AVRA: advanced AVR macro assembler Version 1.0.1 Build 777

; Assembler: AVR Assembler v1,v2

; Target: Atmel ATtiny13.

; Platform: Any.


.INCLUDE "tn13def.inc"

; Register Definitions

.def saveSREG =r0 ;save status reg during ISR

.def zeroReg =r1 ;always leave at zero

.def accelL =r2 ;current acceleration readings

.def accelH =r3 ;

.def totalL =r4 ;used for averaging readings

.def totalH =r5 ;

.def lastL =r7 ;last adc readings

.def lastH =r8 ;

.def f_state =r9 ;filter status

.def f_lnoiseCnt =r10 ;number of low noise samples

.def f_accum =r11 ;current accumulator value

.def f_sampleCnt =r12 ;current total sample count

.def f_lastCnt =r6 ;samplecount when accumulator last overflowed

.def station =r13 ;no. of stations counted

.def alarm =r14 ;no. of stations for alarm.

.def pwm =r15 ;current pwm value from table

.def tempa =r16 ;temporary register

.def tempb =r17 ;another temporary register

.def tempc =r18 ;again with the temporary

.def countL =r19 ;storage for 16bit counter for delay between seconds

.def countH =r20 ;

.def bounceL =r21 ;Low Byte debounce delay count

.def bounceH =r25 ;High byte for bounce counter 

.def tickL =r22 ;counter for 16Hz ADC sampling frequency

.def tickH =r23 ;

.def state =r24 ;machine state

; Machine Status Bits

.equ sLONG =0 ;we are monitoring for a long press

.equ sBUTTON1 =1 ;button1 Status 

.equ sREADADC =2 ;read adc and update filter 

.equ sALARM =3 ;the alarm is going off *click to cancel

.equ sIDLE =4 ;we are in low power.mode

.equ sDEBOUNCE =5 ;we are debouncing a switch

.equ sMOTOR =7 ;motor state

; Filter status Bits

.equ fMOVING =0 ;are we moving?

.equ fLASTMOVING =1 ;were we moving?

.equ fLOWNOISE =2 ;low noise condition met

.equ AVGSIZE =3 ;average (1<<AVGSIZE) samples

.equ ADC_LST =3 ;first ADC channel for X acceleration

.equ ADC_FST =1 ;last ADC channel for X acceleration

.equ MOTOR_PWR =0xFF ;pwm level for motor 0xFF=100% on shutdown

.equ STATION_INC =01 ;number of Stations to add with each click.

.equ PWM_SPD =04 ;speed to read Sine table for pulsing

.equ PWM_FULL =0x40 ;position in Table of full power

.equ PWM_ZERO =0xC0 ;position in table of zero power

; Tuning variables for the filter

.equ ACC_THRESHOLD =25 ;sum of squared deltas considered noise

.equ ACCUMULATORLIMIT =64 ;number of noisy samples to accumulate

.equ LOW_COUNT =16 ;more than this many samples is low noise

.equ HIGH_COUNT =12 ;less than this many samples is high noise

.equ TOP_LIMIT =200 ;hysterysis count limit for stationary, 0 = moving

.equ FCPU = 9600000 ;(9.6Mhz) remember to change CLKSP 

.equ FT0 = FCPU/64 ;Frequency for Timer0

.equ SECONDCOUNT = FT0/510 ;see DS page 66 

.equ BOUNCECOUNT = SECONDCOUNT/50 ;20ms for debounce timer & sine table

.equ TICKCOUNT = SECONDCOUNT/16 ;16Hz tick for ADC reads.

;Do hard reset/ power up initialization here.

.INCLUDE "tiny13_init.asm"


;****************************   Main Program   *****************************



; soft reset and post power up initialization goes here

; We can jump back to here if we ever want to do a "soft" reset.

; (this reset doesn't effect any IO pin state just internal software)

cli ;stop all interrupts while we do some inits

;reset Stack after a "soft reset"

ldi tempa, low(RAMEND)

out SPL,tempa ; Set Stack Pointer to top of RAM

;reset counters and registers.

clr ZH

clr ZL


st Z+,zeroReg ;clear this mem address (0x00->0x1F = registers)

cpi ZL,0x1E ;stop when we get to Z low byte (r30)

brne reg_clr_loop

;load default state

ldi state,(0<<sMOTOR)|(1<<sDEBOUNCE)|(1<<sIDLE)|(0<<sBUTTON1)|(0<<sALARM)|(0<<sLONG)

ldi ZH,high(sine<<1)

ldi ZL,low(sine<<1)

lpm pwm,Z


; sleep enable, power down mode, stop timers so only a pin change should wake us

ldi tempa,(0<<PUD)|(1<<SE)|(1<<SM1)|(0<<SM0)|(0<<ISC01)|(0<<ISC00)

out MCUCR,tempa


; this is our OFF state

; we remain in low power mode until a button is presssed

ldi tempa,(0<<PUD)|(0<<SE)|(0<<SM1)|(0<<SM0)|(0<<ISC01)|(0<<ISC00)

out MCUCR,tempa






;check for alarm status


sbrs state,sALARM

rjmp update_motor

;Update motor for alarm state


out OCR0A,pwm ;set motor power

rjmp check_button1

;Update motor for normal state


sbrs state,sMOTOR

rjmp motor_OFF

out OCR0A,pwm ;set motor power

rjmp check_button1


clr tempb ;turn motor off

; out OCR0A, tempb


; out OCR0A, f_sampleCnt ;FOR DEBUG>> REMOVE ME LATER

;Read buttons

check_button1: ; control switch

sbrs state,sBUTTON1

rjmp update_filter

; Button 1 pushed -> add station to count.

;clear button press flag ;clear alarm if it is sounding

cbr state,(1<<sBUTTON1)|(1<<sIDLE)|(1<<sALARM)

;Add another station to the countdown.


;reset timers

clr countL

clr countH

sbr state,(1<<sLONG) ;Flag for long key press

;increment the alarm setting

ldi tempa,STATION_INC

add alarm,tempa

sbr state,(1<<sMOTOR) ;pulse to acknowledge

ldi ZL,PWM_ZERO ;soft fade in

;Check if motor is off and read adc if an update is due


ldi tempa,(1<<sMOTOR)|(1<<sALARM)

and tempa,state ;dont read acccel when motor is on

brne end

sbrs state,sREADADC ;check if an update is due

rjmp end

cpse alarm,zeroReg ;dont update if no alarm is set

rcall filter ;read ADC and update f_sate etc.

cbr state, (1<<sREADADC) ;clear the flag


; Go to sleep and wait for an interupt to wake us. 

; (IDLE MODE) keep timers running. stop main clock.


ldi tempa,(0<<PUD)|(1<<SE)|(0<<SM1)|(0<<SM0)|(0<<ISC01)|(0<<ISC00)

out MCUCR,tempa


; just woke up, disable sleep

ldi tempa,(0<<PUD)|(0<<SE)|(0<<SM1)|(0<<SM0)|(0<<ISC01)|(0<<ISC00)

out MCUCR,tempa


rjmp loop


; ISR for Timer 0, see Ft0 above for freauency


; Changes: saveSREG,tempa,tempb,state,countL,countH,bounceH,bounceL,tickH,tickL,

; pwm,ZL



in saveSREG,SREG ;Save status register

ldi tempa,0x01

add countL,tempa

adc countH,zeroReg ;add zero to ensure carry is counted

add bounceL,tempa ;increment debounce timer

adc bounceH,zeroReg

add tickL,tempa

adc tickH,zeroReg


cpi tickH, high(TICKCOUNT)

brne ISR_T0_bounce

cpi tickL, low(TICKCOUNT)

brne ISR_T0_bounce

;This is set every 62.5ms to  read the acceldata from the adc

sbr state,(1<<sREADADC)

clr tickH

clr tickL


cpi bounceH, high(BOUNCECOUNT) ;compare bounce time  20mS

brne ISR_T0_one_second

cpi bounceL, low(BOUNCECOUNT)

brne ISR_T0_one_second

;This is run every ~20ms or <20ms after button push

cbr state,(1<<sDEBOUNCE) ;reset debounce status after 20ms

lpm pwm,Z ;read pwm table

ldi tempa,PWM_SPD

add ZL,tempa ;next address(should roll over at 256)

clr bounceL

clr bounceH


cpi countH, high(SECONDCOUNT)

brne ISR_T0_exit

cpi countL, low(SECONDCOUNT)

brne ISR_T0_exit

;This is run once every second.

clr countL ;reset second timers

clr countH ;

mov tempa,state ;check for long press

andi tempa,(1<<sLONG)

brne ISR_T0_longpress

cbr state,(1<<sMOTOR) ;Motor OFF, only ever runs for 1s 

;except in alarm condition.


out SREG,saveSREG



;If flag is set at end of a second then

;button hasn't been released for 1 second.

;so we do a reset without switching off the 


ldi tempa,MOTOR_PWR ;turn motor on

out OCR0A,tempa ;set motor power

;wait for button to be released before continuing



rjmp ISR_T0_btn_on_loop

rjmp MAIN


; Pin Change ISR, called on change of status of PB1 

; (set by PCIMSK and DDRB)


; Changes: saveSREG,tempa,tempb,state,bounceL,bounceH



in saveSREG,SREG ;Save status register

in tempa,PINB ;read buttons immediatly

;check debounce timer

mov tempb,state

andi tempb,(1<<sDEBOUNCE)

brne ISR_PC_exit ;exit if we are debouncing


com tempa ;invert so 1 = pushed 0 = open

andi tempa, (1<<PINB1) ;mask buttons only

or state,tempa ;sBUTTON1 = PB1 ;)

andi tempa, (1<<PINB1) ;look at button again

brne ISR_PC_debounce_start ;if closed continue

cbr state,(1<<sLONG) ;else clear the long press flag


clr bounceH ;reset bounce timer

clr bounceL

sbr state,(1<<sDEBOUNCE) ;set machine state


out SREG,saveSREG



; read and average ADC channel


; Params: tempa mux value

; Returns: accelL,accelH 16Bit averaged value of adc

; Destroys: tempa, tempb



ori tempa,(0<<REFS0)|(0<<ADLAR) ; select ADC port 1,2 or 3

out ADMUX,tempa

; do a dummy read after changing MUX

sbi ADCSRA,ADSC ;Start Conversion



rjmp ADC_read_wait0

;save any other sum

push totalL

push totalH

; clear our sum 

clr totalL

clr totalH

; do 8 reads max 8 * 0x3ff = 0x1ff8

ldi tempa,(1<<AVGSIZE)


  sbi ADCSRA,ADSC ;Start Conversion



rjmp ADC_read_wait1

in accelL,ADCL

in accelH,ADCH

add totalL,accelL ;add to running total

adc totalH,accelH

dec tempa

brne ADC_read_loop0

;average with a shift >> 3 bits

ldi tempa,AVGSIZE


lsr totalH

ror totalL

dec tempa

brne ADC_read_loop1

;save accel Reads for later use

mov accelH,totalH

mov accelL,totalL


pop totalH

pop totalL



; Sqr: Perform a quick nasty limited(clipped) square function


; Params: tempa=input, ACC_THRESHOLD= limit value

; Returns: tempa

; Changes: uses 2 bytes on the stack



push tempb ;tempb is loop counter

push tempc ;tempc is the sum

mov tempb,tempa

clr tempc

tst tempa ;test for zero, return zero

breq sqr_exit


add tempc,tempa

cpi tempc,ACC_THRESHOLD+1 ;exit as soon as result is large

brsh sqr_exit ;enough to trip accumulator

dec tempb

brne sqr_loop


mov tempa, tempc

pop tempc

pop tempb




; filter: Implement the filter to determine if we are moving


; (does lots of nasty things and ignores overflows in the maths). Basically

; takes information from x,y,z Accel and looks at counts number of noisey

; samples in recent history to determine our present state of motion


; Params: None

; Returns: None

; Changes: totalL,totalH,tempa,tempb,tempc, <last_accel>, f_state,

; f_accum, f_accCnt, f_lnoiseCnt, state, station, alarm

; Calls: ADC_read, Sqr


clr totalL ;16bits, maximum total is 3*0x3ff

clr totalH

ldi YL,low(last_accel) ;point to RAM containing last 3 reads

ldi YH,high(last_accel)

;first read the three ADC Channels and calculate a limited squared sum

;of the difference

ldi tempc,ADC_FST ;point to first ADC

filter_loop0: ;tempc is loop counter

mov tempa,tempc

rcall ADC_read

ld lastL, Y ;load this channels last ADC read 

ldd lastH, Y + 3

st Y+, accelL ;save channel for next time and move Y to next 

std Y+2, accelH

;find the delta

cp lastL,accelL

cpc lastH,accelH

brsh filter_delta_plus

mov tempa,accelL ;calculate delta if accl > ax

mov tempb,accelH

sub tempa,lastL

sbc tempb,lastH

rjmp filter_delta_total

filter_delta_plus: ;calculate delta if ax > accl

mov tempa,lastL

mov tempb,lastH

sub tempa,accelL

sbc tempb,accelH


cpse tempb, zeroReg

ser tempa ;set tempa to 0xff if delta is greater than 255

rcall sqr ;square and sum the delta

add totalL, tempa

adc totalH, zeroReg

inc tempc

ldi tempa, ADC_LST

cp tempa, tempc

brsh filter_loop0 ;end of loop, read next ADC

cp totalH, zeroReg 

breq filter_accumulate

ser tempa ;set to 0xff as maximum for rest of calulations

mov totalL,tempa ;high byte is ignored

; Now measure the spread of the signal by counting the 

; number of samples needed to count ACCUMULATORLIMIT samples with a delta above



inc f_sampleCnt ;count sample

brne filter_accumulate_test ;check for overflow

dec f_sampleCnt ;limit overflow to 0xFF

rjmp filter_accumulate_set ;increment accumulator on overflow 


;if (sum>ACC_THRESHOLD){ ;Is the current sample noisey?


cp tempa,totalL

brsh filter_low_count ;No, ok carry on as per normal

inc f_accum ;Yes, ok increment the Accumulator

;if (accum>ACCUMULATORLIMIT){ ;Have we found more than our target of 


cp tempa,f_accum

brsh filter_low_count


sub f_sampleCnt,tempa ;at noisest it takes ACCUMULATORLIMIT


mov f_lastCnt,f_sampleCnt ;save count value 

clr f_accum

clr f_sampleCnt



;Implement some hysteris between stopped and moving states by incrementing 

;f_lnoiseCnt until we hit TOP_LIMIT or 0. Direction is controlled by value of

;last accumulator count (f_accCnt-ACCUMULATORLIMIT) stored in RAM at last_accum


;if (acc_last>LOW_COUNT){

ldi tempb,LOW_COUNT

cp tempb,f_lastCnt

brsh filter_high_count

inc f_lnoiseCnt

ldi tempb,TOP_LIMIT

cp f_lnoiseCnt,tempb

brlo filter_stop_count

;we have hit the top for our low noise count

;we must be stopped

dec f_lnoiseCnt ;dec to prevent ovflw next time

ldi tempa,~(1<<fMOVING) ;Clear moving flag

and f_state,tempa;

rjmp filter_stop_count



;else if (acc_last<HIGH_COUNT){

ldi tempb,HIGH_COUNT

cp f_lastCnt,tempb

brsh filter_stop_count

cpse f_lnoiseCnt,zeroReg

dec f_lnoiseCnt

brne filter_stop_count

;we have hit bottom for our low noise count

;we must be moving again

ldi tempa,(1<<fMOVING) ;Set moving flag

or f_state,tempa;

rjmp filter_exit



mov tempa,f_state

andi tempa,(1<<fMOVING)|(1<<fLASTMOVING)

cpi tempa,(1<<fLASTMOVING) ;Have we just stopped?

brne filter_exit

inc station ;next station

; clr countL

; clr countH

; ldi ZL,PWM_ZERO ;set PWM to zero in table DEBUG ONLY

; sbr state,(1<<sMOTOR) ;Pulse motor to show count DEBUG ONLY

cp station,alarm ;check with alarm value

brne filter_exit

ldi ZL,PWM_FULL ;set PWM to full in table

sbr state,(1<<sALARM) ;WAKE UP!@!!! you have arrived

clr alarm ;set alarm to 0 stations

clr station ;set station count to 0

filter_exit:;save current motion state

ldi tempa,(1<<fMOVING)

and tempa,f_state

breq filter_clearlastmoving

ldi tempa,(1<<fLASTMOVING)

or f_state,tempa

rjmp filter_end


ldi tempa,~(1<<fLASTMOVING)

and f_state,tempa



; Sine wanve table from Jesper Hansens mini DDS

; http://www.myplace.nu/avr/minidds/index.htm

; put this on a 0x0100 boundary (ZH =00 at Table=00)

; this will be the last thing in memory from 0x180 - 0x1FF (0x300-0x3FF in bytes)

.org 0x180

sine: ; 256 step sinewave table

.db 0x80,0x83,0x86,0x89,0x8c,0x8f,0x92,0x95,0x98,0x9c,0x9f,0xa2,0xa5,0xa8,0xab,0xae

.db 0xb0,0xb3,0xb6,0xb9,0xbc,0xbf,0xc1,0xc4,0xc7,0xc9,0xcc,0xce,0xd1,0xd3,0xd5,0xd8

.db 0xda,0xdc,0xde,0xe0,0xe2,0xe4,0xe6,0xe8,0xea,0xec,0xed,0xef,0xf0,0xf2,0xf3,0xf5

.db 0xf6,0xf7,0xf8,0xf9,0xfa,0xfb,0xfc,0xfc,0xfd,0xfe,0xfe,0xff,0xff,0xff,0xff,0xff

.db 0xff,0xff,0xff,0xff,0xff,0xff,0xfe,0xfe,0xfd,0xfc,0xfc,0xfb,0xfa,0xf9,0xf8,0xf7

.db 0xf6,0xf5,0xf3,0xf2,0xf0,0xef,0xed,0xec,0xea,0xe8,0xe6,0xe4,0xe2,0xe0,0xde,0xdc

.db 0xda,0xd8,0xd5,0xd3,0xd1,0xce,0xcc,0xc9,0xc7,0xc4,0xc1,0xbf,0xbc,0xb9,0xb6,0xb3

.db 0xb0,0xae,0xab,0xa8,0xa5,0xa2,0x9f,0x9c,0x98,0x95,0x92,0x8f,0x8c,0x89,0x86,0x83

.db 0x80,0x7c,0x79,0x76,0x73,0x70,0x6d,0x6a,0x67,0x63,0x60,0x5d,0x5a,0x57,0x54,0x51

.db 0x4f,0x4c,0x49,0x46,0x43,0x40,0x3e,0x3b,0x38,0x36,0x33,0x31,0x2e,0x2c,0x2a,0x27

.db 0x25,0x23,0x21,0x1f,0x1d,0x1b,0x19,0x17,0x15,0x13,0x12,0x10,0x0f,0x0d,0x0c,0x0a

.db 0x09,0x08,0x07,0x06,0x05,0x04,0x03,0x03,0x02,0x01,0x01,0x00,0x00,0x00,0x00,0x00

.db 0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x02,0x03,0x03,0x04,0x05,0x06,0x07,0x08

.db 0x09,0x0a,0x0c,0x0d,0x0f,0x10,0x12,0x13,0x15,0x17,0x19,0x1b,0x1d,0x1f,0x21,0x23

.db 0x25,0x27,0x2a,0x2c,0x2e,0x31,0x33,0x36,0x38,0x3b,0x3e,0x40,0x43,0x46,0x49,0x4c

.db 0x4f,0x51,0x54,0x57,0x5a,0x5d,0x60,0x63,0x67,0x6a,0x6d,0x70,0x73,0x76,0x79,0x7c


last_accel: .byte 6 ;2 bytes per reading x,y,z

; last_count: .byte 1 ;hold the last accumulatorcount MOVED TO REGISTER R6