Computing stuff tied to the physical world

JavaScript in a 128 KB µC

As final entry in this episode of “fitting an interpreter into a 128K STM32F103”, there is one other very interesting development to consider. It’s called the Espruino project.

Sure enough, we can fit this JavaScript interpreter into a µC with 128 KB flash and 20 KB RAM, albeit with not too much room to spare:

$ HYTINY_STM103T=1 RELEASE=1 make 2>&1 | head
Generating platform configs
Generating pin info
Generating JS wrappers
WRAPPERSOURCES = src/jswrap_array.c src/jswrap_arraybuffer.c [...]
[... lots of warning messages ...]
GEN espruino_1v83.20_hytiny_stm103t.lst
GEN espruino_1v83.20_hytiny_stm103t.bin
GEN espruino_1v83.20_hytiny_stm103t.hex
bash scripts/ espruino_1v83.20_hytiny_stm103t.bin
PASS - size of 123148 is under 124928 bytes

This thing talks at 9600 baud:

$ screen /dev/cu.usbmodemD5D4C5E3 9600

|   __|___ ___ ___ _ _|_|___ ___ 
|   __|_ -| . |  _| | | |   | . |
|_____|___|  _|_| |___|_|_|_|___|
 1v83.20 Copyright 2015 G.Williams


Make no mistake. We’re solidly in the 21-st century now. This is not BASIC but a fairly impressive implementation of JavaScript. Some quick tests, the JavaScript way:

3 0.33333333333 0.70710678118 0.70710678118

Double precision math, no less. Also, all the usual JavaScript constructs: functions, closures, arrays and objects nested to any depth, prototypical inheritance. It’s all there.

What about the speed of this thing? Let’s use that 10,000x do-nothing loop again, as test:

>function t() {
: var
: for(var i=0;i<10000;i++);
: return
=function () { ... }

Not a speed demon, but still it’s 5x better than the 6502/8080 2-deep BASIC emulations.

But perhaps best of all, this interpreter is asynchronous and event-driven, just like Node.js – which means we can make it do things in the background, as we continue to talk to the console prompt and enter new commands. Here’s a LED blinking in the background:

>setInterval(function() {
: digitalWrite(LED, 1-digitalRead(LED))
:}, 1000)

And we have JavaScript niceties such as JSON within easy reach:

>a = { a:11, b: [1,2,3], c: true, d: [4, [5, 6], 7], e: false }
={ "a": 11, 
  "b": [ 1, 2, 3 ], 
  "c": true, 
  "d": [ 4, 
    [ 5, 6 ], 
    7 ], 
  "e": false }

Pretty-printed, even (sort of). This is in fact quite a comfy environment to write code in!

This code runs in RAM, but there is a way to save it in flash, and as you can see, Espruino does some clever compression to reduce the amount of (limited!) flash memory needed:

Erasing Flash........
Compressed 12240 bytes to 2721

The current configuration reserves 6 KB of flash memory for the save()/load() mechanism. It’s not much, but it’s just about all the unused flash remaining is in this configuration.

An important aspect to keep in mind with this design, is that the full source of your code remains available inside this environment:

>function t(i) { return 2*i }
=function (i) { ... }
=function (i) { ... }
function (i) {return 2*i}

This means you can implement your custom logic, save it in flash, make it run on power up, and when you later come back it’s all there to inspect, modify, and save again. There is no need for an IDE (although keeping track of your code and versions is still a good idea).

It’s all very BASIC like, even though the syntax and semantics are completely different!

The Espruino project is still evolving quite rapidly, so some of its current behaviour, sizes, and limitations may well change over time. All that can be said for now is that a HyTiny STM32F103TB is just about the smallest chip supported by Espruino – far more space and speed is available when using the larger STM32F4xx µCs, for example.

Still – let’s not forget that Espruino is probably the smallest implementation of JavaScript there is, and that (even with its current limitations) it’s quite a practical and usable setup!