Hi folks,
I'm new to programming the ATTiny (though not to programming assembly languages) and I just want to see if there is something that I'm missing here. Apologies for a long post, but I hope I've given enough clues to how I'm trying to work. Like everyone who starts, I've done the blinking LED (using timer overflow interrupts)
Now I am trying to get a FSM working to handle a switch (debouncing and firing useful events).
Working in assembly language, I've set up the interrupt vector table, and implemented a do nothing interrupt handler for INT0 (basically I need to wake the processor up from power down at some future point), and an interrupt handler for timer overflow (which just sets a bit in SRAM). All other interrupts do a RETI. All working happily.
My restart code just sets the stack pointer to RAMEND, initialises the timer (CPU 9.6MHz, CPU clock prescaler 8, Timer prescaler 8, and overflowing every 256 - I reckon somewhere in the region of 3.4ms between timer overflow interrupts) and enables the interrupts before entering the main loop and going to sleep.
On timer overflow , I'm setting a flag. The main loop wakes up (NOP) then checks to see if the flag has been set. If so, calls the tick function which in turn operates the state machine - excerpt follows later. (Still happy - this all seems to be working.)
The FSM is implemented using a branch table, and given a state, it seems to be going to the correct code. (And yes, I'm checking my limits although not shown in the excerpt below: if the state is out of range, it gets forced to state 0). Each state samples the SW_IN pin to determine (along with the number of ticks) what action to take.
switch_table:
rjmp btn_0 ; idle
rjmp btn_1 ; debouncing
rjmp btn_2 ; click
; etc for SWITCH_STATE_NUM items
do_switch:
; get current state
lds TMP0, buttonState
ldi zh, high(switch_table) ; get the start address of the jump table
ldi zl, low(switch_table)
clr TMP1 ; 16 bit add of state, uses TMP1 which contains zero
add zl, TMP0
adc zh, TMP1
ijmp ; indirect jump to address in Z
end_switch: ; should not get here - expect RETs in state handler
ret
;---- state 0 - idle
;---- if button down then reset the tick counter and goto state 1
btn_0:
sbis PORTB, SW_IN ; return if button not pressed (SW_IN is high)
ret
btn_0_1:
clr TMP0 ; otherwise we clear the tick counter
sts buttonTime, TMP0
inc TMP0 ; and set the state to 1
sts buttonState, TMP0
ret
;---- state 1 - debouncing
;---- if pin high
;---- if tick counter < BTN_JITTER_TIMOUT then back to state 0
;---- else (tick counter >= BTN_JITTER_TIMEOUT) then goto state 2
;---- else (pin low)
;---- if tick counter >= BTN_PRESS_TIMOUT
;---- then
;---- set state to 4
;---- execute fire_press()
;---- else increment tick counter
btn_1:
sbis PORTB, SW_IN ; skip if pin high
rjmp btn_1_low
rjmp toggle_blue ; this is a test function
lds TMP0, buttonTime
cpi TMP0, BTN_JITTER_TIMEOUT ; about 50ms
brne btn_1_1 ; back to state 0
ldi TMP0, 2 ; else goto state 2
sts buttonState, TMP0
ret
btn_1_1: ; bit of jitter so back to state 0
clr TMP0
sts buttonState,TMP0
sts buttonTime, TMP0
ret
btn_1_low:
lds TMP0, buttonTime
cpi TMP0, BTN_PRESS_TIMOUT ; about 850ms
brlt btn_1_2
ldi TMP0, 4 ; state 4
rjmp fire_press ; pass control to fire_press event handler
btn_1_2:
inc TMP0 ; increment button time
sts buttonTime, TMP0
ret
fire_press:
rjmp some_action ; the RET is in some_action
Hope that makes sense. Other states are similar in structure.
I have a subroutine that toggles a LED (toggle_blue) so that I can see if we are reaching the correct part of code. But, I'm not sure that it is - if I call toggle_blue at label btn_0, the LED lights up as expected.
If I call toggle_blue at label btn_0_low, I would expect the LED to light up when the pushbutton is pressed. (Internal pullup enabled on PB1, direction set to input, pin passes through switch to 0v).
It doesn't seem to do that.
If I force the state machine to state 1 (ldi TMP0,1 instead of the lds TMP0, buttonState) , and call toggle_blue at btn_1, then again I see the LED lit up.
Sometimes when I hold the switch button down, the blue LED goes off when I don't expect it to, and the symptoms appear to be that I'm not reading the correct value from the input, although I have set the LED state to match the SW_IN state, and that seems to work as expected.
The only thing that occurs to me is that I'm using R16 (TMP0) in the interrupt service routine as well as in other places (and I know that's wrong!), but given the frequency of clock interrupts, and the number of bytes of code, I would expect wake-up, check for timer interrupt flag, do state processing, go back to sleep, to be done in maybe a hundred instructions - which should be much shorter than the interval between timer interrupts.
I don't expect to use more than four or five words of stack unless I've done something wrong.
Am I missing something here?
Are my assumptions about timing correct?
Am I testing the input correctly?
Have I done something stupid (for example, getting my high and low states mixed up)?