AVR-based bootloader and IO card for RC2014 retrocomputer

Similar projects worth following
z80ctrl is an AVR-based bootloader, debugger, and I/O device for a Z80 retrocomputer with the following features:

- Plug-in module for RC2014 retrocomputer replaces the clock, serial, ROM, and CF boards
- Runs CP/M-based software including WordStar, Zork, Ladder, Turbo Pascal and more!
- Loads HEX files, binaries, and CP/M disk images from a FAT32-formatted SD Card
- Provides Altair-compatible serial ports and disk controller
- Full compatibility with unmodified Altair disk images from the SIMH emulator project
- Monitor with memory inspector and built-in Z80 disassembler
- Single-step debugging, breakpoints, and watchpoints to trace Z80 bus activity in real-time
- Allows bridging other SPI peripherals to the Z80 using the optional I/O expander card
- Flexible MIT-licensed firmware can be the basis of your own SBC or breadboard computer

Visit the GitHub Repo for all project artifacts and the GitHub Wiki for documentation. 

Kits are available on Tindie.

x-zip-compressed - 140.88 kB - 01/12/2019 at 19:38


x-zip-compressed - 165.47 kB - 01/12/2019 at 19:39


  • z80ctrl Kits Available

    J.B. Langston01/08/2019 at 23:58 0 comments

    z80ctrl Kits are now available on Tindie.  Get 'em while they're hot!

    These kits are sold by Michael Kamprath with my permission. I do not offer any warranty or guarantee of support.

  • One Year of z80ctrl

    J.B. Langston01/04/2019 at 05:39 0 comments

    Around this time last year, I started bread boarding what would become the z80ctrl. In honor of its first birthday, I've decided to take a little trip down memory lane. 

    I made my first commit to GitHub on January 6.  It didn't do a lot back then.  It had a hard-coded program that added two numbers together and then went into an infinite loop, while the AVR printed out a trace of the signals on the Z80 bus.  But it was enough to prove that my design worked. A day later, I had the AVR doing serial I/O and the Z80 printing "hello, world".

    The first real program it ran was the Altair Turn Key Monitor a few days later.  That was followed in quick succession by a more advanced monitor, which allowed me to paste in Intel Hex programs for the Z80 to run.  By January 12, I had the SD card working and could load hex files directly from it. For the next few days, the z80ctrl monitor started to take shape. The dump and fill commands made their debut and the debugging features started to take shape. The monitor from back then is still recognizable now (although with a much longer list of commands).

    On January 15, I got Altair disk emulation working and achieved what felt like the Holy Grail: running CP/M.  On January 21, I posted my first two videos to YouTube. One detailed the Hardware:

    And the other detailed the software:

    Around this time, I started getting the idea that I wanted to design my own PCB.  I started work on a SBC that would include the Z80, memory, and AVR all on one board, but then I came across the RC2014.  I really liked the modularity of the RC2014 backplane and the community surrounding the RC2014.  So on January 20, I ordered a RC2014 Pro kit, and the next day I introduced myself to the RC2014 community.  I started working on an RC2014 module in KiCad and had a first draft by January 23. 

    My RC2014 arrived on February 1, and to prove to myself it would work, I hooked up my breadboarded circuit to the RC2014 backplane. It worked, an after a few weeks of polishing, I finally mustered the courage to place my order from OSH Park on February 16.  After 10 long days, the boards arrived, and... they didn't work.  I couldn't even get the SD card to work, and I was completely stumped.  

    I felt so defeated by the failure of my first PCB design that I took a break from the project for two months, until two members of the RC2014 mailing list, Rodney and Jay,  encouraged me keep working on it.  By April 23, both had their boards working but mine was still a no go.  Rodney offered to send me one of his boards to test, and when I got it, I found that it didn't work either.  Finally, on May 4, I figured out that it wasn't a problem with my board at all, but two lines shorted together on the RC2014 backplane. After figuring this out, my enthusiasm returned and I made a video going over the board's design and discussing some of the problems I ran into:

    I spent the next few weeks helping people on the mailing list to solve problems with their boards and making a new revision to fix the issues I had identified with the first board.  Also during this time, I got serious about documenting the project and squashed a few bugs. 

    On May 20, I started this Hackaday page. The next few weeks were my most productive period since starting the project in January. I added many... new... sofware... features.  I also designed REV3 of the board that had some major optimizations and new features.

    Also during this time, I started working on my other project, TMS9918A video card, which was the first of my game boards for RC2014. I ordered both the video card and z80ctrl rev3 at the same time.  When I got the rev3 boards built, they worked the first time. By mid-July, I was pretty happy with the point I had gotten the z80ctrl to, and I turned my attention to learning to code...

    Read more »

  • MSX-BASIC on RC2014

    J.B. Langston01/02/2019 at 00:21 0 comments

    I've gotten MSX-BASIC to run on the RC2014 with my TMS9918A video card and my z80ctrl card emulating the MSX keyboard.

    I have checked the keyboard emulation code into GitHub.  It takes ASCII characters from the z80ctrl UART and converts them into MSX keyboard scan codes using a table.  Currently it doesn't support some of the control keys on the MSX keyboard such as stop and the arrow keys.  I will keep working on this, but it's usable as is.

    The MSX-BASIC ROM can be obtained from the blueMSX emulator.  Copy MSX.rom from the Machines\Shared Roms directory of your blueMSX installation to your z80ctrl SD card. Once you've copied it, load and run it from z80ctrl:

    z80ctrl>clkdiv 5
    z80ctrl>loadbin 0 msx.rom
    z80ctrl>run 0

    One of the nice things about MSX-BASIC is that it has built-in graphics primitives for the TMS9918A video card, unlike, say, MBASIC on CP/M.  I have used these primitives to draw an analog clock using the time pulled from the RTC chip on my z80ctrl IO expander, which is shown in the video. The BASIC source code is available on GitHub.

    Currently, there is no support for MSX tape or disk drives, so you can't save your program once you have typed it in. The way I worked around this was to edit the program on my PC and then paste it into TeraTerm.  I had to set a 50 ms per-character delay in order to get the program to reliably type in when pasted.  A faster brute force method of saving your program once you have typed it in is to press the halt button on the z80ctrl and run 

    z80ctrl>savebin 8000 ffff image.bin 

    This will save the complete state of your BASIC interpreter so that you can load it back up the next time using and pick up where you left off.  Next time, to pick up where you left off, run

    z80ctrl>loadbin 0 msx.rom
    z80ctrl>run 0
    *press the halt button*
    z80ctrl>loadbin 8000 image.bin

    Your program should now be in exactly the same state as it was before. You can list it to verify it is there, then run it.

    Here are some good references I have found for MSX-BASIC.

  • Interfacing a Character LCD

    J.B. Langston12/31/2018 at 16:27 0 comments

    I've added a few examples for interfacing a character LCD with the z80ctrl IO expander board.  The specific LCD I am using was purchased from Amazon.  However, almost every character LCD you can find will use a standard interface based on the HD44780 chip, and my examples should work with any of them.

    Since the LCD has a parallel interface to begin with, one could argue that it's a bit convoluted having the Z80 talk to the AVR over the parallel bus, which converts it to a serial signal to send to the IO expander, which converts it back into a parallel signal to send to the LCD.  To that, I say: Shut up, one! It's my project and I'll do what I want!

    The pinout for a character LCD is also fairly standardized.  This is from the LCD I bought:

    Hooking up the LCD to the IO expander board is straightforward:

    • Connect pins GPA0-GPA3 from the GPIO A connector to DB4-DB7 on the LCD
    • Connect pins GPA4, GPA5, and GPA6 from the GPIO A connector to RS, R/W, and E on the LCD, respectively
    • Hook up 5V power to VDD and A on the LCD
    • Hook up ground to VSS and K on the LCD
    • Connect a 10K pot to V0 to adjust the contrast

    Once it's hooked up, I wrote a simple MBASIC program that prints "hello, world" on the LCD:

    10 GOSUB 1000
    70 C=&H28:GOSUB 3000: REM 4-BIT MODE, 2 LINES, 5x8 CHARS
    110 GOSUB 5000
    120 END
    1010 OUT 0, 1: REM GPIO CHIP 1
    1030 OUT 2, 0: REM ALL OUTPUT
    1040 OUT 1, &H12: REM GPIO REGISTER A
    1050 RETURN
    2050 RETURN
    3010 M=0
    3020 GOSUB 2000
    3030 RETURN
    4010 M=&H10
    4020 GOSUB 2000
    4030 RETURN
    5010 FOR I = 1 TO LEN(S$)
    5020 C=ASC(MID$(S$,I,1))
    5030 GOSUB 4000
    5040 NEXT
    5050 RETURN

    The BASIC program has several subroutines that illustrate how to interface the LCD:

    • 1000 initializes the GPIO port on the IO expander to talk to the LCD. It sets the IODIRA register (address 0) to 0 to make all the pins outputs. Then it selects the GPIOA register (address &H12) which will be used by the rest of the program to control the output.
    • 2000 sends the byte in C to the LCD with the register select bit set in M.  To reduce the number of pins required, the LCD is used in 4-bit mode.  Therefore the program has to sends the byte 4 bits at a time, clocking the enable line each time.
    • 3000 is a wrapper that sends a command to the LCD by setting M=0 and calling 2000
    • 4000 is a wrapper that sends a character to the LCD by setting M=1 and calling 2000
    • 5000 sends a complete string in S$ to the LCD.  It iterates over each character in the string and converts it to ASCII, then calls 4000 to send each character individually
    • The main program starting at 10 sends a series of commands to initialize the LCD by setting the value of C and calling 3000 for each one.  Then it sets the value of S$ to "hello, world" and calls 5000 to send the string.
    • Note: MBASIC is slow enough that I didn't have to check the LCD's busy flag between writes, which would have made the code considerably more complicated. Check out the assembly program below to see how that's done.

    I have also written an assembly program to retrieve the date and time from the RTC and display it on the LCD. Since it's quite long, I won't reproduce...

    Read more »

  • IO Expander from MBASIC

    J.B. Langston12/15/2018 at 21:17 2 comments

    I've recently pushed an update to the z80ctrl repo which makes my new IO expander board accessible to the Z80. 

    To access these features, you'll need to pull the latest code from the Github repo, and then uncomment the IOX_BASE variables in the Makefile. After doing so, run 'make clean' and then 'make install' to rebuild and flash the new code.  You can change the IOX_BASE if desired, but you must modify any code that will access the ports accordingly.  

    The board is exposed to the Z80 on 3 consecutive ports starting from IOX_BASE:

    • Writing to port 0 selects device you want to control.  The RTC is always device 0.  The device number of the GPIO chips correspond to the address configured on the jumpers next to the chip. If you select a non-existent device, writing to the other registers will have no effect.
    • Writing to port 1 selects the active register on the selected device. 
      • The registers for the RTC are fully documented in the DS1306+ datasheet. You can also find a concise list in rtc.h.
      • The registers for the GPIO ports are fully documented in the MCP23S17 datasheet.  You can also look in the iox.h file for a concise list of addresses. You will want to use the register addresses for bank 0 unless you change the bank configuration (read about this in the datasheet).
    • Reading or writing port 2 returns or sets the value of the selected register on the selected chip. Refer to the appropriate datasheet for the meaning of the values written to the registers.

    Here is a simple MBASIC program to flash an LED on the first GPIO port 10 times:

    10 OUT 0, 1: REM select chip address 1
    20 OUT 1, 0: REM select register IODIRA0 (0x00)
    30 OUT 2, 0: REM set register value to 0 (all pins outputs)
    40 OUT 1, &H12: REM select register GPIOA0 (0x12)
    50 FOR I = 1 TO 10
    60 OUT 2, 0: REM turn all pins on GPIOA off
    70 FOR J = 1 TO 1000:NEXT
    80 OUT 2, &HFF: REM turn all pins on GPIOA on
    90 FOR J = 1 TO 1000:NEXT
    100 NEXT

    First, we select the IO direction register on the IO expander configured at address 1, and set all pins to outputs. Then we select the GPIO register.  Within a loop, we toggle all of the pins on and then off, pausing between each transition.

    Here is an MBASIC program demonstrating how to read the time from the RTC:

    20 DEF FNBCD(V)=(V AND &HF0)/16*10+(V AND &HF)
    50 OUT 1,6: YR=FNBCD(INP(2))+2000
    60 OUT 1,5: MO=FNBCD(INP(2))
    70 OUT 1,4: DY=FNBCD(INP(2))
    80 OUT 1,2: HR=INP(2)
    90 OUT 1,1: MI=FNBCD(INP(2))
    100 OUT 1,0: S=FNBCD(INP(2))
    120 AMPM$=""
    130 IF (HR AND &H40)=0 THEN 160: REM SKIP IF 24-HR FORMAT
    160 HR=FNBCD(HR)
    170 PRINT USING "The time is ##:##:##& on ##/##/####"; HR, MI, S, AMPM$, MO, DY, YR

    First, we select the RTC device, then read the year, month, day, hour, minute, and second from the corresponding register on the chip.  Next, we check to see if the 12 hour bit is set. If so, we check whether the time is currently AM or PM and then mask off those bits.  Now, we convert all the values from BCD (as they are returned by the RTC) into binary so that BASIC can print them properly.  Finally, we output the date and time in a standardized format. 

  • REV4 Complete

    J.B. Langston12/05/2018 at 03:17 0 comments

    I have completed REV4 of the z80ctrl board. This revision makes the following changes:

    Adds a diode on the /WAIT line to convert it to an open drain output. This is necessary to prevent contention when other boards (such as my SN76489 sound card) need to use the /WAIT line also.  If you plan to use a REV3 board with the SN76489 board, you must cut the wait trace and solder a diode in its place to prevent contention.

    Updates the wait state generator to allow debugging with an external clock. Previously, z80ctrl only generated wait states for IORQ and when debugging, it single stepped the Z80's clock in software.  Now it can optionally also generate wait states for MREQ so that execution of the Z80 can be paused for each memory fetch to allow debugging when the clock cannot be manually controlled.  A jumper (J9) is provided to select between CLK and WAIT modes.  In CLK mode, pin 20 of the AVR is connected to the CLK line on the RC2014 bus, and the z80ctrl provides the Z80's clock like it has in all previous revisions.  In WAIT mode, an external clock must be used and pin 20 is instead used to enable and disable wait states for MREQs. I have not yet written the software to support this mode of operation but the REV4 board will work the same way as REV3 and earlier when the CLK jumper is shorted. Important Note: U3 is now a 74HCT02 quad NOR gate instead of a 74HCT74 dual flip-flop used on previous revisions.

    Adds a jumper to optionally connect the AVR reset pin to the D15 line on the RC2014 bus, which is normally unused. This line can also optionally be connected to the I/O expander's reset pins on the new I/O expander board, allowing the I/O expanders to be reset to a known state whenever the z80ctrl is reset.

  • IO Expander Board

    J.B. Langston11/25/2018 at 22:08 0 comments

    I've recently designed an I/O expander companion board for the z80ctrl that adds an RTC, up to 4 8-bit GPIO ports, and 4 SPI ports.  

    The board makes use of the same MCP23S17 I/O Expander used on the z80ctrl. All of the I/O expanders share the same chip select line and are addressed by a configurable 3-bit address, allowing a total of 8 I/O expanders to share a chip select line.  Jumpers are provided to set the address, so up to 3 I/O expander boards can be added to the RC2014 if desired, giving a maximum of 24 8-bit GPIO ports. The I/O expander on the z80ctrl board which interfaces with the RC2014 bus is hard-coded to address 0, so only address 1 and higher should be configured for the I/O expanders on this board.

    The board also contains a DS1306+ SPI RTC with a battery backup and 32 kHz crystal. This allows the AVR to keep track of the time in order to correctly timestamp files on the SD Card. It can also share the RTC over the Z80's parallel bus so that the RTC is accessible to CP/M, FUZIX, or other operating systems running on the Z80.

    The board allows for up to 4 additional SPI chip selects to connect other SPI peripherals to the board. This is done by ORing the upper 4 bits of the GPIO_B port with the AUX2_CS line from the AVR.  The additional chip select will only become active when both corresponding the GPIO_B line and the AUX2_CS line are set low.  This way, the AVR can communciate with the I/O expander to enable the additional chip selects, but they will only become active once communication with the I/O expander is complete.

    An inverter was also required because the RTC uses an active high chip select whereas most SPI peripherals use active low.  Since I had 5 additional inverters in the package, I also inverted the outputs of the OR gate and exported both the active high and active low chip selects on each of the 4 SPI ports.

    The KiCad design files for the board are available in the GitHub Repo and further details are provided on the GitHub Wiki.

    I have updated the z80ctrl firmware to use the RTC to set the timestamp when creating new files on the SD card. The date can also be viewed and set using the new date monitor command.

    I have also added ioxwrite and ioxread monitor commands that directly write or read I/O expander registers.  This allows low-level configuration of each I/O expander.  The commands take the address of the I/O expander configured via the jumper, and the hex address of the register that you want to interact with.  Refer to the MCP23S17 datasheet for information about the specific registers.  Additional high-level commands can be added to the firmware if a specific function for the I/O expanders is desired.

    Many thanks to JLCPCB for sponsoring the prototypes of this board. Whether  you want to manufacture my boards for your RC2014, or you need to prototype your own electronic project, JLCPCB is an excellent choice. They produce top quality PCBs for an incredibly low price and their service is fast.  I routinely get 5 day or quicker turnaround on my projects, order placed to board in hand.

  • Transparent Paging

    J.B. Langston06/28/2018 at 05:14 0 comments

    I previously added manual paging support using the 512KB RAM/ROM card (either the official or Scott Baker versions), which allowed you to manually specify a set of 4 pages and work with the memory at those pages.  

    I've now added support for 20-bit addresses to z80ctrl with automatic paging of up to 1MB memory (addresses from 0x00000-0xFFFFF). The z80ctrl monitor gives the illusion of one flat address space, paging memory in and out as needed.  The manual "page" command has been replaced by a new command called "base", which allows you to specify any address which falls on a page boundary, from 0x00000 to 0xFC000. Because of the page boundary restriction, the base address must be a multiple of 0x4000. Once set, the base address will be added to any address you specify in a monitor command. This makes it convenient to work within a 64KB block memory without having to specify a long address every time.  The base command also controls the memory that is paged in when the Z80 boots.  Initially, the pages will be set to the contiguous 64KB block of memory starting at the base address. The software running on the Z80 is free to page in different pages using the page registers after it starts.

    Here's an example to help make things clearer.  If you set "base 80000", which is the start of RAM, running dump 0 will actually dump from 80000, and dump 100 will actually dump from 80100 (dump will show this address as well). The four pages starting at base address 80000 will also be loaded when the Z80 is booted. If you set the base address to 80000 and then load a program at address 100, it will actually load the program to 80100, and when you type run 100, the pages starting at address 80000 will be paged in, so the Z80 will see the program at address 100 and run as expected. 

    The flash, fill, loadbin, savebin, disasm, poke, dump, run and debug commands now support 20-bit addresses as parameters, and where appropriate, will display 20-bit addresses when listing memory contents. loadhex and savehex currently do not support 20-bit addresses, but I am working on it.  Until then, you can still use 16-bit addresses with these commands, and setting the base address will transparently adjust their starting address so that they work within the 64KB block starting at the base address.

    Support for 20-bit addresses in the flash command means that z80ctrl is now a fully functional flash programmer, and I have successfully used it to program the flash on the 512KB RAM/ROM board with RomWBW.  Actually running RomWBW with the z80ctrl is currently unsupported.  You will have to remove the z80ctrl module and replace it with the clock module and SIO/2 module in order to use RomWBW for now.  I have, however, confirmed that the image flashes properly and RomWBW boots when using these boards.

  • REV3 Boards Built

    J.B. Langston06/22/2018 at 03:06 0 comments

    The REV3 board is built and working great. No issues discovered so far.  This rev features improved performance, a few fixes, and a two-way IORQ line so that the AVR can control peripherals such as the RomWBW paging registers, or my new TMS9918A board directly.  I've detailed the changes in a previous project log, so I won't rehash them here. Instead I'll just share some pictures of the completed REV3 board.  If you want to make your own REV3 board, you can have it manufactured using the gerbers attached to this project, or from the shared design on OSH Park.

  • Companion Project

    J.B. Langston06/15/2018 at 00:18 0 comments

    I have added a companion project for the z80ctrl, a TMS9918/A-based video card for the RC2014 that I'm calling rc9918.  The TMS9918 was used in the TI-99/4A, MSX, ColecoVision, original Sega SG-1000. Enhanced derivatives were used in later MSX computers, the Sega Master System, and the Sega Genesis. It also influenced the architecture of the PPU in the Nintendo Entertainment System.

    My board is based on a circuit described by Tom LeMense for interfacing the TMS9918A with SRAM. Currently, I have breadboarded the circuit and designed the PCB layout, but I haven't had a PCB manufactured yet.

    I have written a few example Z80 assembly programs, including a Mandelbrot generator and a Nyan Cat.

    I am also adding support for the TMS9918 to the z80ctrl.  z80ctrl will automatically initialize the TMS9918/A in text mode at startup and includes new commands tmsfill and tmsdump, which allows the TMS9918's RAM to be filled or examined, and tmslbin, which allows binary data to be loaded directly into VRAM. Although it's not ready for everyday use yet, I have also tested code that will output the monitor interface from z80ctrl on the TMS9918.

