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