;
;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 *****************************
;***************************************************************************
MAIN:
; 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
reg_clr_loop:
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
sei
; 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
sleep
; 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
cli
;****************************************************************************
; MAIN LOOP
;
loop:
;check for alarm status
check_alm:
sbrs state,sALARM
rjmp update_motor
;Update motor for alarm state
alarm_motor:
out OCR0A,pwm ;set motor power
rjmp check_button1
;Update motor for normal state
update_motor:
sbrs state,sMOTOR
rjmp motor_OFF
out OCR0A,pwm ;set motor power
rjmp check_button1
motor_OFF:
clr tempb ;turn motor off
; out OCR0A, tempb
out OCR0A, f_lnoiseCnt ;FOR DEBUG>> REMOVE ME LATER
; 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.
add_station:
;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
update_filter:
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
end:
; Go to sleep and wait for an interupt to wake us.
; (IDLE MODE) keep timers running. stop main clock.
sei
ldi tempa,(0<<PUD)|(1<<SE)|(0<<SM1)|(0<<SM0)|(0<<ISC01)|(0<<ISC00)
out MCUCR,tempa
sleep
; just woke up, disable sleep
ldi tempa,(0<<PUD)|(0<<SE)|(0<<SM1)|(0<<SM0)|(0<<ISC01)|(0<<ISC00)
out MCUCR,tempa
cli
rjmp loop
;****************************************************************************
; ISR for Timer 0, see Ft0 above for freauency
;
; Changes: saveSREG,tempa,tempb,state,countL,countH,bounceH,bounceL,tickH,tickL,
; pwm,ZL
;
ISR_T0:
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
ISR_T0_tick:
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
ISR_T0_bounce:
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
ISR_T0_one_second:
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.
ISR_T0_exit:
out SREG,saveSREG
reti
ISR_T0_longpress:
;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
;motor
ldi tempa,MOTOR_PWR ;turn motor on
out OCR0A,tempa ;set motor power
;wait for button to be released before continuing
ISR_T0_btn_on_loop:
sbis PINB,PINB1
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
;
ISR_PC:
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
ISR_PC_read_buttons:
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
ISR_PC_debounce_start:
clr bounceH ;reset bounce timer
clr bounceL
sbr state,(1<<sDEBOUNCE) ;set machine state
ISR_PC_exit:
out SREG,saveSREG
reti
;****************************************************************************
; read and average ADC channel
;
; Params: tempa mux value
; Returns: accelL,accelH 16Bit averaged value of adc
; Destroys: tempa, tempb
;
ADC_read:
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
ADC_read_wait0:
sbic ADCSRA,ADSC
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)
ADC_read_loop0:
sbi ADCSRA,ADSC ;Start Conversion
ADC_read_wait1:
sbic ADCSRA,ADSC
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
ADC_read_loop1:
lsr totalH
ror totalL
dec tempa
brne ADC_read_loop1
;save accel Reads for later use
mov accelH,totalH
mov accelL,totalL
ADC_read_exit:
pop totalH
pop totalL
ret
;****************************************************************************
; 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
;
sqr:
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
sqr_loop:
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
sqr_exit:
mov tempa, tempc
pop tempc
pop tempb
ret
;****************************************************************************
; 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
filter:
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
filter_delta_total:
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
; ACC_THRESHOLD
filter_accumulate:
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
filter_accumulate_test:
;if (sum>ACC_THRESHOLD){ ;Is the current sample noisey?
ldi tempa,ACC_THRESHOLD
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
ldi tempa,ACCUMULATORLIMIT ;ACCUMULATORLIMIT Noisey samples yet?
cp tempa,f_accum
brsh filter_low_count
;acc_last=acc_count-ACCUMULATORLIMIT;
sub f_sampleCnt,tempa ;at noisest it takes ACCUMULATORLIMIT
filter_accumulate_set:
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
filter_low_count:
;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
;}
filter_high_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
;}
filter_stop_count:
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
filter_clearlastmoving:
ldi tempa,~(1<<fLASTMOVING)
and f_state,tempa
filter_end:
ret
; 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
.dseg
last_accel: .byte 6 ;2 bytes per reading x,y,z
; last_count: .byte 1 ;hold the last accumulatorcount MOVED TO REGISTER R6