RC2024 – Part 7 – Reading The Rotary Encoder Using Z80 Assembly Language

RC2014 with Rotary Encoder and Digital IO modules

Previously as part of this year’s Retro Challenge, I learnt how to build and run Z80 assembly language programs on my RC2014 Classic 2 computer.

I now want to recreate the BASIC program that moves LEDs using Z80 Assembly Language.

I am using IO address $DE for my Rotary Encoder module. I also have the Digital I/O module using IO address $03. I can define these as constants so I can easily change them if necessary.

I need to store values for the input, the output, and the last value of the CLK pin. The input will just be the value from the IN operation. The output value will be the byte we want to show on the LED output. I will set this to be %00000001 initially. When I turn the encoder, I want this to shift to either the left or right. If it reaches the edge, I want it to wrap. The Z80 operations RLCA and RRCA will do this for me.

To check if a bit is high or low, I can use an AND operation to mask out out other values. For example, to check if the SW1 is being pressed, I can do the following.

    SW1         EQU %00000100
    INPUT_PORT  EQU $DE

    in a,(INPUT_PORT)
    and SW1
    cp SW1
    jp z, end    ; jump to end if SW is high.

I can repeat this logic to check the values of CLK1 and DT1.

This is the code I have come up with.

    OUTPUT rotaryencoderledcontrol.z80

; On the RC2014 Classic 2 running from BASIC the Z80
; code runs from address $9000. 
    ORG $9000

; The input and output ports to use.
INPUT_PORT  EQU $DE
OUTPUT_PORT EQU $03

; The input bits from the rotary encoder.
CLK1    EQU %00000001
DT1     EQU %00000010
SW1     EQU %00000100

; show the inital led value
    ld a,(output)
    out (OUTPUT_PORT),a

loop:
; load the last clk value into register b
    ld a,(lastclk)
    ld b,a

; read the input port and store in "input"
    in a,(INPUT_PORT)
    ld (input),a

; now check if the switch on first rotary encode has been
; pressed. If it has jump to end
    and SW1
    cp SW1
    jp z, end

; now see if clk1 matches the lastclk. If it does loop
    ld a,(input)
    and CLK1
    ld (lastclk),a
    cp b
    jp z, loop

; now work out what direction we are moving
check_case1:
    ld  a,(input)          
    and CLK1
    cp  CLK1             
    jr  nz, check_case2

    ld  a,(input)         
    and DT1
    cp  DT1
    jr  z, left     ; if both CLK and DT are high then left

check_case2:
    ld  a, (input)
    and CLK1
    cp  0 
    jr  nz, right

    ld  a, (input)
    and DT1
    cp  0 
    jr  nz, right  

; we must be turning left so rotate the output to the left
; and store it before going back to the start of the loop.
left:
    ld a,(output)
    rlca
    out (OUTPUT_PORT),a
    ld (output),a

    jp loop

; we must be turning right so rotate the output to the right
; and store it before going back to the start of the loop.
right:
    ld a,(output)
    rrca
    out (OUTPUT_PORT),a
    ld (output),a

    jp loop

; the switch has been pressed, so we clear the output
; and exit.
end:
    ld a,0
    out (OUTPUT_PORT),a

    ret

input:
    db  0
output:
    db  %00000001
lastclk:
    db  0

The LED successfully moves left and right depending on how I turn the rotary encoder. However, because I am checking in both high and low states of CLK1, it is moving two steps per turn. This will be too senstive to use in an application, so my next job is to change this to check once per turn.