Micro8085 Development Board with Tiny BASIC

So, what’s this? The Micro8085, an embedded development board with the Intel 8085 CPU, running Palo Alto Tiny BASIC. And with hardware support for GPIO, Timer, UART, SPI bus, e2mem, ADC, DAC, PWM and comparator input.

Why?

Because it’s fun! The Intel 8085 was the first CPU I learnt to program (early 80’s), and I had a bunch of chips and old projects lying around which would be great if I could make some use of. And I wanted to show that a CPU from 1976(!) with some basic peripheral circuits will still have decent performance.

But perhaps the most important and strongest driving force for this project comes from a notion I have accumulated over several years in my profession as an embedded hardware and software engineer: It’s not easy to find junior developers with a fling for the un-fanciness of binary theory, designing algorithms, handling of arithmetic and logical problems, low level programming, etc. What if this particular board could inspire just one kid with a budding interest in programming, to go deeper into the world of embedded systems? It would be worth a try.

Palo Alto Tiny BASIC

As a platform for the board functionality, and to have some sort of “operating system”, I selected the Palo Alto Tiny BASIC. I guess there is plenty of information on the Internet about Tiny BASIC, but it especially attracted my interest since it’s considered to be the first ever free software/open source project. It originates from a counter movement to the commercial Altair BASIC, developed early 1975 as the first product of the by then newly founded Microsoft company. Altair BASIC sold for $150, a price that inspired hobbyists as well as computer scientists to want to do something of their own.

The Palo Alto Tiny BASIC is one of several versions of a community driven development of a BASIC interpreter that ran on the microprocessors available in the mid 70’s: Intel 8080, MOS Technology 6502 and Motorola 6800, to name the most common ones. Dr. Li-Chen Wang wrote the interpreter source, and the original mnemonics was translated to Intel assembler by Roger Rauskolb. It was originally published in Dr. Dobb’s Journal, May 1976. In the source code, Dr. Wang included the now quite famous copyright statement with a peculiar twist: “@COPYLEFT ALL WRONGS RESERVED”.

Additions to the interpreter

Line editor

I decided that the interpreter should be able to run over a USB/serial port and connect to a terminal emulator program on a PC. So the line input functions of the original code needed a big overhaul in order to accept left and right arrow for moving the cursor, backspace and delete key for error correction, and the ability to insert new characters anywhere in the buffer. Already typed program lines also needed a way to be able to get edited (so you don’t have to retype the whole line, if you realize you made a typo error), and therefore I added the direct command EDIT linenum.

Non-volatile memory

When you’re working with a project on a board like this, I thought it would be very convenient to be able to store and restore BASIC programs to and from a non-volatile memory. Then you could work with your programming ideas, store the program, and don’t have to worry about not turning off power to the board. Next time you just restore the program and continue working. So the board has a 32kB EEPROM memory connected via a SPI (Serial Peripheral Interface) bus, and it is accessible using the added direct commands SAVE and LOAD.

Depending on your choice of terminal emulator, it is of course also possible to just execute the LIST command, then select and copy the lines of your program that you want to save, paste them into an empty document of a plain text editor, and save it as a text file. The n to restore the program, look for a “Send text file...” command in your terminal emulator, open the file containing your program, and transfer it to the Micro8085. You will need to setup the terminal emulator to add a short delay after every sent line. A delay of 50 ms appears to be sufficient. Be sure to type NEW before start sending a program for the old program to be purged, and the line number sorting mechanism will not have to deal with potential line number duplicates and/or have to move data around as more lines with interlaced numbers are received. Then the 50 ms delay may not be sufficient.

Standalone mode

If you find some suitable application for this board, and develop a BASIC program for that task, I thought it would be convenient if the program could start by itself after power on, instead of having to connect via a terminal, type LOAD to read the program from non-volatile memory, and RUN to get it started. Therefore, the reset/boot up functionality always tries to perform a LOAD from non-volatile memory. If that was successful, it looks for a special token on the first program line. If that line/token is found (e.g. 10 REM AUTORUN), it will start to execute the program. If the first line is anything other than that, it will just silently discard the read program and print the normal sign on message and prompt, and wait for user input.

