Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Panic reporting over USB example for Resberry Pi pico #851

Open
ritulahkar opened this issue Sep 16, 2024 · 4 comments
Open

Panic reporting over USB example for Resberry Pi pico #851

ritulahkar opened this issue Sep 16, 2024 · 4 comments

Comments

@ritulahkar
Copy link

ritulahkar commented Sep 16, 2024

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
Copy link
Member

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
Copy link
Author

ritulahkar commented Sep 16, 2024

@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
Copy link
Member

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
Copy link
Member

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

The gist of it is:

  • use panic_persist to get the message stored before a panic induced reset.
  • on startup, check for a possibly stored message with panic_persist::get_panic_message_bytes.
  • once the USB is configure (or even better a command sent on the serial port sent), print that message.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants