Microcontroller

What it is

mcu.o.se is the machine as an Arduino-core library: a sketch instantiates one, and the board is a host. Modules are linked in, and the /o/ vocabulary is bound as C pointers rather than as programs, to fit. The repository is MicrOSCript.

The boards

board what runs on it how you reach it
ESP32-S3 the whole stack, the language included; two machines, with PSRAM. The demo board WiFi, UDP 8888 for a program and TCP 8889 for a machine; or the USB cable, S3SerialEval
Teensy 4.0 the whole stack, one machine, or two with Teensy4TwoMachines the USB cable, Teensy4SerialEval
Raspberry Pi Pico the whole stack, one machine, in a smaller arena the USB cable, PicoSerialEval
Arduino Mega 2560 the machine without the language: a program arrives in the instruction language, as a bundle of control messages the USB cable, MegaSerialVM
Arduino Uno the library without a machine: an OSC node that answers each request with one bundle, ( /d/13 : 1 ), and reports an error as /error the USB cable, UnoOSCNode
ESP32 with PSRAM a machine that runs a program sent over UDP WiFi, ESP32PSRAMWiFiUDPEval

test/check_boards.sh in the repository builds every row, skipping a board whose core is not installed.

From the command line a board is an address, and the scheme picks the cable. Down the USB cable to a Teensy, a Pico or an S3:

({ /out : (1, 2) /o/add }, "serial:///dev/ttyACM0") /o/eval/at

The same program with "tcp://192.168.1.101:8889" reaches an S3 over WiFi. A line of the language typed into a serial monitor at the board’s port works too, and the answer comes back on the same line. The Mega is the one that differs: nothing on it parses the language, so it takes a program written in ose, the instruction language, as a list whose strings carry sigils, sent from the prompt with o.se.serial:

/p : ("/dev/ttyACM1") /o/serial/open,
([13, 1, "/'/!/d/write"], /p) /o/serial/send
([2, 3, "/'/!/add", "/'/!/serial/send"], /p) /o/serial/send,
((/p) /o/serial/recv) /o/println

The first sets pin 13; the second answers 5. Each frame runs to completion before the next is read, and what it leaves on the stack stays, so a program can also arrive one instruction at a time.

Get it running

The ESP32-S3 is the board the examples use, and this is its walk, from nothing installed to a board that answers over WiFi. Needs arduino-cli, which brew install arduino-cli gives you on a Mac; git; a USB data cable, since some carry only power; and a WiFi network that offers 2.4 GHz and lets the devices on it talk to each other. A home network or a phone’s hotspot does; many office, campus and guest networks do not. The module’s name, printed on its metal can, says whether it has PSRAM: N8R8, N16R8, N8R2 and N4R2 do, and the number after the R is how many megabytes.

Get the library, and the ESP32 support for arduino-cli:

git clone --recursive https://github.com/asomadev/MicrOSCript
arduino-cli config init
arduino-cli config add board_manager.additional_urls https://espressif.github.io/arduino-esp32/package_esp32_index.json
arduino-cli core update-index
arduino-cli core install esp32:esp32

The last line downloads about a gigabyte and a half. If config init says a configuration exists, carry on. Inside a clone of the asoma workspace, the same library is hosts/mcu.o.se.

Tell the sketch your network. The board runs InnerMachineUDP. In MicrOSCript/examples/ESP32-S3/InnerMachineUDP:

cp arduino_secrets.h.example arduino_secrets.h

and put your network’s name and password between the quotes in arduino_secrets.h. The file is ignored by git, so the password stays on your laptop.

Tell it your board. board.h in the same directory is short. Its top says which --fqbn goes with which kind of board, and the rest names the LED: if unsure, set BOARD_LED to BOARD_LED_NONE, and nothing lights.

Flash it. Plug the board in, on the port marked USB if it has two, and find it:

arduino-cli board list

The row that names an ESP32 gives the port in its first column, something like /dev/cu.usbmodem1101. Build and flash, from the directory the MicrOSCript clone made, with your port in place of PORT and, if your board’s --fqbn is a different one, that in place of the one shown:

arduino-cli compile --upload -p PORT \
  --fqbn esp32:esp32:esp32s3:PSRAM=opi,CDCOnBoot=cdc \
  --build-property compiler.optimization_flags="-O2 -flto" \
  --build-property compiler.c.elf.extra_flags="-flto" \
  --library "$PWD" examples/ESP32-S3/InnerMachineUDP

The first build takes several minutes and ends with Hard resetting via RTS pin.... If the upload cannot connect, hold BOOT, press and release RESET, release BOOT, and run it again.

