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

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.

RC2024 – Part 6 – Getting Z80 Assembly Language Programs On To The RC2014 Classic 2

As part of this year’s Retro Challenge, I am building a rotary encoder module for the RC2014 computer.

I have built a custom PCB, and I can use it from BASIC. However, I would also like to be able to use it from Z80 machine code.

To run Z80 machine code on my RC2014 Classic 2 I have a few options.

  1. Use BASIC to load in a hex dump of assembled code.
  2. Use the SCM ROM image to load in a hex dump of assembled code.
  3. Burn my assembled code into a ROM and insert that into the RC2014.

I have designed a new ROM PCB to help me do options 2 and 3 in the future. For now, I will use BASIC to load in assembled code and run it.

Before I can load assembled code, I need to write and assemble it.

I am going to do this on an Apple Macbook Pro. I’m going to need an assembler, and something to create hex dumps from the assembled code.

I am going to use SJASMPLUS as my Z80 assembler. On a Mac this needs to be built from the source code. In a terminal window the following should work.

make clean
make
sudo make install

To create the hex files, I am going to use the z88dk-appmake command from z88dk. z88dk is also provides an assembler and a C compiler that can build applications for the RC2014. I’m not going to use these at the moment. There are installation instructions and a binary that can easily be installed on a Mac.

You can use any text editor you want, but I’m going to be using Visual Studio Code. I’m also using the Z80 Assembly extension for syntax highlighting.

I’m going to write a simple Z80 assembly language program to read the the input from the rotary encoder and show it on the Digital I/O module’s LEDs. It’s going to exit when the rotary encoder’s switch is pressed.

The Rotary Encoder module is on input address $DE. The Digital I/O module is on input address $03.

    OUTPUT rotaryencodertest.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 $1
DT1     EQU $2
SW1     EQU $4


loop:
; read the input port
    in a,(INPUT_PORT)
; send the input directly to the output port
    out (OUTPUT_PORT),a

; now check if the switch on first rotary encode has been
; pressed. If it hasn't, loop back.
    and SW1
    cp SW1
    jp nz, loop

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

    ret

I’ve saved this as rotaryencodertest.s.

To assemble to code I need to use the following line in a terminal…

sjasmplus rotaryencodertest.s

To convert the output to intel format hex, I need to use the following line in a terminal…

z88dk-appmake +hex --org 0x9000 -b rotaryencodertest.z80

I should now I have a file called rotaryencodertest.ihx.

To load this onto the RC2014 Classic 2, I can use the example hexload.bas program. I’ll include the full code here.

new
clear
10 REM Created by Filippo Bergamasco,
11 REM and modified by DaveP for the RC2014
12 REM Adapted for z88dk by feilipu
20 REM Version 1.0
30 Print "Loading Data"
40 let mb=&H8900
50 print "Start Address: ";hex$(mb)
60 REM Go to READ Subroutine.
70 GOSUB 1000
80 print "End Address:   ";hex$(mb-1)

90 REM Change USR(0) Pointer for HexLoad
100 GOSUB 1100

110 REM RUN THE HEXLOAD CODE!
120 print usr(0)

130 REM Change USR(0) Pointer to 0x9000
140 GOSUB 1200

150 REM RUN THE PROGRAMME CODE!
160 print usr(0)
170 END 

1000 REM Routine to load Data
1010 REM Needs var mb set to start location
1020 read a
1030 if a>255 then RETURN
1040 rem print HEX$(mb),a
1050 poke mb, a
1060 let mb=mb+1
1070 goto 1020

1100 REM Location of usr address &H8049
1110 print "USR(0) -> HexLoad"
1120 let mb=&H8049 
1130 doke mb, &H8900
1140 RETURN 

1200 REM Location of usr address &H8049
1210 print "USR(0) -> 0x9000, z88dk default"
1220 let mb=&H8049
1230 doke mb, &H9000
1240 RETURN

9010 data 33,116,137,205,109,137,215,254,58,32,251,14
9040 data 0,205,83,137,71,205,83,137,87,205,83,137
9070 data 95,205,83,137,254,1,40,23,254,0,32,33
9100 data 205,83,137,18,19,16,249,205,83,137,121,183
9130 data 32,26,62,35,207,24,207,205,83,137,121,183
9160 data 32,14,33,206,137,205,109,137,201,33,172,137
9190 data 205,109,137,201,33,189,137,205,109,137,201,205
9220 data 100,137,7,7,7,7,111,205,100,137,181,111
9250 data 129,79,125,201,215,214,48,254,10,216,214,7
9280 data 201,126,183,200,207,35,24,249,72,69,88,32
9310 data 76,79,65,68,69,82,32,98,121,32,70,105
9340 data 108,105,112,112,111,32,66,101,114,103,97,109
9370 data 97,115,99,111,32,38,32,102,101,105,108,105
9400 data 112,117,32,102,111,114,32,122,56,56,100,107
9430 data 10,13,58,0,10,13,73,110,118,97,108,105
9460 data 100,32,84,121,112,101,10,13,0,10,13,66
9490 data 97,100,32,67,104,101,99,107,115,117,109,10
9520 data 13,0,10,13,68,111,110,101,10,13,0,0
9550 data 999
9999 END
run

Paste this into a terminal connected to the RC2014 and it will prompt you to enter hex. Cut and paste the rotaryencodertext.ihx and it should load and execute.

Now turning the rotary encoder will show the binary input on the output LEDs. Pressing the switch on the rotary encoder attached to port 1 will return you to BASIC.

This is it running. Ignore the poor soldering on the Digital I/O board. I did that a few years ago and (I think) I have improved since then.

Taking part in Retro Challenge 2024

It’s the 20th anniversary of the Retro Challenge, and also the 10th anniversary of the RC2014 Z80 computer.

So to celebrate, I’m planning on taking part this year, and undertaking a project with my own RC2014 computer. The idea is to complete a project using a retro computer in a month. The RC2014 itself was an entry in 2014.

As I will only have a month to try to complete a project, I’m looking for something achievable.

My plan at the moment is to look at adding rotary encoders to my RC2014, and to be able to read them using Z80 machine code. A rotary encoder is a sensor that measures the rotational position and speed of an object by converting the motion into an electrical signal. An example would be the scroll wheel on a computer mouse.

I have ordered a couple of rotary encoders ahead of the start date so they should be here in time for the start of the project. I have used a rotary encoder on an Arduino a few years ago, but that had a library to simplify the process. This time I will have to write from scratch, and it will give me a good opportunity to write some real Z80 assembly.

I will also need to design a circuit board, and for this I plan to use EasyEDA. This is an area I really need to learn more about, so the challenge gives me a good excuse to get stuck in with it. I will need to start on this early as ordering a PCB can take a few weeks to arrive, so I won’t have time for multiple revisions if I make a mistake.

Let’s see if I can produce something by the end of the project!