RunCPM on the Challenger RP2040 SD/RTC
RunCPM is a command-line tool that allows you to run vintage CP/M programs on modern embedded computers. RunCPM is an open-source project that is regularly updated and maintained and has also has a generic port to the RP2040 micro controller.
We have taken this port and further enhanced it with extended support for accessing GPIO and analog pins by directly accessing registers in the Z80 I/O space. By doing it this way and not extending BDOS call vectors we (or you) can easily add more of the RP2040 hardware support to the system. Another great feature of having it accessible from the Z80 I/O space is that we can directly emulate old hardware which allows you to use old programs without modifying them.
One good example of this is the already implemented support for a real time clock. Some old CP/M systems used a device from EPSON called RTC72421 which is nowadays unobtainium. We have implemented a thin glue layer between the RTC found on board the Challenger RP2040 SD/RTC board (MCP79410) and the register set for the old RTC72421. And voila, we now have a real time clock for our CP/M system.
To get started with RunCPM for the Challenger board there are two steps.
First you will first need to install the base emulator system on your board. You can either do this by simply downloading this zipfile from here, unzip it and copy it to the RPI-RP2 disk when the board is in boot loader mode. You can also clone the repo and build it yourself using the Arduino IDE.
Secondly you will need a FAT32 formatted micro SD Card with the CP/M files you want to use. You can try this package out to get things going.
You can now start RunCPM up and by connecting to it with a terminal emulator and you should get a screen looking something like this.
Now you can type in DIR to get a directly listing of all the files on the A0 drive.
You can now try typing in “URTC” at the prompt to get the current time of the system and it should look something like this, a little depending on how long the system has been on since first power on.
You can set time by entering the date and time + a switch to set the time, like this:
By connecting a small backup battery to the board, you will always have to correct time in your system.
Using digital GPIO
The orginal RP2040 RunCPM implementation (or at least the version that we based our work on) supports access to the RP2040 digital and analog I/O through the use of BDOS calls (220-224). This way of implementing it is a straight forward and quite simple way of doing it. We wanted to take this a step further and make sure that for instance the RTC was compatible with legacy hardware by creating a Z80 IO register model that could emulate different peripherals.
By creating this register model we can also access the hardware very easy from MBASIC and Z80 assembly programming by simply using the INP / OUT commands and instructions. For instance accessing the LED would require two initial setup operations and could be followed by a single read or write operation to do consecutive operations on a single pin. Consider the following example:
10 OUT 192,20 : REM MAKE THE LED PIN ACTIVE 20 OUT 193,1 : REM MAKE THE ACTIVE PIN AN OUTPUT PIN 30 OUT 194,1 : REM TURN THE LED ON 40 FOR I=1 TO 1000 : NEXT I : REM WAIT FOR 250MS 50 OUT 194,0 : REM TURN THE LED OFF 60 FOR I=1 TO 1000 : NEXT I : REM WAIT FOR 250MS 70 GOTO 30
The instructions on lines 10 and 20 is basically the same as the Arduino command pinMode. Combined they configure pin 20 as an output pin. Line 10 also makes pin 20 the “current” pin meaning that all subsequent operations on register 193 and 194 happens on this pin. If you want to select another pin as your current pin a new value must be written to register 192.
For more detailed information on how the digital GPIO interface works read the GPIO interface description below.
Using the analog input pins
Using the analog input pins works pretty much the same as with the digital I/O pins on they are only input pins.
To read a value from the ADC you first need to select the analog input that you want to read. This is done by writing the channel value into register 195. After that the analog value can be read from registers 196 and 197. The on board ADC is 12 bits which is why two registers are needed for the analog value.
The resolution of the ADC can also be adjusted from 8 bits up to 12 bits. This is accomplished by writing the desired number of bits into register 196. The ADC is set default to 12 bits resolution.
Here’s a short example on how you can use the ADC in MBASIC.
10 OUT 192,20 : REM MAKE THE LED PIN THE CURRENT PIN 20 OUT 193,1 : REM CONFIGURE THE CURRENT PIN AS AN OUTPUT PIN 30 OUT 194,0 : REM ENSURE THAT THE LED IS OFF 40 OUT 196,8 : REM I ONLY NEED 8 BITS IN THIS EXAMPLE CODE 50 OUT 195,0 : REM READ VALUES FROM THE POTENTIOMETER 60 AN = INP(196)*256+INP(197) : REM READ VALUE (BOTH REGISTERS MUST BE READ) 70 IF AN > 127 THEN GOSUB 90 ELSE GOSUB 110 80 GOTO 60 90 OUT 194,1 : REM TURN ON LED 100 RETURN 110 OUT 194,0 : REM TURN OFF LED 120 RETURN
Basic is your friend
The GPIO interface
Below is the documentation from the source code file:
/** * GPIO Interface * * The GPIO interface registers are used to map the RP2040 pins and functions * to a Z80 / CP/M register space. A number of I/O locations in the Z80 IO space * are mapped to specific C-functions that implements the required function. * * This is the CP/M pin number map (CP/M pin numbers in paranthesis): * * +----------+ * RST | USB | (20) LED * 3.3V | | * Aref | | * GND | | * A0 (0#) | | BAT * A1 (1#) | | En * A2 (2#) | | USB * A3 (3#) | | (19) D13 * A4 (4) | | (18) D12 * A5 (5) | | (17) D11 * SCK (6) | | (16) D10 * SDO (7) | | (15) D9 * SDI (8) | | (14) D6 * RX (9) | | (13) D5 * TX (10) | | (12) SCL * GND | | (11) SDA * +-----------+ * * Pin numbers marked with # are analog/digital pins, all other pins are purely * digital. * * D I G I T A L G P I O * * The digital gpio hw interface consists of 3 registers, the pin number * register (v_pin_no), the pin function register (v_pin_func) and the pin value * register (v_pin_val). * * All GPIO operations begin with the user first setting up on what pin they * want to do an operation on. This is done by writing the pin number, according * to the pin map diagran above, to the v_pin_no register. * * To define the function of the pin, the register v_pin_func must also be * set to the desired mode of operation. The number written into this register * must follow the following scheme: * INPUT = 0x0, * OUTPUT = 0x1, * INPUT_PULLUP = 0x2, * INPUT_PULLDOWN = 0x3, * OUTPUT_2MA = 0x4, * OUTPUT_4MA = 0x5, * OUTPUT_8MA = 0x6, * OUTPUT_12MA = 0x7, * Any value outside of the above mentioned will cause an undefined behaviour. * Reading this register will return an identifier (0xda) for this block. This * can be used to identify that the GPIO functions are available to the device. * * Lastly, the user can now read or write to the pin through the v_pin_val * register. * * Multiple read and/or write operations can be performed on the same pin by * simply reading and/or writing to the v_pin_val register. * * A N A L O G I N P U T * * The RP2040 has a 12-bit SAR A/D converter that can be accessed through the * analog input interface. * * The analog input interface is built arround 3 different registers. These * registers controls what input to read from as well as two registers that * holds the value of the ADC. * * When reading a value from the ADC the user must first set the desired channel * that he/she wants to read from. This is done by writing the channel to * register v_analog_chan. When this is done the analog value can the be read * directly from the data registers. * * Since the ADC can represent up to 12 bits, two registers are used to hold the * data. The high 4 bits (nibble) are read from the register v_analog_hi and the * 8 low bits (byte) are read from register v_analog_lo. These two value can * then be combined into an integer value in MSBASIC as follows. * * 350 OUT 195,1 : REM SET INPUT A1 * 360 LET AN = INP(196) * 256 + INP(197) * * The resolution of the ADC can be adjusted by writing the desired number of * bits into the v_analog_hi_ctrl register. For example, if you write the value * 8 to this register the resolution of the adc is effectively reduce to 8 bits. * The default setting for the adc resolution is 10 bits. * * Register definition is below. */ #define v_GPIO_ID 0xda #define v_IOBASE 0xc0 #define v_pin_no (v_IOBASE + 0x00) // 192 #define v_pin_func (v_IOBASE + 0x01) // 193 #define v_pin_val (v_IOBASE + 0x02) // 194 #define v_analog_chan (v_IOBASE + 0x03) // 195 #define v_analog_hi_ctrl (v_IOBASE + 0x04) // 196 #define v_analog_lo (v_IOBASE + 0x05) // 197