Tiny Basic with room to spare Dec 2015
One of the first tricks we teach a microcontroller (any computer really), is to raise its level of abstraction, by programming it in a “higher level language”. All computers are really awkward at birth: they only understand machine code, i.e. streams of bytes & numbers.
Nobody does that anymore, there just wouldn’t be enough hours in a day to get anything done. Instead, we use tools like gcc and the Arduino IDE. But that can’t be done on the µC itself, it’s far too complex and requires far too many resources. Which means we need a big computer to make a small one do things. Is there no other way?
It’s worth looking back at the time when there were no big computers. When everything had to be solved on small ones, because “big” was simply unaffordable for hobbyists:
Meet Tiny BASIC - a trend from the mid 1970’s, which made it possible to fit a complete programming environment in just 2..8 KB of memory (including your own code!). Back then, a microcomputer took up half your desk, and memory was sold by the kilobyte.
Here is a typical language definition for these BASIC implementations:
line ::= number statement CR | statement CR statement ::= PRINT expr-list IF expression relop expression THEN statement GOTO expression INPUT var-list LET var = expression GOSUB expression RETURN CLEAR LIST RUN END expr-list ::= (string|expression) (, (string|expression) )* var-list ::= var (, var)* expression ::= (+|-|ε) term ((+|-) term)* term ::= factor ((*|/) factor)* factor ::= var | number | (expression) var ::= A | B | C ... | Y | Z number ::= digit digit* digit ::= 0 | 1 | 2 | 3 | ... | 8 | 9 relop ::= < (>|=|ε) | > (<|=|ε) | =
Single-letter variables, integers only, and jumps / subroutines only using line numbers.
The trend was set with Palo Alto Tiny Basic. Here is a reworked version, including the original listing. And yes, it really consists of less than 2500 bytes of code (although it normally expects to run in 8 KB, with the rest available for your own code and data).
That’s an editor, a runtime, even a crude load/save mechanism (cassette tape, back then).
The trick is interpretation - running code on the µC which examines text you type, and figures out how to perform the requested operations. Some Tiny BASIC implementations even had their own special-purpose virtual machine, to make their internal logic even more compact. By treating our programs as as sequence of canned tasks, each implemented once on the computer itself, you gain the benefit of coding at a higher level of abstraction, while condensing the underlying machine code into what is essentially a “task sequencer”.
Tiny language implementations continue to this day. They may sacrifice performance, but they offer a highly effective (and often also robust) environment in return. There is no need for a “compiler”, let alone a cross-compiler on a separate machine to run your code.
With C being the primary low-level implementation language these days, many new implementations have been created in the past decades - here is a nicely documented one.
So let’s see if we can get Tiny BASIC to run on our HY-Tiny STM32F103 board, using the Arduino IDE as implementation tool (i.e. C/C++) via the Arduino-STM32 extension.
We’ll use the TinyBasic Plus implementation, which has already been adapted for the Arduino runtime. As you can see, it has good documentation, and quite a few features. There is built-in support for SD cards, for example - so we could hook one up and be able to load and save BASIC programs by name. There’s also support for storage in EEPROM (not possible on STM32F103’s without some extra trickery).
What this illustrates is a trend which is probably at the heart of the success of BASIC: the ability to add custom commands to the interpreter, and totally integrate them as part of the language. That’s how PEEK, POKE, but also ELOAD / ESAVE got added, and now even: DREAD, DWRITE, AREAD, AWRITE to access the digital and analog I/O pins.
The code (with minor configuration changes) is available on GitHub. It compiles as:
Sketch uses 16,968 bytes (12%) of program storage space. Global variables use 6,680 bytes of dynamic memory.
This is with an emulated RAM space of 4 KB. And this is how it starts up:
HELLO TinyBasic Plus v0.13 2801 bytes free. OK >
It’s telling us that we have 2801 bytes of room (out of 4096) inside this interpreted context for storing our code and data. So let’s type some things at it:
>print 1+2 3 >print 1/3 0 >print 123*456 -9448 >print 1:print 2 1 2 >help What? >
As you can see, it’s quite limited. Only 16-bit integer arithmetic and no floating point. But it works, and we can enter a program line by line, edit it, and run it:
>20 print "world" >10 print "hello" >list 10 PRINT "hello" 20 PRINT "world" OK >run hello world OK >
And it’s no slouch either:
>10 for i=1 to 30000 >20 next i >list 10 FOR I=1 TO 30000 20 NEXT I OK >run OK >
This loop runs in a fraction of a second. But still, some things don’t work as expected:
>list 10 FOR I=1 TO 30000:NEXT I OK >run How? >
So now we’ve used up about 1/8th of the flash memory and 1/3rd of the available RAM on the HY-Tiny, and we’re able to write little programs, making LEDs blink, responding to input changes, all without any IDE in sight. Just plug this thing in, and talk to it via a serial connection. It may not seem like much, but this mode of operation turns that little board into a fully autonomous and programmable machine.
We could extend the language with numerous new features and keywords, and tailor it to whatever purpose we like. Wireless Sensor Networks? Home Automation? It’ll easily fit.
Entire generations of kids have taught themselves to program this way a few decades ago, creating games and what have you not. And perhaps at least as important: sharing it freely!