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.