My journey with ATTiny4313 (part 5)
Part 5: Implementation of a State Machine
What is a State Machine?
Simply stated, it's an algorithm with a finite number of possible states. The machine transition from one state to another based on the input. In our case, we now what the MO44.DRV driver will send: a reset hardware, followed by the data for each of the 4 outputs. Every time, the /STROBE is pulsed down to warn the MO4 that a new byte is available (cf. part 4).
The algorithm
S1
If Strobe then State = S2
S2
If Strobe then State = S3
S3
If Strobe then State = S4
S4
If Strobe then State = S5
S5
If Strobe then
If unit = 0 then
State = S1
unit = 4
else
Read counter
If counter = 0 then
unit = unit - 1
else
If unit = ME then
State = S62
else
State = S61
End
End
End
End
S61
If Strobe then
While counter > 0 do nothing
State = S5
unit = unit - 1
End
S62
If Strobe then
While counter > 0
Read data
Store data to X
Increment X
End
State = S5
unit = unit - 1
End
The ijmp instruction
This instruction allows to jump to the address pointed by Z (16 bit pointer made by registers r30 and r31). It's very efficient and runs in 2 cycles. Since I know the first incoming byte always represents the quantity of data to follow, I can use the Z register to select what part of code to run. Then, every time /STROBE goes down, it triggers an interrupt; I now what is expected and execute only the good part of code.
I also decided to use a circular buffer in RAM; the X pointer is used to store the data, while another part of the code reads the buffer via the Y pointer and sends the data.
One important thing to consider is the RAM doesn't start at address 0, but at 0x060 and ranges up to 0x160. This has to be taken care of when rotating the address. To simplify and speed up, I truncate the address of the RAM buffer to 255 (0xff), which provides 160 bytes for buffering (0x060-0x0ff). This could sound short, but remember this for one output only, so 160 bytes of backlog for MIDI is quite a lot indeed!
...
; =========================================================
; Interrupt vectors
; =========================================================
Reset: rjmp Init
INT1addr: rjmp strobe
...
; =========================================================
; Configurations
; =========================================================
Init:
...
ldi ZH, hi8(S1)
ldi ZL, lo8(S1) ; Initialize the state machine
ldi r22, 0x04 ; Prepare the unit counter
...
; ==========================================
; Main loop: check if there some data
; ready to be sent in the RAM buffer
; ==========================================
Main:
cpse XL, YL ; If X = Y, no data awaiting
rjmp Main ; Loop
; ---- data awaiting to be sent via MIDI
ld r23, Y+ ; Load from RAM buffer, increment Y
wait_usart:
sbis UCSRA, UDRE ; Wait USART Data Register empty
rjmp wait_usart
STORE UDR, r23 ; Store the byte to send in the USART Data Register
andi YL, 0xff ; Make sure Y never exceeds 255
ori YL, 0x60 ; but at least 96
andi YH, 0x00
rjmp Main ; Loop
; =========================================================
; Interrupt Service Routine (ISR)
; =========================================================
; ---------------------------------
; /STROBE is detected: depending of the current state, perform action and setup the next state.
; ---------------------------------
strobe:
ijmp ; Indirect jump to the good state
S1:
cli
ldi ZH, hi8(S2)
ldi ZL, lo8(S2)
sei
reti
S2:
cli
ldi ZH, hi8(S3)
ldi ZL, lo8(S3)
sei
reti
S3:
cli
ldi ZH, hi8(S4)
ldi ZL, lo8(S4)
sei
reti
S4:
cli
LOAD r16, PORTD
sbr r16, (1<<BUSY)
STORE PORTD, r16 ; Raise BUSY
; Reset the RAM buffer
clr XH
ldi XL, 0x60 ; Set register X to 0x60
clr YH
ldi YL, 0x60 ; Set register Y to 0x60
ldi r22, 0x04 ; Prepare the unit counter
ldi ZH, hi8(S5)
ldi ZL, lo8(S5)
sei
reti
S5: ; Reading the number of bytes coming next
cli
in r21, PORTB ; Read counter from Port B
LOAD r16, PORTD
cbr r16, (1<<BUSY)
STORE PORTD, r16 ; Reset BUSY
cpi r21, 0x00 ; Data coming next ?
breq end ; No -> end
cpi r22, ME ; Are these data for me ?
breq next ; Yes -> prepare to read
ldi ZH, hi8(S61) ; No -> prepare to skip the bytes
ldi ZL, lo8(S61)
sei
reti
next: ; On the next /STROBE, we must read data. We prepare Z for ijmp.
ldi ZH, hi8(S62) ; The next bytes are data
ldi ZL, lo8(S62)
sei
reti
end: ; No data is coming next for this unit. We decrement the unit number. On the last unit (r22 = 0), we reset the state machine.
dec r22 ; Prepare for next unit
breq restart ; If last unit, prepare for next
sei
reti
S61: ; Skipping the incoming bytes
cli
dec r21 ; Decrement counter
breq eod ; End of data
sei
reti
S62: ; Reading and storing the incoming bytes
cli
in r20, PORTB ; Read data from Port B
st X+, r20 ; Store in RAM buffer, increment X
andi XL, 0xff ; Make sure X never exceeds 255
ori XL, 0x60 ; but is at least 96
andi XH, 0x00
dec r21 ; Decrement counter
breq eod ; End of data
sei
reti
eod: ; Either the data were read or skipped, this is the end of data
dec r22 ; Prepare for next unit
breq restart ; If last unit, reset the state machine
ldi ZH, hi8(S5) ; The next incoming byte is a counter
ldi ZL, lo8(S5)
sei
reti
restart: ; restart the state machine
ldi ZH, hi8(S1) ; Reset to S1
ldi ZL, lo8(S1)
sei
reti




Comments