Skip to content

Commit 863ba44

Browse files
committed
dual mcu demo
1 parent 0d18010 commit 863ba44

9 files changed

+2840
-0
lines changed

README.md

+10
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,16 @@ Other ways of creating LittleFS images can be found [here](https://github.com/wo
7474

7575
Currently, the filesystem is not writeable, as the SSI peripheral required for flash writing is not implemented yet. If you're interested in hacking, see the discussion in https://github.com/wokwi/rp2040js/issues/88 for a workaround.
7676

77+
### Dual RP2040 demo
78+
```
79+
npm install
80+
npm run start:dual-mcu
81+
```
82+
83+
This fires up two instances of rp2040js including some glue code that connects the GPIOs 2-10 in an open collector/pull up bus fashion.
84+
It will write a VCD trace of bus signals to the file `dual-mcu-bus-trace.vcd` including a virtual signal indicating conflicts on the bus.
85+
The demo programs on the two RP2040s pull a single line low each (MCU0: D0 then D1 then ... then D7, MCU1 in the opposite direction).
86+
7787
## Learn more
7888

7989
- [Live-coding stream playlist](https://www.youtube.com/playlist?list=PLLomdjsHtJTxT-vdJHwa3z62dFXZnzYBm)

demo/dual-mcu.ts

+120
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import * as fs from "fs";
2+
import { RP2040 } from "../src";
3+
import { GPIOPinState } from "../src/gpio-pin";
4+
import { bootromB1 } from "./bootrom";
5+
import { loadHex } from "./intelhex";
6+
7+
const hex1 = fs.readFileSync("demo/dual-mcu/dual-mcu-0.hex", "utf-8");
8+
const hex2 = fs.readFileSync("demo/dual-mcu/dual-mcu-1.hex", "utf-8");
9+
const mcu1 = new RP2040();
10+
const mcu2 = new RP2040();
11+
mcu1.loadBootrom(bootromB1);
12+
mcu2.loadBootrom(bootromB1);
13+
loadHex(hex1, mcu1.flash, 0x10000000);
14+
loadHex(hex2, mcu2.flash, 0x10000000);
15+
16+
mcu1.uart[0].onByte = (value) => {
17+
process.stdout.write(new Uint8Array([value]));
18+
};
19+
20+
mcu2.uart[0].onByte = (value) => {
21+
process.stdout.write(new Uint8Array([value]));
22+
};
23+
24+
// GPIOPinState: { Low, High, Input, InputPullUp, InputPullDown }
25+
26+
let pin_state: number[][] = [
27+
[0, 0, 0, 0, 0, 0, 0], // result value
28+
[3, 3, 3, 3, 3, 3, 3], // input from mcu1 (pullup initially)
29+
[3, 3, 3, 3, 3, 3, 3], // input from mcu2 (pullup initially)
30+
];
31+
let pin_gpio: number[] = [2, 3, 4, 5, 6, 7, 8, 9];
32+
let pin_label: string[] = ["d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7"];
33+
let vcd_file = fs.createWriteStream("dual-mcu-bus-trace.vcd", {});
34+
let last_conflict_cycle: number = -1;
35+
36+
// This listener connects the two MCUs and writes a VCD signal trace file.
37+
// This code assumes pullups enabled (open collector/pullup bus).
38+
function pinListener(mcu_id: number, pin: number) {
39+
return (state: GPIOPinState, oldState: GPIOPinState) => {
40+
pin_state[mcu_id + 1][pin] = state;
41+
let v: number =
42+
pin_state[0 + 1][pin] === 0 || pin_state[1 + 1][pin] === 0 ? 0 : 1;
43+
mcu1.gpio[pin_gpio[pin]].setInputValue(v == 1 ? true : false);
44+
mcu2.gpio[pin_gpio[pin]].setInputValue(v == 1 ? true : false);
45+
46+
// write signal to VCD file
47+
let pin_vcd_id = String.fromCharCode(pin + 34);
48+
if (pin_state[0][pin] !== v) {
49+
pin_state[0][pin] = v;
50+
vcd_file.write(`#${mcu1.core.cycles} ${v}${pin_vcd_id}\n`);
51+
}
52+
53+
// write conflict flag to VCD file
54+
let conflict: boolean =
55+
(pin_state[0 + 1][pin] === 0 && pin_state[1 + 1][pin] === 1) ||
56+
(pin_state[0 + 1][pin] === 1 && pin_state[1 + 1][pin] === 0);
57+
if (conflict)
58+
console.log(
59+
`Conflict on pin ${pin_label[pin]} at cycle ${mcu1.core.cycles} (${
60+
pin_state[0 + 1][pin]
61+
}/${pin_state[1 + 1][pin]})`
62+
);
63+
let have_new_conflict = conflict && last_conflict_cycle === -1;
64+
let conflict_recently_resolved = !conflict && last_conflict_cycle !== -1;
65+
if (
66+
conflict_recently_resolved &&
67+
mcu1.core.cycles === last_conflict_cycle
68+
) {
69+
// one mcu set conflict and other resolved in same cycle:
70+
// delay until next signal change so that the conflict signal is visible in VCD
71+
return;
72+
}
73+
let write_conflict_flag: boolean =
74+
have_new_conflict || conflict_recently_resolved;
75+
if (write_conflict_flag) {
76+
vcd_file.write(`#${mcu1.core.cycles} ${conflict ? 1 : 0}!\n`);
77+
}
78+
last_conflict_cycle = conflict ? mcu1.core.cycles : -1;
79+
};
80+
}
81+
82+
for (let i = 0; i < pin_label.length; i++) {
83+
mcu1.gpio[pin_gpio[i]].addListener(pinListener(0, i));
84+
mcu2.gpio[pin_gpio[i]].addListener(pinListener(1, i));
85+
}
86+
87+
mcu1.core.PC = 0x10000000;
88+
mcu2.core.PC = 0x10000000;
89+
90+
// write VCD file header
91+
vcd_file.write("$timescale 1ns $end\n");
92+
vcd_file.write("$scope module logic $end\n");
93+
vcd_file.write(`$var wire 1 ! bus_conflict $end\n`);
94+
for (let pin = 0; pin < pin_label.length; pin++) {
95+
let pin_vcd_id = String.fromCharCode(pin + 34);
96+
vcd_file.write(`$var wire 1 ${pin_vcd_id} ${pin_label[pin]} $end\n`);
97+
}
98+
vcd_file.write("$upscope $end\n");
99+
vcd_file.write("$enddefinitions $end\n");
100+
101+
function run_mcus() {
102+
let cycles_mcu2_behind = 0;
103+
for (let i = 0; i < 100000; i++) {
104+
if (mcu1.core.cycles % (1 << 25) === 0)
105+
console.log(`clock: ${mcu1.core.cycles / 125000000} secs`);
106+
// run mcu1 for one step, take note of how many cycles that took,
107+
// then step mcu2 until it caught up.
108+
let cycles = mcu1.core.cycles;
109+
mcu1.step();
110+
cycles_mcu2_behind += mcu1.core.cycles - cycles;
111+
while (cycles_mcu2_behind > 0) {
112+
cycles = mcu2.core.cycles;
113+
mcu2.step();
114+
cycles_mcu2_behind -= mcu2.core.cycles - cycles;
115+
}
116+
}
117+
setTimeout(() => run_mcus(), 0);
118+
}
119+
120+
run_mcus();

demo/dual-mcu/CMakeLists.txt

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
cmake_minimum_required(VERSION 3.12)
2+
include(pico_sdk_import.cmake)
3+
4+
project(dual-mcu C CXX ASM)
5+
set(CMAKE_C_STANDARD 11)
6+
set(CMAKE_CXX_STANDARD 17)
7+
8+
pico_sdk_init()
9+
10+
add_executable(dual-mcu-0
11+
dual-mcu.c
12+
)
13+
14+
add_executable(dual-mcu-1
15+
dual-mcu.c
16+
)
17+
18+
target_compile_options(dual-mcu-0 PRIVATE -Wall -DMCU0)
19+
target_compile_options(dual-mcu-1 PRIVATE -Wall -DMCU1)
20+
21+
target_link_libraries(dual-mcu-0
22+
hardware_pio
23+
pico_stdlib
24+
pico_util
25+
)
26+
27+
target_link_libraries(dual-mcu-1
28+
hardware_pio
29+
pico_stdlib
30+
pico_util
31+
)
32+
33+
pico_enable_stdio_usb(dual-mcu-0 0)
34+
pico_enable_stdio_uart(dual-mcu-0 1)
35+
pico_enable_stdio_usb(dual-mcu-1 0)
36+
pico_enable_stdio_uart(dual-mcu-1 1)
37+
38+
pico_generate_pio_header(dual-mcu-0 ${CMAKE_CURRENT_LIST_DIR}/dual-mcu.pio)
39+
pico_generate_pio_header(dual-mcu-1 ${CMAKE_CURRENT_LIST_DIR}/dual-mcu.pio)
40+
41+
pico_add_extra_outputs(dual-mcu-0)
42+
pico_add_extra_outputs(dual-mcu-1)

0 commit comments

Comments
 (0)