Moving Forth With Mecrisp-Stellaris And Embello

In the last episode, I advocated a little bit for Forth on microcontrollers being a still-viable development platform, not just for industry where it’s usually seen these days, but also for hackers. I maybe even tricked you into buying a couple pieces of cheap hardware. This time around, we’re going to get the Forth system set up on that hardware, and run the compulsory “hello world” and LED blinky. But then we’ll also take a dip into one of the features that make Forth very neat on microcontrollers: easy multitasking.

To work!

Hardware

Mecrisp-Stellaris Forth runs on a great number of ARM microcontrollers, but I’ll focus here on the STM32F103 chips that are available for incredibly little money in the form of a generic copy of the Maple Mini, often called a “STM32F103 Minimum System Board” or “Blue Pill” because of the form-factor, and the fact that there used to be red ones for sale. The microcontroller on board can run at 72 MHz, has 20 kB of RAM and either 64 or 128 kB of flash. It has plenty of pins, the digital-only ones are 5 V tolerant, and it has all the usual microcontroller peripherals. It’s not the most power-efficient, and it doesn’t have a floating-point unit or a DAC, but it’s a rugged old design that’s available for much less money than it should be.

Programmer Connected, Power over USB

Similar wonders of mass production work for the programmer that you’ll need to initially flash the chip. Any of the clones of the ST-Link v2 will work just fine. (Ironically enough, the hardware inside the programmer is almost identical to the target.) Finally, since Forth runs as in interactive shell, you’re going to need a serial connection to the STM32 board. That probably means a USB/serial adapter.

This whole setup isn’t going to cost much more than a fast food meal, and the programmer and USB/serial adapter are things that you’ll want to have in your kit anyway, if you don’t already.

You can power the board directly through the various 3.3 and GND pins scattered around the board, or through the micro USB port or the 5V pins on the target board. The latter two options pass through a 3.3 V regulator before joining up with the 3.3 pins. All of the pins are interconnected, so it’s best if you only use one power supply at a time.

Firmware: The Forth System

Go get the super-special Hackaday-edition Mecrisp Forth package from GitHub. Included in the “ROMs” directory is a Forth system that’ll work for the demos here. I’ve loaded in a decent base from [jcw]’s excellent Embello Forth libraries as well, providing things like easy GPIO configuration, delay functions, and so on. There are many more libraries available, and we’ll look into them next time when we need them.

The coolest thing about using a Forth system is that very little support software is needed at all — the Forth interpreter compiles its own code, and you interact with it over the serial terminal. Everything happens inside the microcontroller. The one hurdle, then, is getting Forth onto the chip. In the old days, this used to be done by toggling in the bytes manually, and Forth is actually small enough that literally bootstrapping it this way is possible. But you already bought that chip programmer, right?

[Texane]’s ST utilities are the easiest way to get Forth onto your chip. Download them from GitHub, and build it yourself or try your luck with your distro’s package manager. (Windows folks, you’re not left out either. Although that binary hasn’t seen updates in a while, it’ll do.)

Connect up the programming wires in the obvious fashion, and issue the magic commands st-flash erase and st-flash write mecrisp-stellaris-hackaday-edition.bin 0x8000000. In five seconds, you’ll be ready to rumble.

  • GND — GND
  • SWCLK — CLK
  • SWSIO — DIO

Having to buy a programmer is a hassle if you don’t have one, but it will make the rest of your life easier, and getting one is as simple as clicking “pay” and waiting. Our own [Al Williams] (no relation) has a recent article on using the same software for debugging C or Arduino code with GDB, so it’s worth your time to set this up.

Software

Serial Hookup, Powered by Laptop

Put the programmer away for now and connect to the STM32 over serial; the default baud rate should be 115,200. If you haven’t unplugged power yet, you might need to hit the reset button on the STM32 board. If all went well, you’ll be greeted by a familiar skull-and-cross-wrenches. Mecrisp is expecting a linefeed at the end of lines, so if you’re sending LF+CR, you’ll be effectively hitting return twice.

  • A9 TX — Serial RX
  • A10 RX — Serial TX
  • GND — GND

[jcw]’s folie is a nice, multi-platform serial terminal emulator for this application. What it does that your normal terminal program doesn’t is allow you to re-enter a command line with the up-arrow, which makes fixing mistakes much, much easier than re-typing a long command. It also automatically includes other files, which I made extensive use of in building the binary for this article. You don’t need to run folie, but I bet you’ll like it.

