A picture of me.

Tom Hodson

Maker, Baker Programmer Reformed Physicist RSE@ECMWF


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:

7 1 1 5 5 10 10 15 15 20 20 25 25 30 30 A A B B C C D D E E F F G G H H I I J J layer 21 text:MADE IN text:ITALY text:Prototype text:Limited text:Edition 13 12 11 10 9 8 7 6 5 4 3 2 element:C1 package:C0603-ROUND element:C2 package:C0603-ROUND element:C3 package:C0603-ROUND element:C4 package:C0603-ROUND element:C5 package:C0603-ROUND element:C6 package:C0603-ROUND element:C7 package:C0603-ROUND element:C8 package:C0603-ROUND element:C9 package:C0603-ROUND element:C11 package:C0603-ROUND element:F1 package:L1812 element:FD1 package:FIDUCIA-MOUNT element:FD2 package:FIDUCIA-MOUNT element:FD3 package:FIDUCIA-MOUNT element:GROUND package:SJ element:L text:L L element:R1 package:R0603-ROUND element:R2 package:R0603-ROUND element:RN1 package:CAY16 element:RN2 package:CAY16 element:RN3 package:CAY16 element:RN4 package:CAY16 element:Z1 package:CT/CN0603 element:Z2 package:CT/CN0603 layer 25 5V text:A0 A0 ANALOG IN AREF text:1 1 GND text:M.Banzi text:D.Cuartielles text:D.Mellis text:TX TX text:RX RX text:G.Martino text:T.Igoe RESET 3V3 text:A1 A1 text:A2 A2 text:A3 A3 text:A4 A4 text:A5 A5 VIN GND GND text:[#=PWM] DIGITAL (PWM= ) text:# text:Arduino Arduino text:TM TM IOREF text:SDA text:SCL element:AD package:1X06 element:C1 package:C0603-ROUND element:C2 package:C0603-ROUND element:C3 package:C0603-ROUND element:C4 package:C0603-ROUND element:C5 package:C0603-ROUND element:C6 package:C0603-ROUND element:C7 package:C0603-ROUND element:C8 package:C0603-ROUND element:C9 package:C0603-ROUND element:C11 package:C0603-ROUND element:D1 package:SMB element:D2 package:MINIMELF element:D3 package:MINIMELF element:F1 package:L1812 element:FD1 package:FIDUCIA-MOUNT element:FD2 package:FIDUCIA-MOUNT element:FD3 package:FIDUCIA-MOUNT element:GROUND package:SJ element:ICSP text:ICSP ICSP package:2X03 element:ICSP text:ICSP ICSP2 package:2X03 element:ICSP1 package:2X03 element:IOH package:1X10@1 element:IOL package:1X08 element:JP2 package:2X02 element:L package:CHIP-LED0805 element:L1 package:0805 element:ON text:ON ON package:CHIP-LED0805 element:PC1 package:PANASONIC_D element:PC2 package:PANASONIC_D element:R1 package:R0603-ROUND element:R2 package:R0603-ROUND element:RESET package:TS42 element:RESET-EN package:SJ element:RN1 package:CAY16 element:RN2 package:CAY16 element:RN3 package:CAY16 element:RN4 package:CAY16 element:RX package:CHIP-LED0805 element:T1 package:SOT-23 element:TX package:CHIP-LED0805 element:U1 package:SOT223 element:U2 package:SOT23-DBV element:U3 package:MLF32 element:U5 package:MSOP08 element:X1 package:POWERSUPPLY_DC-21MM element:X2 package:PN61729 element:Y1 package:QS element:Y2 package:RESONATOR element:Z1 package:CT/CN0603 element:Z2 package:CT/CN0603 element:ZU4 package:DIL28-3 POWER 0 1 TX0 RX0 text:RESET RESET Serial Console
The finished arduino simulation.

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:

#include <Arduino.h>

void setup() {
  Serial.println("Setting pinMode of pin 13");
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  Serial.println("LED On");
  digitalWrite(LED_BUILTIN, HIGH);
  delay(1000);
  Serial.println("LED Off");
  digitalWrite(LED_BUILTIN, LOW);
  delay(1000);
}

In order to get this to compile using emscripten we need to do two things:

  1. write a wrapper script that runs setup() and then calls loop() in an infinite loop
  2. provide implementations of functions like digitalWrite and delay that interface with emscripten.

The first bit is pretty easy:

#include "blink.c"

int main() {
  setup();
  while(1) loop();
}

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.

#include <stdint.h>
#include <stdio.h>
#include <emscripten.h>
#include <emscripten/html5.h>

#define HIGH 1
#define LOW 0
#define LED_BUILTIN 13
#define OUTPUT 1
#define INPUT 0

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.

void pinMode(uint8_t pin, uint8_t value) {}
void digitalWrite(uint8_t pin, uint8_t value) {
    if(pin == 13) {
    EM_ASM({
      document.getElementById("light_led").style.opacity = $0 ? 1 : 0;
    }, value);
}
}

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.

void delay(uint32_t milliseconds) {
    emscripten_sleep(milliseconds);
}

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++:


class SerialClass {
    public:
    void begin(uint32_t baud) {}
    void println(const char string[]) {
        printf("%s\n", string);
    }
};

SerialClass Serial;

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++

em++ -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:

<figure>
<svg>...</svg>
Serial Console
<textarea class="emscripten" id="output" rows="8" 
      style="width: 80%; display: block; margin: 1em;"></textarea>
<figcaption>
The finished arduino simulation.
</figcaption>
</figure>

<script async type="text/javascript" src="loader.js"></script>
<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.

  1. For a real project it’d be nice to integrate emscripten with a makefile that can compile for real hardware this oneÂ