The editor

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.

Hiding the code pane

When the canvas and the code sit side by side, the button at the right-hand end of the code pane's header folds the code away and the canvas takes the whole width. A thin Code bar stays at the right edge of the canvas: click it to bring the code back at the width it had. Ctrl+\ (Cmd+\ on a Mac) does both, wherever the focus is, the code included.

Your browser remembers the choice, so the code stays folded the next time you open the editor. On a narrower window, where the canvas and the code are stacked, and on a phone, the code pane stays where it is.

Assembly tabs

A tab ending .S or .s is assembly, and it is built and linked with the .ino and .cpp tabs of the same board, as the Arduino IDE builds one beside a sketch. A .S goes through the C preprocessor first, so #include <avr/io.h> and #define work in it; a .s is assembled as written. C and assembly call each other through extern "C", and assembly tabs get their own highlighting and the same breakpoint gutter as a sketch.

A board can also be a whole assembly project with no Arduino core: pick a starter under Assembly (no Arduino) in the Firmware section of the board's properties, on the Uno, the Nano, the Pro Mini, the Pico or the STM32F411 Black Pill. Writing assembly covers both, the calling conventions, avr/io.h in a .S, inline asm volatile and what the errors mean.

Writing code

The code pane edits the way a desktop code editor does, Sublime Text in particular, and the keys are the ones its users already reach for. Every one is listed in Keyboard shortcuts.

  • Completion as you type. The list offers the Arduino calls of the board the files belong to (pinMode, digitalWrite, analogRead, millis, the pin names), the C and C++ keywords, and the names the project already uses. After a dot it offers the object's methods: Serial. lists print, println, available and the rest, and lcd. lists what a LiquidCrystal_I2C lcd(...) declared in the file can do. After #include < it lists the libraries the board can compile.
  • Only names that compile. The list is read from the headers the compiler itself uses for that board, so an ESP32 offers ledcAttach and an Uno does not, and a library's classes turn up once the file includes it. A test compiles every suggestion on every board.
  • Snippets. setup writes both setup() and loop(); for, if, ifelse, else, switch, while and do write the whole block. Tab moves to the next blank to fill in.
  • Several cursors. Ctrl/Cmd+D selects the word and then its next match, Alt and a click adds a cursor, and Alt and a drag selects a column.
  • Find and replace, with regular expressions, go to line, bracket matching and closing, folding, toggle comment, duplicate and move line, and an Enter that indents the next line and leaves no trailing spaces behind.
  • Auto Format. Ctrl/Cmd+T, as in the Arduino IDE, or Ctrl/Cmd+Shift+F indents the open file again, two spaces a level, and leaves strings and comments alone. In an ordinary browser window Ctrl/Cmd+T opens a new tab instead, so Ctrl/Cmd+Shift+F is the one to learn. Keyboard shortcuts has the details.
  • Long lines wrap at the edge of the pane, under their own indentation, so a long comment never pushes the start of the code out of sight. Alt+Z turns wrapping off, and the editor remembers. On a phone lines stay whole and the code pans sideways at the size that fits the file.
  • Errors in the code. After a build, each error and warning the compiler gave is underlined on its line. Point at it to read the message, press F8 to go to the next one, or Ctrl/Cmd+Shift+M for the list. The next build clears them.

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.
  • std::atomic. #include <atomic> works on every board whose real Arduino core has it: the ESP32 and ESP8266 boards, the RP2040 boards, the STM32 boards, the Uno R4, the XIAO SAMD21 and the micro:bit. load, store, fetch_add, compare_exchange, atomic_flag and the fences all run. The AVR boards' core has no <atomic>, so on an Uno, Nano, Mega, Leonardo or ATtiny85 the build stops, as it does in the Arduino IDE, with a line that says what to use instead: a volatile variable with noInterrupts() and interrupts() around the code that touches it. ATOMIC_BLOCK from <util/atomic.h> works on the AVR boards too.

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.

