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.

RC2024 – Part 5 – Building And Testing The PCB

My prototype PCBs from JLCPCB has arrived!

Rotary Encoder for RC2014

The parts I needed to solder onto the board have also arrived from AliExpress and CPC Farnell.

I found one very annoying mistake try to solder up the board. I had forgotten to add a sensible spoke width to my copper ground plane. This means the ground plane is soaking up the heat from my soldering iron anytime I try to solder any pin connected to ground. I was able to solder up the board, but those pins took between 10 and 20 seconds to solder. I’m not confident the connection will last.

In the next iteration of the board I will set the spoke width in the EasyEDA CopperArea properties to 0.3mm. This should still give a good ground connection, but also be easy to solder.

I’m not happy with the position of some of the text on the board. Once wired up, pins obscure the Port 1 and Port 2 text. The debug port is also obscured. I will move these so they are visible when connected.

The debug port is a bit too high so the pins are clear of the top of the board. I will move them down.

I also realised that Spencer calls all his PCBs “modules”, so I will call mine “Rotary Encoder Module”.

Rotary Encoder for RC2014

It was with great excitement I plugged the module into my RC2014 and turned it on. The LEDs for Port 1 were all off, but the LEDs for Port 2 were all on. That didn’t seem right, but then I realised I had only plugged a rotary encoder into Port 1. As Port 2 is returning logic 0, the 74HCT14 was inverting this to logic 1 so the LEDs lit. So it was working as expected. Adding another rotary encoder to Port 2 fixed that.

The address was set to 0 on the address switches. This meant I could use the same BASIC program I used before to test the rotary encoders.

Turning the encoders the LEDs were flashing in the expected order. The BASIC program was returning “Left” and “Right” depending how I turned the encoder. It was working!

I decided to test the address switches and so set it them to 01111011, which is hex DE, and decimal 222.

I modified the BASIC program to read address DE instead of 0.

30 LET IN = INP(&HDE)

Running the BASIC program, the module now responds to address DE as expected.

One final test was to plug in my RC2014 Digital IO module. This is set to address 3. I modified the BASIC program so the OUT statement used address 3.

40 OUT 3,2^COUNTER

I can now use my rotary encoder module to successfully move the LEDs on the Digital I/O module.

Despite the issues with soldering to the ground plane, my module works as I hoped. I am going to tweak the layout and order replacement PCBs from JLCPCB.

RC2024 – Part 4 – A Quick Diversion To Look At The RC2014 ROM Board

I have been thinking about what else I want to achieve with this year’s Retro Challenge.

Apart from designing a PCB to interface rotary encoders to my RC2014 computer, I wanted to be able to use them from software. I have used BASIC for my testing so far, but I also want to look at using Z80 assembly language.

To use Z80 assembly language on my RC2014 Classic 2, I have a few options.

  1. Use BASIC to load and execute a hex dump of the my assembled Z80 code.
  2. Use SCM to load and execute a hex dump of my assembled Z80 code.
  3. Burn my assembled code into a ROM and run that directly on the RC2014.

The ROM board for the RC2014 Classic 2 has the ROM chip socketed. If I wanted to use my own ROM image I can lift this out and replace it. However, the board is low down and I would need to also remove the board from the RC2014 backplane. This would add mechanical wear and tear.

I have a similar problem if I want to switch between ROM images. For example, from BASIC to SCM, or to my own custom ROM image. There are jumpers on the ROM board, but I would need to lift the board from the backplane to be able to swap them.

So, what is the solution?

Well, Spencer does have an RC2014 riser card available on the Z80 Kits website to lift the board above the other cards. This makes it possible to reach the jumpers, but still difficult to remove and replace the ROM.

After writing about designing the PCB for the rotary encoder, I thought I could just build my own ROM card. What I want my new ROM board to achieve is…

  • Use a ZIF (zero insertion force) socket for the ROM so I can easily remove and replace it.
  • Use switches instead of jumpers for the ROM select so I can easily change which part of the ROM the RC2014 is seeing.
  • Have the ZIF and switches above the other boards so I can easily access them.

The circuit design of the ROM board is essentially the same as the original ROM board, just with some pull down resistors near the address switches. The board height will be the same as a standard RC2014 board.

I couldn’t find a DIP package with 3 switches in the EasyEDA library, so I have used a 6 pin socket in the design which should have the same footprint.

Here is my PCB layout.

I have sent this off to JLCPCB to be manufactured. The cost including delivery was just £2.53 using their 10 to 14 day delivery option. I’m not in a rush for this board, so I think that is a fantastic price. There is also a $2 discount at the moment, so that also reduced the cost.

I am expecting the board to arrive towards the end of the month but this won’t stop work elsewhere in the project.