View all 16 project logs

View all instructions

Enjoy this project?



wynn.rostek56 wrote 01/22/2019 at 18:58 point

Found it. RAM was flaky due to a bit of crud on the contacts. Replaced the bad jumper, and everything works. Great board. I'm glad I ordered two of them, I have a second backplane that I will bring up as soon as I get the CPU and RAM boards built for it. Thanks for the support and the wonderful board. I think you may have a second board, some kind of I/O am I right or confused?

  Are you sure? yes | no

J.B. Langston wrote 01/22/2019 at 20:33 point

I'm glad you got everything working and that you are enjoying the board. Yes, I have a SPI I/O expander board that adds a RTC and up to 4 8-bit GPIOs.  If you look at some of the project blog entries, you can see a few examples of what it can do.  I also have the game boards (video, sound, controller), but those aren't directly tied in with z80ctrl.

  Are you sure? yes | no

wynn.rostek56 wrote 01/22/2019 at 09:22 point

Error is that I see physical sector size must be 128 and the software kicks back to the monitor.

  Are you sure? yes | no

J.B. Langston wrote 01/22/2019 at 16:00 point

I am pretty sure the problem is not with the sector size unless you are using some other disk format.  Are you trying to use the cpm2.dsk image from SIMH?  If so, it should work. The disk emulation code has not changed in a very long time and I have not had any problems with it.

  Are you sure? yes | no