To cancel the automatic load and run of a program starting with the REM AUTORUN token, press and hold the two push-button switches connected to port C bits 3 and 4 while pressing the reset button. The automatic load/run will now be bypassed, and control is handed over to user input. This can be used as a back door if you by mistake have saved a BASIC program to the non-volatile memory that is set up for automatic start, but has some bug that makes it impossible to interrupt by <CTRL><C>.

SPI bus

The SPI bus is implemented using the Intel 8085 serial input and output pins SID and SOD, and since all the serial stuff needed for the non-volatile memory access meant adding quite a lot of  new implementation in the assembler source, adding SID and SOD as a BASIC function and statement (respectively) was easy since all the fundamental implementation work was already done. So, by typing A=SID or SOD A, you either read a serial byte to variable A, or the value of variable A is transmitted on the serial bus.

The serial bus clock pulse is generated by a dummy CPU write to an unused I/O address. The address select line is gated it with the 8085 write strobe, the trailing edge is delayed just a little bit and then inverted for positive clock polarity. The dummy I/O write is a quite economical way to generate the necessary clock pulse without the need for first setting and then resetting a port pin. But all of this is handled by the serial driver embedded in the SID and SOD functionality.

Time keeping

In any real life embedded product, time keeping is often essential. Therefore I wanted the system to include an interrupt driven milliseconds counter. And, since I also wanted the board to have input and output ports for LED’s and other stuff, I added the Intel 8155 chip which has a 14-bit timer/counter and 22 programmable I/O lines arranged in three ports. The timer is setup to generate a 1kHz square wave which is fed to the RST7.5 interrupt input of the 8085. The interrupt service routine increments a counter which is accessible using the keyword TIME. The TIME counter is a 16 bit memory/register which counts up and rolls over from 65535 (FFFFh) to 0 and continues to count and then roll over, etc.

Signed and unsigned numbers

In the original Tiny BASIC, all numbers are integers in the range of -32768 to +32767. This is the range you get signed 16-bit data, where half of the values are negative and the other half is positive. But to be able to perform arithmetic on values that are supposed to be positive also in the range of 32768 to 65535, we need unsigned representation too. Using the added statements UNSIGN and SIGNED, we can select in which way numbers are supposed to be interpreted.

With unsigned numbers we can subtract any two values, e.g. milliseconds, and get a correct result regardless of which side of the 65535 roll over border the values were sampled. The following program will print the number of elapsed microseconds at an exact interval of one second (= 1000 ms).

10 UNSIGN
20 LET T=TIME
30 IF (TIME-T) < 1000 GOTO 30
40 LET T=T+1000
50 PRINT T
60 GOTO 30 

Beep and wait

I guess it’s always fun to be able to make some noise. And occasionally it may be a good thing to sound of an alarm if for instance some value has reached a threshold level, or if some event has occurred. And since there is a 1kHz square wave already available from the time keeping interrupt, let’s use it! By gating the square wave signal and feeding it to a small buzzer, a sound can be enabled and disabled. For easy access I added the statements BEEP duration and WAIT duration. The two are essentially the same, but WAIT is like BEEP but without sound. The duration sets the length in milliseconds, and can be a number, a variable or an expression. Try this short but annoying program:

10 BEEP 100
20 WAIT 100
30 GOTO 10

Remember that pressing the combination <Ctrl><C> (“Control-C”) will stop execution of the program.

Changing the pitch

By default, the frequency of the sound is 1kHz. That is what we get when we want our time keeper to count milliseconds. And the reason we get 1kHz is because the 8155 counter is pre-loaded with a value that divides the CPU clock frequency (which is fed to the counter input) with exactly the value needed to get the 1ms interrupt rate we want. However, if we are willing to sacrifice correct time keeping, we can use the statement XTAL value to set another clock division value.

The frequency of the crystal on the board is 6.144 MHz (= 6144 kHz) and the default divisor value is 6144. With the following program, the pitch is changed back and forth between the default value and another value. The duration of both BEEP and WAIT will be affected when the division factor is changed and to compensate for that, the duration is set to a higher value when divisor is lower. (The actual CPU clock out frequency seen by the 8155 counter is half of the crystal, but statement XTAL always refers to the crystal frequency.)

