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
  

Follow up:  Part 1  Part 2  Part 3  Part 4  Part 5  Part 6 .

Comments

Popular Posts