Changelog - amir1387aht/MicroCS GitHub Wiki
All notable changes. Versions follow MCS_VERSION_* in include/mcs.h.
-
Studio console input β machine-mode
Console.ReadLineno longer echoes input a second time; raw REPL echo is preserved. Studio forwards trailing spaces in script input unchanged, with echo-on/off, multiple-prompt and runtime regressions. -
Cortex-M flash budget β shared capability lookup / direct peripheral predicates; LTO on the 256 KB Cortex-M0 reference targets and benchmark preserves configured features while accommodating GCC/newlib size differences.
-
Capability-aware templates β allocation-free shell
capsand expandedHal.Has, explicitrequires:metadata for every template, connected-board filtering with offline browsing and conservative older-firmware fallback. Radio examples check Wi-Fi / BLE / ESP-NOW and required roles before referencing optional classes. Software/configuration flags and peripheral subfeatures are checked separately; bus support is not device detection. -
BLE API v2 and examples β backend option read/write discovery without starting the radio, configurable scan interval/window (including ESP menuconfig), operation-level role flags and
Ble.Resetfor clean reruns without erasing bonds. Added seven BLE templates and matching standalone examples. Fixed lost/ignored passkey writes and misleading no-op MTU/PHY/security setters. NimBLE role reports honor the configured roles. BLE remains opt-in. -
Web templates / foreground network lifecycle β moved the raw HTTP example into Web server and WebSocket; servers now run until Stop rather than two seconds and use port 80. Raw TCP listeners share the host privileged-port fallback used by HttpServer. Close sockets, listeners, DNS servers and WebSockets at foreground run exit (normal, error or abort), preserving earlier interactive resources. Native server loops service idle work and Stop even with busy/stalled clients; propagate cancellation through native tail calls and nested route callbacks. Added shell lifecycle regressions.
-
Studio C# highlighting β balanced interpolated/verbatim strings (
$@"and@$"), including multiline HTML, nested quoted expressions, escaped braces and comments; added a dedicated highlighter regression suite. -
ESP BLE menuconfig β discoverable BLE submenu with prerequisite hints, device name, queue / GATT / scan / discovery limits, subscriptions, timeouts and UART buffer. The front-end connection limit follows NimBLE (C2 supports at most two); the BLE overlay uses two connections. Added BLE builds for C2 / C6 / H2 and IDF 5.5.2 builds for C5 / C61, plus non-BLE S2 / P4 checks. Guarded legacy CAN on CAN-FD-only chips (IDF omits the legacy TWAI driver there), fixing C5 firmware link failures. C5 / C61 target defaults use a 2.5 MB app slot; older chips retain their existing flash layout. Corrected TX-power clamping on C6 / C61 (minimum -15 dBm) and the +20 dBm readback on newer controllers. BLE remains opt-in, absent from release builds.
-
HTTP client diagnostics / framing β distinguish a server closing before its status line from truncated headers, body or chunks; reject invalid / overflowing chunk lengths, invalid chunk terminators, incomplete trailers and overlong response lines. Added local wire-format regressions. The basic HTTP GET template no longer depends on a public time API and prints a web page instead.
-
Bluetooth LE (docs/BLE.md) β opt-in (
MCS_ENABLE_BLE), not in any release firmware. C#Ble(radio, name, TX power, MTU, PHY, events),BleAdvertisement(name, UUIDs, manufacturer / service data, iBeacon, Eddystone-URL / -UID, raw data), scanning intoBleDevice(parsed AD fields, beacons), a GATT server (BleService,BleCharacteristic,BleDescriptor: read / write / notify / indicate,OnWrite,OnSubscribe, encrypted / authenticated permissions),BleConnectionin both roles (MTU, RSSI, parameters, pairing), a GATT client (BleRemoteService/Characteristic/Descriptor: discovery, long reads and writes, subscriptions), security (IO capabilities, passkeys, Secure Connections, bonds),BleUart(Nordic UART Service) andBleUuid. Backends: ESP-IDF NimBLE (ports/esp32/example/sdkconfig.ble), Pico W / Pico 2 W BTstack (-DMICROCS_BLE=ON, with or without FreeRTOS / Wi-Fi), Zephyr Bluetooth host (overlay-ble.conf; the west manifest now fetchesmbedtls), ArduinoBLE (tools/make_arduino.py --ble; no central / security), and a simulated radio with peers for the PC (make mcs-ble, CMake-DMICROCS_BLE=ON,--sim).make ble-test(inmake check) and CI builds for every backend. -
Fixed: Pico W / Pico 2 W filesystem β the Wi-Fi firmware (~1.1 MB with the CYW43 blob) overlapped the filesystem, which started at 1 MB, so the board silently fell back to a RAM disk. Builds with the radio now keep 1.5 MB for the firmware (
MCS_RP2_FW_RESERVE) and put the filesystem after it. -
Templates are plain files β
tools/studio/templates/<nn-category>/holds one.cs(or.cfg) file per template and atemplates.txtthat names and describes every file of the folder (how to add one).tools/studio/gen_templates.pybundles them intotemplates.js(Studio still runs fromfile://);--checkruns in CI andmake docs-check, and fails on undescribed, missing or duplicate files. Snippets moved totools/studio/snippets.js. -
Templates dialog β search matches every word and ranks name hits first, with highlighting; Recent category; tags derived from the code (Wi-Fi, OLED, files, jobs, threads, DMA, input); line count, copy button and a link to the source file;
PgUp/PgDn/Home/End,Ctrl+Enter= insert at cursor,Escclears the search first; a link to search all categories when the current one has no hit; narrow-window layout. Arrow keys no longer re-render the whole dialog. -
Fixes β saving a new file (e.g. a template as
main.cs) over an existing file on the device now asks before replacing it; added templates get a name no open tab uses (hello-2.cs); Add / Insert at cursor are disabled without a selection (and Insert for read-only tabs); the save dialog selects the name without the extension for any extension (it assumed.cs) and accepts a relative path. The Wi-Fi and networking folder is28-wifi-and-networking;test_templates.jsfails on a template without source or description. -
Robustness pass (ASan/UBSan, source + shell-protocol fuzzing of tests, examples and templates):
- array / list / buffer allocations check
count * element sizefor overflow β on 32-bit MCUsnew int[int.MaxValue]wrapped to a tiny block and then overflowed the heap; it now fails with an out-of-memory error like any other too-large allocation; -
StringComparisonis honored byIndexOf,LastIndexOf,Contains,StartsWith,EndsWith,Replace,Equals,string.Equalsandstring.Compare(before,IndexOf("x", StringComparison.Ordinal)took the enum for a start index andstring.Compare(a, b, StringComparison.Ordinal)ignored case); addedCurrentCulture/InvariantCulture;StartsWith/EndsWith(v, ignoreCase, culture); -
IndexOf(value, start, count)honorscount;LastIndexOf(value, startIndex[, count])searches backwards fromstartIndex(both ignored the extra arguments); -
Math.Round(x, digits)returns huge values unchanged (Math.Round(1e300, 15)was infinity); -
StringBuilder.Remove/ToString(start, length),LedStrip.Filland u8g2 rounded boxes no longer overflowintin their range checks; -
--sim-virtual: the virtual clock advances on everyHal.Microsread, soGPIO.PulseInand other busy-waits no longer spin forever; RAM filesystem reads/writes of 0 bytes no longer pass a null pointer tomemcpy; - compiler: an unterminated string or char literal ending in
\(or in an interpolation hole) no longer moves the lexer past the end of the source (which then tried to allocate gigabytes for the token array);void.Xin an expression is a syntax error instead of a null-pointerstrlen;mcs -cprints each compile error once (it printed the last one twice). - native GC roots beyond
MCS_MAX_ROOTSspill to the heap: recursion through LINQ callbacks (orSortcomparers ...) ended the script with an uncatchable fatal: too many temporary roots; it now reaches the normal, catchableStackOverflowException. A panic also restores the root count (in the REPL every later native call failed after one such error); -
Json.Parseno longer roots every open object (16 nested objects were fatal); nesting deeper than 32 is aFormatExceptionas documented; -
Enumerable.Range(start, count)throws whenstart + count - 1overflowsint; -
tools/fuzz.py(run in CI) also mutates the examples and every Studio template, and alternates--sim/--sim-virtualruns with compile-only runs.
- array / list / buffer allocations check
-
.NET semantics (
tests/t27_dotnet_semantics.cs):-
int.MinValue / -1andint.MinValue % -1(andMath.DivRem) throwOverflowExceptionlike .NET instead of returningMinValue/0; the compiler no longer folds such constants; -
foreachover aList<T>throwsInvalidOperationExceptionCollection was modified; enumeration operation may not execute. when the loop body changed the list (Add,Insert,Remove*,Clear,Sort,Reverse, an indexer store β¦); over aDictionarywhen a key was added. Removing entries or clearing the dictionary duringforeach(allowed in .NET 8) no longer skips the entry after a removed one. Arrays never throw, as in .NET.t27runs in the .NET 8 parity job, so its output is checked against the real runtime; - bytecode images are version 4: the checked
FOR_ITERform marks its slot byte with bit 7 and uses two more hidden locals. Version 1β3 images still load and run their loops unchecked; older VMs reject v4 images, so recompile.mcsbfiles for them.
-
-
minprofile usesswitchdispatch (MCS_COMPUTED_GOTO=0): ~1 KB less Thumb-1 code for ~3 % more instructions, so them0-64kfirmware keeps ~1 KB of its 64 KB flash free (it had 264 B left with the CI toolchain).
The server side of the network, Wi-Fi on the Raspberry Pi Pico W / Pico 2 W, and Studio templates regrouped by peripheral. Reference: docs/NETWORK.md.
-
HttpServerβnew HttpServer([address, ]port); routesGet/Post/Put/Delete/Patch/On([method, ]path, handler)with{param}and*prefix paths,NotFound,ServeFiles(urlPrefix, folder)(index.html, MIME types,.gzcopies),Cors,Start/Poll(ms)/Run/Stop,Port,Requests. Handlers return a string (HTML),byte[]or any object (JSON) β or answer throughHttpRequest:Method,Path,Query,Headers,Header(name),Body/Bytes/Json,Param(name[, default])(path, query and form fields),Send,SendJson,SendStatus,SendFile,Redirect,SetHeader,Authenticate(HTTP Basic). -
WebSocketβ endpoints on anHttpServer(web.WebSocket(path, onMessage[, onOpen[, onClose]]),Broadcast) and a client (Connect("ws://β¦" / "wss://β¦"),Sendtext or binary,Receive,OnMessage+Poll,Ping,Close(code, reason),CloseStatus); fragmented messages, pings and close handshakes handled; newWebSocketException. -
DnsServerβ answers every name (or oneDomain) with the board's address for captive portals; served byHttpServer.Polltoo. -
Wifi.Wps([timeout[, save]])(push button) andWifi.SmartConfig(...)(ESP-Touch / AirKiss) on the ESP32 and in the simulator; network backends get an optionalprovisionentry (MCS_NET_API_VERSION2). -
Raspberry Pi Pico W / Pico 2 W β new backend
ports/rp2/mcs_port_rp2_net.conpico_cyw43_arch_lwip_sys_freertos: station + AP, scan, events, static IP, hostname, country, power save, TX power, auto-reconnect, DHCP server for AP clients, SNTP, mDNS, ping, sockets,Http, the servers and WebSocket. CMake optionMICROCS_WIFI(on by default for*_wboards withMICROCS_OS=freertos),ports/rp2/wifi/lwipopts.h, the on-board LED as pin"LED"/"WL_GPIO0", RTC throughsettimeofday. CI builds and the release page getpico_wandpico2_wfirmware. -
Saved networks without NVS β
Wifi.Save/Forget/SavedSsidfall back to the file/.wifi(MCS_WIFI_CREDS_FILE) on ports without their own store;Net.Has("Wifi.Save"). - New
Net.Hasnames"HttpServer","WebSocket","DnsServer","Wifi.Wps","Wifi.SmartConfig","Wifi.Save";Net.Backend"rp2". OptionsMCS_HTTP_SERVER_MAX_ROUTES,MCS_HTTP_SERVER_TIMEOUT,MCS_WS_MAX_MESSAGE,MCS_WS_MAX_CLIENTS,MCS_RP2_HOSTNAME,MCS_RP2_WIFI_RETRIES,MCS_RP2_RECONNECT_MS,MCS_RP2_DHCPS_LEASES. -
PC β servers bind ports below 1024 at port + 8000 when that needs root (
:80β:8080, noted on stderr);tests/test_net_server.pytests the HTTP, WebSocket and DNS servers against Python clients. -
Studio β templates regrouped by peripheral: GPIO, PWM, ADC, UART, I2C and SPI each
carry their settings and async versions, new categories QSPI, I2S audio, CAN,
Board and system; new Web server and WebSocket category (LED web page, REST API,
live WebSocket dashboard, WebSocket client, captive portal, files from flash, WPS /
SmartConfig) and ESP-NOW (messages, broadcast, encrypted peers) β 194 templates;
snippets
httpserver,route,wsserver,wsclient,captive,wps; completions, hover and parameter info forHttpServer,HttpRequest,WebSocket,DnsServerand the newWifimethods. Template tests run the server templates one at a time.
C# scripts get the network on every ESP32 with a radio (ESP32, S2, S3, C2, C3, C5, C6), and real sockets + HTTP on the PC. Reference: docs/NETWORK.md.
-
Wifiβ station, soft access point and both at once:Connect(ssid, password[, timeout[, bssid, channel]])(returnsfalsewithStatusName/ReasonName()instead of throwing),Begin+WaitConnected(non-blocking),ConnectEnterprise(WPA2/WPA3-Enterprise, IDF 5.1+),Reconnect,Disconnect,Off;Scan([hidden])βWifiNetwork(SSID, BSSID, RSSI, channel, security, protocols, quality);StartAp/StopAp/ApClients(MAC, IP, RSSI) /SetApIp;SetStaticIp/UseDhcp,Ip,Netmask,Gateway,Dns,Dns2,Ipv6,Mac/ApMac; optionsHostname,Country,TxPower,PowerSave,Protocol/ApProtocol(incl. LR and 11ax),Bandwidth,Band(5 GHz on dual-band chips),MinAuth,ListenInterval,AutoReconnect,Persistent,Antenna; WPA3-SAE and PMF automatically. -
Events β
Wifi.OnEvent((ev, value) => β¦)on the script thread: connected, got/lost IP, disconnected (with reason), scan done, AP start/stop, station joined/left/assigned, ESP-NOW, NTP sync. They travel through the HAL event ring (MCS_HAL_EV_NET). -
Saved networks β
Wifi.Save/Forget/SavedSsidin NVS;Wifi.Connect()joins it. menuconfig Connect at boot (CONFIG_MICROCS_WIFI_SSID/_PASSWORD/_AUTOCONNECT) joins before/main.csruns (mcs_esp32_wifi_autoconnect()for your own firmware). -
EspNowβStart,AddPeer(mac[, channel[, key]]),RemovePeer,SetPmk,Sendwith delivery report,Broadcast,ReceiveβEspNowMessage,OnReceive((mac, data) => β¦). -
Ntp(SNTP, POSIX time zones,LocalTime/UtcTime/UtcOffset),Mdns(host name, services,.locallookups β with theespressif/mdnscomponent, detected automatically),Ping,Net.Has(feature)/Net.Backend(also viaHal.Has). -
Sockets β
TcpClient(connect timeout,ConnectTls,Write/WriteLine/Read/ReadLine/ReadToEnd/ReadByte,Timeout,NoDelay,KeepAlive, endpoints),TcpListener(AcceptTcpClient(timeout)βnull,Pending, port 0),UdpClient(Send/Receive(timeout), broadcast, multicast),Dns. New exceptionsSocketException(anIOException) andHttpRequestException. -
Httpβ HTTP/1.1 client:Get,Head,Delete,Post,Put,Patch,Request,GetString,Download(streamed to a file, atomic rename),UrlEncode; headers asDictionary<string, string>, lines orstring[]; redirects, chunked bodies,Timeout,UserAgent;HttpResponse(StatusCode,Body,Bytes,Header(name),EnsureSuccess()β¦). HTTPS through esp-tls and the ESP-IDF certificate bundle (or your CA). -
Json(every port,MCS_ENABLE_JSON) βJson.ParseβDictionary<string, object>/List<object>/ numbers / strings,Json.Stringify(value[, indent])for collections, values and script objects,Json.Escape, andJsonSerializer.Serialize/Deserialize. Off in theminprofile and in the 256 KB-flash Cortex-M0 reference firmware (-DMCS_ENABLE_JSON=0). -
Simulator β
./mcs --simadds a simulated radio (networks, wrong-password / not-found paths, AP clients, ESP-NOW echo peer, mDNS, ping); sockets and HTTP use the PC's network. -
ESP32 port β
ports/esp32/mcs_port_esp32_net.c; menuconfig MicroCS β Wi-Fi and networking (CONFIG_MICROCS_WIFI, on by default on chips with Wi-Fi;CONFIG_MICROCS_WIFI_RESERVE_KBkeeps RAM for the driver). The driver starts only when a script first uses Wi-Fi. The example'spartitions.csvnow has a 2 MB app partition (Wi-Fi + TLS β 1.6 MB) and ~1.9 MB LittleFS β reflashing it re-formats thestoragepartition, so copy your scripts off first (or keep the old layout:partitions_nowifi.csvwithCONFIG_MICROCS_WIFI=n). -
Release files stay without Wi-Fi β the ready-to-flash ESP32 images are built with
CONFIG_MICROCS_WIFI=nand the unchanged 1.5 MB app / 2.4 MB LittleFS layout (partitions_nowifi.csv), so updating them keeps your files; the Arduino library zip leavesmodules/netout. Wi-Fi is for firmware you build fromports/esp32/example. - Studio β 16 Wi-Fi and networking templates (connect, events, scan, soft AP, options, saved network, HTTP GET/POST JSON, HTTPS download, web server, TCP, UDP, NTP clock, ESP-NOW, mDNS + ping, MQTT publish), 10 new snippets, completions for every new class.
-
Config β
MCS_ENABLE_NET,MCS_ENABLE_SOCKETS,MCS_NET_SIM,MCS_NET_TIMEOUT,MCS_WIFI_CONNECT_TIMEOUT,MCS_WIFI_SCAN_MAX,MCS_NET_MAX_READ,MCS_HTTP_MAX_BODY,MCS_HTTP_MAX_HEADER,MCS_HTTP_MAX_REDIRECTS,MCS_ESPNOW_MAX_PEERS,MCS_NET_SLICE_MS,MCS_ESP32_WIFI_RETRIES,MCS_ESP32_ESPNOW_QUEUE,MCS_ENABLE_JSONβ all in theconfig/mcs_user_config.htemplate. -
Fixes β when a native stops early because the run must end (Ctrl-C, Studio Stop,
--time-limit), the run now ends with the abort instead of a follow-up error such as aNullReferenceExceptionon the native's result. New public C APImcs_dict_len/mcs_dict_atfor modules. - Docs β new NETWORK.md; README, ESP32 port README and guide, ARCHITECTURE, HAL, PORTING, CONFIGURATION, TESTING and STDLIB (now also listing the network, threads and U8g2 classes) updated.
-
Tests β
t24_net(whole network API on the simulated radio, TCP/UDP over loopback),t25_json,tests/test_net.py(25 HTTP checks against a local server, part ofmake test).
1.12.1 β Reliable uploads on ESP32 native USB; Studio templates for U8g2, peripheral settings and DMA
-
Reliable uploads on ESP32 USB-Serial-JTAG (ESP32-C3 / C6 / S3 / H2 native USB). Saving a
script from Studio sometimes stalled ("upload stalled - retrying slower") because ESP-IDF's
USB-Serial-JTAG driver silently drops received bytes when its 256-byte buffer is full β
which happened while LittleFS erased a flash block or while the console waited on UART0.
Fixes:
put <f> <len> <window>β the device acknowledges every window with\x04MORE(MCS_SHELL_PUT_WINDOW, 256), so the host can never overrun it (Studio andtools/mcs_remote.pyuse it; older firmware still gets the plain stream); the ESP32 console installs the driver with a 4 KB receive buffer (MCS_ESP32_USB_RXBUF) and sleeps on the USB port instead of UART0 while a USB host is attached. RP2040 was never affected: TinyUSB holds the host off (USB NAK) instead of dropping bytes. -
Studio templates for U8g2 / U8x8 displays. A new U8g2 displays category with 20
scripts that together cover the whole API: hello, every bus (
I2C,SoftI2C,SPI,SoftSPI,Soft3WireSPI, 8080 / 6800,KS0108), all shapes, text / alignment / font metrics, fonts from files andbyte[], XBM bitmaps and icons, rotation / flip / contrast / sleep / draw colours / clipping, page mode (Draw,FirstPage/NextPage),Print, buttons and text bars, the built-in menus (scripted withSetMenuInput), a custom menu withGetMenuEvent, the u8log terminal, a dashboard, a clock, an animation withUpdateDisplayArea, buffer screenshots (GetBuffer,Dump,WriteBufferXBM), U8x8 text and status screens, two displays. Snippetsu8g2,u8frame,u8page,u8x8; Studio completion, signature help and hover for everyU8g2/U8x8member.tests/studio/test_templates.jsruns them on the simulated SSD1306 with a u8g2 build (new CI step). -
Studio templates for peripheral settings and DMA. 17 new ready-to-run templates in two
new categories: Peripheral settings (what the board supports, button / LED / open-drain
GpioConfig, I2C, SPI and UART on your own pins,PwmConfig,AdcConfig, board options and reusable settings) and DMA (DmaBuffer,SPI.WriteAsyncin the background, a double-buffered display loop,I2C.WriteReadAsync,UART.ReadAsyncwith timeout / cancel,QSPI.ReadAsync,Dma.Copy/Fill). New snippetsgpiocfg,i2ccfg,spicfg,uartcfg,dmabuf,spiasync. Studio now has 169 C# templates and 61 snippets; CI runs every one on the simulated board.
-
Configuration objects for every peripheral.
GpioConfig,UartConfig,I2cConfig,SpiConfig,QspiConfig,PwmConfigandAdcConfigcarry the settings a vendor SDK takes in its init struct β pins, pulls, slew rate / speed, drive strength, Schmitt trigger, pad hold, hardware inversion, glitch filters, initial output level, alternate functions, buffer sizes, RS-485, three-wire SPI, sampling delay, ADC attenuation / oversampling, PWM resolution / polarity / centre alignment, DMA on / off. New overloads:GPIO.Configure,new Pin(pin, cfg),pin.Configure,UART.Open(port, cfg),I2C.Open(bus, cfg),SPI.Open(bus, cfg),new SpiDevice(bus, cs, cfg),QSPI.Open(bus, cfg),PWM.Configure,ADC.Configure. Unset properties stay at the board default; values are range-checked on assignment; a board that cannot honour a setting throwsNotSupportedException/ArgumentExceptionwith the reason (DriveMa: 2, 4, 8 or 12 mA). Nothing is silently ignored. -
Board options: chip-specific settings by name (
cfg.Set("queue_size", 8)), listed with their range and help byHal.Options([peripheral]). -
DMA and asynchronous transfers.
DmaBuffer(DMA-capable memory),SPI.TransferAsync / WriteAsync / ReadAsync,I2C.WriteAsync / ReadAsync / WriteReadAsync,UART.WriteAsync / ReadAsync,QSPI.ReadAsync / WriteAsync, device-object variants, andDma.Copy / Fill / CopyAsync / FillAsync / WaitAll. Each returns aDmaTransfer(Wait,OnComplete,Cancel,Result,Error). Transfers stay in order per bus, keep their buffers alive and complete through the event queue, never in an interrupt. SynchronousSPI.Write / ReadandQSPI.ReadacceptDmaBuffers of any size. Boards without DMA run the same calls synchronously, so scripts are portable.Hal.Has("DMA.SPI")& co. tell what really runs in the background. -
ESP32 port: everything β runtime pins for UART / IΒ²C / SPI / QSPI / PWM (no more C pin
tables needed), GPIO filters (pin and flex glitch filters), hold, invert, drive; UART buffers,
inversion, RS-485 half duplex, RX timeout; IΒ²C pull-ups and filter; SPI three-wire, input delay,
max transfer; LEDC resolution / invert; ADC attenuation / resolution / oversampling; SPI and
QSPI transfers on DMA, async memcpy on GDMA chips,
DmaBufferin internal DMA-capable RAM. - RP2040 / RP2350 port: GPIO pulls, drive, slew, Schmitt, alternate function, invert (pad overrides or software), glitch-free initial level; runtime UART / IΒ²C / SPI pins, UART inversion and RTS / CTS, IΒ²C spike filter, pull-ups and timeout; PWM resolution, polarity and phase-correct mode; ADC oversampling; SPI transfers and memory copy / fill on DMA.
-
STM32 port:
GpioConfig(pull, speed, open drain, alternate function) andPwmConfig(polarity, centre-aligned). Zephyr, Arduino and Cortex-M keep the basic settings. -
Simulator implements and validates every setting and runs DMA transfers through the event
queue, so scripts can be tested on a PC (
mcs --sim --sim-virtual). -
C API:
MCS_HAL_API_VERSION3. New optionalmcs_hal_tmembers (*_configure,options,dma_alloc/dma_free/dma_align/dma_channels,dma_copy,dma_abort,spi_transfer_async,i2c_transfer_async,uart_write_async,uart_read_async,qspi_command_async),mcs_*_config_init(),mcs_hal_opt(),mcs_hal_error(),MCS_HAL_EV_DMA;MCS_HAL_MAX_DMA,MCS_HAL_MAX_OPTSandMCS_ENABLE_HAL_DMA(leave the DMA classes out, ~11 KB; off in the 256 KB Cortex-M0 reference build) inmcs_user_config.h;mcs_userdata_of(). A port can returnMCS_HAL_ENOTSUPfrom an async call to get the synchronous fallback. Board tables written for v1 / v2 compile and work unchanged. - Docs: Peripheral configuration, DMA, step-by-step
guides for peripheral settings and DMA,
example
17_config_dma.cs, Studio completions for every new class.
-
C#
U8g2andU8x8on olikraus' u8g2 (2.37.1, the latest release) for 365 monochrome OLED / LCD / e-paper displays (SSD1306, SH1106, SSD1309, ST7920, ST7565, PCD8544, KS0108, β¦) on hardware IΒ²C / SPI, bit-banged IΒ²C / SPI / 3-wire SPI, 8080 / 6800 parallel and KS0108 pins. The wrapper covers both APIs: full-buffer and page mode (Draw(() => β¦),FirstPage/NextPage), every drawing call, text and UTF-8 in any font, metrics, rotation, clipping, bitmaps, buttons, the three u8g2 menus (buttons on pins orSetMenuInput(() => β¦)), u8log terminals, Arduino-stylePrint, buffer export (GetBuffer, PBM, XBM,Dump), and the U8x8 tile API. Display names work as u8g2 writes them, including Arduino class names. Errors say what is wrong (no display answers at I2C address 0x3C on bus 0, the list of compiled-in displays / fonts). -
Optional, never in the release firmware. u8g2 is not bundled. CMake (
-DMICROCS_U8G2=ON, orMCS_ENABLE_U8G2 1inmcs_user_config.h), ESP-IDF and Zephyr (CONFIG_MICROCS_U8G2), Arduino (make_arduino.py --u8g2+ the U8g2 library), PlatformIO and make (make mcs-u8g2) find the sources inMICROCS_U8G2_DIRor the usual places. Otherwise they stop with instructions, or download 2.37.1 once withMICROCS_U8G2_DOWNLOAD=ON/make fetch-u8g2. -
Displays and fonts are chosen before the build (
MCS_U8G2_DISPLAYS,MCS_U8G2_FONTS,MCS_U8X8_FONTS, orMICROCS_U8G2_DISPLAYS/_FONTSlists that are checked at configure time). Only those are linked; the defaults are four common OLEDs and six small fonts (β 7 KB). Every other font still works:tools/u8g2.py extractwrites it as a file, andSetFont("name")loads/fonts/u8g2_font_name.binat run time (or a path /byte[]). -
tools/u8g2.py:where,fetch,displays,fonts(with sizes),extract, andconfig(prints the lines formcs_user_config.h, CMake, make and PlatformIO). -
Simulator OLED:
mcs --oled[=sh1106,128x32,0x3D]emulates an SSD1306 / SH1106 on the simulated IΒ²C bus and draws each frame in the terminal.make u8g2-testcompares the frames of the test scripts and smoke-runsexamples/u8g2/(7 examples). CI runs it, builds with CMake, and builds a Pico firmware and the Arduino library with u8g2. - HAL:
mcs_hal_spi_config(vm, bus, cfg)reconfigures an open SPI bus (mode, clock) for drivers. - Docs: docs/U8G2.md.
-
C# on real OS threads, opt-in. Choose an OS (ESP-IDF menuconfig Real threads,
Zephyr
CONFIG_MICROCS_THREADS=y, CMake-DMICROCS_OS=freertos|posix,make OS=posix, orMCS_OSinmcs_user_config.h) and scripts and functions run on their own OS tasks, in parallel:Thread.Start(path [, core [, heapKB]]),Thread.Run(path, fn, argsβ¦),Thread.RunOn(core, path, fn, argsβ¦)for the second core of the ESP32 / ESP32-S3 / RP2040 (FreeRTOS SMP) and SMP Zephyr boards, andThread.Every(ms, β¦)for OS-timed periodic work. AWorkerhasJoin,Stop,Result,StateandError; namedChannels copy values between threads. Each thread gets its own VM and heap. The filesystem, console and peripherals are shared under locks. Interrupt callbacks stay with the main VM. -
The scheduler uses the OS too: with an OS chosen,
jobs.cfgfile jobs run on their own OS thread, timed by the OS (MCS_SCHED_THREADS, default on with threads).startupjobs and jobs markedmainstay on the main VM; when no thread slot or heap is free, a job falls back to the main VM. Per job:core=,prio=,stack=,heap=(e.g.every 10ms /control.cs core=1 heap=24k prio=1). Without an OS these options are ignored and the job is polled as before.jobs/.jobsshow thread jobs. -
Release firmware runs on FreeRTOS: the ready-to-flash ESP32 images are built with
threads on ESP-IDF's FreeRTOS, and the Pico / Pico 2 / RP2040-Zero
.uf2files with Raspberry Pi's FreeRTOS SMP kernel (the RTOS pico-examples uses; pico-sdk itself has none), soThread.RunOn(1, β¦)uses the second core out of the box. STM32 images stay bare-metal. -
Headers found or asked for: CMake picks up a
freertos_kernel/FreeRTOS-Kernel/ CubeMX target,MICROCS_FREERTOS_PATH+_PORT+_CONFIG_DIRorFREERTOS_KERNEL_PATH, or a FreeRTOS-Kernel folder in the project. If none is found, configure stops and names the variable to set.make OS=freertosand IDE builds stop the same way. -
ports/rp2/example:-DMICROCS_OS=freertos -DFREERTOS_KERNEL_PATH=β¦(Raspberry Pi's FreeRTOS-Kernel fork) runs the REPL on core 0 under FreeRTOS SMP and leaves core 1 for C#, on RP2040 and RP2350.ports/zephyr/example/overlay-threads.conf. - C API (
mcs_threads.h,mcs_os.h):mcs_thread_start/stop/join/info.mcs_runtimesets threads up by itself (cfg.thread_setup,cfg.thread_heap,cfg.thread_stack). Filesystems can be locked (mcs_vfs_t.lock). Worker VMs can open the HAL without callbacks (mcs_hal_open_lib_ex). -
No OS = no change:
MCS_OS_NONEis the default. Without an OS, no thread code is compiled, and the core gains onlyThread.Cores(1),Thread.CurrentCore(0) andThread.Os("None"). - Core: the parser and
OrderBy().ThenBy()no longer use static buffers, so separate VMs can run at the same time. - Tests:
make threads-test(inmake test),make tsan-test(ThreadSanitizer),make freertos-test(the FreeRTOS POSIX simulator port), and CI builds of ESP32, ESP32-C3, Zephyr and RP2040 with threads.
-
STM32 firmware in every release:
microcs-<version>-<board>.binand.hexfor Nucleo-F401RE, Nucleo-F411RE, Nucleo-F446RE, Black Pill F411CE, Nucleo-G474RE, Nucleo-L476RG and Nucleo-H743ZI. Copy the.binto the Nucleo's ST-LINK drive (Black Pill: USB DFU), open the virtual COM port at 115200 baud or MicroCS Studio and use the C# REPL β no CubeMX project or toolchain needed. Each image sets up the clock from the internal oscillator (no crystal needed), the console UART,new Pin("LED")and every GPIO, I2C1 on the Arduino D14/D15 pins, four PWM /Servooutputs on TIM3 and TinyFS in the chip's own flash (28 KB on F4, 105 KB on G474/L476, 307 KB on H743) for/boot.cs,/main.csand uploads. Pins and flash layout: ports/stm32/firmware/README.md. -
tools/build_stm32_firmware.sh <board>builds them (downloads the STM32Cube HAL + CMSIS of the family); the board setup is one file,ports/stm32/firmware/board.c. - CI builds the 7 images on every push and boots the F4 and H743 ones in the Renode emulator
(
tools/renode_check.py: REPL,Hal.Board, a file written to and read back from the internal flash, the LED pin). - STM32 flash driver:
mcs_stm32_flash_init()with an explicit address on F2/F4/F7 may now use a run of equal small sectors (e.g. the 16 KB sectors 1β3) when the linker script keeps them free and marks them with__mcs_fs_flash_start/__mcs_fs_flash_end.
-
TinyFS, a built-in flash filesystem for the MCU's own flash: STM32s (and any other chip)
without an external NOR/NAND can keep
/boot.cs,/main.cs, uploads and data files in a few KB of internal flash β e.g. 8 KB of a 64/128 KB F103. Part of MicroCS (modules/fs/mcs_vfs_tinyfs.c, MIT, no sources to fetch, ~5 KB of Thumb-2 code, ~1 KB of static RAM, no heap): a log of CRC'd records, power-fail safe (garbage collection ends with a commit record), wear levelling across the erase blocks, works from 2 erase blocks. Selected like LittleFS and YAFFS2:MCS_ENABLE_TINYFS 1, CMake-DMICROCS_FS=tinyfs, ESP-IDF menuconfig TinyFS (CONFIG_MICROCS_FS_TINYFS), Zephyroverlay-tinyfs.conf(CONFIG_MICROCS_TINYFS), Arduinomake_arduino.py --fs tinyfs;mcs_flashfs_mount(..., MCS_FLASHFS_TINYFS, ...)orMCS_FLASHFS_DEFAULT. OptionsMCS_TINYFS_MAX_FILES/_CHUNK/_HANDLES/_MAX_UNIT. -
Internal-flash size:
MCS_INTFLASH_SIZE(CMake-DMICROCS_FS_SIZE=8192) sets how much of the chip's flash holds files on STM32 (MCS_STM32_FS_SIZE) and RP2 (MCS_RP2_FS_SIZE). With TinyFS alone the STM32 driver uses each flash page as a block (MCS_STM32_FS_BLOCKoverrides). -
Flash port for new MCUs: fill a
mcs_flash_port_t(region size, erase unit, program unit,read/write/erase) andmcs_intflash_init()makes themcs_flash_tthat TinyFS, LittleFS and YAFFS2 use (include/mcs_flash.h, skeleton inports/template). STM32, RP2040 / RP2350, ESP32 and Zephyr ship theirs. -
Servodriver (built-inservodriver, likews2812):new Servo(channel[, minUs, maxUs[, maxAngle]]),Angle/Write,Pulse/WritePulse, easedMoveTo(deg, ms),Detach/Attach. Default backend: the board's HAL PWM at 50 Hz on every port and the simulator; registerMCS_SERVO_DRIVER(ops, ctx)for a PCA9685 or a servo bus. Off withMCS_ENABLE_SERVO 0, CMake-DMICROCS_SERVO=OFF,CONFIG_MICROCS_SERVO=n,make_arduino.py --no-servo,MICROCS_SERVO := 0. Studio: Servo templates (sweep, smooth motion, pan/tilt with two servos), aservosnippet, completions and docs. -
Config header with the defaults set:
config/mcs_user_config.hnow holds every option set to its default value (each in#ifndef, so-Dand the build systems still win) β copying it enables it, then edit the values. Only the options detected per compiler / CPU / board stay commented.tools/gen_config.py --profile lowram -o mcs_user_config.hwrites it with another profile's values (--profile auto --ram-kb --flash-kb,-D NAME=VALUE); MicroCS warns when the header's profile differs from the build's.make_arduino.py --profileuses it.make testchecks the template againstinclude/mcs_config.h(gen_config.py --check),make checkbuilds every generated profile. -
CI: 18 popular STM32 boards (Blue Pill, Black Pill F401/F411, F4 / F429 Discovery, Nucleo
F072/F446/F767/G071/G431/G474/H563/H743/L073/L432/L476/U575/WB55) compile in parallel jobs, each
in 8 configurations at once (default, no ws2812/servo, no drivers, auto profile, internal flash +
LittleFS / YAFFS2 / TinyFS) with the ST headers cached.
make tinyfs-test(power cut at every flash operation) runs inmake testand the flash-filesystem job.
-
mcs_user_config.h, one header for every build option: copy the new templateconfig/mcs_user_config.hβ everyMCS_*option of the core, modules and ports, commented out with its default and a description β into your project and uncomment what you change.include/mcs_config.hincludes it before anything else, so the library, the ports and the application all see it. It is found automatically when it is on the include path (__has_include);-DMCS_USER_CONFIG_FILE="β¦"names another file,-DMCS_USER_CONFIG=1forces it on compilers without__has_include,-DMCS_USER_CONFIG=0ignores it. Guide: docs/CONFIGURATION.md. -
Compiler options keep working and win: precedence is
-D(CMake, Kconfig, menuconfig, PlatformIO flags) β the header β the profile β the defaults. -
Profiles by name:
#define MCS_PROFILE MCS_PROFILE_LOWRAM(or-DMCS_PROFILE=β¦) withMCS_PROFILE_FULL/AUTO/EMBEDDED/MCU/LOWRAM/TINY/MIN/LINUX. Build systems pass their choice asMCS_DEFAULT_PROFILE, so a profile in the header wins over CMakeMICROCS_PROFILE, menuconfig or Kconfig. The old-DMCS_USER_CONFIG_FILE='"profiles/mcs_profile_x.h"'still works. -
Build integrations find the header: CMake (next to the top-level
CMakeLists.txtor in itsinclude/,src/,main/;MICROCS_CONFIG=path|NONE), ESP-IDF (project folder /main/, menuconfig Project config header), Zephyr (application folder,include/,src/;CONFIG_MICROCS_USER_CONFIG_FILE), PlatformIO (projectinclude/),microcs.mk(MICROCS_CONFIG,MICROCS_PROFILEnow accepts every profile), Arduino (tools/make_arduino.pywritessrc/mcs_user_config.h; new--configand--profile; it no longer patchesmcs_config.h). -
Ports: the STM32 port reads
MCS_STM32_HAL_HEADERfrom the header too; every port documents its options in the template. -
Examples and tests:
examples/lowramis configured by its ownmcs_user_config.h;make testchecks discovery,MCS_USER_CONFIG_FILE,MCS_USER_CONFIG=0/1and the precedence (tests/c/test_config.c), CI repeats it through CMake,make checkbuilds the unmodified template and the profiles by name. -
Releases and CI: the release also ships an ESP32-C2 image (40 MHz crystal); a manual run of
the Release workflow rebuilds the current version and refreshes its files. Every CI run keeps
the Pico
.uf2and ESP32.binit built as downloadable artifacts (14 days).
-
Releases with ready-to-flash firmware:
.github/workflows/release.ymlpublishes a GitHub release for every newMCS_VERSION_STRINGβ.uf2for Pico / Pico 2 / RP2040-Zero, single-image.binfor ESP32 / -S3 / -C3 / -C6, the Linuxmcsbinary and the Arduino library zip. -
Project: README demo (
assets/demo.gif, recorded bytools/make_demo_gif.js), social preview image (assets/social-preview.png), issue and pull request templates,SECURITY.md,CODE_OF_CONDUCT.md. -
Studio templates: four playable console games under Fun and games β Guess the number, Rock, paper, scissors, Tic-tac-toe (against a simple computer player) and Hangman.
-
Fix: declaring a type again as an
enum(or with a different kind) crashed the compiler; it is now the errorthe type 'X' is already defined(partial classes/structs/interfaces still merge). -
Device drivers (
include/mcs_driver.h,modules/drivers/, docs/DRIVERS.md): a driver = a C# front end + a per-MCU backend (opstable + context), kept in a small registry (mcs_driver_register/_register_default/_unregister/_find). Firmware can add its own drivers or replace a built-in backend without editing MicroCS; C#Drivers.Has(name),Drivers.List, andHal.Hasalso answers for driver and class names. The template port shows aws2812backend and a complete custom driver (Buzzer). -
LedStripis now the built-inws2812driver and optional:MCS_ENABLE_WS2812(CMake-DMICROCS_WS2812=OFFβ on RP2 it also dropshardware_pio; ESP-IDF menuconfig and ZephyrCONFIG_MICROCS_WS2812;make_arduino.py --no-ws2812;microcs.mkMICROCS_WS2812 := 0). The unreleasedmcs_hal_t.ledstrip_writehook is gone (the HAL API stays at v2); backends registerMCS_WS2812_DRIVER(&ops, ctx).include/mcs_ledstrip_drivers.his nowinclude/mcs_ws2812_backends.h(mcs_ws2812_pio_write,mcs_ws2812_rmt_write). -
Filesystem sources are looked up, not downloaded behind your back:
cmake/MicroCSFS.cmakeusesMICROCS_LITTLEFS_DIR/MICROCS_YAFFS2_DIR(CMake or environment), then a copy next to the project or MicroCS (third_party/,lib/,external/,Middlewares/Third_Party/, siblings, the west workspace,make fetch-lfsoutput), then an earlier download; it downloads only with-DMICROCS_FS_DOWNLOAD=ON/MICROCS_FS_DOWNLOAD=1, else stops with a message saying what to set. LittleFS must be v2.x. The Pico example now needs one of those for its default LittleFS. -
Arduino:
tools/make_arduino.py --fs littlefs|yaffs2 [--fs-dir DIR]bundles a flash filesystem into the library (formcs_flashfs_mounton anymcs_flash_t),--define NAME=VALUEsets build options in the packagedmcs_config.h. -
Scheduler jobs run while a script sleeps:
Thread.Sleepin a main loop now runs due jobs (Scheduler.Every/After delegates and jobs.cfg scripts); before, a looping/main.csstarved every job. Nested script jobs keep the outer script's top-level variables intact.startup /main.csin jobs.cfg no longer runs main twice. -
No-space handling:
File.WriteAllText/WriteAllBytes/AppendAllText/Copycheck free space first and throwIOExceptionwithout touching the old file (it used to be truncated); the shell'sputanswersERR not enough space: N bytes, M freebefore the data is sent; Studio refuses uploads / saves that do not fit. -
Studio Reset / reconnect: Reset reboots through the firmware (
Hal.Reset, works on RP2040, STM32 and native-USB ESP32 boards without reset wiring), falling back to the RTS/DTR pulse, and stops a running script first. After a reset or unplug Studio keeps retrying to reopen the port for a minute instead of giving up after the first failed open, and tells you when connecting stopped a running script (e.g. a looping/main.cs). -
Fix:
Console.ReadLineon devices β scripts run by the shell /mcs_runtimenow read the line typed in the console (mcs_shell_readline: input queue, backspace, CR LF, echo). Before, it fell back tofgets(stdin), which never saw the Studio's input and blocked Ctrl-C / Stop. Ctrl-C while waiting stops the script; the wait honours the time limit and idle callback. -
Studio: a console line typed while a script runs goes to the script (
Console.ReadLine) instead of waiting for the script to end; Refresh / Run re-read open, unmodified files that changed on the device (e.g. a log the script appended to); double-click on a template = Add. -
List<int>.Sort()/Array.Sort(int[])sort in place without extra memory (was 16 bytes per element); the CPU benchmark template sizes fit a 160 KB heap (RP2040). -
LedStripβ WS2812 / NeoPixel / SK6812 on any pin (thews2812driver, see Device drivers above):new LedStrip(pin, count[, order]), indexer,SetPixel,Fill,Clear,Show,Brightness,LedStrip.Rgb/LedStrip.Hsv, GRB / RGB / GRBW orders. Drivers: RP2040/RP2350 PIO, ESP32 RMT (all chips with RMT), STM32 cycle-timed bit-bang, Zephyrled_strip(aliasled-strip), Arduino (ESP32 RMT, Arduino-Pico PIO, Adafruit_NeoPixel), simulator log. Pin name"NEOPIXEL"maps to the on-board RGB LED (RP2040-Zero GP16, ESP32-S3 GPIO48, C3/C6 GPIO8). Studio template WS2812 / NeoPixel LED strip now uses it; the SPI version stays as WS2812 LED strip via SPI. -
Fix: indexers (
this[int]) on native classes whose members are registered lazily from ROM. -
Studio template: WS2812 / NeoPixel LED strip (Displays) β addressable RGB LEDs driven through SPI MOSI at 2.4 MHz (3 SPI bits per LED bit, timing done by the SPI hardware): colour wipe, rainbow, theater chase, brightness limit; up to 28 LEDs per
SPI.Write(more with a largerMCS_HAL_MAX_XFER). -
Files on internal flash in every port:
mcs_flashfs_mount()puts LittleFS or YAFFS2 on anymcs_flash_tin one call (formats a blank partition). New internal-flash drivers:mcs_rp2_flash_init()(RP2040/RP2350 QSPI flash viaflash_safe_execute),mcs_stm32_flash_init()(every STM32 family: page and sector flash, ECC flash words, L0/L1 inverted erase),mcs_esp32_partition_flash()+mcs_esp32_flash_fs()andmcs_zephyr_flash_area_init(). The RP2 and STM32 examples now keep/boot.cs,/main.csand uploads on flash. Build choice: CMake-DMICROCS_FS=littlefs|yaffs2(cmake/MicroCSFS.cmakedownloads the sources), ESP-IDF menuconfig Filesystem on the "storage" partition, Zephyroverlay-yaffs2.conf.mcs_flash_t.write_size(program unit) sets LittleFS'sprog_size. Tested on simulated L4-, H7- and RP2-like internal flash; CI builds Pico 2, ESP32-S3 and Zephyr with YAFFS2 and runsnative_simwith both filesystems. -
Byte utilities in the core library (no HAL needed,
MCS_ENABLE_BYTES):Convert.ToBase64String/FromBase64String/ToHexString/ToHexStringLower/FromHexString,BinaryPrimitives(Read/Write{Int16,UInt16,Int32,UInt32,Int64}{Big,Little}Endian),BitConverter.ToString(bytes, start, length).Encoding.UTF8/ASCIIandBitConvertermoved from the HAL module into the core, so they work on every build (off in theminprofile). Testt18_utilsis checked against .NET 8. -
README: performance chart legend says "MicroCS 1.6"; footprint chart label fixed; status and roadmap updated (internal-flash filesystems done; USB mass-storage is a separate roadmap item).
-
Zephyr port completed: files on LittleFS (
mcs_zephyr_fs_mount()onstorage_partition, or any mounted Zephyr filesystem throughmcs_zephyr_fs_ops, e.g. FAT on SD), so/boot.cs,/jobs.cfg,/main.csand Studio uploads persist; I2S (mcs-i2sNaliases, memory-slab streaming), two CAN buses (mcs-can0/1), hardware RTC (rtcalias), watchdog fallbacks (wdt0,wdt,iwdg), polled UART fallback for drivers without interrupts, USB CDC ACM console (overlay-usb.conf+usb.overlay), Kconfig options (MICROCS_HEAP_SIZE,MICROCS_FS,MICROCS_RAMFS_SIZE,MICROCS_CONSOLE_ECHO), board overlays for the nRF52840 DK and the Pico, a west manifest (ports/zephyr/west.yml). Fixed: the example'sapp.overlaydid not build (missing PWM include),clock()link error with newlib,native_simprofile detection (CONFIG_SRAM_SIZE=0), the host-only POSIX backend compiled into Zephyr. CI buildsnative_sim, nRF52840 DK (UART + USB), Pico and Nucleo-F429ZI with Zephyr 4.1 and runstests/zephyr/smoke.pyonnative_sim. -
Arduino port completed: files on any
fs::FS(ESP32 LittleFS / SPIFFS / FFat / SD / SD_MMC, RP2040 LittleFS / SDFS) or the Arduino SD library (MCS_ARDUINO_FS,MCS_ARDUINO_SD_FS,mcs_arduino_fs_ops);MicroCS_REPLis now a full device firmware (LittleFS, boot scripts, Studio), newMicroCS_SDexample; ESP32 I2S, CAN (TWAI), task watchdog and calibratedADC.ReadMillivolts; RP2040 I2S and watchdog; 12/16-bit PWM duty on ESP32 / RP2040 / Teensy;Hal.UniqueIdon RP2040, nRF52, SAMD, STM32. CI compiles every example for ESP32, S3, C3, Pico, Pico 2 and Nano 33 BLE. -
Jobs: shell
cancel all|scripts|files, REPL.cancel, Cmcs_sched_cancel_all(); Studio stops the jobs earlier scripts left running before each Run (option) and has β― β Stop all jobs. Docs explain thatScheduler.Everyjobs outlive their script and file. -
MicroCS Studio (
tools/studio/, online at https://amir1387aht.github.io/MicroCS/): a browser IDE over Web Serial β device file manager (upload/download/rename/delete, drag & drop, free space), C# editor with Visual Studio 2022 Dark colours, API completion and 40+ snippets that saves and runs on the device, 85 templates and examples (boot scripts,jobs.cfg, scheduler patterns, every HAL peripheral, files, C# features) and a REPL / shell console. Connecting recovers an ESP32 left in its ROM bootloader by the port's DTR/RTS lines. Tested in CI against the host shell (mcs --repl --echo, new--echoflag); every template is compiled and run on the simulated board. -
Studio: connecting no longer resets the board. Studio used to set DTR/RTS low right after opening the port; Chrome applies the two lines one after the other, which briefly gives DTR=0/RTS=1 - "hold EN low" on ESP32 auto-reset circuits and on the native USB-Serial-JTAG - so the board rebooted on every connect, and Studio then also reset it again when it did not answer at once. Now the lines are left as the browser opens them (like
mcs_remote.py), a booting board gets up to 14 s (its boot log is shown live), only a board in the ROM download mode is reset (esptool order, one line per call), read errors are reported and survived, and the error message says what was received. Optional: β― β Release DTR/RTS on connect. -
Studio IntelliSense: signatures and docs for the whole library (
docs.js) and a small C# language service (lang.js): member lists with return types and a doc panel, parameter info while typing a call (active parameter, overloads with β/β, hints such asGPIO.Input, Outputβ¦), hover tooltips,usingnamespace completion, type inference forvar x = new T(), method return types (I2C.Scan(0)βList<int>),foreachvariables, and the classes, fields, methods and local functions of the open file; camel-hump matching. -
Studio editor: find / replace (Ctrl+F / Ctrl+H, case / word / regex, F3), go to line (Ctrl+G), format document (Shift+Alt+F), move / copy lines (Alt+ββ, Shift+Alt+ββ), delete line (Ctrl+Shift+K), bracket matching and Ctrl+] jump; unsaved files survive a reload.
-
Studio serial plotter: Console β Plotter charts the numbers a script prints (
temp:21.5 hum:40or12 34), up to 8 series, click to pause. -
Studio templates: 125 (was 85) - new categories Sensors (MPU6050, DS3231, ADS1115, INA219, AHT20/SHT31, DHT22, BH1750, joystick), Displays (MAX7219, LCD1602, TM1637, OLED text), Motors and actuators (H-bridge, steppers, relay, smooth servo), Input devices (rotary encoder, keypad, button gestures, touch), Control and filters (PID, moving average / median / EMA, hysteresis, Kalman), Protocols and data (CRC-8/16/32, Modbus RTU, NMEA, Base64, command shell, JSON, ring buffer), Serial plotter demos, Fun and games, Benchmarks.
-
Docs: the I2S direction constants are
I2S.Transmit/Receive/Duplex(HAL.md saidTx/Rx). -
ESP32: scripts on flash β the example mounts LittleFS on the
storagepartition (mcs_esp32_littlefs()), so uploaded files survive resets and power cycles. -
Free space: C#
DriveInfo(new DriveInfo("/").AvailableFreeSpace,TotalSize,DriveFormat,DriveInfo.GetDrives()), shell/mcs_remote.pycommanddf, andmcs_vfs_statfs()with an optionalstatfsbackend op (RAM, POSIX, LittleFS, YAFFS2, ESP32 LittleFS).
Focus: a precompiled image is now clearly the fast path. Up to 1.5 an image held the same bytecode the on-device compiler emits, so it only saved the compile step (a few ms) and was larger than the source. Numbers: PERFORMANCE.md.
-
fib2.43 β 1.43 M (1.7Γ),loop15.0 β 6.70 M (2.2Γ),objects5.64 β 3.19 M (1.8Γ),sensor0.87 β 0.68 M (1.3Γ),strings0.63 β 0.49 M (1.3Γ);demo.csimage 3.44 β 2.18 M on M4F and 4.54 β 2.96 M on M0. Host: fib(30) 73 β 41 ms, 10 M loop 322 β 95 ms. - Images now run 1.1β2.0Γ faster than the same script from source and need about half the RAM
peak (no compiler state): e.g.
fib31.4 β 15.1 KB,sensor40.7 β 17.0 KB.
- 71 superinstructions (opcodes 86β156, image version 2+): local/immediate/constant
arithmetic with optional store, compare-and-branch on locals/immediates/constants,
accumulate (
x += e),a[i]/a[i] = vandobj.f/obj.f = von locals,return local. Jump threading, loop rotation (one branch per iteration), dead-code removal. -
mcs -c/mcs -C/mcs_compile_image*optimize by default;mcs -O0andMCS_IMAGE_NO_OPTproduce plain bytecode. Source run on the device is unchanged unlessMCS_OPTIMIZE_SOURCE=1. - New switches
MCS_ENABLE_SUPEROPS(default 1; 0 inmcs_profile_min.h, which also turnsMCS_FIELD_CACHEoff) andMCS_ENABLE_OPTIMIZER. A VM without superinstructions rejects optimized images with a clear error;examples/lowram/node_image.his now built with-O0so it runs on every profile.
- Stack pointer kept in a register; in-place int fast paths for arithmetic, shifts, compares,
a[i],a[i] = v;array.Length/List.Count/string.Lengthwithout lookup. -
INVOKEmethod inline cache per call site, constructor cache fornew C(...), direct closure entry, cheaperRETURN; caches are invalidated by a class epoch.
- Varints, delta-coded line tables, every string stored once, f32 constants when exact.
Images are 25β45 % smaller (host
demo.cs2763 β 1942 B;bench/mcu/loop.csimage 166 B for 232 B of source). v1 and v2 images still load.
- Typed top-level variables used inside functions declared before them had the wrong type
(
double ema = 0; void F() { ema = ema * 0.5; }did integer arithmetic). - Calls of hoisted top-level functions keep the declared return type (no extra conversion).
-
foreach (var p in list) acc = β¦no longer emitted a stray tuple-names call. - An interpolation format spec longer than 95 characters overflowed a compiler buffer (found by
tools/fuzz.py; present since 1.0). - Image loader:
FIELD's default-value operand is range-checked (found bytools/fuzz.py --image). - CLI builds with
MCS_ENABLE_COMPILER=0/MCS_ENABLE_BYTECODE_SAVE=0compile warning-free.
-
make mcu-bench(tools/mcu_bench.sh,bench/mcu/*.cs,ports/cortex-m/bench.c): image vs-O0image vs source on emulated M4F/M0, outputs checked against the host. -
make check: +3 flag builds and +3 full-suite configurations (MCS_ENABLE_SUPEROPS=0,MCS_OPTIMIZE_SOURCE=1, switch dispatch); newtests/t17_optimizer.cs.
- Flash: +8β11 KB on Cortex-M with superinstructions (
m0-runtime208.5 KB,m4-full253.1 KB); theminbuild (superinstructions and inline caches off) stays at 60.6 KB of 64 KB. - Compiling an image on the PC takes longer (optimizer): host
demo.cs83 β 120 Β΅s.
Focus: run on the smallest 32-bit parts (the same 16 KB RAM floor as MicroPython, but in 64 KB of flash), configure every port from the chip's resources, and put real filesystems on external NOR and NAND flash. Nothing was removed: every reduction is a compile-time switch.
-
Lazy class tables (
MCS_LAZY_CLASSES=1, default): built-in and module classes are created the first time they are named; constants live in ROM (mcs_const_t,MCS_CONST,mcs_register_consts). A VM now starts in 1.7β8 KB of heap instead of 13β25 KB (Cortex-M0:m0-node13.0 β 1.7 KB,m0-lowram19.9 β 5.0 KB,m0-runtime25.1 β 7.8 KB). -
profiles/mcs_profile_min.h: a complete firmware in 64 KB of flash and 16 KB of RAM (m0-64k: 60.9 KB, run bymake cm-check). New switches, all default on:MCS_ENABLE_STRING_EXTRA,MCS_ENABLE_ARRAY_EXTRA,MCS_ENABLE_STACK_QUEUE,MCS_ENABLE_CONVERT,MCS_ENABLE_DIAGNOSTICS,MCS_ENABLE_STDIO,MCS_ENABLE_MALLOC;MCS_TINY_PRINTF=1uses the new built-insnprintf(src/mcs_fmt.c) instead of libc's. Flash cost of each switch: LOW_RESOURCE.md. -
examples/lowramruns in a 16 KB part (m0-16k: 12 KB pool, 11.2 KB peak); the firmware no longer needsprintfand can report its C-stack high-water mark. -
lowramprofile: 128 stack slots, 24 frames, 12 handlers, 16 roots. - All profile headers are overridable (
#ifndefguards), so-Dflags win over a profile. -
cfg.alloc_overhead(+MCS_POOL_OVERHEAD): the GC threshold andheap_limitcount the allocator's per-block headers (set automatically for the built-in pool).
-
profiles/mcs_profile_auto.hpicks min / tiny / lowram / mcu / embedded / default fromMCS_TARGET_RAM_KB/MCS_TARGET_FLASH_KB(CMakeMICROCS_RAM_KB/MICROCS_FLASH_KB), ZephyrCONFIG_SRAM_SIZE/CONFIG_FLASH_SIZE, or the STM32 / RP2040 / RP2350 / nRF52 / SAMD device macro. Board ports (MCS_PORT_HAL=1) keep the HAL classes on parts with β₯ 128 KB of flash. Default for CMakeMICROCS_PORT=stm32and for Zephyr (newMICROCS_PROFILE_*Kconfig choice); ESP-IDF gets a menuconfig choice. -
STM32 parts below 16 KB RAM or 64 KB flash are no longer supported: their device macros
(C011/C031/C051, F030x4βx8, F031, F038, F042, F048, F051, F058, F070x6, F100, F101x4βxB,
F102, F103x4/x6, F301x6, F302x6, F303x6/x8, F328, F334, G030/G031/G041, L010x4βx8,
L011βL063, L100/L151/L152 small variants, U031) stop the build
(
include/profiles/mcs_target_stm32.h;MCS_ALLOW_SMALL_TARGET=1overrides). - CI compiles every STM32 family twice (default and auto profile,
PORT_CFLAGS).
- ESP32-C2 / ESP8684 support: embedded profile by default (
sdkconfig.defaults.esp32c2), ROM nano-printf disabled (with a#warningif it is re-enabled with floats), LEDC clock from the 60 MHz PLL divider, hardware timer count clamped to the chip, adaptive VM heap in the example (128 KB on the C2, clamped to the largest free block), C2 pin map, 26 MHz crystal note. Built in CI.
-
include/mcs_flash.h(MCS_ENABLE_FLASH): a NOR/NAND device description plus generic SPI NOR (JEDEC id, 3-/4-byte addressing, 4/64 KB erase, power-up unlock) and SPI NAND (W25N-style command set, on-die ECC status, bad-block markers, cached page) drivers, andmcs_flash_hal_xferto run them over a MicroCS HAL SPI bus + GPIO chip select. ~2.6 KB. -
LittleFS on NOR and NAND:
mcs_lfs_flash_config()fills anlfs_configfor a partition; NAND bad blocks βLFS_ERR_CORRUPT(relocation), failing blocks get marked. -
YAFFS2 backend
mcs_yaffs_ops(MCS_ENABLE_YAFFS) +mcs_yaffs_flash_dev(): in-band tags on NOR and (by default) NAND, spare-area tags optional, full bad-block handling; optional single-threaded OS glue (MCS_YAFFS_OSGLUE=1). YAFFS2 is GPLv2 β not bundled. - Tests on simulated chips with real command sets (
tests/c/flash_sim.h):make test(drivers, 42 checks),make lfs-test(now also SPI NOR + SPI NAND with bad blocks), newmake yaffs-test(downloads a pinned yaffs2 revision; NAND in-band + spare tags with a block wearing out, NOR). CI job "Flash filesystems".
- GC: the string intern table no longer doubles while dead strings are waiting for the next
collection (it grows from the live count and defers growth near a collection), and it
shrinks after a collection; this removed the collection storm on
m0-lowram(now 3 collections) and made the 16 KB targets possible. - GC: when the live data was close to ΒΎ of
heap_limit, the next collection threshold landed at or below the bytes already allocated and the VM collected at every safepoint; it is now placed halfway to the limit. Allocator block headers were not counted, so a small pool could run out before the GC threshold was reached (cfg.alloc_overhead). - Images: constant tables are allocated at their exact size when loading.
-
{0:F<huge>}format precision could overflow anint; exponent formatting no longer usessprintf(and cannot truncate). - The compiler treated not-yet-created built-in classes as unknown names / interfaces.
- New/updated: LOW_RESOURCE.md (16 KB / 64 KB, switch costs, auto profile), FILESYSTEM.md (flash layer, LittleFS vs YAFFS2), STM32 supported-parts list, ESP32-C2 section, footprint tables re-measured.
Focus: run MicroCS on real boards with every peripheral, from any build system β either as a library inside existing firmware or as a complete C# firmware with a REPL.
- New classes:
Pin,I2cDevice,SpiDevice,DAC,Timer,I2S,QSPI,CAN+CanFrame,Watchdog,RTC; many new members (GPIO.OnChange/Off/PulseIn/Pin,UART.Open(port, baud, bits, parity, stop),ReadLine,WriteLine,OnReceive,I2C.Scan/ReadRegister(s)/WriteRegister,SPI.Open(bus, hz, mode),ADC.ReadMillivolts/ReadVoltage/ReadAverage,PWM.Servo/Tone/SetPulse/Stop,Hal.Poll/Run/Micros/DelayMicroseconds/Reset/UniqueId/CpuHz/OnEvent/Post/DroppedEvents). - Interrupts and timers: ISR-safe event ring (
mcs_hal_post) or driver queue (poll_event); callbacks run in script context and adapt to their parameter count. - Pin names (
"PA5","GPIO21","P0.13","LED") viapin_lookup/mcs_hal_parse_pin. -
Encoding.UTF8/ASCIIandBitConverterhelpers for packet work. - Fixed:
GPIO.Write(pin, 0)drove the pin high (anint0 was treated as truthy). - The simulator board gained interrupts, timers, DAC, IΒ²S, QSPI flash, CAN and an MPU-6050-style register device. v1 board tables compile unchanged.
-
ports/stm32β every STM32Cube family (C0, F0βF7, G0, G4, H5, H7, L0βL5, U5, WB, WL), using CubeMX handles; compile-checked against 21 family/device combinations. -
ports/esp32β ESP-IDF 5.x for ESP32, S2, S3, C2, C3, C5, C6, H2, P4 (no Wi-Fi/BLE needed). -
ports/rp2β RP2040 and RP2350 with the pico-sdk; example firmware for Pico and Pico 2. -
ports/zephyrβ devicetree-driven port + Zephyr module (beta). -
ports/arduinoβ any 32-bit Arduino core, Arduino library packager (beta). -
ports/templaterewritten: every HAL member, both entry points.
-
mcs_runtime(modules/runtime): a complete firmware in one call β REPL, filesystem, boot scripts, scheduler, callbacks, upload protocol; ormcs_runtime_start/stepfrom your own loop. - Shell REPL: multi-line input, expression printing, Ctrl-C, Ctrl-E paste mode, Ctrl-A machine
mode, dot commands. CLI:
mcs --repl;tools/mcs_remote.py replterminal. - Core:
mcs_arity,mcs_ticks,mcs_sleep(dispatches callbacks while sleeping),mcs_safepoint,mcs_set_idle;Thread.Sleepis abortable.
-
CMakeLists.txt(plain CMake, pico-sdk, CubeMX CMake, ESP-IDF component),microcs.mk,idf_component.yml,library.json(PlatformIO),zephyr/module,include/MicroCS.h. - CI: CMake host build, Pico/Pico 2 firmware, ESP-IDF builds (ESP32/S3/C3/C6), STM32 port checks.
- New README (two ways to use MicroCS, build-system matrix, ports, comparison with MicroPython and nanoFramework), rewritten HAL, PORTING and STANDALONE guides, a README per port.
- 16 commented hardware examples (
examples/hardware/) run bymake test;examples/quickstart_embed.c.
Focus: RAM and flash on weak microcontrollers, without changing script behaviour. Guide: docs/LOW_RESOURCE.md.
-
MCS_COMPACT_VALUES(default on for 32-bit targets): values packed to 4-byte alignment, 12 instead of 16 bytes with double/int64 payloads. On Cortex-M0 + newlib-nano this removed amemcpycall per value copy: the demo image runs in 4.56 M instead of 18.2 M instructions. -
Execute-in-place images:
mcs_exec_image_xip()runs bytecode from the image buffer (flash) instead of copying it to the heap;mcs --xip;MCS_ENABLE_XIP. - VM baseline after
mcs_newon Cortex-M: 50 136 B β 25 688 B (M0, full stdlib). Interned-string set stores keys only, a global's slot lives in its name string (no global index table), built-in exception classes share one field layout, hash tables start atMCS_TABLE_MIN_CAP(4). -
Compact
Dictionary/HashSetindex: 1-, 2- or 4-byte position slots instead of full key/value entries;HashSetstores no values array;Removeno longer rehashes. Host: 700-stringHashSet104 β 58 KB, 100-entryDictionary<int,int>12.1 β 4.3 KB. - Compiler RAM: smaller AST nodes and tokens (24 bytes on 32-bit), token array grows by the observed density instead of doubling. On-device compile + run of the demo on the M4: pool peak 131 528 B β 90 480 B, compile instructions β12 %.
- Lexer keyword lookup by length + first character, cheaper primitive-type check.
- Host (
make bench, same machine, same session as 1.2.0): run times unchanged,mcs_new43 β 30 Β΅s, demo compile 201 β 140 Β΅s, demo heap peak 116 438 β 87 510 B.
-
profiles/mcs_profile_lowram.hfor 32β64 KB RAM parts (images only, single-precision floats β 8-byte values, small VM limits, 4 KB first GC, 4-byte pool alignment). -
MCS_ENABLE_LINQ(LINQ operators +Enumerable, ~12 KB flash on M0),MCS_POOL_ALIGN,MCS_TABLE_MIN_CAP,MCS_ERROR_SIZE,MCS_MAX_PINS,MCS_COMPACT_VALUES,MCS_ENABLE_XIP. - Cortex-M targets
m0-lowram(64 KB RAM, 40 KB pool) andm0-node(the newexamples/lowramfirmware, 48 KB RAM, 32 KB pool) inmake cm-check. -
tools/cm_emu.py --profile N(hottest functions) and--callers SYMBOL.
- LINQ operators on
Dictionary,HashSet,StackandQueue(Where,Select,Skip,Take,Last,Average,GroupBy, β¦ β previously only lists/arrays had the full set). -
HashSet,StackandQueueare accepted wherever a sequence is:string.Join,string.Concat,new List<T>(β¦),AddRange,Concat,Zip,SequenceEqual,new Stack/Queue(β¦)(previouslystring.Join(",", set)printed the type name). -
HashSet.IsSupersetOf,SetEquals,SymmetricExceptWith.
-
base(msg)into a lazily registered native constructor (e.g.class E : IOException) failed with "does not contain .ctor". -
class X : IOExceptionwas treated as an interface by theI+uppercase heuristic.
- New: docs/LOW_RESOURCE.md, examples/lowram/.
- New test
t14_low_resource.cs(exception subclasses of built-ins, globals, string churn, dictionary index widths, collection interop) β byte-identical to .NET 8. - Every script test now also runs as an XIP image (46 runs); 104 C unit checks (XIP images,
300 globals, intern churn, shared layouts, dictionary index);
make checkadds the lowram profile and five alternative configurations that run the whole suite.
-
Tuples: literals
(1, "a"), named elements(x: 1, y: 2), tuple types(int x, int y)in locals, fields, parameters,outparameters, return types and generic arguments (Dictionary<(int, int), string>);Item1..Item7; structural==/!=, hashing andToString(). - Tuple element name projection (
var t = (a, b); t.a) and names that survive later assignments,outparameters, tuple-typed fields/properties and object initializers. -
Deconstruction:
var (a, b) = t;,(int a, var b) = t;, swaps(a, b) = (b, a)without allocation,_discards,foreach (var (k, v) in dict). -
Index from end and ranges:
x[^1],x[1..^1],x[..3],x[2..]on strings, arrays and lists. -
Patterns in
is: relational,and,or,not(t is < 0 or > 100);andalso in switch arms andcaselabels. -
sizeof(builtin),checked(...)/unchecked(...)expressions,staticlocal functions with tuple return types;.HasValue/.Value/.GetValueOrDefault()on nullable values. - Declarations used as embedded statements (
while (c) int i = 0;) are now a compile error (as in C#).
- LINQ
Zip(tuple and selector forms) andChunk;Stack.TryPop/TryPeek,Queue.TryDequeue/TryPeek. - Generated API reference
docs/STDLIB.md(tools/gen_stdlib_doc.py).
- Image format v2 with superinstructions:
SET_LOCAL_POP,SET_GLOBAL_POPand fused compare-and-branchJF_EQ/NE/LT/LE/GT/GE. v1 images still load. - Per-site inline cache for field access (
MCS_FIELD_CACHE), exact-arity call fast path, integer/%fast path. - Token array freed right after parsing. Host: fib β20 %, loop β22 %; Cortex-M4 demo β17 % instructions; on-device compile peak 163 KB β 132 KB.
- Unbounded growth of the string intern table with many temporary strings (eventually OOM).
- Stack-slot leak when an unbraced loop/if body introduced locals (
out var, deconstruction temps). - Lexer:
1..3is a range, not1.followed by.3.
- Image loader validates local/upvalue operands, closure captures and that every branch lands on an instruction boundary.
-
mcs -d file.mcsbdisassembles images (mcs_disassemble_image). -
tools/fuzz.pymutation fuzzer (source and--imagemodes), clean under ASan/UBSan;make asan-testruns the whole suite with sanitizers; GitHub Actions CI. -
.NET 8parity check extended tot11,t13,examples/tour.cs. - New examples (
blink.cs,sensor_logger.cs,tour.cs) run bymake test.
- Virtual filesystem (RAM, POSIX, LittleFS) with C#
File/Directory/Path. - HAL with C#
GPIO,UART,I2C,SPI,ADC,PWM+ simulator board. - Job scheduler, standalone shell with UART upload protocol, execution limits.
-
ref/out/in,casepatterns withwhen. - Cortex-M0/M4/M33 reference port verified in an emulator. Memory fixes in the compiler.
- Lexer, parser, single-pass compiler, stack VM with computed goto, precise GC, stdlib, bytecode images, CLI/REPL.