10 XTAL 6144
20 BEEP 100
30 WAIT 100
40 XTAL 4096
50 BEEP 150
60 WAIT 150
70 GOTO 10

Peek and Poke

Any decent BASIC interpreter with the ambition to be used for embedded programming would need the classic PEEK and POKE keywords, which are used for read and write access of the memory. So, those are added. Reading memory with PEEK is safe regardless which address you read from, but writing to memory with POKE must be performed with care, since writing to addresses in use by the Tiny BASIC interpreter itself, may cause the system to crash or behave erratic.

Below is a sample program that reads and displays the first 512 bytes of program (ROM) memory. Note the commas at the end of lines 20 and 30 which will suppress the new line character, thereby improving formatting and readability of the printed data.

Also note the print formatter “$4” on line 20 which will print the address in variable A using hexadecimal notation with four digits. The formatter “$3” on line 30 will print two hexadecimal digits (which is enough because it is a 8-bit value) with one leading space (1 space + 2 digits = 3). Print formatter for decimal numbers is #n, where the number ‘n’ sets the alignment. But decimal format is default so we only need to use the #-formatter if we want to change the number of leading spaces.

10 FOR A=0 TO 511
20 IF (A%16)==0 PRINT : PRINT $4,A,
30 PRINT $3,PEEK(A),
40 NEXT A
50 PRINT

On line 20, the IF statement checks if the address in variable A is even divisible with 16 (=when the modulus division result is zero). If so, it prints a new line (the solitaire PRINT statement) and goes on with printing the address at the beginning of the new line.

In and Out

The Intel 8085 has a 16-bit address bus, thereby being able to address 65536 memory locations (0 to 65535). But it also has another address range for I/O (input/output) access, which is 8-bit wide, thus spanning from 0 to 255. To access I/O ports, the added keywords are IN and OUT (which are the same as their corresponding 8085 assembler instruction names).

Next sample is a program that triggers an analogue to digital conversion, reads the result and displays the result on the LED’s connected to 8155 port B. The first statement (line 10) is used to set the port B pins direction to outputs. For a full I/O port address list, se hardware description further below.

The $ (dollar sign) is used to tell Tiny BASIC that the following value shall be interpreted as a hexadecimal number (refer to the similar meaning of the print formatter).

In this example, REM (remark) statements have been used for comments. However, the GOTO command will not accept any additional statements on the same line as it would become ambiguous as to which statement should be executed; the one it is supposed to jump to, or the additional statement. Colon separates statements on the same line. Additional space characters may be used for readability.

10 OUT 0,2      : REM SET PORT B PINS TO OUT
20 OUT $40,0    : REM START AN AD CONVERSION
30 LET R=IN $40 : REM READ THE RESULT
40 OUT 2,R      : REM SHOW RESULT ON PORT B
50 GOTO 20

It was not a completely easy decision to use the $-sign as formatter for hexadecimal numbers, as it is used for string-variables in some other BASIC dialects. But on the other hand, the $-sign is used for hexadecimal notation in other environments. In the original Tiny BASIC, the #-sign was already in use for decimal formatting, and since strings are not used in Tiny BASIC and no better candidate was available, I chose to implement the $-sign for hexadecimal annotation.

Getc and Putc

The GETC function can be used to check if the user has pressed a key, and if so, the ASCII code will be returned. If no key pressed, it returns zero. To output a single character to terminal, you can use PUTC which will print the character of the supplied ASCII code.

This example waits for a key to be pressed (lines 10 and 20) and if  it’s a letter ‘A’ or higher, it prints the character with the succeeding ASCII code (lines 30 and 40).

10 LET A=GETC
20 IF A==0 GOTO 10
30 IF A>='A' LET A=A+1
40 PUTC A
50 GOTO 10

Again, remember that pressing the combination <Ctrl><C> will stop execution.

USR function

Many BASIC interpreters use the USR keyword for a function that calls an assembler routine on a specified memory address and returns a result to a variable. But we will need a function to call to test this. Either we look up a function in the assembler list file (where we can retrieve its actual address), or we make a function for ourselves.

Example below puts a couple of assembler instructions in memory, and then calls the function. After function returns, we print the return value.

