Using emscripten to simulate an arduino project
In another post I talked about the Sensor Watch project which has this nifty JS simulation for testing the firmware. This saves you having to dissasemble the watch every time you want to test a change to the firmware so makes the develop/test loop much faster. Here I’ll go through a toy example of how that works.
In the end we’ll end up with this:
We could easily add buttons, extra inputs and outputs etc. If you’re working on a project where much of the work consists of configuring the hardware correctly then the effort of making a simulation like this is probably not worth it. For projects like the sensor watch, however, where the inputs and outputs are pretty much fixed while lots of people will want to modify the software it makes a lot of sense.
The sensor watch project has a firmware with a clearly defined interface and the trick is that you swap out the implementation of this interface between the real hardware and the simulation. I wanted to get a better understanding of this so I thought I’d so a super simple version myself, let’s do that classic arduino project… blinky!
Let’s grab the code for blinky.ino, we could easily compile this for a real arduino using the IDE or using a makefile. I’m gonna skip the details of getting this working for both real hardware and for emscripten to keep it simple.1 The starting point is to try to compile a simple arduino sketch like this one:
1#include <Arduino.h> 2 3void setup() { 4 Serial.println("Setting pinMode of pin 13"); 5 pinMode(LED_BUILTIN, OUTPUT); 6} 7 8void loop() { 9 Serial.println("LED On"); 10 digitalWrite(LED_BUILTIN, HIGH); 11 delay(1000); 12 Serial.println("LED Off"); 13 digitalWrite(LED_BUILTIN, LOW); 14 delay(1000); 15}
In order to get this to compile using emscripten we need to do two things:
- write a wrapper script that runs
setup()
and then callsloop()
in an infinite loop - provide implementations of functions like
digitalWrite
anddelay
that interface with emscripten.
The first bit is pretty easy:
It’s not typical to include a .c file like this. To avoid having to recompile everything all the time, you’re really supposed to just include the .h file, compile .c files to .o object files and then link them all together at the end. For this small example it’s fine though.
Writing Arduino.h
Ok, now let’s write a quick implementation of Arduino.h
that just covers what we need for this example, I’ll include the code in the header file for brevity. First we have some includes. We need stdint
for the fixed width integer types like uint8_t
, stdio for printf
which emscripten provides its own implementation of and <emscripten.h>
to embed javascript implementations. We also have some simple definitions that appear in our sketch.
Next we do digitalWrite
. We use the EM_ASM
macro which just pastes that JS code into the compiled wasm, substiting in the value
for $0
. I grabbed a creative commons licensed svg off wikipedia, added a little overlay in inkscape with the id light_led
and then we toggle it’s opacity.
For delay
, it’s a bit more complicated because the JS that runs in browsers has to share the CPU, it can’t delay by just wasting cycles the way we can on an arduino. So we use the asyncify feature of emscripten. This gives us a function emscripten_sleep
that effectively yields to other code running on the page for a certain period of time, this allows us to implement delay
in a non-blocking way.
Finally, Serial.println
should be pretty easy, we just call printf
. However we need to do something to mimic to the Serial.print
syntax which involves a little C++:
Compiling it
And with that we’re almost done! We have three files, blink.c
that represents our arduino sketch, main.c
that wraps it and Arduino.h
that implements the lower level stuff. To compile it we need the emscripten C++ compiler em++
1em++ -sASYNCIFY -O3 main.c -o build/main.js -I./
-sASYNCIFY
tells emscripten that it should us asyncify, -O3
runs optimisations as recommended when using asyncify and -I./
tells the compiler that Arduino.h
can be found in the same directory as main.c
. We get two files as output main.js
and main.wasm
. The former is another wrapper script and main.wasm
contains the actual compiled webassembly code.
Another Wrapper to wrap it up
So how do we use main.js
and main.wasm
? We need to include main.js
and some extra glue code loader.js
on our HTML page, along with our SVG and a textarea tag that the serial output will go to:
1<figure> 2<svg>...</svg> 3Serial Console 4<textarea class="emscripten" id="output" rows="8" 5 style="width: 80%; display: block; margin: 1em;"></textarea> 6<figcaption> 7The finished arduino simulation. 8</figcaption> 9</figure> 10 11<script async type="text/javascript" src="loader.js"></script> 12<script async type="text/javascript" src="main.js"></script>
And that gives us the final result. I’ve put all the files in this repository.