Skip to content

Commit 01f5f22

Browse files
committed
dual mcu demo
1 parent df95e5a commit 01f5f22

9 files changed

+2835
-0
lines changed

README.md

+10
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,16 @@ sudo umount fat12/ # unmount the filesystem
107107
While CircuitPython does not typically use a writeable filesystem, note that this functionality is unavailable (see MicroPython filesystem
108108
support section for more details).
109109

110+
### Dual RP2040 demo
111+
```
112+
npm install
113+
npm run start:dual-mcu
114+
```
115+
116+
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.
117+
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.
118+
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).
119+
110120
## Learn more
111121

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

demo/dual-mcu.ts

+115
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
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+
const 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+
const pin_gpio: number[] = [2, 3, 4, 5, 6, 7, 8, 9];
32+
const pin_label: string[] = ['d0', 'd1', 'd2', 'd3', 'd4', 'd5', 'd6', 'd7'];
33+
const 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+
const v: number = pin_state[0 + 1][pin] === 0 || pin_state[1 + 1][pin] === 0 ? 0 : 1;
42+
mcu1.gpio[pin_gpio[pin]].setInputValue(v == 1 ? true : false);
43+
mcu2.gpio[pin_gpio[pin]].setInputValue(v == 1 ? true : false);
44+
45+
// write signal to VCD file
46+
const pin_vcd_id = String.fromCharCode(pin + 34);
47+
if (pin_state[0][pin] !== v) {
48+
pin_state[0][pin] = v;
49+
vcd_file.write(`#${mcu1.core.cycles} ${v}${pin_vcd_id}\n`);
50+
}
51+
52+
// write conflict flag to VCD file
53+
const conflict: boolean =
54+
(pin_state[0 + 1][pin] === 0 && pin_state[1 + 1][pin] === 1) ||
55+
(pin_state[0 + 1][pin] === 1 && pin_state[1 + 1][pin] === 0);
56+
if (conflict)
57+
console.log(
58+
`Conflict on pin ${pin_label[pin]} at cycle ${mcu1.core.cycles} (${pin_state[0 + 1][pin]}/${
59+
pin_state[1 + 1][pin]
60+
})`
61+
);
62+
const have_new_conflict = conflict && last_conflict_cycle === -1;
63+
const conflict_recently_resolved = !conflict && last_conflict_cycle !== -1;
64+
if (conflict_recently_resolved && mcu1.core.cycles === last_conflict_cycle) {
65+
// one mcu set conflict and other resolved in same cycle:
66+
// delay until next signal change so that the conflict signal is visible in VCD
67+
return;
68+
}
69+
const write_conflict_flag: boolean = have_new_conflict || conflict_recently_resolved;
70+
if (write_conflict_flag) {
71+
vcd_file.write(`#${mcu1.core.cycles} ${conflict ? 1 : 0}!\n`);
72+
}
73+
last_conflict_cycle = conflict ? mcu1.core.cycles : -1;
74+
};
75+
}
76+
77+
for (let i = 0; i < pin_label.length; i++) {
78+
mcu1.gpio[pin_gpio[i]].addListener(pinListener(0, i));
79+
mcu2.gpio[pin_gpio[i]].addListener(pinListener(1, i));
80+
}
81+
82+
mcu1.core.PC = 0x10000000;
83+
mcu2.core.PC = 0x10000000;
84+
85+
// write VCD file header
86+
vcd_file.write('$timescale 1ns $end\n');
87+
vcd_file.write('$scope module logic $end\n');
88+
vcd_file.write(`$var wire 1 ! bus_conflict $end\n`);
89+
for (let pin = 0; pin < pin_label.length; pin++) {
90+
const pin_vcd_id = String.fromCharCode(pin + 34);
91+
vcd_file.write(`$var wire 1 ${pin_vcd_id} ${pin_label[pin]} $end\n`);
92+
}
93+
vcd_file.write('$upscope $end\n');
94+
vcd_file.write('$enddefinitions $end\n');
95+
96+
function run_mcus() {
97+
let cycles_mcu2_behind = 0;
98+
for (let i = 0; i < 100000; i++) {
99+
if (mcu1.core.cycles % (1 << 25) === 0)
100+
console.log(`clock: ${mcu1.core.cycles / 125000000} secs`);
101+
// run mcu1 for one step, take note of how many cycles that took,
102+
// then step mcu2 until it caught up.
103+
let cycles = mcu1.core.cycles;
104+
mcu1.step();
105+
cycles_mcu2_behind += mcu1.core.cycles - cycles;
106+
while (cycles_mcu2_behind > 0) {
107+
cycles = mcu2.core.cycles;
108+
mcu2.step();
109+
cycles_mcu2_behind -= mcu2.core.cycles - cycles;
110+
}
111+
}
112+
setTimeout(() => run_mcus(), 0);
113+
}
114+
115+
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)