10 POKE $C000,$21 : REM LXI H (LOAD HL IMMEDIATE)
20 POKE $C001,$2B : REM LSB (IMMEDIATE DATA)
30 POKE $C002,$1A : REM MSB (IMMEDIATE DATA)
40 POKE $C003,$C9 : REM RET (RETURN)
50 LET A=USR $C000
60 PRINT $4,A," = ",#4,A

The assembler instruction on first line is LXI H ($21), which is poked to address $C000. This instruction is a 16-bit load of CPU register HL which needs two byte of data. We plan to put the hexadecimal number 1A2B in register HL, and since Intel CPU’s read values in little endian byte order (least significant byte first), we put $2B on address $C001 and $1A on address $C002.

Last instruction is RET ($C9) which means return from function. As it happens, the HL register is used for returning values from the USR function, so the value 1A2B will be put in variable A. On the last line of the program we print the value, first with hexadecimal notation, then a = sign and then print it again with decimal notation.

Memory address $C000 and above (up to $FFFF) is safe for using with POKE, since Tiny BASIC doesn’t use this range for any internal purpose.

If the assembler function that you want to call expects any argument passed in any of the CPU registers, you have the ability to preload the registers before making the USR call. If we for instance want to load HL with 1000, DE with 2000, BC with 3000 and the Accumulator with 128, we change line 50 from the example above into:

50 LET A=USR $C000,1000,2000,3000,128

The tiny function we made up with the POKE instructions will of course not be able to use any of the data that we supply, but in some other case it might come in handy. If you decide to preload any register, you must supply values for all of them (HL, DE, BC and A, in that order).

Operands

In the original Tiny BASIC, the most common operands were implemented but it felt like there was something missing. I thought it lacked left and right shift, modulus, logical and, or and xor, and the unary minus, inverse and not operator. So I set about implementing them. I also changed the equality operator from single = to double == to disambiguate the relation operator from an assignment. The not equal was originally using the number sign # but I changed it to the more common != combination.

The interpreter is implemented as a recursive decent parser, and the operands section allows multiple levels of nested parenthesis while maintaining correct operand precedence. This part was perhaps the most cumbersome of the additions to the Tiny BASIC. (Well, maybe with the exception of implementing the multiple page write driver for the EEPROM.)

Man

As an attempt of making the board as self-instructive as possible, I added the direct command MAN which prints a tiny manual. Even after trying to compress the information as much as possible, the text is still about 4kByte long which is actually slightly larger than the entire Tiny BASIC rom code. To fit both parts (the code and the manual) within an 8kByte EPROM chip, I couldn’t be to elaborative with the manual text. Please see the files section for the manual.

Hardware description

Board power

The Micro8085 board is powered from the USB connection supplied by the FTDI LC234X USB to serial module. The board accepts “piggy back” mounting of the module by one 6-pin 2.54 mm socket and one 3-pin 2 mm socket, where the latter is also used for selecting 5V output voltage from the module. The 5V selection is realized by a copper route on the board, so no jumper is necessary. Alternate source is from a DC jack, and selection between the two is done by setting jumper JP1 in correct position. A fairly large TVS/Zener diode (D10) protects board (at least to some extent) from reverse polarity and overvoltage.

CPU and memory

The connection of the Intel 8085 (IC1) is pretty straight forward. Unused inputs (HOLD, INTR, TRAP and READY) are pulled to their inactive levels, and unused outputs (HLDA, INTA, S0 and S1) are left open.

Code/ROM memory (IC4) accepts any of 2764/27128 (8kB/16kB) eprom or 28C64 e2prom. Chip select address range is 0000 – 1FFF (16kB), so the full range of a 27128 can be used. However, the 28C64 e2prom is much easier to source, and the entire Tiny BASIC code plus a text based manual fits without problem in 8kB.

Data/RAM memory (IC5) is 32kB realized by a 62256 static RAM chip, address range 8000 – FFFF.

Non-volatile memory (IC6)  is implemented as a SPI bus connected e2prom using CPU signals SOD and SID for SPI bus MOSI and MISO signals. SPI clock is generated by a dummy write to an I/O address. The serial shift out/shift in is software implemented (assembly) and yields a clock rate of about 64 kHz.

GPIO and Timer

