Skip to content

Improve standalone emulator run cli #146

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 60 additions & 36 deletions demo/emulator-run.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,70 @@
import * as fs from 'fs';
import packageJson from '../package.json';
import sade from 'sade';
import { GDBTCPServer } from '../src/gdb/gdb-tcp-server.js';
import { RP2040 } from '../src/index.js';
import { Simulator } from '../src/simulator.js';
import { bootromB1 } from './bootrom.js';
import { loadHex } from './intelhex.js';
import { loadUF2 } from './load-flash.js';
import minimist from 'minimist';

const args = minimist(process.argv.slice(2), {
string: [
'image', // An image to load, hex and UF2 are supported
],
});

const simulator = new Simulator();
const mcu = simulator.rp2040;
mcu.loadBootrom(bootromB1);

const imageName = args.image ?? 'hello_uart.hex';

// Check the extension of the file
const extension = imageName.split('.').pop();
if (extension === 'hex') {
// Create an array with the compiled code of blink
// Execute the instructions from this array, one by one.
const hex = fs.readFileSync(imageName, 'utf-8');

console.log(`Loading hex image ${imageName}`);
loadHex(hex, mcu.flash, 0x10000000);
} else if (extension === 'uf2') {
console.log(`Loading uf2 image ${imageName}`);
loadUF2(imageName, mcu);
} else {
console.log(`Unsupported file type: ${extension}`);
process.exit(1);

type CliOptions = {
image: string;
gdb: boolean;
'gdb-port': number;
};

function loadImage(imageName: string, mcu: RP2040) {
const extension = imageName.split('.').pop();

if (extension === 'hex') {
// Create an array with the compiled code of blink
// Execute the instructions from this array, one by one.
const hex = fs.readFileSync(imageName, 'utf-8');
console.log(`Loading hex image ${imageName}`);
loadHex(hex, mcu.flash, 0x10000000);
} else if (extension === 'uf2') {
console.log(`Loading uf2 image ${imageName}`);
loadUF2(imageName, mcu);
} else {
console.log(`Unsupported file type: ${extension}`);
process.exit(1);
}
}

const gdbServer = new GDBTCPServer(simulator, 3333);
console.log(`RP2040 GDB Server ready! Listening on port ${gdbServer.port}`);
function simulateImage(opts: CliOptions) {
const simulator = new Simulator();
const mcu = simulator.rp2040;
mcu.loadBootrom(bootromB1);

mcu.uart[0].onByte = (value) => {
process.stdout.write(new Uint8Array([value]));
};
try {
loadImage(opts.image, mcu);
} catch (err) {
const message = err instanceof Error ? err.message : String(err);

console.log(`Error: Failed to load image file: "${message}"`);
process.exit(1);
}

if (opts.gdb) {
const gdbServer = new GDBTCPServer(simulator, opts['gdb-port']);
console.log(`RP2040 GDB Server ready! Listening on port ${gdbServer.port}`);
}

mcu.uart[0].onByte = (value) => {
process.stdout.write(new Uint8Array([value]));
};

simulator.rp2040.core.PC = 0x10000000;
simulator.execute();
}

simulator.rp2040.core.PC = 0x10000000;
simulator.execute();
sade('rp2040js', true)
.version(packageJson.version)
.describe(packageJson.description)
.option('-i, --image', 'Provide an image to run (.uf2, .hex)', 'hello_uart.hex')
.option('-g, --gdb', 'If a GDB server should be started or not', true)
.option('-p, --gdb-port', 'The port to start the gdb server on', 3333)
.example('--image ./hello_world.uf2')
.action(simulateImage)
.parse(process.argv);
154 changes: 92 additions & 62 deletions demo/micropython-run.ts
Original file line number Diff line number Diff line change
@@ -1,66 +1,64 @@
import fs from 'fs';
import minimist from 'minimist';
import packageJson from '../package.json';
import sade from 'sade';
import { GDBTCPServer } from '../src/gdb/gdb-tcp-server.js';
import { RP2040 } from '../src/index.js';
import { Simulator } from '../src/simulator.js';
import { USBCDC } from '../src/usb/cdc.js';
import { ConsoleLogger, LogLevel } from '../src/utils/logging.js';
import { bootromB1 } from './bootrom.js';
import { loadCircuitpythonFlashImage, loadMicropythonFlashImage, loadUF2 } from './load-flash.js';

const args = minimist(process.argv.slice(2), {
string: [
'image', // UF2 image to load; defaults to "RPI_PICO-20230426-v1.20.0.uf2"
'expect-text', // Text to expect on the serial console, process will exit with code 0 if found
],
boolean: [
'gdb', // start GDB server on 3333
'circuitpython', // use CircuitPython instead of MicroPython
],
});
const expectText = args['expect-text'];

const simulator = new Simulator();
const mcu = simulator.rp2040;
mcu.loadBootrom(bootromB1);
mcu.logger = new ConsoleLogger(LogLevel.Error);

let imageName: string;
if (!args.circuitpython) {
imageName = args.image ?? 'RPI_PICO-20230426-v1.20.0.uf2';
} else {
imageName = args.image ?? 'adafruit-circuitpython-raspberry_pi_pico-en_US-8.0.2.uf2';
}
console.log(`Loading uf2 image ${imageName}`);
loadUF2(imageName, mcu);

if (fs.existsSync('littlefs.img') && !args.circuitpython) {
console.log(`Loading uf2 image littlefs.img`);
loadMicropythonFlashImage('littlefs.img', mcu);
} else if (fs.existsSync('fat12.img') && args.circuitpython) {
loadCircuitpythonFlashImage('fat12.img', mcu);
// Instead of reading from file, it would also be possible to generate the LittleFS image on-the-fly here, e.g. using
// https://github.com/wokwi/littlefs-wasm or https://github.com/littlefs-project/littlefs-js
}
type CliOptions = {
image: string | null;
'expect-text': string | null;
gdb: boolean;
'gdb-port': number;
'circuit-python': boolean;
};

function loadImage(mcu: RP2040, image: string | null, useCircuitPython: boolean) {
let selectedImage: string;
if (image) selectedImage = image;
else if (useCircuitPython)
selectedImage = 'adafruit-circuitpython-raspberry_pi_pico-en_US-8.0.2.uf2';
else selectedImage = 'RPI_PICO-20230426-v1.20.0.uf2';

if (args.gdb) {
const gdbServer = new GDBTCPServer(simulator, 3333);
console.log(`RP2040 GDB Server ready! Listening on port ${gdbServer.port}`);
console.log(`Loading uf2 image ${selectedImage}`);

try {
loadUF2(selectedImage, mcu);
} catch (err) {
const message = err instanceof Error ? err.message : String(err);

console.log(`Error: Failed to load image file: "${message}"`);
process.exit(1);
}

if (fs.existsSync('littlefs.img') && !useCircuitPython) {
console.log(`Loading uf2 image littlefs.img`);
loadMicropythonFlashImage('littlefs.img', mcu);
} else if (fs.existsSync('fat12.img') && useCircuitPython) {
loadCircuitpythonFlashImage('fat12.img', mcu);
// Instead of reading from file, it would also be possible to generate the LittleFS image on-the-fly here, e.g. using
// https://github.com/wokwi/littlefs-wasm or https://github.com/littlefs-project/littlefs-js
}
}

const cdc = new USBCDC(mcu.usbCtrl);
cdc.onDeviceConnected = () => {
if (!args.circuitpython) {
// We send a newline so the user sees the MicroPython prompt
cdc.sendSerialByte('\r'.charCodeAt(0));
cdc.sendSerialByte('\n'.charCodeAt(0));
} else {
function handleDeviceConnected(cdc: USBCDC, useCircuitPython: boolean) {
if (useCircuitPython) {
cdc.sendSerialByte(3);
return;
}
};

let currentLine = '';
cdc.onSerialData = (value) => {
// We send a newline so the user sees the MicroPython prompt
cdc.sendSerialByte('\r'.charCodeAt(0));
cdc.sendSerialByte('\n'.charCodeAt(0));
}

function handleSerialData(value: Uint8Array, expectText: string | null) {
process.stdout.write(value);
let currentLine = '';

for (const byte of value) {
const char = String.fromCharCode(byte);
Expand All @@ -75,20 +73,52 @@ cdc.onSerialData = (value) => {
currentLine += char;
}
}
};

if (process.stdin.isTTY) {
process.stdin.setRawMode(true);
}
process.stdin.on('data', (chunk) => {
// 24 is Ctrl+X
if (chunk[0] === 24) {
process.exit(0);
}
for (const byte of chunk) {
cdc.sendSerialByte(byte);

function simulateMicropythonImage(opts: CliOptions) {
const simulator = new Simulator();
const mcu = simulator.rp2040;
mcu.loadBootrom(bootromB1);
mcu.logger = new ConsoleLogger(LogLevel.Error);

loadImage(mcu, opts.image, opts['circuit-python']);

if (opts.gdb) {
const gdbServer = new GDBTCPServer(simulator, opts['gdb-port']);
console.log(`RP2040 GDB Server ready! Listening on port ${gdbServer.port}`);
}
});

simulator.rp2040.core.PC = 0x10000000;
simulator.execute();
const cdc = new USBCDC(mcu.usbCtrl);
cdc.onDeviceConnected = () => handleDeviceConnected(cdc, opts['circuit-python']);
cdc.onSerialData = (value) => handleSerialData(value, opts['expect-text']);

if (process.stdin.isTTY) process.stdin.setRawMode(true);

process.stdin.on('data', (chunk) => {
// 24 is Ctrl+X
if (chunk[0] === 24) {
process.exit(0);
}
for (const byte of chunk) {
cdc.sendSerialByte(byte);
}
});

simulator.rp2040.core.PC = 0x10000000;
simulator.execute();
}

sade('rp2040js-micropython', true)
.version(packageJson.version)
.describe(packageJson.description)
.option('-i, --image', 'UF2 image to load')
.option(
'-e, --expect-text',
'Text to expect on the serial console, process will exit with code 0 if found',
)
.option('-g, --gdb', 'If a GDB server should be started on 3333 or not', false)
.option('-p, --gdb-port', 'The port to start the gdb server on', 3333)
.option('-c, --circuit-python', 'If CircuitPython should be used instead of MicroPython', false)
.example('--image ./my-image.uf2')
.action(simulateMicropythonImage)
.parse(process.argv);
Loading