Browse Source

Support for CAN peripherals with the `bxcan` crate (#293)

* Support for CAN peripherals with the `bxcan` crate

* can: Update loopback example with masked filters

* can: Rename `rtfm` to `rtic` in loopback example

* can: Use v0.3.0 of the `bxcan` crate

* can: Fix warnings in examples

* can: Update to bxcan v0.4.0

* Add CHANGELOG entry

Co-authored-by: TheZoq2 <frans.skarman@protonmail.com>
Timo Kröger 3 years ago
parent
commit
0f367e600f
8 changed files with 611 additions and 3 deletions
  1. 1 0
      CHANGELOG.md
  2. 18 3
      Cargo.toml
  3. 90 0
      examples/can-echo.rs
  4. 117 0
      examples/can-loopback.rs
  5. 240 0
      examples/can-rtic.rs
  6. 141 0
      src/can.rs
  7. 2 0
      src/lib.rs
  8. 2 0
      src/rcc.rs

+ 1 - 0
CHANGELOG.md

@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 
 - Support for OpenDrain pin configuration on SPI CLK and MOSI pins
 - LSB/MSB bit format selection for `SPI`
+- Support for CAN peripherals with the `bxcan` crate
 
 ### Fixed
 - Fix > 2 byte i2c reads

+ 18 - 3
Cargo.toml

@@ -21,6 +21,7 @@ nb = "0.1.2"
 cortex-m-rt = "0.6.8"
 stm32f1 = "0.11.0"
 embedded-dma = "0.1.2"
+bxcan = "0.4.0"
 
 [dependencies.void]
 default-features = false
@@ -45,7 +46,7 @@ panic-semihosting = "0.5.2"
 panic-itm = "0.4.1"
 cortex-m-rtic = "0.5"
 cortex-m-semihosting = "0.3.3"
-heapless = "0.4.3"
+heapless = "0.5.6"
 m = "0.1.1"
 mfrc522 = "0.2.0"
 serde_derive = "1.0.90"
@@ -78,7 +79,7 @@ doc = []
 rt = ["stm32f1/rt"]
 stm32f100 = ["stm32f1/stm32f100", "device-selected"]
 stm32f101 = ["stm32f1/stm32f101", "device-selected"]
-stm32f103 = ["stm32f1/stm32f103", "device-selected"]
+stm32f103 = ["stm32f1/stm32f103", "device-selected", "has-can"]
 stm32f105 = ["stm32f1/stm32f107", "device-selected", "connectivity"]
 stm32f107 = ["stm32f1/stm32f107", "device-selected", "connectivity"]
 
@@ -89,7 +90,9 @@ high = ["medium"]
 # Devices with 768 Kb ROM or more
 xl = ["high"]
 # Connectivity line devices (`stm32f105xx` and `stm32f107xx`)
-connectivity = ["medium"]
+connectivity = ["medium", "has-can"]
+# Devices with CAN interface
+has-can = []
 
 [profile.dev]
 incremental = false
@@ -131,3 +134,15 @@ required-features = ["rt", "medium"]
 [[example]]
 name = "exti"
 required-features = ["rt"]
+
+[[example]]
+name = "can-echo"
+required-features = ["has-can"]
+
+[[example]]
+name = "can-loopback"
+required-features = ["has-can"]
+
+[[example]]
+name = "can-rtic"
+required-features = ["has-can", "rt"]

+ 90 - 0
examples/can-echo.rs

@@ -0,0 +1,90 @@
+//! Simple CAN example.
+//! Requires a transceiver connected to PA11, PA12 (CAN1) or PB5 PB6 (CAN2).
+
+#![no_main]
+#![no_std]
+
+use panic_halt as _;
+
+use bxcan::filter::Mask32;
+use cortex_m_rt::entry;
+use nb::block;
+use stm32f1xx_hal::{can::Can, pac, prelude::*};
+
+#[entry]
+fn main() -> ! {
+    let dp = pac::Peripherals::take().unwrap();
+
+    let mut flash = dp.FLASH.constrain();
+    let mut rcc = dp.RCC.constrain();
+
+    // To meet CAN clock accuracy requirements an external crystal or ceramic
+    // resonator must be used. The blue pill has a 8MHz external crystal.
+    // Other boards might have a crystal with another frequency or none at all.
+    rcc.cfgr.use_hse(8.mhz()).freeze(&mut flash.acr);
+
+    let mut afio = dp.AFIO.constrain(&mut rcc.apb2);
+
+    let mut can1 = {
+        #[cfg(not(feature = "connectivity"))]
+        let can = Can::new(dp.CAN1, &mut rcc.apb1, dp.USB);
+        #[cfg(feature = "connectivity")]
+        let can = Can::new(dp.CAN1, &mut rcc.apb1);
+
+        let mut gpioa = dp.GPIOA.split(&mut rcc.apb2);
+        let rx = gpioa.pa11.into_floating_input(&mut gpioa.crh);
+        let tx = gpioa.pa12.into_alternate_push_pull(&mut gpioa.crh);
+        can.assign_pins((tx, rx), &mut afio.mapr);
+
+        bxcan::Can::new(can)
+    };
+
+    // APB1 (PCLK1): 8MHz, Bit rate: 125kBit/s, Sample Point 87.5%
+    // Value was calculated with http://www.bittiming.can-wiki.info/
+    can1.modify_config().set_bit_timing(0x001c_0003);
+
+    // Configure filters so that can frames can be received.
+    let mut filters = can1.modify_filters();
+    filters.enable_bank(0, Mask32::accept_all());
+
+    #[cfg(feature = "connectivity")]
+    let _can2 = {
+        let can = Can::new(dp.CAN2, &mut rcc.apb1);
+
+        let mut gpiob = dp.GPIOB.split(&mut rcc.apb2);
+        let rx = gpiob.pb5.into_floating_input(&mut gpiob.crl);
+        let tx = gpiob.pb6.into_alternate_push_pull(&mut gpiob.crl);
+        can.assign_pins((tx, rx), &mut afio.mapr);
+
+        let mut can2 = bxcan::Can::new(can);
+
+        // APB1 (PCLK1): 8MHz, Bit rate: 125kBit/s, Sample Point 87.5%
+        // Value was calculated with http://www.bittiming.can-wiki.info/
+        can2.modify_config().set_bit_timing(0x001c_0003);
+
+        // A total of 28 filters are shared between the two CAN instances.
+        // Split them equally between CAN1 and CAN2.
+        let mut slave_filters = filters.set_split(14).slave_filters();
+        slave_filters.enable_bank(14, Mask32::accept_all());
+        can2
+    };
+
+    // Drop filters to leave filter configuraiton mode.
+    drop(filters);
+
+    // Select the interface.
+    let mut can = can1;
+    //let mut can = _can2;
+
+    // Split the peripheral into transmitter and receiver parts.
+    block!(can.enable()).unwrap();
+
+    // Echo back received packages in sequence.
+    // See the `can-rtfm` example for an echo implementation that adheres to
+    // correct frame ordering based on the transfer id.
+    loop {
+        if let Ok(frame) = block!(can.receive()) {
+            block!(can.transmit(&frame)).unwrap();
+        }
+    }
+}

+ 117 - 0
examples/can-loopback.rs

@@ -0,0 +1,117 @@
+//! Showcases advanced CAN filter capabilities.
+//! Does not require additional transceiver hardware.
+
+#![no_main]
+#![no_std]
+
+use bxcan::{
+    filter::{ListEntry16, ListEntry32, Mask16},
+    ExtendedId, Frame, StandardId,
+};
+use panic_halt as _;
+
+use cortex_m_rt::entry;
+use embedded_hal::digital::v2::OutputPin;
+use nb::block;
+use stm32f1xx_hal::{can::Can, pac, prelude::*};
+
+#[entry]
+fn main() -> ! {
+    let dp = pac::Peripherals::take().unwrap();
+
+    let mut flash = dp.FLASH.constrain();
+    let mut rcc = dp.RCC.constrain();
+
+    // To meet CAN clock accuracy requirements, an external crystal or ceramic
+    // resonator must be used.
+    rcc.cfgr.use_hse(8.mhz()).freeze(&mut flash.acr);
+
+    #[cfg(not(feature = "connectivity"))]
+    let can = Can::new(dp.CAN1, &mut rcc.apb1, dp.USB);
+
+    #[cfg(feature = "connectivity")]
+    let can = Can::new(dp.CAN1, &mut rcc.apb1);
+
+    let mut can = bxcan::Can::new(can);
+
+    // Use loopback mode: No pins need to be assigned to peripheral.
+    // APB1 (PCLK1): 8MHz, Bit rate: 500Bit/s, Sample Point 87.5%
+    // Value was calculated with http://www.bittiming.can-wiki.info/
+    can.modify_config()
+        .set_bit_timing(0x001c_0000)
+        .set_loopback(true)
+        .set_silent(true);
+
+    let mut filters = can.modify_filters();
+    assert!(filters.num_banks() > 3);
+
+    // The order of the added filters is important: it must match configuration
+    // of the `split_filters_advanced()` method.
+
+    // 2x 11bit id + mask filter bank: Matches 0, 1, 2
+    // TODO: Make this accept also ID 2
+    filters.enable_bank(
+        0,
+        [
+            // accepts 0 and 1
+            Mask16::frames_with_std_id(StandardId::new(0).unwrap(), StandardId::new(1).unwrap()),
+            // accepts 0 and 2
+            Mask16::frames_with_std_id(StandardId::new(0).unwrap(), StandardId::new(2).unwrap()),
+        ],
+    );
+
+    // 2x 29bit id filter bank: Matches 4, 5
+    filters.enable_bank(
+        1,
+        [
+            ListEntry32::data_frames_with_id(ExtendedId::new(4).unwrap()),
+            ListEntry32::data_frames_with_id(ExtendedId::new(5).unwrap()),
+        ],
+    );
+
+    // 4x 11bit id filter bank: Matches 8, 9, 10, 11
+    filters.enable_bank(
+        2,
+        [
+            ListEntry16::data_frames_with_id(StandardId::new(8).unwrap()),
+            ListEntry16::data_frames_with_id(StandardId::new(9).unwrap()),
+            ListEntry16::data_frames_with_id(StandardId::new(10).unwrap()),
+            ListEntry16::data_frames_with_id(StandardId::new(11).unwrap()),
+        ],
+    );
+
+    // Enable filters.
+    drop(filters);
+
+    // Sync to the bus and start normal operation.
+    block!(can.enable()).ok();
+
+    // Some messages shall pass the filters.
+    for &id in &[0, 1, 2, 8, 9, 10, 11] {
+        let frame_tx = Frame::new_data(StandardId::new(id).unwrap(), [id as u8]);
+        block!(can.transmit(&frame_tx)).unwrap();
+        let frame_rx = block!(can.receive()).unwrap();
+        assert_eq!(frame_tx, frame_rx);
+    }
+    for &id in &[4, 5] {
+        let frame_tx = Frame::new_data(ExtendedId::new(id).unwrap(), [id as u8]);
+        block!(can.transmit(&frame_tx)).unwrap();
+        let frame_rx = block!(can.receive()).unwrap();
+        assert_eq!(frame_tx, frame_rx);
+    }
+
+    // Some messages shall not be received.
+    for &id in &[3, 6, 7, 12] {
+        let frame_tx = Frame::new_data(ExtendedId::new(id).unwrap(), [id as u8]);
+        block!(can.transmit(&frame_tx)).unwrap();
+        while !can.is_transmitter_idle() {}
+
+        assert!(can.receive().is_err());
+    }
+
+    let mut gpiob = dp.GPIOB.split(&mut rcc.apb2);
+    let mut led = gpiob.pb9.into_push_pull_output(&mut gpiob.crh);
+    led.set_high().unwrap();
+
+    loop {}
+}

+ 240 - 0
examples/can-rtic.rs

@@ -0,0 +1,240 @@
+//! Interrupt driven CAN transmitter with RTIC.
+//!
+//! CAN frames are allocated from a static memory pool and stored in a priority
+//! queue (min heap) for transmisison. To start transmission the CAN TX
+//! interrupt has to be triggered manually once. With each successful
+//! transmission the interrupt is reentered and more data is fetched from the
+//! queue.
+//! Received frames are simply echoed back. In contrast to the naive `can-echo`
+//! example all messages are also correctly prioritized by the transmit queue.
+
+#![no_main]
+#![no_std]
+
+use core::cmp::Ordering;
+
+use bxcan::{filter::Mask32, ExtendedId, Frame, Interrupts, Rx, StandardId, Tx};
+use heapless::{
+    binary_heap::{BinaryHeap, Max},
+    consts::*,
+};
+use nb::block;
+use panic_halt as _;
+use rtic::app;
+use stm32f1xx_hal::{
+    can::Can,
+    pac::{Interrupt, CAN1},
+    prelude::*,
+};
+
+#[derive(Debug)]
+pub struct PriorityFrame(Frame);
+
+/// Ordering is based on the Identifier and frame type (data vs. remote) and can be used to sort
+/// frames by priority.
+impl Ord for PriorityFrame {
+    fn cmp(&self, other: &Self) -> Ordering {
+        self.0.priority().cmp(&other.0.priority())
+    }
+}
+
+impl PartialOrd for PriorityFrame {
+    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+impl PartialEq for PriorityFrame {
+    fn eq(&self, other: &Self) -> bool {
+        self.cmp(other) == Ordering::Equal
+    }
+}
+
+impl Eq for PriorityFrame {}
+
+fn enqueue_frame(queue: &mut BinaryHeap<PriorityFrame, U16, Max>, frame: Frame) {
+    queue.push(PriorityFrame(frame)).unwrap();
+    rtic::pend(Interrupt::USB_HP_CAN_TX);
+}
+
+#[app(device = stm32f1xx_hal::pac, peripherals = true)]
+const APP: () = {
+    struct Resources {
+        can_tx: Tx<Can<CAN1>>,
+        can_tx_queue: BinaryHeap<PriorityFrame, U16, Max>,
+        tx_count: usize,
+        can_rx: Rx<Can<CAN1>>,
+    }
+
+    #[init]
+    fn init(cx: init::Context) -> init::LateResources {
+        let mut flash = cx.device.FLASH.constrain();
+        let mut rcc = cx.device.RCC.constrain();
+
+        let _clocks = rcc
+            .cfgr
+            .use_hse(8.mhz())
+            .sysclk(64.mhz())
+            .hclk(64.mhz())
+            .pclk1(16.mhz())
+            .pclk2(64.mhz())
+            .freeze(&mut flash.acr);
+
+        #[cfg(not(feature = "connectivity"))]
+        let can = Can::new(cx.device.CAN1, &mut rcc.apb1, cx.device.USB);
+
+        #[cfg(feature = "connectivity")]
+        let can = Can::new(cx.device.CAN1, &mut rcc.apb1);
+
+        // Select pins for CAN1.
+        let mut gpioa = cx.device.GPIOA.split(&mut rcc.apb2);
+        let can_rx_pin = gpioa.pa11.into_floating_input(&mut gpioa.crh);
+        let can_tx_pin = gpioa.pa12.into_alternate_push_pull(&mut gpioa.crh);
+        let mut afio = cx.device.AFIO.constrain(&mut rcc.apb2);
+        can.assign_pins((can_tx_pin, can_rx_pin), &mut afio.mapr);
+
+        let mut can = bxcan::Can::new(can);
+
+        // APB1 (PCLK1): 16MHz, Bit rate: 1000kBit/s, Sample Point 87.5%
+        // Value was calculated with http://www.bittiming.can-wiki.info/
+        can.modify_config().set_bit_timing(0x001c_0000);
+
+        can.modify_filters().enable_bank(0, Mask32::accept_all());
+
+        // Sync to the bus and start normal operation.
+        can.enable_interrupts(
+            Interrupts::TRANSMIT_MAILBOX_EMPTY | Interrupts::FIFO0_MESSAGE_PENDING,
+        );
+        block!(can.enable()).unwrap();
+
+        let (can_tx, can_rx) = can.split();
+
+        let can_tx_queue = BinaryHeap::new();
+
+        init::LateResources {
+            can_tx,
+            can_tx_queue,
+            tx_count: 0,
+            can_rx,
+        }
+    }
+
+    #[idle(resources = [can_tx_queue, tx_count])]
+    fn idle(mut cx: idle::Context) -> ! {
+        let mut tx_queue = cx.resources.can_tx_queue;
+
+        // Enqueue some messages. Higher ID means lower priority.
+        tx_queue.lock(|mut tx_queue| {
+            enqueue_frame(
+                &mut tx_queue,
+                Frame::new_data(StandardId::new(9).unwrap(), []),
+            );
+            enqueue_frame(
+                &mut tx_queue,
+                Frame::new_data(ExtendedId::new(9).unwrap(), []),
+            );
+
+            enqueue_frame(
+                &mut tx_queue,
+                Frame::new_data(StandardId::new(8).unwrap(), []),
+            );
+            enqueue_frame(
+                &mut tx_queue,
+                Frame::new_data(ExtendedId::new(8).unwrap(), []),
+            );
+
+            enqueue_frame(
+                &mut tx_queue,
+                Frame::new_data(StandardId::new(0x7FF).unwrap(), []),
+            );
+            enqueue_frame(
+                &mut tx_queue,
+                Frame::new_data(ExtendedId::new(0x1FFF_FFFF).unwrap(), []),
+            );
+        });
+
+        // Add some higher priority messages when 3 messages have been sent.
+        loop {
+            let tx_count = cx.resources.tx_count.lock(|tx_count| *tx_count);
+
+            if tx_count >= 3 {
+                tx_queue.lock(|mut tx_queue| {
+                    enqueue_frame(
+                        &mut tx_queue,
+                        Frame::new_data(StandardId::new(3).unwrap(), []),
+                    );
+                    enqueue_frame(
+                        &mut tx_queue,
+                        Frame::new_data(StandardId::new(2).unwrap(), []),
+                    );
+                    enqueue_frame(
+                        &mut tx_queue,
+                        Frame::new_data(StandardId::new(1).unwrap(), []),
+                    );
+                });
+                break;
+            }
+        }
+
+        // Expected bus traffic:
+        //
+        // 1. ID: 0x00000008  <- proper reordering happens
+        // 2. ID: 0x00000009
+        // 3. ID: 0x008
+        // 4. ID: 0x001       <- higher priority messages injected correctly
+        // 5. ID: 0x002
+        // 6. ID: 0x003
+        // 7. ID: 0x009
+        // 8. ID: 0x7FF
+        // 9. ID: 0x1FFFFFFF
+        //
+        // The output can look different if there are other nodes on bus the sending messages.
+
+        loop {
+            cortex_m::asm::nop();
+        }
+    }
+
+    // This ISR is triggered by each finished frame transmission.
+    #[task(binds = USB_HP_CAN_TX, resources = [can_tx, can_tx_queue, tx_count])]
+    fn can_tx(cx: can_tx::Context) {
+        let tx = cx.resources.can_tx;
+        let tx_queue = cx.resources.can_tx_queue;
+
+        tx.clear_interrupt_flags();
+
+        // There is now a free mailbox. Try to transmit pending frames until either
+        // the queue is empty or transmission would block the execution of this ISR.
+        while let Some(frame) = tx_queue.peek() {
+            match tx.transmit(&frame.0) {
+                Ok(None) => {
+                    // Frame was successfully placed into a transmit buffer.
+                    tx_queue.pop();
+                    *cx.resources.tx_count += 1;
+                }
+                Ok(Some(pending_frame)) => {
+                    // A lower priority frame was replaced with our high priority frame.
+                    // Put the low priority frame back in the transmit queue.
+                    tx_queue.pop();
+                    enqueue_frame(tx_queue, pending_frame);
+                }
+                Err(nb::Error::WouldBlock) => break,
+                Err(_) => unreachable!(),
+            }
+        }
+    }
+
+    #[task(binds = USB_LP_CAN_RX0, resources = [can_rx, can_tx_queue])]
+    fn can_rx0(cx: can_rx0::Context) {
+        // Echo back received packages with correct priority ordering.
+        loop {
+            match cx.resources.can_rx.receive() {
+                Ok(frame) => {
+                    enqueue_frame(cx.resources.can_tx_queue, frame);
+                }
+                Err(nb::Error::WouldBlock) => break,
+                Err(nb::Error::Other(_)) => {} // Ignore overrun errors.
+            }
+        }
+    }
+};

+ 141 - 0
src/can.rs

@@ -0,0 +1,141 @@
+//! # Controller Area Network (CAN) Interface
+//!
+//! ## Alternate function remapping
+//!
+//! TX: Alternate Push-Pull Output
+//! RX: Input Floating Input
+//!
+//! ### CAN1
+//!
+//! | Function | NoRemap | Remap |
+//! |----------|---------|-------|
+//! | TX       | PA12    | PB9   |
+//! | RX       | PA11    | PB8   |
+//!
+//! ### CAN2
+//!
+//! | Function | NoRemap | Remap |
+//! |----------|---------|-------|
+//! | TX       | PB6     | PB13  |
+//! | RX       | PB5     | PB12  |
+
+use crate::afio::MAPR;
+#[cfg(feature = "connectivity")]
+use crate::gpio::gpiob::{PB12, PB13, PB5, PB6};
+use crate::gpio::{
+    gpioa::{PA11, PA12},
+    gpiob::{PB8, PB9},
+    Alternate, Floating, Input, PushPull,
+};
+use crate::pac::CAN1;
+#[cfg(feature = "connectivity")]
+use crate::pac::CAN2;
+#[cfg(not(feature = "connectivity"))]
+use crate::pac::USB;
+use crate::rcc::APB1;
+
+mod sealed {
+    pub trait Sealed {}
+}
+
+pub trait Pins: sealed::Sealed {
+    type Instance;
+    fn remap(mapr: &mut MAPR);
+}
+
+impl sealed::Sealed for (PA12<Alternate<PushPull>>, PA11<Input<Floating>>) {}
+impl Pins for (PA12<Alternate<PushPull>>, PA11<Input<Floating>>) {
+    type Instance = CAN1;
+
+    fn remap(mapr: &mut MAPR) {
+        #[cfg(not(feature = "connectivity"))]
+        mapr.modify_mapr(|_, w| unsafe { w.can_remap().bits(0) });
+        #[cfg(feature = "connectivity")]
+        mapr.modify_mapr(|_, w| unsafe { w.can1_remap().bits(0) });
+    }
+}
+
+impl sealed::Sealed for (PB9<Alternate<PushPull>>, PB8<Input<Floating>>) {}
+impl Pins for (PB9<Alternate<PushPull>>, PB8<Input<Floating>>) {
+    type Instance = CAN1;
+
+    fn remap(mapr: &mut MAPR) {
+        #[cfg(not(feature = "connectivity"))]
+        mapr.modify_mapr(|_, w| unsafe { w.can_remap().bits(0b10) });
+        #[cfg(feature = "connectivity")]
+        mapr.modify_mapr(|_, w| unsafe { w.can1_remap().bits(0b10) });
+    }
+}
+
+#[cfg(feature = "connectivity")]
+impl sealed::Sealed for (PB13<Alternate<PushPull>>, PB12<Input<Floating>>) {}
+#[cfg(feature = "connectivity")]
+impl Pins for (PB13<Alternate<PushPull>>, PB12<Input<Floating>>) {
+    type Instance = CAN2;
+
+    fn remap(mapr: &mut MAPR) {
+        mapr.modify_mapr(|_, w| w.can2_remap().clear_bit());
+    }
+}
+
+#[cfg(feature = "connectivity")]
+impl sealed::Sealed for (PB6<Alternate<PushPull>>, PB5<Input<Floating>>) {}
+#[cfg(feature = "connectivity")]
+impl Pins for (PB6<Alternate<PushPull>>, PB5<Input<Floating>>) {
+    type Instance = CAN2;
+
+    fn remap(mapr: &mut MAPR) {
+        mapr.modify_mapr(|_, w| w.can2_remap().set_bit());
+    }
+}
+
+/// Interface to the CAN peripheral.
+pub struct Can<Instance> {
+    _peripheral: Instance,
+}
+
+impl<Instance> Can<Instance>
+where
+    Instance: crate::rcc::Enable<Bus = APB1>,
+{
+    /// Creates a CAN interaface.
+    ///
+    /// CAN shares SRAM with the USB peripheral. Take ownership of USB to
+    /// prevent accidental shared usage.
+    #[cfg(not(feature = "connectivity"))]
+    pub fn new(can: Instance, apb: &mut APB1, _usb: USB) -> Can<Instance> {
+        Instance::enable(apb);
+        Can { _peripheral: can }
+    }
+
+    /// Creates a CAN interaface.
+    #[cfg(feature = "connectivity")]
+    pub fn new(can: Instance, apb: &mut APB1) -> Can<Instance> {
+        Instance::enable(apb);
+        Can { _peripheral: can }
+    }
+
+    /// Routes CAN TX signals and RX signals to pins.
+    pub fn assign_pins<P>(&self, _pins: P, mapr: &mut MAPR)
+    where
+        P: Pins<Instance = Instance>,
+    {
+        P::remap(mapr);
+    }
+}
+
+unsafe impl bxcan::Instance for Can<CAN1> {
+    const REGISTERS: *mut bxcan::RegisterBlock = CAN1::ptr() as *mut _;
+}
+
+#[cfg(feature = "connectivity")]
+unsafe impl bxcan::Instance for Can<CAN2> {
+    const REGISTERS: *mut bxcan::RegisterBlock = CAN2::ptr() as *mut _;
+}
+
+unsafe impl bxcan::FilterOwner for Can<CAN1> {
+    const NUM_FILTER_BANKS: u8 = 28;
+}
+
+#[cfg(feature = "connectivity")]
+unsafe impl bxcan::MasterInstance for Can<CAN1> {}

+ 2 - 0
src/lib.rs

@@ -146,6 +146,8 @@ pub mod afio;
 pub mod backup_domain;
 #[cfg(feature = "device-selected")]
 pub mod bb;
+#[cfg(all(feature = "device-selected", feature = "has-can"))]
+pub mod can;
 #[cfg(feature = "device-selected")]
 pub mod crc;
 #[cfg(feature = "device-selected")]

+ 2 - 0
src/rcc.rs

@@ -651,6 +651,8 @@ bus! {
 #[cfg(feature = "connectivity")]
 bus! {
     ADC2 => (APB2, adc2en, adc2rst),
+    CAN1 => (APB1, can1en, can1rst),
+    CAN2 => (APB1, can2en, can2rst),
 }
 #[cfg(all(feature = "stm32f103", feature = "high",))]
 bus! {