wynn.rostek56 wrote 01/22/2019 at 09:12 point

J.B. J4 is to the far right and J9 is to the left (CLK) but am still unable to get cpm to boot.

  Are you sure? yes | no

J.B. Langston wrote 01/22/2019 at 15:57 point

The behavior sounds to me like the wait state generator isn't working properly.

For U3, are you using a 74HCT02? This part changed in REV4; previously it was a 74HCT74.  

  Are you sure? yes | no


[this comment has been deleted]

J.B. Langston wrote 01/20/2019 at 23:12 point

Which position do you have jumpered for J4? Try putting it on the far right.

  Are you sure? yes | no


[this comment has been deleted]

J.B. Langston wrote 01/20/2019 at 23:10 point

I'm glad you figured that out. And you're right about the typo in ihex.c. I have pushed a fix to Github. Thanks for letting me know!

  Are you sure? yes | no


[this comment has been deleted]

J.B. Langston wrote 01/20/2019 at 16:43 point

Do you get an error when you run loadhex, or it just doesn't have any effect?

Can you also try "fill 0 ffff asc"? It should fill the memory with values 0-FF ascending, over and over.  This is a little more complex pattern that would be a better test than filling with 0, but still rule of some of the complexity of loadhex.

  Are you sure? yes | no

villaromba wrote 10/08/2018 at 18:19 point

Fully understand - likewise!!!

At some point it would be good to have (as outlined in your possible expansion)

to add other SPI peripherals and implement I/O port interfaces to them for the Z80. Examples: real-time clock, GPIO, 7-segment display, LCD display, etc.

  Are you sure? yes | no

villaromba wrote 10/08/2018 at 16:08 point


Are you going to be adding any further work to this project?


  Are you sure? yes | no

J.B. Langston wrote 10/08/2018 at 18:04 point

Probably at some point, but I have been focused on other stuff lately. Is there something specific you are hoping I would work on?

  Are you sure? yes | no

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates