Skip to content

Commit a9d04bb

Browse files
committed
dual mcu demo
1 parent 71e06fc commit a9d04bb

File tree

9 files changed

+2827
-0
lines changed

9 files changed

+2827
-0
lines changed

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,16 @@ Other ways of creating LittleFS images can be found [here](https://github.com/wo
5959

6060
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.
6161

62+
### Dual RP2040 demo
63+
```
64+
npm install
65+
npm run start:dual-mcu
66+
```
67+
68+
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.
69+
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.
70+
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).
71+
6272
## Learn more
6373

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

demo/dual-mcu.ts

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
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 = ((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+
let 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+
let conflict: boolean = ((pin_state[0+1][pin]===0)&&(pin_state[1+1][pin]===1))||((pin_state[0+1][pin]===1)&&(pin_state[1+1][pin]===0));
54+
if(conflict) console.log(`Conflict on pin ${pin_label[pin]} at cycle ${mcu1.core.cycles} (${pin_state[0+1][pin]}/${pin_state[1+1][pin]})`);
55+
let have_new_conflict = conflict&&(last_conflict_cycle === -1);
56+
let conflict_recently_resolved = (!conflict)&&(last_conflict_cycle !== -1);
57+
if(conflict_recently_resolved && (mcu1.core.cycles === last_conflict_cycle)) {
58+
// one mcu set conflict and other resolved in same cycle:
59+
// delay until next signal change so that the conflict signal is visible in VCD
60+
return;
61+
}
62+
let write_conflict_flag: boolean = have_new_conflict || conflict_recently_resolved;
63+
if(write_conflict_flag) {
64+
vcd_file.write(`#${mcu1.core.cycles} ${conflict?1:0}!\n`);
65+
}
66+
last_conflict_cycle = conflict ? mcu1.core.cycles : -1;
67+
};
68+
}
69+
70+
for(let i = 0; i < pin_label.length; i++) {
71+
mcu1.gpio[pin_gpio[i]].addListener(pinListener(0, i));
72+
mcu2.gpio[pin_gpio[i]].addListener(pinListener(1, i));
73+
}
74+
75+
mcu1.core.PC = 0x10000000;
76+
mcu2.core.PC = 0x10000000;
77+
78+
// write VCD file header
79+
vcd_file.write("$timescale 1ns $end\n");
80+
vcd_file.write("$scope module logic $end\n");
81+
vcd_file.write(`$var wire 1 ! bus_conflict $end\n`);
82+
for(let pin = 0; pin < pin_label.length; pin++) {
83+
let pin_vcd_id = String.fromCharCode(pin+34);
84+
vcd_file.write(`$var wire 1 ${pin_vcd_id} ${pin_label[pin]} $end\n`);
85+
}
86+
vcd_file.write("$upscope $end\n");
87+
vcd_file.write("$enddefinitions $end\n");
88+
89+
function run_mcus() {
90+
let cycles_mcu2_behind = 0;
91+
for (let i = 0; i < 100000; i++) {
92+
if((mcu1.core.cycles%(1<<25))===0) console.log(`clock: ${mcu1.core.cycles/125000000} secs`);
93+
// run mcu1 for one step, take note of how many cycles that took,
94+
// then step mcu2 until it caught up.
95+
let cycles = mcu1.core.cycles;
96+
mcu1.step();
97+
cycles_mcu2_behind += mcu1.core.cycles - cycles;
98+
while(cycles_mcu2_behind > 0) {
99+
cycles = mcu2.core.cycles;
100+
mcu2.step();
101+
cycles_mcu2_behind -= mcu2.core.cycles - cycles;
102+
}
103+
}
104+
setTimeout(() => run_mcus(), 0);
105+
}
106+
107+
run_mcus();

demo/dual-mcu/CMakeLists.txt

Lines changed: 42 additions & 0 deletions
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)