Watch it, and read its address. In a terminal of its own, open the board’s serial line and then press RESET:

arduino-cli monitor -p PORT -c baudrate=115200

About twelve seconds later the board prints what it is doing:

outer: connected, ip 192.168.1.101
=== ready; send OSEN to udp 8888 ===
=== and SLIP-framed OSC on tcp 8889 ===

You have it when you see === ready. The first line is the board’s address, which every example asks for. Leave this terminal open while you follow one: it shows the board’s side, and what the board prints. You will not flash the board again; everything from here is sent to it over the network while it runs.

what you see do
the board is not in arduino-cli board list another USB-C cable, or the board’s other port
nothing after RESET in the board’s terminal the sketch was built without CDCOnBoot=cdc; build with the --fqbn shown
this board has no PSRAM, or was built without it the other PSRAM setting from board.h’s table
the board joins no network the network’s name or password in arduino_secrets.h, or the network offers no 2.4 GHz

Other boards. Each has a sketch under examples/, and the compile is the same with the board’s own name, from a clone of the workspace:

arduino-cli compile --library "$PWD/hosts/mcu.o.se" \
    --fqbn teensy:avr:teensy40 hosts/mcu.o.se/examples/Teensy4/Teensy4SerialEval
arduino-cli compile --library "$PWD/hosts/mcu.o.se" \
    --fqbn rp2040:rp2040:rpipico hosts/mcu.o.se/examples/RP2040/PicoSerialEval
arduino-cli compile --library "$PWD/hosts/mcu.o.se" \
    --fqbn arduino:avr:mega hosts/mcu.o.se/examples/AVR/MegaSerialVM

The Arduino IDE only looks in ~/Documents/Arduino/libraries, so for the IDE put a symlink there pointing at the library. Each networked example takes its SSID and password from an arduino_secrets.h in its own directory, as above.

First program

On a Teensy or a Pico, flash its SerialEval sketch, open a serial monitor on its port and type (1, 2) /o/add: the board answers : 3. With no board to hand, examples/PlatformAgnostic/CreateBundle builds for anything: no network, no secrets. It makes a machine in 2 KB, pushes two messages into it once a second, and prints the bundle’s size on the serial console at 115200 baud (arduino-cli monitor -p <port> -c baudrate=115200). Then, on an S3 set up as above, a line from the command line’s prompt, with the board’s address in place of this one:

({ ((2, 3) /o/add) /o/println }, "tcp://192.168.1.101:8889") /o/eval/at

prints : 5 in the board’s terminal. Talk to a board is the walk.

What it adds

Into the host register: pins (/digital/pinmode, /digital/read, /digital/write, /digital/sample, /analog/read, /analog/sample), the radio (/wifi/addr/ip, /wifi/addr/mac, /udp/sendto, /udp/port/listening) and the console (/serial/println, /print, /println).

Every door is the same door: what arrives goes on the stack and the door’s bound name runs it, a serial line through /exec/line, and a serial frame, a datagram or a TCP frame through /exec/here. The board says landed when a machine is admitted. A line that fails stops at the failing step, and what it bound before the raise stays bound.

The board keeps a queue of its own, two slots: ({ body }, ms, "/name") /continue/every runs a body every period in a machine of its own, one loop per name, so a program sent again replaces itself; ("/name") /continue/stop ends it, and (ms) /continue/after inside a body pauses that loop’s period. A loop whose period fails is parked in its slot, as it was at the fault, until a fixed copy comes back with /o/machine/resume and it carries on. A program that runs on the board this way is one that was sent to it, and can be sent again, changed, while the board runs; the view drops one on the board by dragging.

On WiFi a board is a node: /node/id is its hardware address, it announces itself to the census on UDP 19993 when it boots, and it answers the census’s question like any host. On the S3 /time/now answers the microseconds since the board started, as a timetag, and /udp/arrival/time the moment the datagram the door last took came in.

The InnerMachineUDP sketch adds the board’s own light and the ways a program that runs there is looked after: (r, g, b) /rgb/write sets the LED, (r, g, b, ms) /rgb/pulse flashes it and fades to a glow over that many milliseconds, (pin, r, g, b) /rgb/write/at says which pin; () /inner/reset discards every binding and every scheduled program, a board that knows nothing without a power cycle; and /machine/get, asked over TCP on 8889, answers with a machine that faulted and was parked, whole, as it was at the fault. The heart-rate program it is sent publishes what it finds into a register it asks for first, /hr, so /hr/bpm, /hr/hrv and /hr/beats read from a prompt or from a program that arrives later, while the program’s own working names stay its own.