Skip to content
This repository has been archived by the owner on Jan 30, 2024. It is now read-only.

App-specific communication over probe-run's connection? #261

Open
lynaghk opened this issue Sep 14, 2021 · 3 comments
Open

App-specific communication over probe-run's connection? #261

lynaghk opened this issue Sep 14, 2021 · 3 comments
Labels
type: question A question that isn't answered by the docs

Comments

@lynaghk
Copy link

lynaghk commented Sep 14, 2021

Thanks for making probe-run and the related machinery (defmt, rtt), the dev experience is amazing compared to the alternatives =D

This 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:

  • send messages ("stop", "set velocity to 10", etc.) over an RTT channel from my laptop (from a Rust program responding to keyboard events or whatever)
  • receive these messages in device firmware
  • while retaining 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 with probe-run's logging?

@Urhengulas Urhengulas added this to Incoming in Issue Triage via automation Sep 14, 2021
@Urhengulas Urhengulas added the type: question A question that isn't answered by the docs label Sep 14, 2021
@Urhengulas
Copy link
Member

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.

@lynaghk
Copy link
Author

lynaghk commented Sep 22, 2021

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:

On the target

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);

On the host

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:

let mut logging_channel = if let Some(address) = elf.rtt_buffer_address() {

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

while !exit.load(Ordering::Relaxed) {
is my YOLO fix =D

@Urhengulas
Copy link
Member

@lynaghk Good job!

Not sure the best abstraction for general usage yet

That's also what we discussed internally. Looking forward to your ideas!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
type: question A question that isn't answered by the docs
Projects
No open projects
Development

No branches or pull requests

2 participants