Hello World

Now it’s “Hello World” time. If you’re new to Forth, here comes an extremely selective introduction. Type 2 2 + and hit enter. It says ok.. That’s reassuring, right? Now type . (read “dot”) and it will print out the not-surprising result. Dot is the first of a few global Forth shorthands that you’ll want to internalize. Most commands with a dot print out their results immediately. .s (dot-ess) prints out the stack contents, for instance. Two more idioms that we’ll see a lot of are @ for getting a variable or reading an input and ! for setting a variable or output. Read these as “get” and “set” in your head when scanning Forth code.

Next, let’s see how to write a function. : starts a function definition and ; ends it. So : four 2 2 + ; defines a function that adds two and two together. (And compiles it in real time!) You can then turn around and call this function immediately. four .s will show you that our function has left the sum of two and two on the stack. In this sense, functions in Forth aren’t really functions. They don’t take explicit arguments or return explicit values. They just operate on whatever data is on the stack, and leave the results there too. That’s why Forth functions are called “words”. I’ll be sticking to this convention from now on.

Here, finally, is “Hello World”: : hw ." Hello, World!" cr ;" Strings are a little strange in Forth, largely because of the way the language is parsed — the compiler reads up to a space and then executes what it has found, so there has to be a space between the print-a-string command (.") and the first character that you want to print. The print command scans forward until it finds a closing ", though, so you don’t need an extra space there. cr sends a carriage return. Type hw at the prompt. Hello, World!

Blinking LEDs

Even though serial text input and output is so easy in Forth, blinking an LED is the traditional “hello world” of microcontrollers, so it’s time for some GPIO. Because the system is already configured for this particular microcontroller board, turning an LED on is as easy as typing led.on at the prompt. Want to turn it off? led.off. Manual blinking will get old pretty quickly, though, so let’s write a blink word. : blink led.on 100 ms led.off 200 ms ; will do the trick. Try blink blink blink. See my blink demo code for elaboration. (More on ms in a few thousand milliseconds.)

The details of the GPIO initialization are hidden in core/Hackaday/LED.fs and in Embello’s stm32f1/io.fs respectively. Digging through, you’ll see the standard initialization procedure: the particular pin is set as output by flipping some bits in the STM32’s peripheral control registers. [jcw] has defined a bunch of these, making setting a pin as output, with the push-pull driver, as easy as PC13 OMODE-PP io-mode!. (Remember the “!” means set the value in a variable or register.)

To configure pin PA7 for ADC input: PA7 IMODE-ADC io-mode!. Testing buttons, using the built-in pullup or pulldown resistors: PA3 IMODE-PULL io-mode! and then set the output to pull up or down using true PA3 io! or PA3 ios!. You’ll then be able to read the button state with PA3 io@ (“io get”) later on.

GPIO on the STM32 chips is very flexible, and if you want to get deep into the configuration options in the datasheet, you can set all of this fairly easily using [jcw]’s io.fs code. For instance, io.all prints all of the GPIO registers and their values, which is a great help for interactive debugging. That said, there’s some room here for a more user-friendly hardware-abstraction layer, if you want to contribute one.

Multitasking on the Quick

So now we’ve got a blinking LED and serial-port printing “Hello World”. Not a bad start, and both of these make good use of Forth’s interactivity: the LED only lights up when you type blink. One of the chief virtues of Forth, for me, is the ease of going between interactive testing of words like this, and then deploying the functionality in a working system. One reason is that almost all Forths support simple cooperative multitasking. Here’s what I mean.

First, let’s loop our blink function so that we don’t have to type so much. : bb begin blink again ; creates a function, bb for “bad blink”, that will run forever. The problem with “run forever” in Forth is that you never get back to the interpreter’s command line without physically pressing the reset button, and then everything you were working on in RAM is lost.

Instead, let’s blink in a loop with a way out. : gb begin blink key? until ; creates a function that will run our blink command until there’s some input from the keyboard — the return key is pressed. This particular looping construct is very useful for testing out functions that you’d like to run continuously, without hanging the system. Keep it in mind.

Once we’ve tweaked our blink function to run just the way we want it, let’s create a background task so it can blink unattended.