RC2024 – Part 3 – Designing A PCB For The Rotary Encoder

One thing I really wanted to do as part of this year’s Retro Challenge was to build my first custom PCB for the RC2014 computer.

I have chosen to use EasyEDA to design my PCB. I was heavily influenced by watching James Sharman’s excellent YouTube videos in which he designs his own CPU using EasyEDA. The alternative was KiCad, which seems more complex for a beginner.

Spencer has kindly put the specifications for a RC2014 module template up on the RC2014 website. I will try to follow this when it comes to laying out my PCB.

The Circuit

The RC2014 Peripheral Addressing didn’t originally target exact addresses, instead it used 74LS138 chips to target ranges of addresses.

The SC219 takes a different approach, and uses a 74HCT688 8-Bit Magnitude Comparator to target a specific address. I think this is the best approach for my circuit. Using switches for the comparison will mean I can easily change the matched address if there are conflicts with any other cards on my RC2014. I will use the IORQ line from the RC2014 as the OE (Output Enable) line. I can take the output from the 74HCT688 and use a 74HCT32 OR Gate with the RD line to know when the peripheral address I want to respond to is being used. I will use a 74HCT245 Octal Bus Transceiver to present data on the data pins when active.

In my initial post I mentioned I had to use a 74HCT14 Schmitt Trigger to invert the input from the rotary encoder and help produce a cleaner digital signal. This will be connected to the 74HCT245’s D0 to D5 pins. This will allow me to support two rotary encoders, and leave D6 and D7 spare. I will provide two blocks of input pins, one block for each rotary encoder. Each of these will support the CLK, DT, and SW pins, as well as GND and +5V.

I will add some debugging support to the circuit. I like the input LEDs on the SC219 so I will also add these to my circuit so I can see the incoming data from the rotary encoders. I think a breakout of the incoming data lines before the 74HCT245 will be useful to see what is going on, and also allow the board to be used as an input device when the rotary encoders aren’t being used. Finally, I saw a discussion on one of the Sinclair Spectrum groups about adding a hook to GND to attach an oscilliscope to when debugging. This could be very useful if the circuit doesn’t work, so I will add in provision for this.

This is my initial design.

Rotary Encoder circuit

The PCB

As I mentioned earlier, there is a specification for the shape and size of PCBs for the RC2014.

Laying out the board I have tried to use EasyEDA’s measuring tool to ensure this is correct.

Adding the curved corner I found tricky, but eventually I was able to do this by targetting the BoardOutLine layer in EasyEDA.

I read on various forums that adding a ground plane to a PCB was a good idea, so I have done this on both the top and bottom layer of my PCB.

As my circuit is reasonably simple, I used EasyEDA’s auto routing functionality. This worked quickly and I didn’t need to do any manual routing.

For manufacturing I decided to use JLCPCB as EasyEDA has built in support for this manufacturer.

The board itself cost $3.20 for 5. I paid for express delivery because if I have made a mistake and need to create a new revision, it could take several weeks to arrive. The total for the 5 PCBs, delivery, and tax was £20.21. These are due for delivery on October 8th 2024.

JLCPCB adds a manufacturing code to each PCB. You can specify where this appears by adding the text “JLCJLCJLCJLC” to one of the silk layers. I put mine on the BottomSilkLayer so it’s not forward facing.

This is the final layout for my board. I can’t wait for it to arrive so I can solder it up and see if it works.

Rotary Encoder PCB

RC2024 – Part 2 – Reading The Rotary Encoder From BASIC

My Retro Challenge this year is to get rotary encoders working on my RC2014 computer.

Previously, I explained what a rotary encoder was, how to use one, and how to link it to the RC2014.

I now want to try reading it on the RC2014 and using it to control some output.

I’m using the SC219 Digital I/O Board on the RC2014 to accept inputs from a rotary encoder. I have mine mapped to I/O address port 0. Using Microsoft BASIC I can read this using the INP(0) statement. This will return a byte representing the values of D0 to D7 on the input port.

I am using D0 for the CLK, D1 for DT, and D2 for SW. D3 to D7 are not used at the moment. I am only interested in D0 and D1 for the rotary motion. I can use AND statements to mask bits when I’m checking the input value. So to check if D0 was 1 or 0, I could use INP(0) AND 1. This would return either 0 or 1. To check D1, I could use INP(0) AND 2. This would return either 0 or 2.

The following UML activity diagram shows at a high level how I need my BASIC program to operate.

The Microsoft BASIC on the RC2014 is old and limited compared to modern languages, but it is functional.

