David-OConnor / stm32-hal

This library provides access to STM32 peripherals in Rust.
MIT License
172 stars 45 forks source link

bxcan driver not working properly #85

Closed gworkman closed 1 year ago

gworkman commented 1 year ago

Hi! This has been a great library, and I've been loving the ability to use rust over the CubeMX C projects. I have encountered one bug though, and wanted to see if I could get some additional eyes into what I'm doing wrong.

First of all, I'd like to bring it to your attention that the bxcan driver is not gated properly - there are two different names for the feature. One is can_bx (defined in Cargo.toml) and the other one is bx_can (defined in can.rs). I fixed this issue, so I will submit a quick PR to showcase the issue and the fix.

Second, I am using an STM32L431 chip. For this chip, there are 14 filter banks for the CAN filters. However, the following line in can.rs excludes the L4 family from having a definition for the NUM_FILTER_BANKS const (see below). In the PR I will link to this issue, I set the feature gating to just the L4 family. I didn't do any other research on the other families, so maybe that is something we would need to address before merging this into a release.

        unsafe impl bxcan::FilterOwner for Can {
            #[cfg(any(feature = "f3", feature = "f4"))]
            const NUM_FILTER_BANKS: u8 = 28;
            #[cfg(not(any(feature = "f4", feature = "l4")))]
            const NUM_FILTER_BANKS: u8 = 14;
        }

Finally, with these changes, I can compile the library with the features enabled: stm32-hal2 = { path = "../stm32-hal", version = "^1.6.3", features = ["l4x1", "l4rt", "can_bx"]}

However, once trying to use the CAN peripheral, I get stuck. The can.transmit(&frame) function works on the first call, but after that I get errors. It looks like the transmit is not working at all for some reason. The message might be getting queued, but the FIFO seems to be busy. The error is as follows:

ERROR panicked at 'calledResult::unwrap()on anErrvalue: WouldBlock', src/main.rs:149:35

When wrapping the call to transmit in the nb::block! macro, it simply hangs indefinitely.

All of this leads me to believe that my device config must be wrong, but I just can't figure out where. Some additional eyes would be good for this issue. The full code for the example is below

#![no_std]
#![no_main]
#![feature(type_alias_impl_trait)]

use defmt_rtt as _;
use panic_probe as _;

#[rtic::app(device=stm32_hal2::pac, dispatchers=[TIM3])]
mod app {
    use super::*;
    use bxcan::{filter::Mask32, Fifo, Frame, StandardId};
    use cortex_m::delay::Delay;
    use stm32_hal2::{
        can::Can,
        clocks::Clocks,
        pac::TIM2,
        timer::{Timer, TimerInterrupt},
    };

    #[shared]
    struct Shared {
    }

    #[local]
    struct Local {
        tim: Timer<TIM2>,
        can: bxcan::Can<Can>,
    }

    #[init]
    fn init(cx: init::Context) -> (Shared, Local) {
        defmt::println!("init");

        let clock_cfg = Clocks::default();
        clock_cfg.setup().unwrap();

        let mut sample_timer = Timer::new_tim2(
            cx.device.TIM2,
            1.0,
            stm32_hal2::timer::TimerConfig {
                ..Default::default()
            },
            &clock_cfg,
        );

        let mut led = Pin::new(Port::B, 7, PinMode::Output);
        led.set_high();

        let mut can = {
            let mut can_rx = Pin::new(Port::A, 11, PinMode::Alt(9));
            let mut can_tx = Pin::new(Port::A, 12, PinMode::Alt(9));

            can_rx.output_type(OutputType::PushPull);
            can_tx.output_type(OutputType::PushPull);
            let can = Can::new(cx.device.CAN1);

            bxcan::Can::builder(can)
                // APB1 (PCLK1): 80MHz, Bit rate: 1000kBit/s, Sample Point 87.5%
                // Value was calculated with http://www.bittiming.can-wiki.info/
                .set_bit_timing(0x001c0009)
                .enable()
        };

        sample_timer.enable();
        sample_timer.enable_interrupt(TimerInterrupt::Update);

        (
            Shared { encoder },
            Local {
                tim: sample_timer,
                can,
            },
        )
    }

    // Optional idle, can be removed if not needed.
    #[idle]
    fn idle(_: idle::Context) -> ! {
        defmt::println!("idle");

        loop {
            continue;
        }
    }

    #[task(binds=TIM2, local = [tim, can])]
    fn task1(mut cx: task1::Context) {
        let tim = cx.local.tim;
        let can = cx.local.can;
        tim.clear_interrupt(TimerInterrupt::Update);

        let mut test: [u8; 8] = [0; 8];
        let id: u16 = 0x500;

        test[0] = 72;
        test[1] = 1;
        test[2] = 2;
        test[3] = 3;
        test[4] = 4;
        test[5] = 5;
        test[6] = 6;
        test[7] = 7;
        let test_frame = Frame::new_data(StandardId::new(id).unwrap(), test);
        can.transmit(&test_frame).unwrap(); // errors are here

        defmt::println!("Hello from task1!");
    }

    #[defmt::panic_handler]
    fn panic() -> ! {
        cortex_m::asm::udf()
    }
}

Cargo.toml:

[package]
authors = ["redacted"]
name = "redacted"
edition = "2021"
version = "0.1.0"

[dependencies]
defmt = "0.3.2"
defmt-rtt = "0.4.0"
crc_all = "0.2.2"

panic-probe = { version = "0.3.0", features = ["print-defmt"] }

cortex-m = {version = "^0.7.7", features = ["critical-section-single-core"]}
cortex-m-rt = "0.7.3"
rtic = { version = "2.0.0-alpha.1", features = [ "thumbv7-backend" ] }
# local library which applies the patches specified for compiling with CAN support
stm32-hal2 = { path = "../stm32-hal", version = "^1.6.3", features = ["l4x1", "l4rt", "can_bx"]}
rtic-monotonics = { version = "1.0.0-alpha.2", features = [ "cortex-m-systick" ]}
bxcan = "0.7.0"
nb = "1.1.0"

# cargo build/run
[profile.dev]
codegen-units = 1
debug = 2
debug-assertions = true # <-
incremental = false
opt-level = 3 # <-
overflow-checks = true # <-

# cargo test
[profile.test]
codegen-units = 1
debug = 2
debug-assertions = true # <-
incremental = false
opt-level = 3 # <-
overflow-checks = true # <-

# cargo build/run --release
[profile.release]
codegen-units = 1
debug = 2
debug-assertions = false # <-
incremental = false
lto = 'fat'
opt-level = 3 # <-
overflow-checks = false # <-

# cargo test --release
[profile.bench]
codegen-units = 1
debug = 2
debug-assertions = false # <-
incremental = false
lto = 'fat'
opt-level = 3 # <-
overflow-checks = false # <-
David-OConnor commented 1 year ago

Thanks for the L4 Fix!

I use CAN with this lib regularly, but it's on G4 and H7 that use the (completely different) FD CAN periph and support library. So, I haven't tested with BX CAN; I have no immediate insight into why it's not working.

Have you tried it in loopback mode (Assuming BX CAN has that)? That will help you narrow it down to CAN periph config vice transceiver or GPIO.

Also, try printing the CAN status register contents; see what (error etc) flags are set.

David-OConnor commented 1 year ago

Another thing to investigate is if you can receive CAN messages from other devices.

gworkman commented 1 year ago

Thanks! I just tried the loopback mode. It helped to step away from it for a day, it was definitely a user issue. I didn't have any other devices hooked up on the CAN bus, so of course it couldn't send any packets. Loopback works, and it seems to work now!

Thanks :)