rp-rs / rp-hal

A Rust Embedded-HAL for the rp series microcontrollers
https://crates.io/crates/rp2040-hal
Apache License 2.0
1.46k stars 237 forks source link

Panic reporting over USB example for Resberry Pi pico #851

Open ritulahkar opened 2 months ago

ritulahkar commented 2 months ago

I was looking for an example which will guide me to report panics to the host PC over USB. I count not find one, so I wrote mine. I know it's not good as I am not a professional developer. Still like to post it here if somehow it helps someone. If it's good enough please add it to the examples after modifying if required. If possible add facility for unwinding and back-trace. I don't know why but I am not getting the message like out of bound.

`

        //! # Pico USB Serial Example
        //!
        //! Creates a USB Serial device on a Pico board, with the USB driver running in
        //! the main thread.
        //!
        //! This will create a USB Serial device echoing anything it receives. Incoming
        //! ASCII characters are converted to upercase, so you can tell it is working
        //! and not just local-echo!
        //!
        //! See the `Cargo.toml` file for Copyright and license details.

        #![no_std]
        #![no_main]

        // The macro for our start-up function
        use rp_pico::entry;

        // Ensure we halt the program on panic (if we don't mention this crate it won't
        // be linked)
        // use panic_halt as _;
        // Used to demonstrate writing formatted strings
        use core::fmt::Write;
        use heapless::String;

        // A shorter alias for the Peripheral Access Crate, which provides low-level
        // register access
        use rp_pico::hal::pac;

        // A shorter alias for the Hardware Abstraction Layer, which provides
        // higher-level drivers.
        use rp_pico::hal;

        // USB Device support
        use usb_device::{class_prelude::*, prelude::*};

        // USB Communications Class Device support
        use usbd_serial::SerialPort;

        #[entry]
        fn main() -> ! {
            // to excape the compilation checks and cause panic at run time.
            let mut n = 0;
            let mut index = 1;
            loop {
                n += 1;
                if n > 2000 {
                    let arr = [1, 2, 3, 4, 5];

                    let _ = arr[index]; // panic out of bound, idkw but the message not getting printted.
                    panic!("here we panic\n"); // this will be written as message, if panic is run like this.
                }
                index += 1;
            }
        }

        // Custom panic hanlder reporting panic to host

        #[panic_handler]
        fn panic(info: &core::panic::PanicInfo) -> ! {
            // Panic function for pico. It will report to PC repeatedly.
            let mut pac = unsafe { crate::pac::Peripherals::steal() };
            let mut watchdog = hal::Watchdog::new(pac.WATCHDOG);
            let clocks = hal::clocks::init_clocks_and_plls(
                rp_pico::XOSC_CRYSTAL_FREQ,
                pac.XOSC,
                pac.CLOCKS,
                pac.PLL_SYS,
                pac.PLL_USB,
                &mut pac.RESETS,
                &mut watchdog,
            )
            .ok()
            .unwrap();
            let usb_bus = UsbBusAllocator::new(hal::usb::UsbBus::new(
                pac.USBCTRL_REGS,
                pac.USBCTRL_DPRAM,
                clocks.usb_clock,
                true,
                &mut pac.RESETS,
            ));
            let mut serial: SerialPort<'_, rp2040_hal::usb::UsbBus> = SerialPort::new(&usb_bus);
            let mut usb_dev = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x16c0, 0x27dd))
                .strings(&[StringDescriptors::default()
                    .manufacturer("Fake company")
                    .product("Serial port")
                    .serial_number("TEST")])
                .unwrap()
                .device_class(2)
                .build();
            let msg = match info.message().as_str() {
                Some(m) => m,
                None => "None",
            };
            let mut file: String<400> = String::new();
            let mut line = String::<32>::new();
            let mut column = String::<32>::new();
            match info.location() {
                Some(s) => {
                    file.push_str(s.file()).unwrap();
                    let kjh = s.column();
                    let _ = write!(column, "{kjh}");
                    let kjh = s.line();
                    let _ = write!(line, "{kjh}");
                }
                None => {
                    file.push_str("").unwrap();
                }
            }
            let mut panicreport = String::<500>::new();
            panicreport.push_str("\n\n\nProgram paniced!!!").unwrap();
            panicreport.push_str("\n\nFile : ").unwrap();
            panicreport.push_str(&file).unwrap();
            panicreport.push_str(" Line : ").unwrap();
            panicreport.push_str(&line).unwrap();
            panicreport.push_str(" Column : ").unwrap();
            panicreport.push_str(&column).unwrap();
            panicreport.push_str("\n\nMessage : ").unwrap();
            panicreport.push_str(&msg).unwrap();
            loop {
                for _ in 1..100000 {
                    usb_dev.poll(&mut [&mut serial]);
                }
                let _ = serial.write(&panicreport.as_bytes());
                for _ in 1..100000 {
                    usb_dev.poll(&mut [&mut serial]);
                }
            }
        }

`

thejpster commented 2 months ago

Your example defers all of the hardware initialisation to the panic handler, which means most of the code is running at the boot-up clock speed without the PLLs initialised. Users also won't be able to use any hardware within the main thread because creating the mutable references within the panic handler would then be UB.

In general, doing something as complex as talking over USB is difficult to do in a panic handler, because a panic indicates an invalid state has been detected and yet the USB driver has to assume that the system state is valid in order to function correctly. It's probably a better idea to write the panic message to a non-initialised piece of RAM, reboot, and pick it up from RAM after the reboot. The panic-persist crate can help with this.

ritulahkar commented 2 months ago

@thejpster Can you please provide an example code how to do that with, if possible, ability to backtrace and unwind the panic over USB? It will be very helpful in the development process.

Another request for an example of how to keep the USB connection open when the thread is doing something else like some calculation. Like I should be able to write or read the serial port when I want without polling it all the time, like we can do in C++ in Arduino IDE. Now the computer thinks the connection is dead if I don't poll all the time and does not read the data if I send without polling. Thank you.

thejpster commented 2 months ago

I don't have capacity at the moment to write bespoke examples free of charge. If you require support, there are several Rust consultancies that can provide this on a commercial basis.

If you wish to run the USB at the same time as running application logic, I recommend looking at ether Embassy or RTIC. I'm sure Embassy has good USB examples for the RP2040.

ithinuel commented 2 months ago

My keyboard firmware optionnaly does that (feature gated). It is not a minimal example though.

The gist of it is: