Showing posts with label cpm. Show all posts
Showing posts with label cpm. Show all posts

Tuesday, September 20, 2016

Controlling Arduino with DX-Forth

Forth is a very powerful programming language. It can scale from very low level, near-assembly code, which you can run on Arduino Uno with 2 kilobytes of RAM, to very high level, domain-specific language, suitable for modern home computers (you can even implement object-oriented Forth in the Forth itself).

Due to its inherently low-level nature, Forth is not very popular among contemporary programmers. It is often considered a high level assembly - it stores and manipulates data using anonymous data stack (and optional floating point stack), and variables are nothing more than labelled memory cells. But at the same time Forth provides a set of tools to create building blocks, which can be used to make even the most sophisticated software (like satellite antenna controllers). Developers who understand how a CPU works and are not afraid to juggle with bytes, usually have no fear of Forth and find it a very smart and productive tool.

There are many implementations of Forth for almost all existing operating systems (many of them are self-contained and can run without an operating system at all, like amForth or Jupiter ACE's Forth). Among them there is excellent, and still actively maintained, DX-Forth for CP/M. Thanks to amazing work of Marcelo Dantas and his fantastic CP/M emulator for Arduino Due, and my humble contribution in the form of CP/M Arduino Interface, you can now enjoy writing software for Arduino using DX-Forth.

RunCPM emulator contains an example Forth application, PHOTOLED.4TH. It begins with a couple of Forth words, which define the Arduino interface. The first one is pinmode:
: pinmode ( val pin -- ) 8 lshift + 220 bdos drop ;
From its declaration, pinmode expects two bytes on a stack, the first one being a value to assign to the pin, and the pin number. It uses a word bdos, which DX-Forth defines as follows:
BDOS ( DE u -- A )
Perform CP/M BDOS call number u. DE is the value passed to the DE register. Return the contents of the A register.
So first we need to put on the stack some value, which will then be passed to CPU register DE, and then a BDOS function number. Question is, what should be passed as DE and the call number? The answer can be found in the Arduino interface description:
BDOS function 220 (0xDC) - PinMode:
LD C, 220
LD D, pin_number
LD E, mode (0 = INPUT, 1 = OUTPUT, 2 = INPUT_PULLUP)
By convention, you pass BDOS function number in CPU register C, and arguments in registers D and E. All those registers are 8-bit, but can be combined in 16-bit pairs: BC, DE and HL. So for the Forth word bdos we need two numbers to be put on the stack:
- First, a 16-bit number with pin number in high 8-bits and mode (0, 1 or 2) in low 8-bits. This number will be passed to CP/M through CPU register pair DE.
- Second, number 220 as the function call.
For example, to set pin 13 into output mode, we need the following command in Forth:
3329 220 bdos
3329 is a product of 13 * 256 + 1. Multiplication by 256 is the same as shifting 8 bits left. The pinmode does exactly that: it takes the pin number from the stack, shifts it 8 bits left, add the next value from the stack (val) and calls BDOS function 220:
: pinmode ( val pin -- ) 8 lshift + 220 bdos drop ;
The result of the call can be ignored, so the return value is simply dropped.

All the next Arduino interface words follow the same schema:
: dout ( val pin -- ) 8 lshift + 222 bdos drop ;
: aout ( val pin -- ) 8 lshift + 224 bdos drop ;
With din we want to preserve the result of bdos call on the stack, so we don't drop it:
: din ( pin -- n ) 8 lshift 221 bdos ;
Additionally, with word ain, we use fdos instead of bdos:
: ain ( pin -- n ) 8 lshift 223 fdos drop ;
It's because function number 223 makes a call to AnalogRead, which returns a 10-bit value and needs more than one 8-bit register to pass the result back to Forth. The value of AnalogRead is returned by the Arduino interface in register pair HL, and fdos returns this pair along with register A, unlike bdos, which returns only A:
FDOS ( DE u -- HL A )
Perform CP/M BDOS call number u. DE is the value passed to the DE register. Return the contents of the HL and A registers.
Since we need HL only, we can drop what has been returned in A, and leave only HL on the stack.

Now we can define words which use pinmode, din, dout, ain and aout. For example to turn a led on and off we can define the following words:
: ledon ( pin -- ) 1 over pinmode 1 swap dout ;
: ledoff ( pin -- ) 1 over pinmode 0 swap dout ;
Both words require the pin number to be on the stack. They both put the pin to output using pinmode and then set its state to HIGH (1) or LOW (0) respectively.

A more complicated example requires a simple circuit to be built. It should consist of a LED and a LDR (photoresistor) according to the following diagram:
The LED is connected to pin D9 (it can't be any digital pin, it must be PWM capable) through a 68 ohm resistor (unlike Uno, Arduino Due outputs 3.3V), and the LDR is connected to pin A8 and uses a 10k ohm pull-down resistor. We want a LED to light up when it gets darker, and get dim when it's not. Because, as you remember, analog input can return values with 10-bit resolution, and digital output accepts only 8-bit values, we need to calculate the LED intensity according to the following formula:
LED = 255 - (LDR / 4)
This can be achieved through a new word fade (it uses shifting 2 bits to the right to do a division by 4):
: fade ( led ldr -- ) ain 2 rshift 255 swap - swap aout ;
Now when you run the following command in DX-Forth:
9 8 fade
the LED intensity gets adjusted according to the current value returned by the LDR.

Of course it is tedious to run it by hand. We can define a new word, which will run fade in a loop:
: run begin 9 8 fade 100 ms key? until ;
It runs 9 8 fade with 100 millisecond delay until any key gets pressed. The final result looks like this:



If the LED responds too slow, or flickers, you should try tuning the delay. You can also get rid of it completely:
: run begin 9 8 fade key? until ;

Wednesday, September 14, 2016

The first operating system for Arduino

RunCPM, a CP/M emulator for Arduino Due, can now use Arduino's analog and digital pins thanks to five additional BDOS calls, which virtually makes it the first (and so far the only one) fully fledged, professional operating system for Arduino. You can write brand new, or modify any existing, CP/M software, using any programming language available for CP/M platform, to interface Arduino devices. The only requirement is that it should allow you to call arbitrarily chosen BDOS functions (natively or via inline assembly code), because RunCPM uses newly defined routines (220 - 224), unused by standard CP/M implementations.

RunCPM includes a few sample programs in Assembly, Microsoft Basic, Hi-Tech C, Turbo Pascal and DX-Forth to get you started. I also prepared a short video tutorial showing the Turbo Pascal example in action:



A few tips:

Because analogRead returns values that won't fit in a single byte, when calling function 223 in Turbo Pascal you should use BdosHL instead of Bdos to get the value returned in HL register pair, like this:
writeln(BdosHL(223, pin shl 8);
The same applies to Hi-Tech C - see PHOTO.C and PHOTO.PAS examples.

Also, since MBasic does not allow direct BDOS calls, I used USR functions to call small assembly procedures encoded in DATA lines.

Happy coding on Arduino!

Saturday, July 16, 2016

CP/M on a stick

CP/M, created by Gary Kildall, was for early computers what Android is for today's smartphones and tablets. It was an operating system that could be easily customized to run on any machine with Intel 8080 or Zilog Z80 processor, and was de-facto standard during 1970s and 1980s, running on hundreds of computers coming from different manufacturers, from Altair 8800 to early IBM PCs. Unfortunately, computers that can still run CP/M are very rare and expensive, and their modern counterparts, made by hobbyists, are either also quite expensive (like Altair 8800 clone) or require some more skills and effort than just simple soldering to build (like Grant Searle's Multikomp).

Luckily for all who want to have their own CP/M computer, there is an Arduino based solution, which is quite cheap and very simple to build. All you need is Arduino Due or its clone and an SD shield (like this one), which you have to fit on top of your Arduino board.
Now you need to download Marcelo Dantas' RunCPM, which is a multiplatform CP/M emulator, and upload it to the board with Arduino IDE. I managed to build RunCPM without problems on Ubuntu 12.04 using version 1.6.7 for Linux 64bits, just remember to install support for Arduino Due with Boards Manager under Tools menu. Next, connect the board to your computer with a micro USB cable inserted into the programming port of Arduino, and then select "Arduino Due (Programming Port)" for the Board and the Port (remember: Arduino must be connected to the computer). Open RunCPM.ino project, select "Verify/Compile" under the Sketch menu to produce the binary image, and then select "Upload" to flash the board with it.

Now, when you have a fully fledged Z80 board, all you need is the CP/M operating system. RunCPM comes with the most popular version 2.2 of CP/M. To run it, format an SD card as FAT32, and put the CPM22.BIN from RunCPM repository in the main folder of the card. Also create folders "A" and "B" - these will be your disk drives, everything you put in there will appear to the system as located on disks "A:" and "B:" respectively. You can create folders from A up to P (as this is the limit of CP/M itself). Now turn off the Arduino, put the card into the SD card shield, and connect it back. You now have the smallest CP/M computer in the world up and running.

Ok, the system has started, but how do you talk to it? Well, you need a terminal, just like for a real CP/M machine! Depending on your operating system, you will need a terminal software. For Windows I recommend PuTTY, in Linux you can install minicom package. Set serial transmission parameters to 8N1, speed to 9600 bauds, and select a serial port to which your CP/M machine is connected. In Ubuntu it's:
minicom -b 9600 -o -D /dev/ttyACM0
With MacOSX it's even simpler, just start typing "screen /dev/tty.usb" from the terminal and press TAB to unfold the device full name. In my case the full command looks as follows:
screen /dev/tty.usbmodem1421
If you configured the connection correctly, you will now be able to work on your Arduino board like on a real CP/M computer.
Just remember to disconnect when leaving the terminal session (ctrl+a and then z in minicom or ctrl+a and then ctrl+\ in OSX) otherwise you may get some strange errors when trying to reconnect.

I did some speed tests with prime sieve Basic benchmark by John Metcalf in Microsoft Basic-80 and Z80 Instruction Exerciser and compared them with results achieved by z80pack simulations at different CPU clocks. As far as I can tell Arduino Due with RunCPM runs very close to a 5MHz Z80 computer, which makes it even more realistic.

RunCPM allows you to have your own, modern, inexpensive CP/M computer, running at the speed of original Z80 based hardware, and use all the huge library of CP/M software with it. At the same time, you can connect to it with USB instead of a serial cable, you can easily exchange software with an SD card, it takes very little power, and last but not least - it easily fits in a pocket.