For General Purpose Input Output the board uses the Intel 8155 or 8156 (IC2). The only difference between the two is the active level of the chip select, which is selectable by jumper JP2. Once the board is populated, it would probably be best to solder in a wire jumper for the correct chip type so it won’t get accidentally changed.

Port A and B are pulled down by 10k to GND for the pins to sense a low input level after the chip is reset. In that way the LED’s are inactive after reset, and user must configure the ports (A and/or B) as outputs before writing data to port. LED’s are on when a logic ‘1’ is written to the corresponding bit of the port it’s connected to.

Port C has 10k pullup and acts as inputs for the two user buttons SW1 and SW2 (PC3 and PC4), and for the comparator output (PC5).

The 14-bit timer is used for generating a 1kHz square wave signal. It connects to the RST7.5 interrupt input of the CPU, which generates the 1 ms interrupt implementing a millisec counter. The square wave is also gated by an enable signal and feeds the small buzzer for making sound.

The internal 256 byte of static RAM of the 8155/8156 chip is ignored in order to reduce complexity of the address decoding and chip select logic. The board already got 32kB, so another 256 bytes wouldn’t be of much good anyway.

UART

The serial connection is realized by the Intel 8251A Universal Asynchronous Receiver Transmitter (IC3). Receiver and transmitter clock (307.2 kHz) comes from CPU clock out (XTAL/2 = 3.072 MHz) divided by 10 by a 74LS90. With a clock div factor of 16, this yields a buadrate of 19200 bps. CTS input of the 8251 is tied to GND so it ignores disconnection from USB host, otherwise it would stop transmitting and any running program using the serial port would hang if USB gets disconnected. The DTR and RTS outputs a used by the board’s “operating system” for e2mem chip select and as enable line for the buzzer.

ADC

The board contains two 8-bit analogue to digital converters (IC14 and IC15). To start a conversion, write something to its I/O address, and after waiting for conversion to finish, read the result from the same I/O address. One of the ADC’s drive both clocks, and with 10k/100p the clock is about 520 kHz which yields a conversion time of about 140 us.

To reduce complexity, I decided to omit routing the INT signals to a input port, and instead let user implement a software delay between the start of conversion write trig, and reading the result. But this will only be an issue if you are writing assembler routines. When running BASIC, it’s safe to trig the conversion and then read the result in the next BASIC statement.

Below is a small sample program that reads the first ADC and displays value on Port B (addr 2), then reads second ADC and puts out result on Port A (addr 1). First statement sets port A and B as outputs.

10 OUT 0,3
20 OUT $40,0
30 OUT 2,(IN $40)
40 OUT $50,0
50 OUT 1,(IN $50)
60 GOTO 20

The analog inputs are buffered by a voltage follower, which consists of a dual rail to rail input and output OP amp (IC22). For generating a variable signal to the ADC’s (for testing your programs), there are two trim potentiometers (P1 and P2) which are fed to the analogue buffer inputs via removable jumpers. Remove jumpers if you want to source some other signal to the input pins available in connector J4.

DAC and comparator

For generating a general purpose analogue output, the board contains one 8-bit digital to analogue converter (IC16). The output is amplified/buffered by one half of a rail to rail OP (IC21). The other half is set up as an astable multivibrator, where the triangle wave (well almost; it’s a log curve) is fed to one comparator input (half of IC20), where the level is compared with the DAC output. This circuit generates a 4kHz PWM output, where the duty cycle depends on the output level from the DAC. The PWM signal drives a white light emitting diode (LED1), and the brightness is controlled by the value you write to the DAC. To enable the PWM output, the user must write value 1 to I/O address $30 which sets bit 0 (one of the select lines) of IC8, which releases the PWM comparator output (accomplished by diode D9).

The DAC output is also fed to the other half of the comparator (IC20), where it’s compared to an input value. The output of this comparator is routed to port C bit 5, via a removable jumper. This could be used as one additional analogue input; generate an analogue output from DAC and compare with the input. Comparator will tell if DAC is too high or too low, and your program can track the analogue input or even perform a successive approximation analogue to digital conversion. Below is a small program that does just that. Variable R is the result, B is the bit value for testing towards the analogue input. At the end, one of the “real” ADC’s is also triggered for a conversion of the same value, to compare and validate the calculated result. The ADC value is displayed on port B and the result on port A.

