The code editor and compiling

One project per board, a real clang running in the tab, and diagnostics you can click.

One project per board

The right-hand pane is a workstation, not a text box. Every board in the diagram is programmed separately and has files of its own: a sketch, plus any headers and extra sources you add, exactly as a real project does. A board picker at the top chooses which board you are looking at; a file strip under it has tabs, and buttons to add, rename and delete a file.

Files travel with the diagram. They are saved next to it against the board's id, exported and imported with it, and deleted with the board.

A diagram with no board at all still has one sketch, so you can write code before you have decided what will run it. File names may hold letters, digits, _, . and -, and anything ending .ino, .c, .cpp, .cc, .h, .hpp or .hh gets C++ highlighting.

What happens when you press Run

Each board is asked one question: does this need building? There are three answers.

  • Run the image as it is. An uploaded ELF was not built from these files, and an untouched built-in example is already built.
  • Reuse the last build. The decision is a hash of every file name and every byte in it, so a keystroke and an undo leave you exactly where you were.
  • Build it. Compile, then run the result.

The build itself is a real clang and a real lld, compiled to WebAssembly and running in a Web Worker so a four second first build never costs the canvas a frame. Nothing is uploaded and there is no build server, no queue and no build minutes.

The first build, and every one after it

The toolchain is about seventy megabytes of clang, streamed once from our own storage and then kept in the browser's cache. The first build in a fresh browser takes a few seconds; a return visit is about a second and a half, and an edit and run after that is roughly two tenths of a second.

Alongside it comes the runtime: our own freestanding C and C++ for that board family, with the same headers, start-up code, linker script and flags the repository's own build script uses. That is why a sketch that includes one of the shared drivers compiles on any board that has them.

The Arduino language, on every board

The runtime is Mokxi's own, but it speaks the Arduino language reference: a sketch copied from a tutorial uses the same calls here as on a real board.

  • Floating point. float and double arithmetic work everywhere, from a software floating-point library in the runtime, as on a real Uno, where the chip has no floating-point hardware either. double is 32 bits on the AVR boards (Uno, Nano, Mega, Leonardo, ATtiny85), exactly as in the Arduino AVR core, and 64 bits on the rest. Serial.print(x, 3) prints three places.
  • The math library. sqrt, pow, sin, cos, tan, atan2, log, exp, floor, round and the rest of math.h, plus PI, radians(), degrees(), map() and constrain(). Include <math.h> or not; either works.
  • String. Arduino's String class: + and +=, indexOf, substring, toInt, toFloat, trim, replace and the rest. Its text lives on the heap, which on an Uno is whatever of its 2 KB the sketch leaves free, so building long Strings in a loop runs out here as it does on the bench.
  • Reading Serial. parseInt, parseFloat, readString, readStringUntil, readBytes, find and setTimeout, and a serialEvent() function is called between loop()s when input arrives.
  • Timing a pulse. pulseIn and pulseInLong return the length of a pulse in microseconds, 0 on a timeout, which is how an HC-SR04 is read. shiftIn and shiftOut clock a byte in or out.
  • Sound. tone(pin, frequency) and tone(pin, frequency, duration) play a square wave from a timer interrupt without stopping the sketch, and noTone(pin) stops it. On an Uno it takes timer 2, as the Arduino core does, so PWM on pins 3 and 11 pauses while a note plays. On the XIAO and the Uno R4, a note plays on the PWM pins. #include "pitches.h" gives the NOTE_ names.
  • The C library. sprintf, snprintf, atoi, atof, strtol, strcpy, strcmp, strtok, dtostrf, itoa, malloc and friends. As on a real Uno, %f in sprintf prints ? on the AVR boards; use dtostrf there.
  • Characters. isDigit, isAlpha, isSpace, isUpperCase and the rest.

The ATtiny85 has 8 KB of flash and its calls only reach 4 KB, so a sketch that pulls in the math library or much floating point is often too big for it, here as on the bench.

Arduino libraries that compile as they are

Paste a tutorial and its includes work: the runtime carries the stock libraries people use most, under their usual names. Each is Mokxi's own header, written on the drivers for the parts in the bin; none of it is the upstream library's code, and each keeps the calls tutorials use.

Header What it drives Boards
Servo.h, ESP32Servo.h a hobby servo on any pin every board that compiles
Wire.h I2C as a master, on the board's own I2C pins or any two every board that compiles
SPI.h SPI on the board's own SPI pins every board that compiles
EEPROM.h read, write, update, get, put, EEPROM[i] every board that compiles
LiquidCrystal_I2C.h the 16x2 LCD behind its I2C backpack every board that compiles
Adafruit_GFX.h, Adafruit_SSD1306.h the 128x64 I2C OLED all but the ATtiny85
Adafruit_NeoPixel.h WS2812 strips and panels ESP32-C3 and ESP32-C6
DHT.h the DHT11 and DHT22 every board that compiles
OneWire.h, DallasTemperature.h DS18B20 thermometers, any number on one pin all but the ATtiny85
Adafruit_BME280.h, Adafruit_BMP280.h, SparkFunBME280.h the Bosch pressure sensors all but the ATtiny85
RTClib.h the DS3231 and DS1307 clocks, DateTime and TimeSpan all but the ATtiny85
Adafruit_INA219.h the INA219 current monitor all but the ATtiny85
Adafruit_ADS1X15.h the ADS1115 sixteen-bit ADC all but the ATtiny85
Adafruit_MCP23X17.h the MCP23017 port expander all but the ATtiny85

Where they differ from the upstream libraries, which each part's help page spells out:

  • Servo sends one pulse per write() rather than pulsing from a timer, and its default window is the part's 1000 to 2000 microseconds.
  • EEPROM is the chip's own EEPROM on the Uno, Nano, Mega, Leonardo and ATtiny85, and 4 KB of RAM on every other board, whose models have nothing writable that survives a reset. A fresh Run starts erased either way.
  • The OLED's font is Mokxi's 5x7: capitals, digits and punctuation, and lower case prints as capitals.
  • DHT hands back floats built from the sensor's whole tenths. They print, isnan and computeHeatIndex work, and so does any arithmetic of your own on them.
  • Wire is a master only, and bit-banged, like every bus here.
  • The sensor libraries hand back floats the same way as DHT, built from the chips' own counts. DEVICE_DISCONNECTED_C compares with a reading as the tutorials do, and the pressure tutorials' readPressure() / 100.0F works as written; readPressureHPa() is there too.

Each board's help article says which pins Wire.begin() uses on it. Any other library stops at clang's file not found, and the Build tab names the Mokxi header that does the same job, or says there is none yet.

The Build tab

Under the file strip. It stays out of the way until there is something to say, and then shows three things: the steps with their timings, so a slow first build reads as progress rather than a hang; the compiler's diagnostics as lines you can click to land on the file, line and column it is complaining about; and one plain sentence at the end, such as Built esp1.elf, 1.5 KB, 0.21 s.

The four boards

Every board family compiles in the browser on one toolchain: the ESP32-C3 (riscv32 -march=rv32imc), the Arduino Uno (avr -mmcu=atmega328p), the Pico (thumbv6m, Cortex-M0+) and the STM32F411 (thumbv7em, Cortex-M4, soft float).

The full detail, down to the command list and the cache keys, is in compiling in the browser.

If a build fails, the sketch does not compile walks through what the Build tab is telling you.