task: blinktask
: blink&  
    blinktask activate 
    begin blink again
;
multitask
blink&

Briefly, the task: word creates some memory space for our blinking background task that we’re calling blinktask. The function blink& does the work in the background. blink& starts off by declaring that it will use the blinktask task context, and that it should start off running. Then it goes into an endless blinking loop from which it never leaves. multitask turns multitasking on, and blink& executes our task. Run it, and the LED blinks while you can still interact with the console. Sweet. Type tasks and you’ll see that there are two active: one is our blinker and the other is the interactive interpreter.

But how does the blink task know when to yield to other simultaneous processes? In Forth, the word pause yields from the current context and moves on to the next, round-robin multitasking. The ms function, among others, contains a pause command, so what looks like a blocking delay in a single-task setup ends up playing fantastically well with your other tasks.

The nice thing about cooperative multitasking is that you control exactly when there’s going to be a context switch, which can help eliminate glitches that you’ll find in preemptive systems. The downside is that you’re responsible for remembering to pause your functions now and then, and you have to verify the timing yourself. Of course, this is a microcontroller, and you have the ARM’s quite rich internal interrupt controller to play with as well.

The real point of multitasking on micros in Forth is that it makes a great workflow for writing, testing, and deploying little daemons: functions that want to be “always on”. First, write the function that does the action once. Second, test it in a loop with an escape hatch. Third, once it’s working, remove the escape and make a background task for it. You can then turn it on and off using idle and wake, even from within other tasks. See Mecrisp’s multitask.txt, the source, for more details.

What’s Next?

So far, we’ve set up Mecrisp-Stellaris, with additional libraries from Jeelabs’ Embello Forth framework, and run some quick demos. If this has piqued your interest, I’ll take you on a walkthrough of building some real software next time. There’s a lot more to say about the way that Mecrisp handles the nuances of flash versus RAM, inputs and outputs, and the practice of interactive development. Some of the really freaky aspects of working in Forth will raise their funny-shaped heads, and we’ll learn to love them anyway.

In the meantime, get your cheap STM32F103 boards flashed up with our binary, and get a little bit used to playing around in the Forth environment on the chip. Blink some LEDs. Look around the Mecrisp-Stellaris glossary and the embello API documentation. Or just type list to see all the command at your disposal and start hacking away.

