From 77066a6326e350ce27c8b7c05d3a90f70c2f3cd8 Mon Sep 17 00:00:00 2001 From: xoviat Date: Mon, 18 Jan 2021 20:27:36 -0600 Subject: [PATCH] CAN: add support with 'bxcan'. --- .github/workflows/ci.yml | 2 +- CHANGELOG.md | 1 + Cargo.toml | 9 ++- examples/can-send.rs | 100 +++++++++++++++++++++++++++++ src/can.rs | 135 +++++++++++++++++++++++++++++++++++++++ src/lib.rs | 5 ++ 6 files changed, 250 insertions(+), 2 deletions(-) create mode 100644 examples/can-send.rs create mode 100644 src/can.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e6cdd909..24d0015c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,4 +46,4 @@ jobs: - uses: actions-rs/cargo@v1 with: command: check - args: --features=${{ matrix.mcu }},rt,usb_fs,sdio --examples + args: --features=${{ matrix.mcu }},rt,usb_fs,sdio,can --examples diff --git a/CHANGELOG.md b/CHANGELOG.md index b15bff4c..17d09bcd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Added support for hardware-based CRC32 functionality - Add `MonoTimer` and `Instant` structs for basic time measurement. - Added support for I2S and SAI clocks +- Added support for canbus with the bxcan crate. ### Fixed diff --git a/Cargo.toml b/Cargo.toml index aa88b5c5..0bca851a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,10 +22,11 @@ repository = "https://github.com/stm32-rs/stm32f4xx-hal" version = "0.8.3" [package.metadata.docs.rs] -features = ["stm32f429", "rt", "usb_fs"] +features = ["stm32f429", "rt", "usb_fs", "can"] targets = ["thumbv7em-none-eabihf"] [dependencies] +bxcan = { version = "0.3.0", optional = true } cortex-m = ">=0.5.8,<0.7" cortex-m-rt = "0.6.10" nb = "0.1.2" @@ -88,6 +89,8 @@ stm32f479 = ["stm32f4/stm32f469", "device-selected"] usb_fs = ["synopsys-usb-otg", "synopsys-usb-otg/fs"] usb_hs = ["synopsys-usb-otg", "synopsys-usb-otg/hs"] +can = ["bxcan"] + sdio = ["sdio-host"] [profile.dev] @@ -138,3 +141,7 @@ required-features = ["rt", "stm32f407"] [[example]] name = "qei" required-features = ["rt", "stm32f411"] + +[[example]] +name = "can-send" +required-features = ["can", "stm32f405"] diff --git a/examples/can-send.rs b/examples/can-send.rs new file mode 100644 index 00000000..452f3b09 --- /dev/null +++ b/examples/can-send.rs @@ -0,0 +1,100 @@ +//! Simple CAN example. +//! Requires a transceiver connected to PB8, 9 (CAN1) or PB5 PB6 (CAN2). + +#![no_main] +#![no_std] + +use panic_halt as _; + +use bxcan::filter::Mask32; +use bxcan::{Frame, StandardId}; +use cortex_m_rt::entry; +use nb::block; +use stm32f4xx_hal::{can::Can, pac, prelude::*}; + +#[entry] +fn main() -> ! { + let dp = pac::Peripherals::take().unwrap(); + + let 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(); + + let gpiob = dp.GPIOB.split(); + let mut can1 = { + let rx = gpiob.pb8.into_alternate_af9(); + let tx = gpiob.pb9.into_alternate_af9(); + + let can = Can::new(dp.CAN1, (tx, rx)); + + bxcan::Can::new(can) + }; + can1.configure(|config| { + // APB1 (PCLK1): 8MHz, Bit rate: 500kBit/s, Sample Point 87.5% + // Value was calculated with http://www.bittiming.can-wiki.info/ + config.set_bit_timing(0x001c_0000); + }); + + // Configure filters so that can frames can be received. + let mut filters = can1.modify_filters(); + filters.enable_bank(0, Mask32::accept_all()); + + let _can2 = { + let tx = gpiob.pb13.into_alternate_af9(); + let rx = gpiob.pb12.into_alternate_af9(); + + let can = Can::new(dp.CAN2, (tx, rx)); + + let mut can2 = bxcan::Can::new(can); + can2.configure(|config| { + // APB1 (PCLK1): 8MHz, Bit rate: 500kBit/s, Sample Point 87.5% + // Value was calculated with http://www.bittiming.can-wiki.info/ + config.set_bit_timing(0x001c_0000); + }); + + // A total of 28 filters are shared between the two CAN instances. + // Split them equally between CAN1 and CAN2. + filters.set_split(14); + let mut slave_filters = filters.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. + let mut test: [u8; 8] = [0; 8]; + let mut count: u8 = 0; + let id: u16 = 0x500; + + test[1] = 1; + test[2] = 2; + test[3] = 3; + test[4] = 4; + test[5] = 5; + test[6] = 6; + test[7] = 7; + loop { + test[0] = count; + let test_frame = Frame::new_data(StandardId::new(id).unwrap(), test); + block!(can.transmit(&test_frame)).unwrap(); + if count < 255 { + count += 1; + } else { + count = 0; + } + } +} diff --git a/src/can.rs b/src/can.rs new file mode 100644 index 00000000..f1fabbde --- /dev/null +++ b/src/can.rs @@ -0,0 +1,135 @@ +//! # Controller Area Network (CAN) Interface +//! + +use crate::bb; +#[cfg(any(feature = "stm32f405", feature = "stm32f407"))] +use crate::gpio::{ + gpioa::{PA11, PA12}, + gpiob::{PB12, PB13, PB5, PB6, PB8, PB9}, + gpiod::{PD0, PD1}, + gpioh::PH13, + gpioi::PI9, + Alternate, AF9, +}; + +#[cfg(feature = "stm32f446")] +use crate::gpio::{ + gpioa::{PA11, PA12}, + gpiob::{PB12, PB13, PB5, PB6, PB8, PB9}, + gpiod::{PD0, PD1}, + Alternate, AF9, +}; +use crate::pac::{CAN1, CAN2}; +use crate::stm32::RCC; + +mod sealed { + pub trait Sealed {} +} + +pub trait Pins: sealed::Sealed { + type Instance; +} + +/* + order: tx, rx similar to serial +*/ +macro_rules! pins { + ($($PER:ident => ($tx:ident, $rx:ident),)+) => { + $( + impl sealed::Sealed for ($tx>, $rx>) {} + impl Pins for ($tx>, $rx>) { + type Instance = $PER; + } + )+ + } +} + +/* + See DS8626 Rev 9 Table 9. +*/ +#[cfg(any(feature = "stm32f405", feature = "stm32f407"))] +pins! { + CAN1 => (PA12, PA11), + CAN1 => (PB9, PB8), + CAN1 => (PD1, PD0), + CAN1 => (PH13, PI9), + CAN2 => (PB13, PB12), + CAN2 => (PB6, PB5), +} + +/* + See DS10693 Rev 9 Table 11. +*/ +#[cfg(feature = "stm32f446")] +pins! { + CAN1 => (PA12, PA11), + CAN1 => (PB9, PB8), + CAN1 => (PD1, PD0), + CAN2 => (PB13, PB12), + CAN2 => (PB6, PB5), +} + +/// Enable/disable peripheral +pub trait Enable: sealed::Sealed { + fn enable(); +} + +macro_rules! bus { + ($($PER:ident => ($peren:literal),)+) => { + $( + impl sealed::Sealed for crate::pac::$PER {} + impl Enable for crate::pac::$PER { + #[inline(always)] + fn enable() { + unsafe { + let rcc = &(*RCC::ptr()); + bb::set(&rcc.apb1enr, $peren) + }; + } + } + )+ + } +} + +bus! { + CAN1 => (25), + CAN2 => (26), +} + +/// Interface to the CAN peripheral. +pub struct Can { + _peripheral: Instance, +} + +impl Can +where + Instance: Enable, +{ + /// Creates a CAN interaface. + pub fn new

(can: Instance, _pins: P) -> Can + where + P: Pins, + { + Instance::enable(); + Can { _peripheral: can } + } + + pub fn new_unchecked(can: Instance) -> Can { + Instance::enable(); + Can { _peripheral: can } + } +} + +unsafe impl bxcan::Instance for Can { + const REGISTERS: *mut bxcan::RegisterBlock = CAN1::ptr() as *mut _; +} + +unsafe impl bxcan::Instance for Can { + const REGISTERS: *mut bxcan::RegisterBlock = CAN2::ptr() as *mut _; +} + +unsafe impl bxcan::FilterOwner for Can { + const NUM_FILTER_BANKS: u8 = 28; +} + +unsafe impl bxcan::MasterInstance for Can {} diff --git a/src/lib.rs b/src/lib.rs index 3eaecf6f..3dc89b0e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -87,6 +87,11 @@ pub use crate::stm32::interrupt; pub mod adc; #[cfg(feature = "device-selected")] pub mod bb; +#[cfg(all( + feature = "can", + any(feature = "stm32f405", feature = "stm32f407", feature = "stm32f446") +))] +pub mod can; #[cfg(feature = "device-selected")] pub mod crc32; #[cfg(all(