Steve Wozniak once said that creating the Apple 1 integer basic interpreter was the biggest professional challenge of his life. I tried to do what Steve did, write a BASIC interpreter from scratch without using any code from anyone else. I wanted something useful on a very small computer.
It turned out that it may be a nice tool for an Arduino. Interactive access to the I/O functions makes it easy to test electronics. All pins can be steered with BASIC commands. Programs and data can be stored in the EEPROM.I implemented most of the standard BASIC language including strings, floating point and most of the Dartmouth language set.
See my github page to find the code https://github.com/slviajero/tinybasic
Use the code in IoTBasic, this is the released code.
More about BASICThis text is part one of a multi part tutorial. It focuses on installing the BASIC interpreter and showing some of its features. As the project has grown quite a bit, I split the information into a set of texts. The first two sequels are.
Built-In examples show the Arduino I/O function in more detail. Writing and reading digital and analog I/O, pulseIn and tone are the focus here.
Using small displays shows elementary display functions typically used for text based LCD display applications.
Download and compile the BASIC interpreterDownload the code in https://github.com/slviajero/tinybasic/tree/main/IoTBasic and import it into your Arduino IDE. Open the sketch and review the settings section a the very beginning of the code. For standard 8bit AVR systems like the ones mentioned above the initial sections needs to look like this
#undef MINGW
#undef MSDOS
#undef RASPPI
Don't change this unless you want to compile for MSDOS or Windows. As a language feature section below these settings can be
#undef BASICFULL
#undef BASICINTEGER
#define BASICSIMPLE
#undef BASICMINIMAL
#undef BASICTINYWITHFLOAT
If you use an Arduino UNO define the BASICSIMPLE macro for am Integer BASIC with a reasonable language set containing strings, arrays and the full set of BASIC control structures.
For Arduinos with larger flash memory, BASICFULL can be used. This would generate a 36 kB executable on 8bit platforms like the MEGA or NANO Every.
Open hardware-arduino.h. In the hardware definition section all should be on `#undef` except for
#define ARDUINOEEPROM
Compile the code with these settings and review the memory consumption. For an UNO this should be a 25 kB executable and there should be 1.2 kB of free RAM reported after compilation. Upload the code.
Try it out!Use the serial monitor of the Arduino IDE with the following settings * baudrate 9600 * Newline
The interpreter should show a startup line like
Stefan's Basic 1.2 Memory 1024 1024
and then prompt for input.
The first number is the release of basic.
The second number is the available RAM for BASIC, the second the available EEPROM. The available RAM may vary depending on the language features. 1KB is about the amount one gets as the internal variables of BASIC need the rest.
Enter your first BASIC program
10 PRINT "HELLO WORLD"
and run it.
RUN should display "HELLO WORLD" and return to the input prompt.
Typing SAVE will store the program into the EEPROM. You can try this out by resetting your Arduino and then reload the program with LOAD. Type LIST to check it.
The blink programThe equivalent to "Hello World" on Arduinos is the blink program.
In BASIC it looks like this
10 PINM 13,1
20 DELAY 1000
30 DWRITE 13,1
40 DELAY 1000
50 DWRITE 13,0
60 GOTO 20
Type it in and start it with RUN command. The little LED on your Arduino should blink now in 1 second intervals. Sending the character '#' will interrupt the program and make the interpreter return to the command prompt.
'#' interrupts the program. All other input is ignored. Typing any other character than '#' will flood the serial buffer and the interrupt character will no longer be recognised.
The Arduino functions work pretty much as in C++. Check the manual for the ported commands. You can try them in interactive mode. This is one of the advantages of running BASIC on an Arduino. Again, you can save your work to the EEPROM by typing SAVE and reload the program at a later time using LOAD.
Autorun from EEPROMIf you have saved a program to EEPROM you can activate the autorun flag by typing SET 1, 1
If you reset the Arduino now using the reset button or disconnecting and connecting the power supply, the program is started automatically. No command prompt will appear.
Sending '#' from the serial console will interrupt the program.
If you type LIST now, no program will be shown. Autorun programs are not loaded to RAM but are executed directly from the EEPROM. This way, the entire RAM is available in autorun mode. The autorun flag can be removed by the command SET 1, 0.
The command prompt will appear after reset.
Reading dataYou need any analog sensor and a cable for this project. One nice example would be the photo sensor shown in the picture. It returns an analog value 1023 at absolute darkness and 0 at maximum brightness.
Connect the analog output pin of the sensor to the analog port A0 and the digital output to the digital port D2 of the Arduino. Then type in the BASIC program
10 PINM AZERO ,0
20 M0=1024:M1=0
30 FOR I=1 TO 10
40 L=AREAD (AZERO )
50 IF M0>L THEN M0=L
60 IF M1<L THEN M1=L
70 DELAY 1000
80 NEXT I
90 PRINT "Minimum ",M0
100 PRINT "Maximum ",M1
and save it to the EEPROM with SAVE. Running the program will record the value of the sensor read every second for 10 times and calculate the minimum and maximum values. AZERO is a constant containing the lowest analog pin A0. A1 would be AZERO+1.
Storing dataThe EEPROM of the Arduino can be used to store measurement data. The next measures the average light intensity for one minute and stores the result in the EEPROM:
10 PRINT "Previous measurement ",@E(1)
20 PRINT "Starting new measurement"
30 L=0
40 FOR I=1 TO 60
50 L=L+AREAD(AZERO)/10
60 DELAY 1000
70 NEXT
80 @E(1)=L/60
90 PRINT "Average = ",@E(1)
After activation of the autorun feature with SET 1, 1 the Arduino can be used without a computer to measure data. The value can be read the next time the Arduino is connected to a computer.
Terminal programsThe Arduino IDE has a simple serial monitor but cannot download and upload data. Many solutions for this exist. I recommend and use Roger Meier's CoolTerm.
Binaries for Windows, Linux and Mac exist. The program is a full featured serial terminal. I downloaded my Mac binary from MacUpdate.
Before connecting to the Arduino the terminal programs has to be configured in the Options menu:
Set the port to your serial port (the same as the Arduino IDE used), the baudrate to 9600, data to 8, parity to none and stop to 1. These are the default settings of Arduino serial and IoT BASIC. In the terminal configuration page chose these options:
Raw mode transfers every keystroke, key emulation has to be Unix style LF. In addition to this "Handle DEL and BS" is needed to delete data in a line and "Handle FF" enables clearing the screen from BASIC. These are the minimal terminal functions in the standard BASIC language set.
The terminal settings for the use of Stefan's BASIC on an Arduino can be downloaded here: CoolTerm settings. The program is not a full features vt52/100 compatible terminal program.
Uploading or downloading a programFor file upload goto the Connection menu in CoolTerm and select Send Text/ Binary File.
One nice example could the the letter guessing game from 101 BASIC games. It fit's into a 1k Arduino BASIC memory. Upload the program and enjoy 1975 gaming fun.
With the current settings of CoolTerm there is no flow control. Timing problems can cause the upload to fail as BASIC on the Arduino doesn't receive characters fast enough. A simple fix would be to add small delays in the Transmit page of CoolTerm's configuration menu. A 3ms line delay does the job.
A program can be downloaded from the Arduino using the Capture Text/ Binary File function. After starting the capture type LIST and then stop the capture. Edit the file on your computer to remove the last and first line.
LCD shield based BASIC systemsOne of the nicest small hardware extensions for Arduino are the 16x2 LCD shield with 4 buttons. They can be used from BASIC programs easily. Compile the interpreter as before but use the additional setting
#define LCDSHIELD
#define DISPLAYCANSCROLL
in hardware-arduino.h.
This activates the display driver and the hardware code for the LCD shield. Compile and upload the script.Connect to the Arduino with the Serial Monitor or CoolTerm. Enter
PRINT &2, "hello world"
and look on the display. It should display the string in the upper left corner of the display.
The modifier &2 redirects the output of the print command to the display. With the settings above the display can be accessed as a mini terminal, capable of scrolling and a few terminal commands. Type PUT &2, 12 to clear the display screen. 12 is the ASCII code of Form Feed and is used to clear the screen.
Keypad functions of the LCD shieldType in the code
10 PUT &2, 12
20 PRINT &2,"LCD shield test"
30 PRINT &2,"Key code: ";
40 @X=8:@Y=1
50 GET &2,A
60 IF A<>0 THEN PRINT &2,A;
70 DELAY 10
80 GOTO 30
or upload it to the Arduino with CoolTerm using shield.bas. Type RUN to start the program then press one of the shield keys. The key code is displayed in the second line.
The keys are mapped to single character ASCII values - select = LF, right =2, left = 1, up = 3 and down = 4.
GET &2, A returns these values into the variable A whenever the key is pressed and 0 if no key is pressed. This code uses the special variables @X and @Y to position the cursor. They contain the horizontal and vertical position of the cursor starting at 0 and can be read or assigned by the program.
Display buffer arrayType in the following code then type RUN.
10 PUT &2, 12
20 X=0: Y=0
30 P=X+16*Y+1
40 @D(P)="X"
50 GET &2, A : IF A=0 THEN 50
60 A$=A
70 @D(P)=" "
80 IF A$="3" AND Y>0 THEN Y=Y-1
90 IF A$="4" AND Y<1 THEN Y=Y+1
100 IF A$="1" AND X>0 THEN X=X-1
110 IF A$="2" AND X<15 THEN X=X+1
120 IF A=10 GOSUB 200
130 DELAY 300
140 GOTO 30
200 PRINT "Selected", X, Y
210 DELAY 500
220 RETURN
You can move around the 'X' symbol with the keys. Pressing select outputs the position of the symbol on the screen.
The code uses the display buffer array @D(). Writing to it displays a character directly on the screen. @D() starts at array subscript 1 and counts cells from left to right and top to bottom. Note how the program converts strings to integers in line 40 and 60. In assignments strings yield the ASCII code of the first character as their integer values. This only works in assignments or on the right hand side of integer expressions. The program has the typical structure of Arduino sketches - line 10 and 20 are setup(), lines 30 to 140 are the loop and lines 200 to 220 are a function.
The stopwatchAn example for a standalone program is the stopwatch.
100 REM "A little stopwatch using the 16*2 shield"
200 REM "The setup()"
210 D=0:T=0:U=0:S=0:C=0
220 PUT &2,12 : PRINT &2;"Time = ";
230 GOSUB 600
300 REM "The loop()"
320 GET &2,A
330 IF A=10
340 IF C=10
350 IF A="l" THEN 500
360 DELAY 10
370 IF S=1 THEN T=MILLIS(10)-D
380 IF T<>U THEN U=T:GOSUB 600
390 GOTO 300
400 S=(S+1)%2:IF S=1 THEN D=MILLIS (10)
499 GOTO 300
500 END
600 @Y=0:@X=8:PRINT &2;" ";
605 @Y=0:@X=8:PRINT &2;T;
610 RETURN
Type in the program or upload it with Coolterm. Then type SAVE : SET 1, 1 to activate autorun from the EEPROM. Powering up the Arduino from an external power source like a little power bar makes it a standalone stopwatch.
Timers and interruptsBASIC comes with built-in commands to process timers and interrupts. A blink program without delay would look like this.
10 S=0
20 EVERY 1000 GOSUB 1000
30 GOTO 30
1000 DWRITE LED, S
1010 S=(S+1)%2
1020 RETURN
The BASIC interpreters branches every 1000ms to line 1000, changes the LED state and then returns to the loop. This code can be used to poll for I/O events or refresh display while the Arduino does stuff in a loop.
Another timer function would be to interrupt a loop.
10 AFTER 3000 GOTO 1000
20 PRINT "In the loop"
30 DELAY 300
40 GOTO 10
1000 PRINT "Loop has ended"
The AFTER timer only reacts once while EVERY is periodic. AFTER and EVERY use independent timers and can both be used in a program.
Reacting to an interrupt is done with the command EVENT. This is demonstrated in the example program
10 PINM 2, 2
20 EVENT 2, 1 GOSUB 1000
30 GOTO 30
1000 PRINT "Interrupt": RETURN
The PINM command in the first line sets pin 2 to INPUT_PULLUP. EVENT reacts on interrupt type 2 which is CHANGE. Once the pin is connected to ground the interrupt code is started.
Allowed interrupt types in Arduinos are 0 (LOW), 1 (CHANGE), 2 (FALLING) and 3 (RAISING). Some MCUs have more interrupt modes.
Color displays and graphicsA number of displays are supported as part of the BASIC code. One of the most common is the parallel port TFT display with a 480x320 resolution.
These display are supported by the MCUFRIEND_kcb library which needs itself based on Adafruit_GFX. Make sure to install both libraries with the Arduino library manager. An Arduino MEGA2560 or is needed for this application as the libraries need a lot of flash memory. On this MCU the full BASIC interpreter can be used.
Define
#define BASICFULL
in the language definition of the sketch and activate the macros
#define DISPLAYCANSCROLL
#define ARDUINOMCUFRIEND
in hardware-arduino.h.
Mount the shield on the Arduino MEGA2560 and upload the sketch.
On the serial monitor, type
PRINT &2, "hello world"
CIRCLE 100, 100, 50
and watch the display. The output should look like this:
To do a little more with graphics, upload the program
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
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
to your little BASIC computer. It calculates the Mandelbrot set.
This configuration can be extended to a full standalone computer. Read my project on a ZX81 lookalike for more information.
What's next?BASIC has grown much beyond the features described in this project. In the meantime it has Wire, many different displays, networking and can power bigger computers. I also ported most of the Arduino example library for it.
If you are interested in this, please follow any of these links
- Standalone computer with an Arduino RP2040 connect
- Home computer based on a DUE
- An AIM65 lookalike with a MEGA
- A 10 Euro IoT computer
- The ZX81 lookalike
All the standard examples from the Arduino library are in my repo.
Comments