This tutorial is about building a 80s style home computer with BASIC interpreter on the basis of FabGL and the ESP32 microcontroller family. The system supports sound, graphics, and networking. Input device is a PS2 keyboard.
The microcontroller hardware is a board produced by TTGO with the VGA, PS2, and audio sockets already installed.It was originally designed for retro computing and running emulators of 70s and 80s operating systems. The project here showcases a BASIC interpreter that has been newly developed.
As the software setup and some of the features of the board are not completely beginner level, I have been asked to publish a tutorial on the detail. Here it is.
To build the computer you will need the following hardware components:
- TTGO ESP32 VGA board. I got mine directly from the source at Aliexpress. Prices are currently around 15 Euros.
- VGA monitor or an old TV set with VGA input and a VGA cable.
- PS2 keyboard.
- 5V power supply, preferably with 2A power output.
- Micro SD cards.
- Loudspeaker with 3.5mm AUX input
Software components needed are
- an Arduino IDE, version 1.8.15 or newer.
- the ESP32 board definitions.
- the FabGL library.
Details on these libraries and how to put it all together will be given further down in the tutorial. This is simple but a little care is required on library versions and settings.
A Brief Look On The TTGO BoardThe board is specifically designed for retro computing and gaming. Its core is a ESP32 microcontroller. This chip drives all the functions of the computer. Sound signals, VGA signals, and keyboard handling is all done in software.
It has a VGA connector using 8 of the ESP32s GPIO pins. Mouse and keyboard connector use another 4 pins. The SPI bus is attached to the SD card slot on the board. Standard micro SD cards can be used. The bus is also lead out on a small area next to the power plug. SPI is on pins 12, 2, and 14 with 13 being the chip select of the SD card. Pins 39 and 34 are also freely available.
The board is designed for use with the FabGL library http://www.fabglib.org. This library contains many useful software components like sound generators, ASCII terminals, and emulators. The library can be download either from the repo or the Arduino IDE library manager.
With its 520 kB RAM it has enough memory for the graphics buffers and user programs. Timing and interrupts are tricky on the board. Software development on it is nothing for beginners.
Download The Software ComponentsYou will need the Arduino IDE, version 1.8.15 or newer. I develop on 1.8.15 myself and test on 2.0 from time to time. the IDE can be downloaded from https://www.arduino.cc/en/software.
In addition to the Arduino IDE you might need some drivers for the USB to serial chips. Some come bundled with the IDE. The ESP website has some information on the drivers. A step by step installation guide is found here https://www.instructables.com/Installing-ESP32-on-Arduino-IDE-the-Easy-Method/
The next step to get your development environment reads is to install the board definitions. Go to the preferences menu of the Arduino IDE. At the bottom you see a box for additional board URLS. Add the following line
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_dev_index.json
as shown in the picture.
After you have done this, you need to open the board manager menu and install the esp32 board definitions. A few screenshots on this can be found here: https://www.instructables.com/Installing-ESP32-on-Arduino-IDE-the-Easy-Method/.
I use version 2.0.5. Please make sure that you use a newer 2.0.x version. The initial versions where a little buggy.
After you have the IDE ready, you still need to download FabGL. Go to the library manager of the Arduino IDE and type in fabgl in the search box. Download the newest version. I use 1.0.8 at the moment. This is tested to work with the ESP 2.0.5 board definitions. Older versions of FabGL do not compile on ESP 2.x cores.
Now you have you IDE all set up.
Download And Compile The BASIC InterpreterDownload the BASIC interpreter from my repo https://github.com/slviajero/tinybasic/tree/main/IoTBasic.
All the files in this directory are needed. IoTBasic.ino is the interpreter, basic.h the headers and hardware-arduino.h the hardware definition files. IoTBasic is a full featured BASIC interpreter and developed from scratch using concepts from various sources.
Open IoTBasic.ino to set the language features. Near the beginning of the file you will see the hardware definition section. Make sure that it looks like this
#define BASICFULL
#undef BASICINTEGER
#undef BASICSIMPLE
#undef BASICMINIMAL
#undef BASICTINYWITHFLOAT
This activates all the features of the interpreter including strings, arrays, floating point arithmetic and some other new things.
Next look into hardware-arduino.h. In the section
#undef USESPICOSERIAL
#undef ARDUINOPS2
#undef ARDUINOUSBKBD
#undef ARDUINOZX81KBD
#undef ARDUINOPRT
#undef DISPLAYCANSCROLL
#undef ARDUINOLCDI2C
#undef ARDUINONOKIA51
#undef ARDUINOILI9488
#undef ARDUINOSSD1306
#undef ARDUINOMCUFRIEND
#undef ARDUINOGRAPHDUMMY
#undef LCDSHIELD
#undef ARDUINOTFT
#undef ARDUINOVGA
#undef ARDUINOEEPROM
#undef ARDUINOI2CEEPROM
#undef ARDUINOEFS
#undef ARDUINOSD
#undef ESPSPIFFS
#undef RP2040LITTLEFS
#undef ARDUINORTC
#undef ARDUINOWIRE
#undef ARDUINOWIRESLAVE
#undef ARDUINORF24
#undef ARDUINOETH
#undef ARDUINOMQTT
#undef ARDUINOSENSORS
#undef ARDUINOSPIRAM
#undef STANDALONE
everything should be #undef. This is the default in the repo.
A little further down comes the board section. These are predefined hardware settings.
#undef UNOPLAIN
#undef AVRLCD
#undef WEMOSSHIELD
#undef MEGASHIELD
#define TTGOVGA
#undef DUETFT
#undef MEGATFT
#undef NANOBOARD
#undef MEGABOARD
#undef UNOBOARD
#undef ESP01BOARD
#undef RP2040BOARD
#undef RP2040BOARD2
#undef ESP32BOARD
#undef MKR1010BOARD
Make sure that only TTGOVGA is selected and all other settings are #undef.
To compile the BASIC interpreter, go to the ESP32 section of the boards menu and compile for "TTGO T7 1.4 mini32". This is the board definition with the right pinout.
Compile and upload. Connect the board to the keyboard and the monitor. After a while the welcome message should appear.
Add the SD card and start programming.To exchange programs with your big computer and store programs you wrote on the ESP32 homecomputer, a SD card can be used. I prefer the 1GB Kingston cards. They work well on all kind of SPI buses of micro controllers. Some of the cards won't work well. One needs to give it a try.
Format the card with a FAT filesystem, ideally on a Windows computer. Mac formatted cards can also work, but I have seen more difficulties with them.
Let's take the tour through some of the demo programs to see what the BASIC interpreter can do with this hardware. All of the demo programs shown here can be downloaded from the examples section in my repo. Follow this link to find it https://github.com/slviajero/tinybasic/tree/main/examples/00tutorial.
Run the Graphics TestFirst try out the graphics test program PLOT.BAS. The code looks like this
100 REM "Test the graphics"
110 SX=640: SY=480
200 T=MILLIS(1)
210 FOR I=1 TO 10000
220 X=RND(SX)
230 Y=RND(SY)
240 R=RND(40)
250 C=RND(16)
260 COLOR C
270 FCIRCLE X,Y,R
280 NEXT
290 COLOR 15
300 PRINT "1000 Random circles drawn in",(MILLIS(1)-T)/1000,"seconds"
You can either type it in, but better load it from the SD card using
LOAD "plot.bas"
Then start it by typing
RUN
on the command line. The output would look like this:
The computer has drawn 10000 colored circles in 1.3 seconds. This shows how fast FabGLib on an ESP32 system is. BASIC directly uses the graphics calls of the library.
Let's look at the code for a moment.
BASIC uses a coordinate system with its origin in the upper left corner. The X axis goes to the right and the Y axis goes down. Screen dimensions with these settings are 640 to 480 pixels. 16 VGA colors can be displayed.
The command
FCIRCLE X, Y, R
draws a filled circle of radius R at the point X, Y. The command
COLOR C
sets the color to the VGA value C. 0 is black while 15 is white.
A command that is new and not found in many other BASIC interpreter is the function MILLIS(1). This function gives the milliseconds since start of the interpreter. The argument is a divisor which can be used to change the time scale. MILLIS(10) would give the time in 10 ms units.
Calculate Digits of PIThe program has been donated by Guido Lehwalder. The original code was written by Peter Dassow as part of a classical computer benchmark project.
First erase the program in memory and then load the program by typing
NEW
LOAD "CPINEW2.BAS"
You can look at it by typing
LIST
The program code is displayed
100 REM "CALCULATING PI FOR HUNDREDS OF DIGITS"
110 REM "THX TO ROSETTACODE.ORG FOR THE BASE OF THE PROGRAM"
120 REM "SHOULD BE A BENCHMARK FOR THE BASIC INTERPRETER/COMPILER"
130 REM "WRITTEN IN 2023 FOR FORUM.CLASSIC-COMPUTING.DE BY PETER DASSOW"
135 REM "Ported to IoT BASIC by Guido Lehwalder"
136 REM "Added some IoT BASIC features - Stefan Lenz, 2023"
137 CLS
140 PRINT "Calculating Pi as a BASIC benchmark."
150 PRINT "Enter number of digits: ";
160 INPUT S$
170 IF S$="" THEN PRINT "Nothing done.": END
180 N=VAL(S$): IF N<1 THEN PRINT "Not a valid number.": GOTO 150
190 REM "N DETERMINES ALSO THE ARRAY (ABOUT 3-4 TIMES BIGGER)"
200 LN=INT(10*N/3)+16
204 REM "@ is the space on the heap for numbers, reserve 64 bytes"
205 IF LN>(@-64) THEN PRINT "Not enough memory": GOTO 150
210 ND=1
220 SM=MILLIS(1)
225 REM "Delete the array if already there"
230 IF FIND(A())<>0 THEN CLR A()
235 REM "Then redim"
240 DIM A(LN)
250 N9=0
260 PD=0
270 REM
280 FOR J=1 TO LN
290 A(J)=2
300 NEXT J
310 REM "Use of the modulo operator % speeds things up"
320 FOR J=1 TO N
330 Q=0
340 FOR I=LN TO 1 STEP -1
350 X=10*A(I)+Q*I
360 A(I)=X%(2*I-1)
370 Q=INT(X/(2*I-1))
380 NEXT I
390 A(1)=Q%10
400 Q=INT(Q/10)
410 IF Q=9 THEN N9=N9+1: GOTO 610
420 IF Q<>10 THEN GOTO 540
430 REM "Q==10"
440 D=PD+1: GOSUB 670
450 IF N9<=0 THEN GOTO 500
460 FOR K=1 TO N9
470 D=0: GOSUB 670
480 NEXT K
490 REM "END IF"
500 PD=0
510 N9=0
520 GOTO 610
530 REM "Q<>10"
540 D=PD: GOSUB 670
550 PD=Q
560 IF N9=0 THEN GOTO 610
570 FOR K=1 TO N9
580 D=9: GOSUB 670
590 NEXT K
600 N9=0
610 NEXT J
620 C$=("0"+PD%10)
621 PRINT C$
630 EM=MILLIS(1)-SM
635 PRINT "Calculation time ";EM/1000;" seconds."
640 GOTO 140
650 REM
660 REM "OUTPUT DIGITS"
670 C$=("0"+D%10)
671 IF ND=0 THEN PRINT C$;: RETURN
680 IF D=0 THEN RETURN
691 PRINT C$;".";
700 ND=0
710 RETURN
This program calculates and arbitrary number of digits of PI. For memory reasons around 3000 would be the maximum on a 64 kB computer. Type
RUN
to start the program and try to calculate the first 100 and 1000 digits of PI. The output looks like this:
BASIC calculates 100 digits of PI in 3.4 seconds. On my old Macbook the same BASIC interpreter would do the job in 0.7 seconds. This shows again how fast the ESP32 is.
The program showcases some of the unusual features of the BASIC interpreter.
Look at the lines
230 IF FIND(A())<>0 THEN CLR A()
235 REM "Then redim"
240 DIM A(LN)
The code checks is an array already exists using the FIND command and the deletes it using the CLS command. The array can then be safely redimensioned with DIM. Unlike the old day BASIC interpreters, this implementation gives full heap control to the program.
Another unusual feature can be seen in the line
670 C$=("0"+D%10)
A string gets assigned a character expression. This is probably understandable to every C programmer. Strings in this BASIC dialect are like C strings. They are character arrays and can be used in the same way as in C.
Calculate the Mandelbrot SetOne of the favorite test programs for home computers the Mandelbrot set calculation. The tutorial has one. Clear the memory with NEW and then LOAD "mandelv.bas".
The program looks like this:
10 REM "Caculate the mandelbrot set"
20 REM "The iteration cutoff, threshold, and resolution"
30 N=24
40 T=4
50 R=100
100 C0=0: C1=0
110 CLS
200 REM "walk through the grid"
210 FOR J=2*R+1 TO 1 STEP -1
220 FOR I=1 TO 3*R+1
230 C0=-2+(I-1)/R: C1=-1+(J-1)/R
240 GOSUB 4000
250 GOSUB 5000
280 NEXT
300 NEXT
310 FOR I=1 TO 12: PUT 27, "B": NEXT
999 END
4000 REM "do an iteration on c"
4010 Z0=C0: Z1=C1
4020 FOR K=1 TO N
4030 S0=Z0*Z0: S1=Z1*Z1
4040 IF S0+S1>T THEN BREAK
4050 X0=S0-S1+C0
4060 X1=2*Z0*Z1+C1
4070 Z0=X0: Z1=X1
4080 NEXT
4090 RETURN
5000 REM "plot a point"
5010 IF K>N: COLOR 0: PLOT I,J: RETURN
5020 COLOR 255-INT(K/N*255)
5030 PLOT I,J
5040 RETURN
It is very simple and does not use the symmetry of the set. A drawing area of 300 times 200 pixels is used for it. The output of the program would look like this
The drawing time with this resolution is approximately one minute. The program can always be interrupted by typing # which is the break character of BASIC. This character can be freely defined.
One detail is the positioning of the cursor after the program has ended. It is placed outside the drawing area. This is achieved with the sequence
310 FOR I=1 TO 12: PUT 27, "B": NEXT
at the end of the program. I sends 12 time ESC B. This is the VT52 command for cursor down. The text terminal of BASIC is VT52 compatible. It implements the control characters as described here: https://en.wikipedia.org/wiki/VT52.
More informationA lot of demo programs have been ported this BASIC dialect. You can download the demos from the repo folders https://github.com/slviajero/tinybasic/tree/main/examples in the examples section. Best start with the tutorial. If you have never programmed BASIC or if you want to learn the specialities of this particular dialect.
There is a manual on all the language features and command in https://github.com/slviajero/tinybasic/blob/main/MANUAL.md. There is a special section in it on this particular hardware https://github.com/slviajero/tinybasic/blob/main/MANUAL.md#esp32-vga-with-fabgl including the sound commands.
Most features of this BASIC interpreters are portable between different micro controllers. You can build an Arduino or RP2040 based system with TFT displays and find the same graphics and console commands as well as the same language features.
Please look at the wiki in my repo for more information https://github.com/slviajero/tinybasic/wiki.
Comments