Open lynaghk opened 3 years ago
Hi,
I think what you want are "rtt down channels", for host to target communication. This currently isn't implemented in either defmt-rtt
or probe-run
, but I don't see why we shouldn't do it. rtt_target
support them, but this can't be used together with defmt-rtt
.
I forked probe-run
and have an RTT-based solution working for myself.
Not sure the best abstraction for general usage yet; once I have some firsthand experience I'll update this thread with ideas for discussion.
In the mean time, for anyone who has a similar need, here's what I did:
Cargo deps should be:
defmt = "0.2"
rtt-target = { version = "0.3", features = ["cortex-m"] }
rather than defmt-rtt
, which explicitly disallows downchannels.
You can basically copy/paste defmt-rtt impl and modify it to take a channel at initialization time.
(Or just use this crate, which does basically that: https://github.com/akiles/defmt-rtt-target)
Then initialize your RTT channels:
let channels = rtt_target::rtt_init! {
up: {
0: { // channel number
size: 1024 // buffer size in bytes
mode: NoBlockSkip // mode (optional, default: NoBlockSkip, see enum ChannelMode)
name: "defmt" // gotta call it this so that probe run logs it.
}
}
down: {
0: {
size: 64
name: "Controls"
}
}
};
log::init(channels.up.0);
Fork probe-run and copy/paste setup_logging_channel to open your downchannel. Do whatever app-specific logic you need in there. Here's mine, where I'm reading from stdin and sending app specific message structs:
fn setup_my_downchannel(rtt_buffer_address: u32, sess: Arc<Mutex<Session>>) -> anyhow::Result<()> {
let scan_region = ScanRegion::Exact(rtt_buffer_address);
match Rtt::attach_region(sess.clone(), &scan_region) {
Ok(mut rtt) => {
log::info!("Successfully attached my downchannel RTT");
let mut channel = rtt
.down_channels()
.take(0)
.ok_or_else(|| anyhow!("RTT down channel 0 not found"))?;
let mut buf = [0u8; 256];
std::thread::spawn(move || loop {
use interface::Msg;
if let Some(msg) = match read_line().unwrap().as_str() {
"start" => Some(Msg::Start),
"stop" => Some(Msg::Stop),
"status" => Some(Msg::Status),
"tick" => Some(Msg::Tick),
_ => None,
} {
channel.write_all(msg.serialize(&mut buf).unwrap()).unwrap();
}
});
}
Err(probe_rs_rtt::Error::ControlBlockNotFound) => {
log::trace!("Could not attach because the target's RTT control block isn't initialized (yet). retrying");
}
Err(e) => {
return Err(anyhow!(e));
}
}
Ok(())
}
I ran this setup in the same place probe_run sets up logging: https://github.com/knurling-rs/probe-run/blob/fd606d6f90b863190c2a0170bd7262150595727c/src/main.rs#L204
I ran into substantial (> 1 second) latency writing to the downchannel and realized this was due to lock contention with probe run trying to read log messages from the upchannel.
Adding std::thread::sleep_ms(1);
in this loop https://github.com/knurling-rs/probe-run/blob/fd606d6f90b863190c2a0170bd7262150595727c/src/main.rs#L230 is my YOLO fix =D
@lynaghk Good job!
Not sure the best abstraction for general usage yet
That's also what we discussed internally. Looking forward to your ideas!
Thanks for making
probe-run
and the related machinery (defmt
,rtt
), the dev experience is amazing compared to the alternatives =DThis is a design / "would this PR be welcome?" question rather than bug report (let me know if I should move it elsewhere): How should I build a laptop-to-microcontroller communication channel while retaining
probe-run
's logging?Say I'm prototyping a stepper motor; I'd like to:
probe-run
's logging, stacktrace printing, etc.In production one would use the microcontroller's USB or USART channel for app-specific messages. But for prototyping and one-offs, being able to send application data over the probe-run link would be very handy.
Presumably I could fork
probe-run
and add my own messaging logic, but is there a more composable solution that can be reused by the community? How can app-specific RTT communication channels coexist withprobe-run
's logging?