33 thoughts on “Moving Forth With Mecrisp-Stellaris And Embello

  1. “In the old days, this used to be done by toggling in the bytes manually, and Forth is actually small enough that literally bootstrapping it this way is possible. ”

    Would have been quite the hack to include toggle switches.

  2. Another reason why Forth is appropriate for microcontrollers has to do with its history: At the time it was developed, the state of the art was Fortran, usually run as a one-shot batch job: program and data in, results out and done.
    Forth was created for real-time operations that ran continuously, specifically telescope control (astronomy was mentioned in the comments last episode). So continuous (looped) monitoring of inputs and control of outputs has been in Forth’s genes from day 1.

      1. You can alter a Forth system while it is running. In some applications, that’s incredibly valuable.

        And the whole concatenative language thing is an interesting approach to language design.

        Has anyone tried Joy? That is a really cool language. Not sure how practical it is, but it’s beautifully designed.

      2. This is absolutely true, if you’ve also got a good debugger running on the micro so that you can easily inspect its state at any given moment while it’s running.

        As @Pez mentions, you can debug, test, and code without necessarily losing that state, which you can’t do with C where a compile/flash/run cycle loses your “place”. So you end up writing test fixtures in C to get you to where you need to do the debugging reliably. (Sometimes having these test case setups fixed in code is an advantage.) But it’s more work.

        And Forth is also a command language for running the device/machine when you’re done. Since it’s interactive, the same functions that you’ve been writing are also commands that you can issue, without any extra overhead. In C, you’d have to code up your own interpreter, which I’ve definitely done for some bigger projects. With Forth, it’s already there.

        For a brilliant example of this, see RigTig’s Forth G-code interpreter. He writes Forth words (commands) that happen to match the G-code commands. The Forth system doesn’t know if it’s talking to a human interactively, or to a G-code dump.
        Interpreter done. https://hackaday.io/project/13420-rigtigs-big-3d-printer/log/53758-esp14-as-gcode-interpreter

        I totally agree that the compile/flash cycle is very short these days. In fact, if you’re like me and edit most of the time on the big computer and then send the code over, it’s not much different in Forth. It’s the interactivity.

  3. Another great article by Elliot. One of the reasons Forth is so good on SoC MCUs is the history. If you consider the facilities of the smallest, cheapest device relative to the systems Forth was developed on, they’re monsters.

    worrydream.com/refs/Moore%20-%20Forth%20-%20The%20Early%20Years.pdf

    Matthias Koch has done a magnificent job with Mecrisp.

  4. anything that is a control system or needs to be safety rated will always be C on a microcontroller, also C is easier to write reliable and predictable runtime code for MCU’s hence why its never been upseated by any other language or platform.

    Now in the case of DIY projecsts and education where safety or realtime performance doesn’t matter sure use whatever you want, but if is real engineering, C or bust, and by bust I mean FPGA lolz

    1. Well… if you have to validate someone else’s C compiler for a safety critical system that can be pretty difficult. Forth systems used for safety critical systems are validated right down to the tiny primitive routines used to construct the language. That is harder with C. And then there are the “nasal demons” of C compilers that come from unspecified behavior for specific code. You can’t compare the amount of projects written in C versus Forth because Forth is not used nearly as frequently, but there are safety critical systems out there that were developed in Forth. A bomb disposal system in the UK comes to mind.

      1. Absolutely correct Kevin.
        I believe the inside joke is that once your code complies with that spec it is “misrable” or maybe just the engineer is miserable, I can’t remember.
        :-)

    2. @Jack: “anything that is a control system or needs to be safety rated will always be C on a microcontroller” is entirely false.

      Anything on a microcontroller is in that microcontroller’s machine code, which is probably one-to-one with its assembly language. The rest is compiled. Safe code can be written in (almost) any (subset of any) language.

      Forth is closer to assembly, if anything, than C. A Forth compiler is a few dozen lines of code. A C compiler?

      So don’t be fooled by Forth’s interactive features — it’s really very low level. Much like the kind of C you have to write for high reliability. See comments about MISRA.

      There are power plants whose control systems run C and power plants whose control systems run Forth. Satellites, radio telescopes, phone switches, industrial machinery of all kinds. There are definitely more written in C than Forth. But that’s not because it’s easier to write “safe” code in C — it’s despite the difficulty.

  5. A Forth revival. I love this “hack”. I bought two of these boards for $15. And mecrisp-stellaris works excellent. Thank you for bringing me computer joy. A Forth lover since 1981.

  6. I wrote in forth in 1981 on an Apple II and ported it to a Z80 co-processor so I could do asynchronous multiprogramming with both CPUs from a single CLI. Worked pretty well for a novice programmer. It wasn’t elegant. I simply had no idea that I shouldn’t be able to do this.

  7. Nice series of articles… but is this really worth it as a “go” for new projects (or even for the ones learning a new language)?. I mean, you can port almost any “ancient” language to an ARM processor these days, but will you use it for any good?

  8. All memory is shared. For cooperative multitasking, unlike preemptive, you do not need locks, everything you wish is atomic unless you release the processor at a place of your choice. You need to take care of atomic sequences only together with interrupts.

  9. Questions, in this essay they’re using the STM32F103C8 (bluepill). Any considerable modification when using the actual STM32F103CB (maple mini) other than led on (pc13/pb01) and the additional button (pb8), I also read both mcu’s have 128k of flash is that correct? Should I always use the usb/serial adapter(pa9/pa10 on the buluepill) or I can just use it with a single usb cable?

    1. With the actual Maple Mini, you have a secondary bootloader that allows uploading through USB. You cannot use this to install mecrisp – you *must* use either STLink or serial upload, and doing so will blow away the maple bootloader.

      The easiest way to go is with a serial uploader.

      jumper the boot1 pin to gnd. connect your serial thingummajig to tx1/rx1, you can probably also use the serial adaptor to power the board. Power up with the user button pressed, it will come up ready to accept your new firmware.

      Use stm32loader.py to load your firmware. Don’t forget to use the -e option as well as -w in order to erase flash before trying to program.

      If you were to look in the embello git repository, project ‘suf’, you might find a binary for the maple mini that puts the forth environment on the USB connector, rather than requiring a separate USB-serial adaptor for interaction. That’s pretty handy.

  10. Just got my STM32F103C8 (bluepill) soldered, flashed, and talking to the terminal.
    Its been a long time away from forth. I used FPC (running on DOS-3.2) in the early 80s to build/program a sound card on an IBM-XT. Forth allowed me to dig into the XT from the DMA controller to the clock chip. It was a bust $$$ wise but a blast to do.

    Have a few projects in mind for the STM series chips. Looking forward to digging around in this amazing little chip. Got an STM32f407VGZ6 board. But I’m gonna get my chops up on the 2 buck ‘BluePill’

    A question: most of the info on STM32F103C8 is that they have 128KB. How would one check this and and access 128KB address space?

  11. Without an stlink programmer, just with any 3.3V ttl usb-serial adaptor: connect A9/A10 on the blue pill to RX/TX on the adaptor, and also connect 5V/GND to power the blue pill.

    The boot jumpers have to be set 10 at poweron time — on the blue pill this means the one closer to B11/B10/B1 is in the 1 position, and the other is in the 0 position.

    Plug the usb-serial adaptor in **but not the usb connection on the blue pill itself!**

    Then (presumably on any debian, and I did it on a odroid xu4 with ubuntu 16.04.2 LTS):

    sudo apt install stm32flash
    git clone https://github.com/hexagon5un/hackaday-forth
    stm32flash -g 0x8000000 -w hackaday-forth/ROMS/mecrisp-stellaris-hackaday-edition.bin /dev/ttyUSB0

    Then reconfigure the boot jumpers to 0 0 and reset the blue pill, then:

    python -m serial.tools.miniterm –eol LF /dev/ttyUSB0 115200

    To connect. You should see `ok.`…

    1. Hi remydyer,
      i’ve tried as you suggested but got this :-
      Using Parser : Raw BINARY
      Interface serial_posix: 57600 8E1
      Failed to init device.

      I’ve tried just about everything else using prior to the serial cable route having failed to get the ST-LinkV2 working. I’m sure it’s probably something like a
      “press any key – where’s the ‘any key’ scenario” but any advice would be most gratefully appreciate
      cheers

  12. So, i’m stuck.
    I’ve downloaded the stlink-master. i cd into the stlink-master file and make release then make debug. I’ve got my STlink V2 hooked up and am powering the blue pill from the 3V3 on the STlink. The orange LED is on all the time, the green LED is flashing. From ~stlink-master $ st-flash erase and it replies st-flash:command not found.

    It builds correctly. I’m using Linux Mint. Any thoughts much appreciated

  13. GREAT STUFF ! I love FORTH although a complete amateur. Used TERA TERM on Windows 10 with a Blue Pill and it works ! ( Had to load usb driver first ) -Flashed using an SMT-V2 usb-serial device. I note there re lot of I2C words eg I2C -start , I2C-stop, >I2C , I2C> et. etc. What are the stack inputs / outputs for the entire I2C commands -Are they referenced anywhere ?

  14. I needed to flash mecrisp-stellaris-stm32f103-hackaday-edition_usb.bin instead of mecrisp-stellaris-hackaday-edition.bin to get a serial port to appear. folie seems to be a dead project now. I’m using Coolterm on my Debian system. I’m open to suggestions for other serial emulators to use with Forth.

    1. I’ve been using PuTTY and minicom on a machine running Ubuntu 22.04.

      In minicom, I needed to select the “Add Carriage Return” option to get the output to line up properly.
      In Putty, I needed to add myself to the “dialout” group using
      $ sudo adduser dialout
      in order to get PuTTY to open.
      I also needed to set the “CR implies LF” and “LF implies CR” radio buttons to get the output to line up properly.
      Finally, I needed to change the font in PuTTY to Deja Vu Sans in order to prevent a fatal error.

      I hope this is useful.

  15. Great article series. I’ve been able to successfully enter the FORTH code to make the onboard LED on the Bluepill blink. However, when I disconnect the USB cable from my computer the Bluepill forgets the programs I entered. Is there a way to make the Bluepill remember the programs I enter even when disconnected?

Leave a Reply

Please be kind and respectful to help make the comments section excellent. (Comment Policy)

This site uses Akismet to reduce spam. Learn how your comment data is processed.