diff --git a/.gitignore b/.gitignore index 9a77ea6..c043e4f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .idea/* target/* example/target/* -Cargo.lock \ No newline at end of file +Cargo.lock +.vscode/* diff --git a/Cargo.toml b/Cargo.toml index 4eb77df..eab96ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ readme = "README.md" documentation = "https://docs.rs/mcp2517" [dependencies] -byteorder = { version = "1.5.0", default-features = false } +byteorder = { version = "^1.3.0", default-features = false } bytes = { version = "1.6.0", default-features = false } embedded-can = "0.4.1" embedded-hal = { version = "0.2.7", features = ["unproven"] } @@ -26,5 +26,10 @@ serde = { version = "1.0.197", features = ["derive"], default-features = false } mockall = "0.11.0" [features] -default = [] +default = ["example"] +# Mocks for doc examples +example = [] + strict = [] + + diff --git a/example/src/main.rs b/example/src/main.rs index 6bef75d..56aabb7 100644 --- a/example/src/main.rs +++ b/example/src/main.rs @@ -122,12 +122,14 @@ fn main() -> ! { let can_id = Id::Standard(StandardId::new(0x55).unwrap()); // Create filter object for RX - let filter = Filter::new(can_id, 0).unwrap(); + let mut filter = Filter::new(can_id, 0).unwrap(); + // Set mask to match if only 2 LSB of ID match with filter + filter.set_mask_standard_id(0xFF); let _ = can_controller.set_filter_object(filter); // Create message frame let message_type = Can20::<8> {}; - let payload = [1, 2, 3, 4, 5, 6, 7, 8]; + let payload = [0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8]; let pl_bytes = Bytes::copy_from_slice(&payload); let can_message = TxMessage::new(message_type, pl_bytes, can_id).unwrap(); diff --git a/src/can.rs b/src/can.rs index 3904e83..8f0c9d9 100644 --- a/src/can.rs +++ b/src/can.rs @@ -1,3 +1,23 @@ +//!# CAN Controller device +//! +//!``` +//!# use mcp2517::can::MCP2517; +//!# use mcp2517::config::Configuration; +//!# use mcp2517::example::*; +//!# +//! let sys_clk = ExampleClock::default(); +//! let spi_bus = ExampleSPIBus::default(); +//! let cs_pin = ExampleCSPin{}; +//! +//! // Initialize controller object +//! let mut can_controller = MCP2517::new(spi_bus,cs_pin); +//! +//! // Use default configuration settings +//! let can_config = Configuration::default(); +//! +//! // Configure CAN controller +//! can_controller.configure(&can_config, &sys_clk).unwrap(); +//! ``` use crate::can::BusError::{CSError, TransferError}; use crate::can::ConfigError::{ClockError, ConfigurationModeTimeout, RequestModeTimeout}; use crate::config::{ClockConfiguration, Configuration}; @@ -249,6 +269,14 @@ impl, CS: OutputPin, CLK: Clock> MCP2517 { Ok(()) } + /// Disable corresponding filter + pub fn disable_filter(&mut self, filter_index: u8) -> Result<(), BusError> { + let filter_reg = Self::filter_control_register_byte(filter_index); + self.write_register(filter_reg, 0x00)?; + + Ok(()) + } + /// Reads and returns the operation status pub fn read_operation_status(&mut self) -> Result> { let data = self.read_register(REGISTER_C1CON + 2)?; @@ -312,14 +340,6 @@ impl, CS: OutputPin, CLK: Clock> MCP2517 { Ok(()) } - /// Disable corresponding filter - pub fn disable_filter(&mut self, filter_index: u8) -> Result<(), BusError> { - let filter_reg = Self::filter_control_register_byte(filter_index); - self.write_register(filter_reg, 0x00)?; - - Ok(()) - } - /// Writes a single register byte fn write_register(&mut self, register: u16, value: u8) -> Result<(), BusError> { let mut buffer = self.cmd_buffer(register, Operation::Write); diff --git a/src/config.rs b/src/config.rs index a087251..930e6f9 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,3 +1,53 @@ +//! # CAN Module configuration +//! The [Configuration] struct provides an abstraction for configuring the CAN module registers. +//! ## Fifo configuration +//! The following example shows a FIFO buffer configuration. At the moment, there is one TX Fifo and one +//! RX Fifo. The configuration sets the max payload size of messages transmitted/received in both Fifo buffers +//! to 8 bytes. The number of message the RX Fifo buffer can hold is 10 while it is 32 for the TX Fifo. +//! +//! The priority for the messages in the TX Fifo are given the highest possible priority (32) and the retransmission +//! attemps are set to be unlimited. +//!``` +//!# use mcp2517::config::{FifoConfiguration,PayloadSize,RetransmissionAttempts}; +//!# +//! let fifo_config = FifoConfiguration{ +//! pl_size: PayloadSize::EightBytes, +//! rx_size: 10, +//! tx_attempts: RetransmissionAttempts::Unlimited, +//! tx_enable: true, +//! tx_priority: 32, +//! tx_size: 32, +//! }; +//!``` +//! ## Clock configuration +//! The CAN system clock is determined through setting the `system_clock` and `pll`. In this example, +//! the pll setting used is to directly use the crystal oscillator without any multiplication, and the +//! `system_clock` divisor is set to 1. Meaning the frequency of the SYSCLK matches the crystal used. +//! The `clock_output` divisior is set to 2. So that the CLKO outputs half the freq of the crystal. +//! Refer to the [datasheet](https://ww1.microchip.com/downloads/en/DeviceDoc/MCP2517FD-External-CAN-FD-Controller-with-SPI-Interface-20005688B.pdf) section 5.0 +//! +//!``` +//!# use mcp2517::config::{ClockConfiguration, ClockOutputDivisor, PLLSetting, SystemClockDivisor}; +//!# +//! let clock_config = ClockConfiguration{ +//! clock_output: ClockOutputDivisor::DivideBy2, +//! system_clock: SystemClockDivisor::DivideBy1, +//! pll: PLLSetting::DirectXTALOscillator, +//! disable_clock: false, +//! }; +//!``` +//! ## Bit rate configuration +//! It is recommended to use a SYSCLK frequency of 20 MHz or 40 MHz for the MCP2517FD CAN chip. +//! Based on the SYSCLK frequency used and the baud rate chosen, the CiNBTCFG regsiter values are configured. +//!``` +//!# use mcp2517::config::{BitRateConfig,CanBaudRate,SysClk}; +//!# +//! let bit_rate_config = BitRateConfig{ +//! sys_clk: SysClk::MHz20, +//! can_speed: CanBaudRate::Kpbs500 +//! }; +//! +//! use crate::status::OperationMode; /// Entire configuration currently supported diff --git a/src/example.rs b/src/example.rs new file mode 100644 index 0000000..2ef5ce9 --- /dev/null +++ b/src/example.rs @@ -0,0 +1,124 @@ +//! # Mock dummy structure for doc examples +//! +//! This code can be removed by disabling the `example` feature +use alloc::vec; +use alloc::vec::Vec; +use core::cell::RefCell; +use core::convert::Infallible; +use embedded_hal::blocking::spi::Transfer; +use embedded_hal::digital::v2::OutputPin; +use embedded_time::clock::Error; +use embedded_time::duration::{Duration, Fraction}; +use embedded_time::fixed_point::FixedPoint; +use embedded_time::timer::param::{Armed, OneShot}; +use embedded_time::{Clock, Instant, Timer}; + +#[derive(Default)] +pub struct ExampleSPIBus { + read_calls: u32, +} + +impl Transfer for ExampleSPIBus { + type Error = Infallible; + + fn transfer<'w>(&mut self, words: &'w mut [u8]) -> Result<&'w [u8], Self::Error> { + // write command -> returns empty buffer + if (words[0] >> 4) == 0x2 { + return Ok(&[0u8; 3]); + } + + // RAM read command + if words.len() == 8 && words == [0u8; 8] { + words.iter_mut().enumerate().for_each(|(i, val)| { + *val += (i + 1) as u8; + }); + return Ok(&[0u8; 8]); + } + + // SFR Read command + if words[0] >= 0x3 { + return match words[1] { + // addr: C1CON reg 2 + 0x2 => { + // configuration mode + if self.read_calls == 0 { + self.read_calls += 1; + return Ok(&[0, 0, 0b1001_0100]); + } + + // return operation mode NormalCANFD mode (called in configure and during transmission) + Ok(&[0x0, 0x0, 0b0000_0000]) + } + // C1FIFOSTA2 + 0x6C => Ok(&[0, 0, 0x1]), + // C1FIFOUA2 (2 extra bytes in beginning for cmd+addr) + 0x70 => Ok(&[0, 0, 0, 0, 0x04, 0xA2]), + // C1FIFOCON2 register 1 + 0x69 => Ok(&[0, 0, 0]), + // C1FIFOSTA1 + 0x60 => Ok(&[0, 0, 0x1]), + // C1FIFOUA1 + 0x64 => Ok(&[0, 0, 0, 0x04, 0x7C]), + + _ => Ok(&[0, 0, 0]), + }; + } + + Ok(&[0u8; 3]) + } +} + +pub struct ExampleCSPin {} + +impl OutputPin for ExampleCSPin { + type Error = Infallible; + + fn set_low(&mut self) -> Result<(), Self::Error> { + Ok(()) + } + + fn set_high(&mut self) -> Result<(), Self::Error> { + Ok(()) + } +} + +#[derive(Debug, PartialEq, Eq)] +pub struct ExampleClock { + pub next_instants: RefCell>, +} + +impl ExampleClock { + pub fn new(next_instants: Vec) -> Self { + Self { + next_instants: RefCell::new(next_instants), + } + } +} + +impl Default for ExampleClock { + fn default() -> Self { + Self::new(vec![ + 100, // Config mode: Timer start, + 200, // Config mode: First expiration check + 10_000, // Request mode: Timer start + 10_100, // Request mode: First expiration check + ]) + } +} + +impl Clock for ExampleClock { + type T = u64; + const SCALING_FACTOR: Fraction = Fraction::new(1, 1_000_000); + + fn try_now(&self) -> Result, Error> { + if self.next_instants.borrow().len() == 0 { + return Err(Error::Unspecified); + } + + Ok(Instant::new(self.next_instants.borrow_mut().remove(0))) + } + + fn new_timer(&self, duration: Dur) -> Timer { + Timer::new(self, duration) + } +} diff --git a/src/filter.rs b/src/filter.rs index 2dcd1d1..313f859 100644 --- a/src/filter.rs +++ b/src/filter.rs @@ -1,3 +1,19 @@ +//!# CAN Filter +//! The [Filter] object is used to create a CAN filter. The MCP2517FD CAN chip has 32 filter/mask registers. +//! Lower index of the filter means higher priority (highest priority =0, lowest priority = 31). +//! +//! ``` +//!# use mcp2517::filter::Filter; +//!# use embedded_can::{Id,ExtendedId}; +//!# +//! // ID to match +//! let id = Id::Extended(ExtendedId::new(0xC672).unwrap()); +//! // Create filter with index 2 +//! let mut filter = Filter::new(id,2).unwrap(); +//! // Set mask MSB bits, so that only the MSB of the message ID needs to match the filter +//! filter.set_mask_extended_id(0xFF00); +//! +//! use crate::message::{EXTENDED_IDENTIFIER_MASK, STANDARD_IDENTIFIER_MASK}; use crate::registers::{FilterMaskReg, FilterObjectReg}; use embedded_can::{ExtendedId, Id, StandardId}; @@ -6,15 +22,15 @@ use embedded_can::{ExtendedId, Id, StandardId}; #[derive(Default, Debug)] pub struct Filter { /// filter & mask index - pub index: u8, + pub(crate) index: u8, /// mask register bitfield - pub mask_bits: FilterMaskReg, + pub(crate) mask_bits: FilterMaskReg, /// filter register bitfield - pub filter_bits: FilterObjectReg, + pub(crate) filter_bits: FilterObjectReg, } impl Filter { - /// Create new filter from embedded_can::Id and index, no mask + /// Create new filter from [embedded_can::Id] and index, no mask pub fn new(identifier: Id, index: u8) -> Option { if index > 31 { return None; diff --git a/src/lib.rs b/src/lib.rs index d5f9e6c..8d66090 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,16 +3,97 @@ #![allow(dead_code)] #![allow(clippy::identity_op)] +//! # Library for MCP2517FD CAN controller +//! +//! Crate currently offers the following features: +//! * CAN2.0 and CAN FD format support +//! * Standard and extended ID formats for CAN frames +//! * `no_std` support +//! +//!## Example +//! For detailed example with rp-pico check [example](https://github.com/atlas-aero/rt-mcp2517/tree/main/example) +//! +//!## CAN TX/RX example +//! +//!``` +//!use mcp2517::example::{ExampleClock,ExampleCSPin,ExampleSPIBus}; +//!use mcp2517::can::{MCP2517,CanController}; +//!use mcp2517::message::{Can20,TxMessage}; +//!use mcp2517::filter::Filter; +//!use mcp2517::config::*; +//!use bytes::Bytes; +//!use embedded_can::{Id,StandardId}; +//! +//!let cs_pin = ExampleCSPin{}; +//!let spi_bus = ExampleSPIBus::default(); +//!let clock = ExampleClock::default(); +//! +//!let mut controller = MCP2517::new(spi_bus, cs_pin); +//! +//!// configure CAN controller +//!controller +//! .configure( +//! &Configuration { +//! clock: ClockConfiguration { +//! clock_output: ClockOutputDivisor::DivideBy10, +//! system_clock: SystemClockDivisor::DivideBy1, +//! disable_clock: false, +//! pll: PLLSetting::TenTimesPLL, +//! }, +//! fifo: FifoConfiguration { +//! rx_size: 16, +//! tx_attempts: RetransmissionAttempts::Three, +//! tx_priority: 10, +//! pl_size: PayloadSize::EightBytes, +//! tx_size: 20, +//! tx_enable: true, +//! }, +//! mode: RequestMode::NormalCANFD, +//! bit_rate: BitRateConfig{ +//! sys_clk: SysClk::MHz20, +//! can_speed: CanBaudRate::Kpbs500 +//! }, +//! }, +//! &clock, +//! ).unwrap(); +//! +//!// Create message frame +//!let can_id = Id::Standard(StandardId::new(0x55).unwrap()); +//! +//!// Important note: Generic arg for message type for CAN2.0 +//!// should be either 4 or 8, the DLC will be based off the +//!// length of the payload buffer. So for a payload of 5 bytes +//!// you can only use Can20::<8> as the message type +//!let message_type = Can20::<8> {}; +//!let payload = [0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8]; +//!let pl_bytes = Bytes::copy_from_slice(&payload); +//!let can_message = TxMessage::new(message_type, pl_bytes, can_id).unwrap(); +//! +//!// Create and set filter object +//!let filter = Filter::new(can_id, 0).unwrap(); +//!let _ = controller.set_filter_object(filter); +//! +//!// Transmit CAN message +//!controller.transmit(&can_message).unwrap(); +//! +//!// Receive CAN message +//!let mut buff = [0u8;8]; +//!let result = controller.receive(&mut buff); +//!assert!(result.is_ok()); +//!assert_eq!(buff,[0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8]); +//!``` + extern crate alloc; pub mod can; pub mod config; -pub mod status; - +#[cfg(feature = "example")] +pub mod example; pub mod filter; pub mod message; #[cfg(test)] pub(crate) mod mocks; mod registers; +pub mod status; #[cfg(test)] mod tests; diff --git a/src/message.rs b/src/message.rs index be0e99a..ada997f 100644 --- a/src/message.rs +++ b/src/message.rs @@ -1,3 +1,43 @@ +//!# CAN Message +//! This library supports both CAN2.0 (up to 8 data bytes per CAN Frame) +//! and CAN FD (up to 64 data bytes per CAN frame) +//! formats with both standard and extended frame ID formats +//! +//! ## CAN 2.0 message construction example +//! ``` +//!# use bytes::Bytes; +//!# use mcp2517::message::{Can20,TxMessage}; +//!# use embedded_can::{Id,StandardId}; +//!# +//! // Frame ID +//! let message_id = Id::Standard(StandardId::new(0x123).unwrap()); +//! // Set message type to CAN2.0 with a maximum of 4 data bytes +//! let message_type = Can20::<4>{}; +//! // Create payload buffer of 3 data bytes. DLC determined by length of payload buffer. +//! let payload = [0x1, 0x2, 0x3]; +//! // Create Bytes object +//! let bytes = Bytes::copy_from_slice(&payload); +//! // Create message object +//! let tx_message = TxMessage::new(message_type,bytes,message_id).unwrap(); +//!``` +//! ## CAN FD message construction example +//! ``` +//!# use bytes::Bytes; +//!# use mcp2517::message::{CanFd,TxMessage}; +//!# use embedded_can::{Id,StandardId}; +//!# +//! // Frame ID +//! let message_id = Id::Standard(StandardId::new(0x123).unwrap()); +//! // Set message type to CANfd with a max of 24 data bytes with bit rate switch enabled +//! let message_type = CanFd::<24>{bitrate_switch: true}; +//! // Create payload buffer with 22 data bytes (here DLC will be 24 because 22 is not a supported DLC code) +//! let payload = [0u8;22]; +//! // Create Bytes object +//! let bytes = Bytes::copy_from_slice(&payload); +//! // Create message object +//! let tx_message = TxMessage::new(message_type,bytes,message_id).unwrap(); +//! ``` + use bytes::Bytes; use embedded_can::{ExtendedId, Id, StandardId}; use log::debug; diff --git a/src/status.rs b/src/status.rs index 5023c4d..e463510 100644 --- a/src/status.rs +++ b/src/status.rs @@ -1,3 +1,5 @@ +//! # Mapped status registers + /// Operation status read from C1CON register #[derive(Copy, Clone, Debug)] pub struct OperationStatus { diff --git a/src/tests/can.rs b/src/tests/can.rs index 6b22863..0fdb4e4 100644 --- a/src/tests/can.rs +++ b/src/tests/can.rs @@ -192,10 +192,10 @@ fn test_transmit_can20() { let tx_message = TxMessage::new(msg_type, payload_bytes, Id::Extended(identifier)).unwrap(); let tx_message_copy = tx_message.clone(); - // mock fifo status register read byte 0 (1st attempt) -> tx fifo full + // mock fifo status register read byte 0 (1st attempt) -> TX fifo full mocks.mock_register_read::<0b0000_0000>([0x30, 0x6C], &mut seq); - // mock fifo status register read byte 0 (2nd attempt) -> tx fifo not full + // mock fifo status register read byte 0 (2nd attempt) -> TX fifo not full mocks.mock_register_read::<0b0000_0001>([0x30, 0x6C], &mut seq); // mock read operation status @@ -561,7 +561,7 @@ fn test_receive() { .return_const(Ok(())) .in_sequence(&mut seq); - // set uinc bit in Rx FIFO control register + // set uinc bit in RX FIFO control register mocks .pin_cs .expect_set_low()