10 OUT 0,3
20 LET R=0
30 LET B=128
40 LET R=R|B
50 OUT $60,R
60 IF !(IN(3) & $20) LET R=R&~B
70 LET B=B>>1
80 IF B!=0 GOTO 40
90 OUT $50,0
100 OUT 2,(IN $50)
110 OUT 1,R
120 GOTO 20

Extension connectors

Connectors J3 and J4 can be used, individually or together, for extension board(-s) or other add-on functionality. Address and data bus, other CPU signals, and the SPI bus are available in J3, and the GPIO ports and analogue inputs and outputs in J4. They are both 34 pin connectors, located 3000 mil (76.2 mm) c-c apart, so mating connectors can be mounted on any 100 mil (2.54 mm) prototype board and connect “piggy-back” to the Micro8085.

Prototype area

To the right of extension connector J4, there’s a small area for prototype build or (re-)routing signals for another connector pinout, etc. It doesn’t show very well through the dark blue solder mask, but the holes are connected in groups of 3 – 2 – 3, i.e. the two holes of every row in the two center columns are connected to each other, and the three to the left, and the three to the right, respectively. At the time of PCB design I hadn’t any particular plan for this, but if any idea comes up later there’s at least a small space for a test build.

PCB ver 1 defects

Most of the functionality of this board, and the additions to the Tiny BASIC interpreter, was developed using an old project PCB with a lot of manual circuit re-work for testing and verifying functionality. But some of the circuits were not possible to implement without rather time consuming prototype building. The DAC, OP amp and comparator section is one example.

The Texas Instruments TLC7524 DAC is actually a current source converter, thus needing a negative supply for the analogue output. Theoretically, the R-2R ladder can be “turned around” and be used for generating a voltage output. The datasheet implies this, and I had used the TLC7524 many years ago in this setup, so I thought I was home free. However, what I didn’t realize (and probably missed last time) was that the internal analogue hi/lo switches wouldn’t allow operation close to the VCC rail. The output range was found to be 0 – 4V instead of 0 – 5V. Therefore, the originally intended OP amp follower (half of IC21) had to be modified into an amplifier. Resistors R43 and R44 are on the schematics (ver 1.1), but not on the PCB (which only exists in ver 1.0). The modification consists of two cut lines on the PCB and two resistors (2x 0603 SMD) hidden under the IC socket of IC21. And the route to the comparator that was originally tapped directly from DAC output had to be moved to the OP output.

The UART (IC3) Rx data input is connected to the Tx (output) of the USB to serial module. This works fine regardless if the board is powered through the USB module (UART Rx is held high) or powered from DC jack (USB module is without power, UART Rx being held low). But I missed the case when the USB module is not mounted, and board is powered from DC-jack. Then the Rx input is left floating, which I found out can in some cases lead to a latch-up condition of the 8251, and it will not report correct status of transmitter data empty bit when read by CPU. This may lead to unwanted execution halt. Pullup resistor R45 is added on the schematic (ver 1.1) but not present on PCB (1.0).

The inverters/drivers for the LED arrays of ports A and B are implemented by three 74HCT04 hex inverters (leaving two unused). However, the PCB space is just (barely) enough for three chips in a row, and I’m contemplating a change into two 74HCT240 octal inverting buffer. Nothing is wrong with the HCT04’s, but it doesn’t look that good to have them jammed together.

And lastly, there seem to have been some text size ambiguity in the gerber files. The first batch of boards had slightly oversized texts that occasionally impaired reading and/or made parts of text overwrite other texts or prints. Not a very big issue, but if I’ll ever order more boards, it would be great to find the reason for this.

Summary

As a start, I made a small order of 10 PCB’s from PCBway (11 shipped!), and I’ve built a couple of boards. I’m quite pleased with the result, but now the interesting part of this project starts: Can I find any actual useful application for this board? I guess that’s a relevant question for any type of hobbyist project :-) But as I mentioned at the beginning of this text, one thing I would like is if this project could be used for learning/teaching embedded programming. We’ll see. It was fun doing, so I guess, mission accomplished!