Skip to content
 
 

Repository files navigation

MD/JS: JavaScript Worker for Atari ST

Microfirmware for the SidecarTridge Multi-device by Neil Rackett

Introduction

MD/JS turns your SidecarT into a persistent JavaScript Worker for the Atari ST, enabling you to:

  • Upload JavaScript source from your ST
  • Call named functions with JSON arguments
  • Use ES5.1, ES6/2015 and most ES2020 features
  • Load text or JSON files using fetch()
  • Read JSON results

Within these limits:

  • 32KB JavaScript heap (48KB if compiled without fetch)
  • 2KB result buffer
  • fetch() using GET over HTTP only

To see MD/JS in action, simply install the microfirmware, open the cartridge icon on your ST's desktop (lower case c drive) and run MDJSDEMO.PRG.

If you'd like to integrating MD/JS into your own ST apps, instructions are below.

Examples

MD/JS Code

MD/JS Code

MD/JS Code is an example GEM application that you can download from the releases page to edit and run JavaScript source code on your Atari ST.

STJSPONG — Pong Battle: ST vs JS

STJSPONG

A self-playing Pong demo that pits a native 68000 AI (your ST) against a JavaScript AI running on your SidecarT (the JS). The JS "brain" is uploaded at start-up and drives its paddle through the non-blocking async API — mdjs_call_asyncmdjs_statusmdjs_result — so the ST keeps rendering a smooth 50 fps while Core 1 predicts where the ball is going. A compact worked example of driving MD/JS from a real-time loop; source in examples/stjspong/, or build it with make examples.

Hardware requirements

  • SidecarTridge Multi-device (RP2040-based ROM cartridge emulator)
  • Atari ST, STE, MegaST, or MegaSTE
  • Raspberry Pi Debug Probe or Picoprobe for flashing/debugging (optional but recommended for development)

Installation

🎉 MD/JS is now available in the official SidecarTridge Multi-device catalogue! 🎉

  1. Install MD/JS using your SidecarT's web interface.
  2. Download MDJSCODE.PRG demo app and example JS files from the releases page and save them to a floppy or hard disk.
  3. Enjoy!

Manual installation

  1. Download the latest files from the releases page.
  2. Copy the .uf2 and .json files to the /apps folder of your SidecarT's microSD card.
  3. On the Booster screen, press ESC for the app list and select the MD/JS app.
  4. To return to Booster, turn on your ST while holding the SELECT button on your SidecarT.

You'll know if the microfirmware is working because you'll see "MD/JS: JavaScript Worker is ready" on your ST's screen as it boots.

How it works

The RP2040 runs a full JerryScript ES.next runtime (48 KB heap) on Core 1, with Core 0 continuing to service the cartridge bus to avoid blocking.

Atari ST (68000)                     RP2040
────────────────                     ──────
mdjs_ping()           ──CMD 0x10──►  Core 0: tprotocol decode
mdjs_upload(src)      ──CMD 0x11──►       ↓  multicore FIFO
mdjs_call(f, a, r)    ──CMD 0x12──►  Core 1: JerryScript runtime
mdjs_reset()          ──CMD 0x13──►       ↓  jerry_parse / jerry_run / jerry_call
mdjs_call_async(f, a) ──CMD 0x14──►       ↓  result → ROM-in-RAM @ $FAF100
mdjs_poll()           ──CMD 0x15──►  Core 0: writes random token (unblocks ST)

mdjs_result(r)        ◄────────────  Status byte @ $FAF008 (no bus transaction)

The result buffer is mapped into the ST's ROM4 address space at $FAF100 and is directly readable with move.b instructions — the RP2040 pre-swaps bytes so character reads come out correctly. Use the C API (mdjs_result()) if you'd rather not handle that yourself. The async status byte lives at $FAF008 — a zero-overhead read.

ST-side API

Include mdjs.h and link against mdjs.c and sidecart_stubs.S in your ST project.

#include "mdjs.h"

/* Check the worker is present */
if (mdjs_ping() != 0) {
  /* No SidecarTridge / worker not running */
}

/* Upload JavaScript source (evaluated immediately) */
mdjs_upload("function greet(name) { return 'Hello, ' + name + '!'; }");

/* Call a function — args as a JSON array, result as a JSON value */
char result[256];
mdjs_call("greet", "[\"World\"]", result, sizeof(result));
/* result == "\"Hello, World!\"" */

/* Clear the JS context and start fresh */
mdjs_reset();

All functions return 0 on success, non-zero on timeout or error. Results are NUL-terminated strings in the caller-supplied buffer. mdjs_upload handles chunking automatically — just pass the full source string.

mdjs_ping() returns quickly whether or not a worker is present — it checks a readiness flag before issuing the protocol command, so detection never blocks waiting for a timeout.

Async calls

mdjs_call blocks the 68000 until the RP2040 finishes executing the JavaScript. For long-running functions you can use the non-blocking variant instead:

/* Submit the call and return immediately */
int err = mdjs_call_async("heavyCalc", "[1000]");
if (err != 0) { /* busy or protocol error */ }

/* Do other work while JS runs on Core 1 */
while (mdjs_status() == MDJS_STATUS_BUSY) {
    do_other_work();
}

if (mdjs_status() == MDJS_STATUS_DONE) {
    char result[256];
    mdjs_result(result, sizeof(result));
    /* result now contains the JSON return value */
}

mdjs_status() is a zero-overhead single byte read from MDJS_STATUS_ADDR ($FAF008) — no bus transaction. Only one async call can be in flight at a time; submitting a second returns MDJS_STATUS_BUSY immediately.

Cutting per-call latency

Every command-emitting call (upload, call, call_async, reset) first busy-waits a short settle to avoid a command race right after the worker boots. In a warm loop that already spaces its calls — e.g. a game firing a prediction every few frames — that wait is pure overhead (tens of milliseconds, enough to stall a per-frame loop). Once the worker is known-good, switch it off:

mdjs_upload(brain);   /* first commands settle normally — cold-boot safe */
mdjs_set_settle(0);   /* warm now: no per-call stall */

for (;;) {
    mdjs_call_async("tick", state);   /* fires immediately, no settle */
    /* ... render a frame, poll mdjs_status() ... */
}

mdjs_set_settle(iterations) takes a raw busy-loop count (0 = off); MDJS_SETTLE_DEFAULT is the default and mdjs_get_settle() reads the current value. A flaky SidecarTridge can raise it instead.

API limits

Parameter Limit / address
JS source per upload Up to ~16 KB (8 chunks × 2096 bytes)
Function name 63 characters
Result JSON 2048 bytes
Args JSON 2031 bytes (max, with 63-char function name)
JerryScript heap 48 KB
Result buffer (ST) $FAF100 (ROM4 + 0xF100)
Async status byte (ST) $FAF008 (ROM4 + 0xF008)

Building

To build or monitor the microfirmware, the following make targets are available:

# Production build
make

# Debug build
make debug

# Monitor debug build over UART
make uart

If you'd like more information about coding for the SidecarT, the docs are here.

License

Source code is licensed under the GNU General Public License v3.0. See LICENSE for the full text.

About

JavaScript Worker for Atari ST with Sidecartridge Multi-device

Resources

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages