Forget what you know, please Feb 2016
Here’s a summary of that evolution again, in terms of technology:
- wires: just hardware circuits and manually inserted patch cords, yikes!
- binary data, i.e. “machine code”: a very tedious and low-level way of programming
- assembly: symbolic notation, macros, you have to keep track of all registers in your head
- Fortran, Cobol, Algol, Pascal, C: yeay, it gets compiled for us!
- Basic, Lisp, Icon, Snobol, Perl, Python, Ruby - no compiler: immediate interpretation!
- but interpreters are slow - we can compile on-the-fly and just-in-time …
The story could end here, but then there is that embedded microcontroller world, with smart chips in just about anything powered by electricity. While powerful and capable of generating byte code and even machine code, they do not have the storage and memory to run a high-end optimising compiler. Even if projects such as Espruino and MicroPython have come a long way to bring complete self-contained environments to the µC world - they still depend heavily on a larger machine to produce those run-time environments we can flash into the µC.
But what if we want more performance? - or run on a smaller µC with 32..128 KB of flash?
Which is where we stand today, in 2016: tethered software development, with the source code and tools living in one world (our laptops or the web), and the µC being sent a firmware upload to perform the task we’ve coded up for it, after translation by our toolchain:
- you have to set up that toolchain (for your choice of Windows, Mac OSX, Linux)
- you have to keep track of the source code, in case you ever need to change it
- the µC will do its thing, but any change to it will require going back to the host
- software debugging is tedious: add a print statement, compile, upload, try, rinse, repeat
- hardware debugging requires proper toolchain support and maybe also learning “gdb”
What if we just want to investigate the hardware, check out a few settings in the chip, briefly toggle a pin or adjust a hardware register setting? Tough luck: you have to leave the flow of design and implementation, and enter the (completely different) world of remote debugging.
Our µC might as well be on Mars. With all our fancy tools (constantly updated, improved, changed) we’re virtually coding in the dark nowadays. We’re adding layer upon layer of technology and infrastructure, just to make that darn LED blink! Or read out a sensor, or make something turn, or respond to sensor changes, whatever. Does it really have to be so hard?
(speaking of Mars: Forth has been used in several NASA space missions)
What if we could talk to an embedded µC directly over a serial port connection, give it simple commands, tell it things to do now, or save for later, or do continuously. As we gradually build up our application, the µC records what we’ve done, lets us change things as much and as often as we like, selectively wiping some previously saved definitions.
Forth can do that. It’s a programming language, but it’s also a full-blown development system. Once you store the Forth “core” into the µC, you’re done. From then on, you can type at it, make it do things, and go wild. If you make a mistake (as we all do, especially while trying out stuff), you simply press reset to return to a stable system.
There is hardly a toolchain involved. The Mecrisp Forth core is written in GNU “assembler”, producing a 16..20 KB “.bin” or “.hex” file, and that’s it. You never need to go back and change it. Everything else can be built on top. Mecrisp Forth is extremely fast, so what you write can also be. There’s an assembler for the ARM Cortex written in Forth: if you load it in, you can extend the core by adding assembler code (using a Forth-like syntax). There’s even a disassembler…
(please note that assembly language is there if you want it, but hardly ever needed in Forth)
But there is one major (and very painful) drawback, in today’s world with millions of lines of code written in C and C++: Forth and C don’t really mix. A µC running the Forth core cannot easily interoperate with C code, although it can be tricked into calling external routines with C linkage (Forth can generate assembler instructions for any purpose, after all).
To sum it all up: think of the Mecrisp Forth core as a boot loader - you have to get it onto the µC once, and then it becomes the new “communication language” for the chip. From there on, this µC will understand plain text Forth commands, including saving potentially large amounts of (your!) Forth definitions after its own flash memory area. All you need, is a serial port + terminal interface, plus a robust way to send larger amounts of Forth source code to the chip.
With Forth, you don’t have a “build environment”. Forth is the environment and it’s running on the chip you’re programming for. It’s intensely interactive and there are no layers of complexity. There is no compiler, no linker, no uploader (other than a text-mode send tool), no bytecode, no firmware image, no object code, there are no binary libraries, no conditionals, no build flags.
For turnkey use, you can define a function called “
init” and save it in
flash memory. Then your chip will run that code on every reset. But beware: if
you don’t include a mechanism to get back to command mode, then the only way to
get back control is to reflash the chip with a fresh core…
There is one other “drawback”: Forth blows away every notion of language syntax and software development methodology you’re probably used to - but that’s for the next articles…