In a recent article, I mentioned the Forth language and the Mecrisp implementation, which includes a series of builds for ARM chips. As it turns out, the
mecrisp-stellaris-... archive on the download page includes a ready-to-run build for the 28-pin DIP LPC1114 µC, which I happened to have lying around:
It doesn’t take much to get this chip powered and connected through a modified BUB (set to 3.3V!) so it can be flashed with the Mecrisp firmware. Once that is done, you end up with a pretty impressive Forth implementation, with half of flash memory free for user code.
First thing I tried was to connect to it and list out all the commands it knows – known as “words” in Forth parlance, and listed by entering “words” + return:
$ lpc21isp -termonly -control x /dev/tty.usbserial-AH01A0EG 115200 0 lpc21isp version 1.97 Terminal started (press Escape to abort) Mecrisp-Stellaris 2.1.3 with M0 core for LPC1114FN28 by Matthias Koch words words --- Mecrisp-Stellaris Core --- 2dup 2drop 2swap 2nip 2over 2tuck 2rot 2-rot 2>r 2r> 2r@ 2rdrop d2/ d2* dshr dshl dabs dnegate d- d+ s>d um* m* ud* udm* */ */mod u*/ u*/mod um/mod m/mod ud/mod d/mod d/ f* f/ 2! 2@ du< du> d< d> d0< d0= d<> d= sp@ sp! rp@ rp! dup drop ?dup swap nip over tuck rot -rot pick depth rdepth >r r> r@ rdrop rpick true false and bic or xor not clz shr shl ror rol rshift arshift lshift 0= 0<> 0< >= <= < > u>= u<= u< u> <> = min max umax umin move fill @ ! +! h@ h! h+! c@ c! c+! bis! bic! xor! bit@ hbis! hbic! hxor! hbit@ cbis! cbic! cxor! cbit@ cell+ cells flash-khz 16flash! eraseflash initflash hflash! flushflash + - 1- 1+ 2- 2+ negate abs u/mod /mod mod / * 2* 2/ even base binary decimal hex hook-emit hook-key hook-emit? hook-key? hook-pause emit key emit? key? pause serial-emit serial-key serial-emit? serial-key? cexpect accept tib >in current-source setsource source query compare cr bl space spaces [char] char ( \ ." c" s" count ctype type hex. h.s u.s .s words registerliteral, call, literal, create does> <builds ['] ' postpone inline, ret, exit recurse state ] [ : ; execute immediate inline compileonly 0-foldable 1-foldable 2-foldable 3-foldable 4-foldable 5-foldable 6-foldable 7-foldable constant 2constant smudge setflags align aligned align4, align16, h, , ><, string, allot compiletoram? compiletoram compiletoflash (create) variable 2variable nvariable buffer: dictionarystart dictionarynext skipstring find cjump, jump, here flashvar-here then else if repeat while until again begin k j i leave unloop +loop loop do ?do case ?of of endof endcase token parse digit number .digit hold hold< sign #> f#S f# #S # <# f. f.n ud. d. u. . evaluate interpret hook-quit quit eint? eint dint ipsr nop unhandled reset irq-systick irq-fault irq-collection irq-adc irq-i2c irq-uart --- Flash Dictionary --- ok.
That’s over 300 standard Forth words, including all the usual suspects (I’ve shortened the above to only show their names, as Mecrisp actually lists these words one per line).
Here’s a simple way of making it do something – adding 1 and 2 and printing the result:
- type “1 2 + .” plus return, and it types ” 3 ok.” back at you
Let’s define a new “hello” word:
: hello ." Hello world!" ; ok.
We’ve extended the system! We can now type hello, and guess what comes out:
hello Hello world! ok. ----- + <CR>
Note the confusing output: we typed “hello” + a carriage return, and the system executed our definition of hello and printed the greeting right after it. Forth is highly interactive!
Here’s another definition, of a new word called “count-up”:
: count-up 0 do i . loop ; ok.
It takes one argument on the stack, so we can call it as follows:
5 count-up 0 1 2 3 4 ok.
Again, keep in mind that the ” 0 1 2 3 4 ok.” was printed out, not typed in. We’ve defined a loop which prints increasing numbers. But what if we forget to provide an argument?
count-up 0 1 2 [...] Stack underflow
Whoops. Not so good: stack underflow was properly detected, but not before the loop actually ran and printed out a bunch of numbers (how many depends on what value happened to be in memory). Luckily, a µC is easily reset!
This post isn’t meant to be an introduction to Mecrisp (or Forth), you’ll have to read other documentation for that. But one feature is worth exploring: the ability to interactively store code in flash memory and set up the system so it runs that code on power up. Here’s how:
compiletoflash ok. : count-up 0 do i . loop ; ok. : init 10 count-up ; ok.
In a nutshell: 1) we instruct the system to permanently add new definitions to its own flash memory from now on, 2) we define the
count-up word as before, and 3) we (re-)define the special
init word which Mecrisp Forth will automatically run for us when it starts up.
Let’s try it, we’ll reset the µC and see what it types out:
$ lpc21isp -termonly -control x /dev/tty.usbserial-AH01A0EG 115200 0 lpc21isp version 1.97 Terminal started (press Escape to abort) Mecrisp-Stellaris 2.1.3 with M0 core for LPC1114FN28 by Matthias Koch 0 1 2 3 4 5 6 7 8 9
Bingo! Our new code has been saved in flash memory, and starts running the moment the LPC1114 chip comes out of reset. Note that we can get rid of it again with “
As you can see, it would be possible to write a full-blown application in Mecrisp Forth and end up with a standalone µC chip which then works as instructed every time it powers up.
Forth code runs surprisingly fast. Here is a delay loop which does nothing:
: delay 0 do loop ; ok.
And this code:
10000000 delay ok.
… takes about 3.5 seconds before printing out the final “ok.” prompt. That’s some 3 million iterations per second. Not too shabby, if you consider that the LPC1114 runs at 12 MHz!