Skip to content

Conversation

@magmastonealex
Copy link

This commit implements a simple driver for the MCAN peripheral on MSPM0, supporting both blocking and non-blocking mode.

An example is included for the MSPM0G3107 (the only part I have access to for testing), but this should work on all parts with a CANFD/MCAN peripheral.

As discussed in Matrix - this also configures SYSPLL in a slightly silly way to allow for providing a valid functional clock without requiring an external oscillator. In testing, this is fine at moderate bitrates.

This driver is intended to be a starting point, and does not yet include a variety of useful features for a well-rounded driver. In particular it does not include:

  • Async operation (in progress, but not yet implemented here)
  • CAN-FD
  • Filtering
  • TX confirmation
  • More complex clocking
  • Bitrate calculation
  • Automatic bus-off recovery

I hope to work towards implementing these features, but I can't commit to any sort of timeline.

This commit implements a simple driver for the MCAN peripheral
on MSPM0, supporting both blocking and non-blocking mode.

An example is included for the MSPM0G3107 (the only part I have
access to for testing), but this _should_ work on all parts with
a CANFD/MCAN peripheral.

This driver is intended to be a starting point, and does not yet include
a variety of useful features for a well-rounded driver. In particular it
does not include:

 - Async operation (in progress, but not yet implemented here)
 - CAN-FD
 - Filtering
 - TX confirmation
 - More complex clocking
 - Bitrate calculation
 - Automatic bus-off recovery

I hope to work towards implementing these features, but I can't commit to any
sort of timeline.
mspm0-metapac = { git = "https://github.com/mspm0-rs/mspm0-data-generated/", tag = "mspm0-data-d9f54366c65cec47957065f42fe04542b852a8cd" }
rand_core = "0.9"
mspm0-metapac = { git = "https://github.com/mspm0-rs/mspm0-data-generated/", tag = "mspm0-data-566cf58760f60a9126c957ae9b6b3364e46ed595" }
bitfield = "0.19.4"
Copy link
Author

@magmastonealex magmastonealex Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bitfield is also used by embassy-net-adin1110 and I couldn't find other similar bitfield crates in use within embassy. This is used to construct some of the fields for message RAM - I'm open to using a different crate if preferable.

Comment on lines +733 to +738
match sysctl.version {
Some("g350x_g310x_g150x_g110x") | Some("g351x_g151x") => {
cfgs.enable("sysctl_syspll");
}
_ => {}
}
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels a little strange to me. Open to suggestions on how better to identify if SYSPLL is available.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do have metadata for a clock tree from sysconfig, just haven't plumbed it up yet.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this approach reasonable for now, and just plan to improve it down the line?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now this is fine.

Comment on lines +184 to +188
// TODO: should these be found via the PAC instead of hard-coded?
// From looking at the G series TRM, these addresses are constant,
// but it still feels wrong.
// Taken from table "Table 1-135. FACTORYREGION_TYPEG Registers"
// (constants identical to Table 1-116. FACTORYREGION_TYPEA Registers for PLL.)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Longer term I do want to expose the factory region in this crate in a factory module. Haven't gotten to it yet.

@magmastonealex
Copy link
Author

magmastonealex commented Jan 25, 2026

CI fails due to:

error[E0432]: unresolved import `rand_core`
  --> /ci/code/embassy-mspm0/src/trng.rs:13:5
   |
13 | use rand_core::{TryCryptoRng, TryRngCore};
   |     ^^^^^^^^^ use of unresolved module or unlinked crate `rand_core`
   |
   = help: if you wanted to use a crate named `rand_core`, use `cargo add rand_core` to add it to your `Cargo.toml`

I think this has been the case since #5172 ? No - it's because a bad rebase on my end dropped rand_core from Cargo.toml. Fixed now.


## Checklist before running examples

The MSPM0G3107 does not have a Launchpad or official development board, so you likely need to modify examples to update pin numbers or peripherals to match the specific MCU or board you are using.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be worth mentioning G3507 will be quite similar to this.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated this line to mention that the examples should also run there with minimal changes.

I decided to create a separate examples folder for this specific MCU since I don't have access to others to test against.

@i509VCB
Copy link
Member

i509VCB commented Jan 25, 2026

CI fails due to:

error[E0432]: unresolved import `rand_core`
  --> /ci/code/embassy-mspm0/src/trng.rs:13:5
   |
13 | use rand_core::{TryCryptoRng, TryRngCore};
   |     ^^^^^^^^^ use of unresolved module or unlinked crate `rand_core`
   |
   = help: if you wanted to use a crate named `rand_core`, use `cargo add rand_core` to add it to your `Cargo.toml`

I think this has been the case since #5172 ?

Weird, the pull request clearly adds that to Cargo.toml?

@magmastonealex
Copy link
Author