The table is the handful tutorials reach for most, not the whole list. Type #include < in the editor to see every library the board you are on can compile.

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 AVR boards, ESP32 family, Pico, Uno R4
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
SoftwareSerial.h a serial port on two ordinary pins, for an HC-05 or a GPS Uno, Nano, Pro Mini, Mega, Leonardo, Pro Micro, ESP8266, ESP32, ESP32-S3
TinyGPS++.h NMEA from the NEO-6M GPS tested on the Uno, Mega and NodeMCU

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. Its widths are the upstream library's: attach(pin) is 544 to 2400 microseconds, write(90) is 1472, and attach(pin, min, max) sets another window. The servo part's defaults are the same 544 to 2400.
  • 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 display font of the Adafruit GFX displays is Mokxi's own 5x8, with lower case, in upstream's 6x8 cell. setFont() draws a font the sketch carries exactly, and the Fonts/ headers are Mokxi's letters at each font's size, not the GNU FreeFont typefaces.
  • 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.
  • SoftwareSerial on the AVR boards works as the Arduino library does: a pin-change interrupt and counted loops, one port listening at a time, half duplex, a shared 64-byte buffer. Receive needs a pin with a pin-change interrupt, which on a Mega is 10 to 15, 50 to 53 and A8 to A15. On the ESP8266, ESP32 and ESP32-S3 it is the EspSoftwareSerial way: edges timed on an interrupt, so several ports receive at once. 8N1 only. Any other board says so at compile time.
  • 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.

Adding a library

Three strips under the code handle libraries, and adding one is free on every plan:

  • Libraries lists the project's own libraries, each with a remove button. + Add library takes a .zip in the Arduino layout, or loose .h and .cpp files, and says which files it kept and which it skipped, such as examples.
  • Arduino Library Manager: every library, one tap. Press Search and type a name, a header or a word about it. Each result has Install, which fetches it with the libraries it depends on. A library Mokxi already builds in says Built in, one in the project says Installed, and one that does not list the board you are editing installs with a warning.
  • Built-in libraries. Press Browse to search the libraries Mokxi carries, with This board only to keep what builds on the board you are on. Add #include puts the library's header at the top of the open file (or says it is already there), and Details shows its headers and Open example.

A sketch that includes a library the project does not have stops at clang's file not found. The Build tab then names the Mokxi header that does the same job, or offers Install for the library from the Arduino Library Manager, which puts it in the project and builds again.

Python on the Pico, the ESP32 and the Raspberry Pi

These boards can run Python instead of a sketch. The choice is the program list in the Firmware section of the board's properties:

  • Raspberry Pi Pico. Choose MicroPython (main.py) and write main.py. Run boots the official MicroPython firmware on the simulated chip and copies your .py files onto it, the way Thonny does. print() shows in the Serial Monitor. The Pico W does the same with its own build, with network.WLAN on the simulated network. See the Pico and the Pico W.
  • ESP32 boards. The same choice boots the official MicroPython firmware for the chip, with network.WLAN on the simulated network, on the classic ESP32 (DevKit V1, DevKitC, LOLIN32 Lite), the ESP32-S3 (with the XIAO ESP32-S3 and the Nano ESP32), the ESP32-C3 (with the XIAO ESP32-C3 and the SuperMini) and the ESP32-C6 (with the XIAO ESP32-C6). The ESP8266 has no MicroPython here. See MicroPython on the ESP32.
  • CircuitPython on the Pico, the Pico W, the Arduino Nano RP2040 Connect and the ESP32-S3. Choose CircuitPython (code.py) and write code.py. Run boots the official CircuitPython firmware and saves code.py onto the board's CIRCUITPY drive, with the Adafruit libraries it imports. See CircuitPython on the Pico.
  • Raspberry Pi 4, Pi 5 and Zero 2 W. The board gets a main.py, and Run starts it with gpiozero and RPi.GPIO driving the header. There is no Linux under it. See the Raspberry Pi.

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.

Every board, one toolchain

Every board compiles in the browser on the same clang toolchain, each for its own chip: the AVR Arduinos such as the Uno (avr -mmcu=atmega328p), the RISC-V ESP32 boards such as the ESP32-C3 (riscv32 -march=rv32imc), and the Arm boards such as the Pico (thumbv6m, Cortex-M0+) and the STM32F411 (thumbv7em, Cortex-M4, soft float). The boards page lists every one.

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.