Repository navigation
Bluetooth LE
C# scripts can use the Bluetooth LE radio of the board in every role: advertiser (beacons), observer (scanning), peripheral / GATT server (your own services, notifications) and central / GATT client (connect to sensors, read, write, subscribe), plus pairing and bonding, and a ready-made Nordic UART serial port.
Important
BLE is opt-in for custom builds (the stacks cost 100–250 KB of flash and 30–60 KB of RAM). ESP32 release images enable it where the chip has BLE; Pico W release images currently include Wi-Fi but not BLE. For other builds see Turn it on.
// peripheral: a battery service phones understand
var battery = new BleService("180F");
var level = battery.AddCharacteristic("2A19", BleProperty.Read | BleProperty.Notify, new byte[] { 90 });
Ble.AddService(battery);
Ble.StartAdvertising(); // name + service UUIDs; again after every disconnect
level.Notify(new byte[] { 89 });
// observer
foreach (var d in Ble.Scan(3000)) Console.WriteLine($"{d.Address} {d.Name} {d.Rssi} dBm");
// central
var conn = Ble.Connect("C0:FF:EE:00:00:01");
Console.WriteLine(conn.Characteristic("180F", "2A19").Read()[0] + " %");
conn.Characteristic("180D", "2A37").Subscribe(data => Console.WriteLine("bpm " + data[1]));Contents: Boards · Try it on the PC · Turn it on · C# API · Events and threads · Options · Errors · C API (ports)
| Port | Stack | Radio | Roles | Security / bonds | Enable |
|---|---|---|---|---|---|
| ESP-IDF | NimBLE | ESP32, ESP32-S3, -C2, -C3, -C5, -C6, -C61, -H2 | all | yes (bonds in NVS) | sdkconfig.ble |
| Raspberry Pi Pico W / Pico 2 W | BTstack | CYW43439 | all | yes (bonds in flash) | -DMICROCS_BLE=ON |
| Zephyr | Zephyr Bluetooth host | nRF52 / nRF53 / nRF54, any CONFIG_BT board |
all | yes (bonds with CONFIG_BT_SETTINGS) |
overlay-ble.conf |
| Arduino | ArduinoBLE | Nano 33 BLE / IoT, Nano RP2040 Connect, UNO R4 WiFi, Portenta, Nano ESP32 … | advertiser, observer, peripheral | no | make_arduino.py --ble |
| Host CLI | simulated radio | — | all (with simulated peers) | yes |
make mcs-ble, --sim
|
Ble.Has(feature) tells at run time what the board can do: "Ble", "Ble.Advertise",
"Ble.Scan", "Ble.Server", "BleUart", "Ble.Central" (also "Ble.Client"),
"Ble.Security", "Ble.Bonds". A call the board lacks throws NotSupportedException.
Ble.Backend names the stack ("nimble", "btstack", "zephyr", "arduinoble", "sim").
Ble.OptionNames, Ble.CanReadOption(name) and Ble.CanSetOption(name) discover exact
property support without enabling the radio. Names are case-sensitive: TxPower, Mtu,
Appearance, IoCapability, Bonding, Mitm, SecureConnections, Passkey, AddressType,
MaxConnections, Phy. Unknown names return false. Reading hardware properties or setting
a supported hardware option may enable the stack; an option report is not a promise that any
value is valid. MaxConnections is read-only. AddressType reads the address-kind string
and accepts 0 (public) / 1 (static random) on backends supporting its setter.
| Backend | Writable settings | Not runtime configurable |
|---|---|---|
| ESP NimBLE | TX power, MTU, appearance, address type; security with SMP; PHY with BLE 5 enabled | actual PHY/range depends on chip and host configuration |
| Pico BTstack | MTU (clamped to port maximum), appearance, security, address type | TX power, preferred PHY |
| Zephyr | IO capability, bonding, MITM with SMP; appearance with dynamic appearance; passkey with fixed-passkey config | MTU, preferred PHY, Secure Connections build policy, TX power, address type |
| ArduinoBLE | appearance | MTU negotiates internally; TX power/security/PHY settings not exposed |
| Host simulator | all except maximum connections | no physical radio validation |
Ble.ScanInterval / Ble.ScanWindow are milliseconds, default 100 / 50. Configure
5 <= window <= interval <= 10240, while scanning is stopped. Timing setters require
Hal.Has("Ble.ScanParameters") (ESP, Pico, Zephyr and simulator; not ArduinoBLE). These
preferences do not enable the radio. Passive scanning (Ble.StartScan(callback, false, true))
reduces transmitted scan requests; the last argument enables duplicate reports.
Ble.Reset() turns off the radio, drops links, clears services, subscriptions, callbacks and
queued reports, invalidating old GATT objects. Call it at the start of a new standalone BLE
example to avoid accumulating services/callbacks when rerunning in the same VM. It preserves
stored bonds, the device name and scan preferences; do not call it inside a BLE callback or
an in-progress operation. Use Ble.ClearBonds() only when intentionally deleting saved keys.
Ble.Disable() alone does not discard the service definitions or registered callbacks.
Passkey writes now apply before first use and propagate backend errors rather than silently
caching an unsupported value.
Studio now includes diagnostics, passive scan timing, manufacturer/service-data advertising,
device-information/descriptors, GATT exploration, connection timing/RSSI and safe bond
inspection, alongside scanner, beacons, heart rate, LED, UART, central and pairing examples.
All have explicit role requirements and runtime guards. New examples are also in examples/ble.
make mcs-ble # host CLI with the simulated radio
./build/mcs_ble --sim examples/ble/heart_rate.cs
./build/mcs_ble --sim examples/ble/central_client.cs
make ble-test # tests/ble/*.cs in several configurations(CMake: cmake -S . -B build -DMICROCS_BLE=ON, then build/mcs --sim.) The simulated radio has
a few advertisers (a heart-rate sensor, an iBeacon, a UART device …) that scripts can scan for,
connect to, read, write, subscribe to and pair with; a local GATT server sees a simulated phone
connect, subscribe and write. Without --sim the Ble classes are not registered.
| Simulated peer | |
|---|---|
C0:FF:EE:00:00:01 "MicroCS-HRM" |
heart rate 180D (2A37 notifies every second), battery 180F, device information 180A
|
C0:FF:EE:00:00:02 "UART-Echo" |
Nordic UART: every write to RX comes back on TX |
12:34:56:78:9A:BC |
iBeacon E2C56DB5-DFFB-48D2-B060-D0F5A71096E0, major 1, minor 42 |
DE:AD:BE:EF:00:04 |
Eddystone-URL https://github.com
|
C0:FF:EE:00:00:05 "Thermo" |
environmental sensing 181A: temperature 2A6E (read, indicates every 2 s), humidity 2A6F (needs Pair() first) |
| "Sim Phone" | connects to the board's connectable advertising, raises the MTU to 185, subscribes to every notify / indicate characteristic, writes "hello" to the first writable one, answers BleUart lines in upper case |
ports/esp32/example/sdkconfig.ble turns on NimBLE and CONFIG_MICROCS_BLE:
cd ports/esp32/example
idf.py -D SDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.ble" set-target esp32s3 build flash monitorFor an already configured project, prefer menuconfig rather than deleting your settings:
Component config → Bluetooth → enable Bluetooth, choose the NimBLE host and enable the
controller; keep extended advertising disabled. Then enable the C# API under
Component config → MicroCS → Bluetooth LE. That menu remains visible on BLE-capable chips
and displays prerequisite hints. It also exposes the device name, queue / GATT / scan /
discovery limits, subscriptions, timeouts and BleUart buffer. Connections and MTU are set in
NimBLE Options; the C# connection table follows NimBLE (the C2 allows at most two).
The supplied overlay uses two connections and enables bond persistence. ESP32 / S3 / C2 /
C3 / C6 / H2 have BLE build coverage on IDF 5.3.2; C5 / C61 use IDF 5.5.2. S2 and P4 have
no onboard BLE radio. H2 has no Wi-Fi. C5 / C61 target defaults use a 2.5 MB app partition
(partitions_large.csv) for their larger Wi-Fi + TLS + NimBLE firmware. If you change an
existing device's partition table, back up and restore scripts: its storage offset changes. Real-board operation still requires hardware testing.
CONFIG_MICROCS_BLE_RESERVE_KB
(default 48) is kept free for the controller and host when the C# heap is sized. Wi-Fi and BLE run
together (the radio is time-shared). The original ESP32 is tight on IRAM with Wi-Fi + BLE: the
file moves the Wi-Fi / lwIP fast paths to flash and builds with -Os.
cmake -S ports/rp2/example -B build -DPICO_BOARD=pico_w -DMICROCS_BLE=ON \
-DMICROCS_FS=littlefs -DMICROCS_LITTLEFS_DIR=$HOME/littlefs
cmake --build build # pico-sdk submodules: lib/cyw43-driver lib/btstackBLE alone works without an OS; with Wi-Fi add -DMICROCS_OS=freertos -DFREERTOS_KERNEL_PATH=…
(MICROCS_WIFI follows the board). ports/rp2/ble/btstack_config.h is the BTstack
configuration (2 connections, 247-byte MTU, LE Secure Connections); point
-DMICROCS_BTSTACK_CONFIG_DIR=dir at your own copy to change it. Bonds live in the last 8 KB of
flash, the filesystem ends below them. Changing Wifi.Country after Ble.Enable() throws (the
CYW43 is shared).
west build -b nrf52840dk/nrf52840 ports/zephyr/example -- -DEXTRA_CONF_FILE=overlay-ble.confports/zephyr/example/overlay-ble.conf turns on the host (peripheral + central, GATT client,
dynamic GATT database, SMP) and CONFIG_MICROCS_BLE. The west manifest fetches mbedtls, which
CONFIG_BT_SMP / LE Secure Connections need. Bonds across resets: add CONFIG_BT_SETTINGS=y,
CONFIG_SETTINGS=y and a settings backend (CONFIG_NVS or CONFIG_ZMS on a storage partition).
python3 tools/make_arduino.py --ble # library depends on ArduinoBLE
arduino-cli lib install ArduinoBLEmcs_arduino_hal_init registers the backend and services BLE.poll() from the HAL event poll,
so callbacks run while the script waits, sleeps or calls Ble.Poll(). ArduinoBLE offers no
central GATT client or security API, so Ble.Connect and Pair are not supported;
characteristics have a fixed maximum length (AddCharacteristic(…, maxLength)), and as
ArduinoBLE cannot remove services, changing them restarts the stack (connections drop).
Fill a mcs_ble_t table (below) and call mcs_ble_set(&table) before the VM opens the HAL
library. Build with MCS_ENABLE_BLE=1 in your mcs_user_config.h.
The full member list is generated in STDLIB.md. UUIDs are
16-bit numbers or strings (0x180D, "180D", "2A37") or full 128-bit strings
("6E400001-B5A3-F393-E0A9-E50E24DCCA9E"). Addresses print as "C0:11:22:33:44:55". Values
are byte[] or string (UTF-8).
| Member | |
|---|---|
Ble.Enable() / Disable() / Enabled
|
the stack starts by itself on first use |
Ble.Name (≤ 29 chars), Appearance, TxPower (dBm), Mtu (preferred), Phy (Ble.Phy1M, Phy2M, PhyCoded) |
GAP settings |
Ble.Address, AddressType, Backend, MaxConnections, Has(feature)
|
|
Ble.Timeout |
ms for GATT client calls (default MCS_BLE_TIMEOUT) |
Ble.Poll() |
run pending callbacks now (they also run while the script sleeps or waits) |
Ble.DroppedEvents |
events lost because the queue (MCS_BLE_QUEUE) was full |
Ble.StartAdvertising(); // name + service UUIDs of Ble.AddService
Ble.StartAdvertising("Sensor-1"); // another name
var adv = new BleAdvertisement("Thermo");
adv.Interval = 200; adv.Connectable = false;
adv.SetManufacturerData(0xFFFF, new byte[] { 1, 2, 3 });
adv.AddServiceData("181A", new byte[] { 0x10, 0x09 });
Ble.StartAdvertising(adv);
Ble.StartAdvertising(BleAdvertisement.IBeacon("FDA50693-A4E2-4FB1-AFCF-C6EB07647825", 1, 42));
Ble.StartAdvertising(BleAdvertisement.EddystoneUrl("https://github.com"));BleAdvertisement: Name, Connectable, Interval (ms), Duration (ms, 0 = forever),
IncludeTxPower, Appearance, Flags, AddServiceUuid, SetManufacturerData(companyId, data),
AddServiceData(uuid, data), Raw / ScanResponse (your own AD bytes), Build() /
BuildScanResponse() (the bytes that go on air; throws when they exceed 31 bytes).
Statics: IBeacon(uuid, major, minor[, measuredPower]), EddystoneUrl(url[, txPower]),
EddystoneUid(namespaceHex, instanceHex[, txPower]). Ble.AutoAdvertise (default true)
restarts advertising after a peripheral connection ends.
foreach (var d in Ble.Scan(3000)) // ms; Scan(ms, active = true)
if (d.IsIBeacon) Console.WriteLine($"{d.BeaconUuid} {d.BeaconMajor}/{d.BeaconMinor} {d.Rssi}");
Ble.StartScan(d => Console.WriteLine(d), true, false); // callback, active, report duplicates
Ble.StopScan();BleDevice: Address, AddressType, Name, Rssi, Connectable, TxPower, Appearance,
Flags, ServiceUuids, HasService(uuid), ManufacturerData, CompanyId,
ServiceData(uuid), Data / ScanResponse (raw), iBeacon (IsIBeacon, BeaconUuid,
BeaconMajor, BeaconMinor, BeaconPower), EddystoneUrl, Connect([timeoutMs]).
Ble.Scan merges the scan response into the device and returns at most MCS_BLE_SCAN_MAX.
var svc = new BleService("6E400001-B5A3-F393-E0A9-E50E24DCCA9E");
var rx = svc.AddCharacteristic("6E400002-B5A3-F393-E0A9-E50E24DCCA9E",
BleProperty.Write | BleProperty.WriteWithoutResponse, null, 128);
var tx = svc.AddCharacteristic("6E400003-B5A3-F393-E0A9-E50E24DCCA9E", BleProperty.Notify);
var cfg = svc.AddCharacteristic("FF01", BleProperty.Read | BleProperty.Write | BleProperty.Encrypted, "auto");
cfg.Description = "Mode"; // 0x2901 user description
rx.OnWrite((data, conn) => tx.Notify(data)); // echo back to subscribers
tx.OnSubscribe((conn, flags) => Console.WriteLine(flags)); // 0 off, 1 notify, 2 indicate
Ble.AddService(svc);
Ble.StartAdvertising();new BleService(uuid[, primary]), AddCharacteristic(uuid, properties[, value[, maxLength]]).
BleProperty: Read, Write, WriteWithoutResponse, Notify, Indicate, Broadcast,
AuthenticatedSignedWrites, ExtendedProperties and the permissions Encrypted
(needs an encrypted link) and Authenticated (needs MITM-protected pairing).
BleCharacteristic: Value / SetValue (what a read returns; up to MaxLength),
StringValue, Notify([data[, conn]]) / Indicate(…) (all subscribers, or one connection;
without data: the current value), Subscribers, OnWrite((data, conn) => …),
OnSubscribe((conn, flags) => …), AddDescriptor(uuid, value[, writable]), Description,
Uuid, Handle, Properties. The client characteristic configuration descriptor (0x2902)
is added by the stack. Added services reach the stack's database when advertising starts.
Change the services (AddService, ClearServices) while not advertising and not connected —
otherwise InvalidOperationException.
Ble.OnConnect(conn => …), Ble.OnDisconnect((conn, reason) => …), Ble.Connections,
Ble.Connected. BleConnection: Address, AddressType, Role ("central" /
"peripheral": the role of this board), Connected, Mtu, Rssi, Encrypted,
Authenticated, Bonded, Interval (1.25 ms units), Latency, SupervisionTimeout (10 ms
units), Disconnect(), RequestMtu(), UpdateParameters(minInterval, maxInterval, latency, timeout) (same units: UpdateParameters(12, 24, 0, 400) = 15–30 ms, 4 s), Pair().
var conn = Ble.Connect("C0:FF:EE:00:00:01", 5000); // or BleDevice.Connect()
foreach (var s in conn.Services())
foreach (var c in s.Characteristics())
Console.WriteLine($"{s.Name ?? s.Uuid} / {c.Name ?? c.Uuid} {(c.CanRead ? "R" : "")}{(c.CanWrite ? "W" : "")}");
var name = conn.Characteristic("1800", "2A00").ReadString();
var ctl = conn.Characteristic("FFE0", "FFE1");
ctl.Write(new byte[] { 1 }); // with response; Write(data, false) without
ctl.Subscribe((data, c) => Console.WriteLine(BitConverter.ToString(data)));conn.Services(), Service(uuid), Characteristic(serviceUuid, charUuid) (null when missing).
BleRemoteService: Uuid, Name (Bluetooth SIG name or null), Handle, EndHandle,
Characteristics(), Characteristic(uuid). BleRemoteCharacteristic: Uuid, Name,
Handle, Properties, CanRead / CanWrite / CanNotify / CanIndicate, Read() (long
reads past the MTU), ReadString(), Write(data[, withResponse]) (long writes),
Subscribe(callback[, indicate]), Unsubscribe(), Descriptors(). BleRemoteDescriptor:
Read(), Write(data). Client calls block the script (other callbacks keep running) until the
peer answers or Ble.Timeout passes (TimeoutException).
Ble.IoCapability = Ble.IoDisplayOnly; // IoNone (default: Just Works), IoDisplayYesNo, IoKeyboardOnly, IoKeyboardDisplay
Ble.Bonding = true; Ble.Mitm = true; Ble.SecureConnections = true;
Ble.Passkey = 123456; // fixed key shown / entered (-1 = random)
Ble.OnPasskey((conn, kind, key) => { // kind: Ble.PasskeyDisplay, PasskeyEntry, PasskeyCompare, PasskeyConfirm
if (kind == Ble.PasskeyDisplay) Console.WriteLine("enter " + key);
return kind == Ble.PasskeyEntry ? 123456 : 1; // the key to enter, or 1 / 0 = accept / reject
});
conn.Pair(); // or let the peer start it
foreach (var a in Ble.Bonds) Console.WriteLine(a);
Ble.RemoveBond("C0:FF:EE:00:00:01"); Ble.ClearBonds();var uart = new BleUart("MicroCS-UART"); // NUS service 6E400001-…
uart.OnReceive(text => uart.WriteLine("echo: " + text.Trim())); // or poll: Available / Read / ReadString / ReadLine
uart.Start(); // adds the service and advertisesWorks with "Serial Bluetooth Terminal", nRF Toolbox UART, Adafruit Bluefruit Connect and
similar apps. Write splits long data into MTU-sized notifications; received bytes wait in an
MCS_BLE_UART_RX-byte buffer.
Ble.OnEvent((ev, conn, value) => …) sees every event: Ble.EventConnect, EventDisconnect,
EventConnectFailed, EventAdvertisingDone, EventScanDone, EventMtu, EventSubscribe,
EventEncrypted, EventPairingDone, EventConnectionUpdate, EventIndicateDone, EventPhy;
Ble.EventName(ev) gives the name. BleUuid: Normalize, ToLong (128-bit form),
Equals, IsValid, Name (Bluetooth SIG name of well-known services / characteristics).
The Bluetooth stack runs in its own task (NimBLE host task, BTstack run loop, Zephyr's BT RX
thread) or, on Arduino, inside BLE.poll(). It never calls into the VM: events, scan reports,
writes and notifications are copied into one queue (MCS_BLE_QUEUE bytes) and the VM runs the
C# callbacks on its own thread — while the script sleeps (Thread.Sleep), waits in a blocking
BLE call, or calls Ble.Poll() — just like GPIO interrupt callbacks. Reads of local
characteristics are answered by the stack at once from the values the script set, so a phone
can read while the script is busy.
Set them in your mcs_user_config.h (CONFIGURATION.md):
| Option | Default | |
|---|---|---|
MCS_ENABLE_BLE |
0 | the C# BLE classes (needs MCS_ENABLE_HAL); the ports set it from their build switch |
MCS_BLE_SIM |
host builds | simulated radio for mcs --sim
|
MCS_BLE_NAME |
"MicroCS" |
default Ble.Name
|
MCS_BLE_QUEUE |
2048 | bytes of events / reports / data waiting for the script |
MCS_BLE_MAX_CONN |
4 | connections tracked (the stack may allow fewer) |
MCS_BLE_MAX_SERVICES |
8 | local services |
MCS_BLE_MAX_ATTRS |
32 | local characteristics + descriptors |
MCS_BLE_VALUE_POOL |
2048 | bytes for local attribute values |
MCS_BLE_MAX_VALUE |
512 | largest attribute value |
MCS_BLE_SCAN_MAX |
32 | devices Ble.Scan returns |
MCS_BLE_SCAN_INTERVAL |
100 | default scan interval (ms) |
MCS_BLE_SCAN_WINDOW |
50 | default scan window (ms, no greater than interval) |
MCS_BLE_MAX_DISC |
32 | services / characteristics / descriptors one discovery returns |
MCS_BLE_MAX_SUBS |
8 | remote characteristics subscribed to at once |
MCS_BLE_TIMEOUT |
5000 | default Ble.Timeout (ms) |
MCS_BLE_CONNECT_TIMEOUT |
10000 | default Ble.Connect timeout (ms) |
MCS_BLE_UART_RX |
512 |
BleUart receive buffer |
Port switches: ESP-IDF / Zephyr CONFIG_MICROCS_BLE (+ CONFIG_MICROCS_BLE_RESERVE_KB on
ESP-IDF), CMake MICROCS_BLE (Pico W, host), tools/make_arduino.py --ble. The C# front end
adds about 50 KB of flash (Cortex-M4, -Os, including the Bluetooth SIG name table) and about
10 KB of RAM with the defaults above, on top of the stack itself.
| Exception | When |
|---|---|
NotSupportedException |
the board / backend lacks the feature (Ble.Has) |
TimeoutException |
connect, discovery, read, write or MTU exchange took longer than the timeout |
InvalidOperationException |
not connected, services changed while advertising, too many services / attributes / subscriptions |
ArgumentException |
bad UUID, address, value too long for MaxLength, advertising data over 31 bytes |
UnauthorizedAccessException |
the peer refused: insufficient authentication / encryption / authorization (ATT error) |
ArgumentOutOfRangeException |
a setting out of range (Ble.Passkey, …) |
IOException |
any other stack or ATT error (the message has the code) |
include/mcs_ble.h documents the table. A port fills what its stack supports and leaves the
rest NULL. API v2 appends readable_options, writable_options (bits from
MCS_BLE_OPTION(MCS_BLE_OPT_*)) and features (MCS_BLE_FEATURE_SCAN_PARAMETERS). Fill
these truthfully; zero means unknown/unsupported for discovery. Existing custom backends
must be recompiled and add masks to participate in option-aware examples. Do not claim a
setter merely because it caches a value without applying it to the stack:
| Group | Members |
|---|---|
| radio |
enable, address, set_name, opt_get / opt_set (TX power, MTU, appearance, IO capability, bonding, MITM, SC, PHY, passkey), poll, lock / unlock
|
| advertiser / observer |
adv_start(mcs_ble_adv_t*) (raw AD + scan response bytes built by the front end), adv_stop, scan_start(mcs_ble_scan_t*), scan_stop
|
| GATT server |
gatts_set(svcs, n) (register the database, fill the handles), gatts_notify, gatts_value (optional: stacks that keep their own copy of values) |
| connections |
disconnect, conn_info, conn_rssi, conn_update
|
| GATT client |
connect, connect_cancel, discover, read, write, subscribe, exchange_mtu
|
| security |
pair, passkey_reply, bonds
|
The stack reports with mcs_ble_on_connect, mcs_ble_on_disconnect, mcs_ble_on_report,
mcs_ble_on_subscribe, mcs_ble_on_notify, mcs_ble_on_passkey and mcs_ble_on_event
(completions, MTU, encryption …) from any task; peers' reads and writes of the local database
go to mcs_ble_attr_read / mcs_ble_attr_write.
modules/ble/mcs_ble_sim.c is a complete reference backend; the four hardware backends are
ports/esp32/mcs_port_esp32_ble.c, ports/rp2/mcs_port_rp2_ble.c,
ports/zephyr/mcs_port_zephyr_ble.c and ports/arduino/mcs_arduino_ble.cpp.
Generated from the MicroCS repository - edit the files there, not in the wiki.
Start here
Step-by-step guides
- Guides
- Guide PC Simulator
- Guide ESP32
- Guide ESP32 C3 OLED
- Guide Raspberry Pi Pico
- Guide STM32
- Guide Arduino
- Guide Zephyr
- Guide Peripheral Settings
- Guide DMA
Hardware
- Hardware API
- Power Management
- Bluetooth LE
- Peripheral Configuration
- DMA
- Device Drivers
- Wi Fi and Networking
- U8g2 Displays
- Filesystem
- Threads
- Scheduler
Ports
- Porting and Build Systems
- Port ESP32
- Port RP2040 RP2350
- Port STM32
- Port STM32 Firmware
- Port Zephyr
- Port Arduino
C# and the runtime
Embedding and tuning
Internals
Project