To save space, I’ll use variable A for CLK, and B for DT. I’ll need to keep track of the last state of A so I know when it has changed. To do this I’ll create a variable LAST_A. Finally, to keep track of the INP(0) result, I’ll create variable INPUT.

When I detect a change in A, I need to look to see if B matches A. If it does, then I know the encoder is being turned to the right. If it doesn’t, then it is being turned to the left.

Here is the BASIC code I came up with to do this. When it detects a change, it prints either “Left” or “Right” to the console.

10 LET LAST_A = 0
20 LET IN = INP(0)
30 LET A = IN AND 1
40 IF A = LASTA THEN GOTO 200
50 LET B = IN AND 2
60 IF (A=1 AND B=2) OR (B=0 AND A=0) THEN GOTO 100
80 PRINT "Right"
90 GOTO 200
100 PRINT "Left"
200 LET LAST_A = A
210 GOTO 20

The SC219 has digital outputs as well as inputs. Using the built-in output LEDs, I can move a dot left or right depending on the input from the rotary encoder.

The 8 output LEDs represent each bit of a byte. So we have the values 1, 2, 4, 8, 16, 32, 64, and 128 represented on bits D0 to D7. This is 2 to the power X, where X is between 0 and 7. We can use the ^ (power) operator in BASIC to help set these bits.

So we need to have a variable, let’s call it COUNTER, to keep track of which power we are currently at. We can say turning the rotary encoder left will increment COUNTER by 1. Turning the rotary encoderrigtht will decrement COUNTER by 1. We need some guards, so if COUNTER goes below 0, we set it to 7. If it goes above 7, we set it to 0. We set one of the LEDs by using OUT 0,2^COUNTER. This means when we turn the rotary encoder, we can move the LED.

Here is the BASIC code I wrote to do this.

10 LET COUNTER = 0
20 LET LAST_A = 0
30 LET IN = INP(0)
40 OUT 0,2^COUNTER
50 LET A = IN AND 1
60 IF A = LASTA THEN GOTO 200
70 LET B = IN AND 2
80 IF (A=1 AND B=2) OR (B=0 AND A=0) THEN GOTO 150
90 IF COUNTER <= 0 THEN GOTO 120
100 LET COUNTER = COUNTER - 1
110 GOTO 130
120 LET COUNTER = 7
130 PRINT "Right"
140 GOTO 200
150 IF COUNTER >= 7 THEN GOTO 180
160 LET COUNTER = COUNTER + 1
170 GOTO 190
180 LET COUNTER = 0
190 PRINT "Left"
200 LET LAST_A = A
210 GOTO 30

Here is a short video of it in action.

RC2024 – Part 1 – The Rotary Encoder

It’s October 2024, and Retro Challenge RC2024/10 has started.

I’m looking at interfacing rotary encoders with the RC2014 computer this year.

What is a rotary encoder?

A rotary encoder is an electronic device that measures rotational movement.

A rotary encoder

The rotary encoder I’m using generates two pulses as it turns. The pulses are on pins CLK (Clock) and DT (Direction of Travel). These pulses are out of phase with each other and this tells us which direction the encoder is turning. The encoder starts with both pins at logic 1. We then look for when CLK transitions to logic 0. At this point we check DT to see if it’s logic 1 or logic 0. This will tell us which way the encoder is being turned.

Let’s look at this on a timing diagram.

Timing diagram for a rotary encoder.

Going from A to B shows us turning the rotary encoder clockwise. We see CLK going from logic 1 to logic 0. We then sample DT and see it is still logic 1, so we know we are turning clockwise.

Going from B to A shows us turning the encoder anticlockwise. We see CLK going from logic 1 to logic 0. We then sample DT and see it is logic 0, so we know we are turning anticlockwise.

You may have noticed my rotary encoder has another pin call SW.

This is a switch triggered by pressing the shaft of the rotary encoder down. It is usually logic 1, but is logic 0 when pressed.

Wiring the rotary encoder to the RC2014

I need a way to be able to read the the CLK, DT, and SW pins.

For prototyping, I am using Stephen Cousins SC129 Digital I/O board for the RC2014. This has 8 input and 8 output pins, as well as LED indicators. I can use this from MS-BASIC on my RC2014 Classic 2.

The output from the rotary encoder could be noisy. Because of this, I am going to use a 74HCT14 Inverting Schmitt Trigger to clean the signals, and also invert them. Inverting the signals means they appear at logic 0 at rest. I will need to look for CLK transitioning to logic 1 when turning.

Wiring up the rotary encoder and turning it, I can see the input LEDs for 1 and 2 flashing. This is showing the logic levels changing from 0 to 1 and back again. I can also see they are out of sync with each other as expected.

My next step will be to read the incoming data from the rotary encoder in software.

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!