Ugh, yeah. I accidentally deleted rand_core = "0.9" from Cargo.toml and didn't notice. Let me add that back in.

Comment on lines +115 to +127
pub struct Config {
/// Input clock divider
pub clock_div: ClockDiv,

/// CAN timings to use for standard CAN. (CAN-FD support to come later.)
pub timing: CanTimings,

/// If remote frames should be accepted into the RX queue.
pub accept_remote_frames: bool,

/// If extended ID frames should be accepted into the RX queue.
pub accept_extended_ids: bool,
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a feeling that the code could calculate some of the timings for you to get a specific bitrate and back calculate from clock config. See the UART driver for an idea of this.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It certainly can! I have listed it as an excluded feature for now to get something minimal working.

The algorithm is a bit complex, which is why I left it out, but there are other implementations out there (even in embassy-stm32).

I can include it if you want - but my original intention was to follow up and add it in later since it’s another ~250 lines of code.

#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
/// Config Error
pub enum InitializationError {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use ConfigError to match other drivers.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, updated.

return Err(BusError::BusOff);
}

cortex_m::asm::delay(100);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Required due to timing bugs in hardware?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed. Didn’t mean to leave this in. Thanks for catching it.

Comment on lines +361 to +368
// I am really not sure that these are good abstractions for real applications.
// They do not support "confirmable" mesage sending (i.e the docs specifically state that
// transmit() only enqueues frames, there is no feedback mechanisim to confirm if/when a frame
// was actually sent)
// They also do not support monitoring the peripheral's status or recovering from bus-off.
// I will implement them regardless to play nice with the overall ecosystem,
// but similar to embassy-stm32, I'm going to offer a HAL-specific API which provides
// more useful functionality, and add an async variant at some point.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel this is a question that could be an issue on the embedded_can crate. I am not 100% familar with CAN, but would confirmation be something that hardware reports or is that part of a "CAN stack".

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It depends on the hardware as far as I know - the MCAN IP does support hardware reports of confirmed successful transmission, but I don’t think all IPs do. Some higher level protocols might include protocol-layer ACKs as well, since the CAN ACK just means some device received it correctly, not that the destination received it.

I don’t neccessarily see it as an issue within embedded_can - confirmable messages and asynchronous support are important to the specific applications that I have built, but general hardware support may not be there to expect every MCU to support them.

It’s more that our peripheral can do more, so I want to expose methods to make more use of what is there.

use crate::can::msgram::{McanMessageRAM, MessageRAMAccess};
use crate::gpio::{AnyPin, PfType};
use crate::mode::{Blocking, Mode};
use crate::pac::canfd::{Canfd as Regs, vals as CanVals};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usually we don't rename path elements. Leaving it as vals is fine.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ooops - leftover from out of tree. Updated.

Comment on lines +273 to +281
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(C)]
pub(super) struct RxBufferElement {
// a bit awkward to split this into two structs, but this is hidden from downstream consumers anyways.
pub hdr: MsgHeader,
pub rxhdr: RxHeader,
pub data: [u8; MAX_DATA_LEN],
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another question around packed and C repr.

Comment on lines +293 to +301
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(C)]
pub(super) struct TxBufferElement {
// a bit awkward to split this into two structs, but this is hidden from downstream consumers anyways.
pub hdr: MsgHeader,
pub txhdr: TxHeader,
pub data: [u8; MAX_DATA_LEN],
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Packed question again

Comment on lines +313 to +320
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(C)]
pub(super) struct TxEventElement {
// a bit awkward to split this into two structs, but this is hidden from downstream consumers anyways.
pub hdr: MsgHeader,
pub event: EventHeader,
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another packed struct question

Comment on lines +333 to +342
#[repr(C)]
pub(super) struct McanMessageRAM {
filters: [StandardFilter; 0],
extended_filters: [ExtendedFilter; 0],
rxfifo0: [RxBufferElement; NUM_RX_ELEMENTS],
rxfifo1: [RxBufferElement; 0], // Note: while we're not using most of these features yet, I've included their offsets and structures anyways to save the next person some pain.
rxbuffers: [RxBufferElement; 0],
txevents: [TxEventElement; NUM_TX_EVENTS],
txfifo0: [TxBufferElement; NUM_TX_ELEMENTS],
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another packed struct question

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one actually doesn’t need to be packed and alignment doesn’t matter because we tell the peripheral the offsets within msgram for each array.

};
}

macro_rules! impl_ram_access {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if https://github.com/google/zerocopy could do a lot of this macro magic for you? Not going to demand a move to zerocopy at the moment.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be able to implement the same logic, but I don’t see support within zerocopy for volatile reads and writes, which should be used here IMO because the peripheral is also modifying or reading from RAM.

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

Successfully merging this pull request may close these issues.

2 participants