-
Notifications
You must be signed in to change notification settings - Fork 201
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add I2C slave mode to fix #636 #671
Draft
tgross35
wants to merge
4
commits into
atsamd-rs:master
Choose a base branch
from
tgross35:i2c-slave
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from 3 commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -276,6 +276,9 @@ pub use config::*; | |
|
||
mod impl_ehal; | ||
|
||
// mod client; | ||
// pub use client::*; | ||
|
||
/// Word size for an I2C message | ||
pub type Word = u8; | ||
|
||
|
@@ -293,18 +296,30 @@ pub enum InactiveTimeout { | |
Us205 = 0x3, | ||
} | ||
|
||
pub trait I2cMode {} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would make this trait sealed so that it can't be implemented outside of this crate:
and also add a short doc comment. |
||
/// Marker type for I2C master mode | ||
pub struct Master; | ||
/// Marker type for I2C client/slave mode | ||
pub struct Sleve; | ||
impl I2cMode for Master {} | ||
impl I2cMode for Sleve {} | ||
|
||
/// Abstraction over a I2C peripheral, allowing to perform I2C transactions. | ||
pub struct I2c<C: AnyConfig> { | ||
pub struct I2c<C: AnyConfig<Mode = M>, M: I2cMode = Master> { | ||
config: C, | ||
} | ||
|
||
impl<C: AnyConfig> I2c<C> { | ||
/// Implementation for both modes | ||
impl<C: AnyConfig<Mode = M>, M: I2cMode> I2c<C, M> { | ||
/// Obtain a pointer to the `DATA` register. Necessary for DMA transfers. | ||
#[inline] | ||
pub fn data_ptr(&self) -> *mut Word { | ||
self.config.as_ref().registers.data_ptr() | ||
} | ||
} | ||
|
||
/// Host-only implementation | ||
impl<C: AnyConfig<Mode = Master>> I2c<C, Master> { | ||
/// Read the interrupt flags | ||
#[inline] | ||
pub fn read_flags(&self) -> Flags { | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
//! WIP module for I2C client/slave configuration | ||
|
||
mod config; | ||
mod flags; | ||
mod reg; | ||
|
||
use super::Error; | ||
use super::InactiveTimeout; | ||
use super::PadSet; | ||
use super::Status; | ||
use super::Word; | ||
pub use config::{ClientAnyConfig, ClientConfig, ClientSpecificConfig}; | ||
pub use flags::ClientFlags; | ||
use reg::Registers; | ||
|
||
/// Abstraction over an I2C peripheral in client mode | ||
pub struct I2cClient<C: ClientAnyConfig> { | ||
config: C, | ||
} | ||
|
||
impl<C: ClientAnyConfig> I2cClient<C> { | ||
/// Obtain a pointer to the `DATA` register. Necessary for DMA transfers. | ||
#[inline] | ||
pub fn data_ptr(&self) -> *mut Word { | ||
self.config.as_ref().registers.data_ptr() | ||
} | ||
|
||
/// Read the interrupt flags | ||
#[inline] | ||
pub fn read_flags(&self) -> ClientFlags { | ||
self.config.as_ref().registers.read_flags() | ||
} | ||
|
||
/// Clear interrupt status flags | ||
#[inline] | ||
pub fn clear_flags(&mut self, flags: ClientFlags) { | ||
self.config.as_mut().registers.clear_flags(flags); | ||
} | ||
|
||
/// Enable interrupts for the specified flags. | ||
#[inline] | ||
pub fn enable_interrupts(&mut self, flags: ClientFlags) { | ||
self.config.as_mut().registers.enable_interrupts(flags); | ||
} | ||
|
||
/// Disable interrupts for the specified flags. | ||
#[inline] | ||
pub fn disable_interrupts(&mut self, flags: ClientFlags) { | ||
self.config.as_mut().registers.disable_interrupts(flags); | ||
} | ||
|
||
/// Read the status flags | ||
#[inline] | ||
pub fn read_status(&self) -> Status { | ||
self.config.as_ref().registers.read_status() | ||
} | ||
|
||
/// Clear the status flags | ||
#[inline] | ||
pub fn clear_status(&mut self, status: Status) { | ||
self.config.as_mut().registers.clear_status(status); | ||
} | ||
|
||
#[cfg(feature = "dma")] | ||
#[inline] | ||
pub(super) fn start_dma_write(&mut self, address: u8, xfer_len: u8) { | ||
self.config | ||
.as_mut() | ||
.registers | ||
.start_dma_write(address, xfer_len) | ||
} | ||
|
||
#[cfg(feature = "dma")] | ||
#[inline] | ||
pub(super) fn start_dma_read(&mut self, address: u8, xfer_len: u8) { | ||
self.config | ||
.as_mut() | ||
.registers | ||
.start_dma_read(address, xfer_len) | ||
} | ||
|
||
#[cfg(feature = "dma")] | ||
#[inline] | ||
pub(super) fn check_bus_status(&self) -> Result<(), Error> { | ||
self.config.as_ref().registers.check_bus_status() | ||
} | ||
|
||
#[inline] | ||
fn do_write(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Error> { | ||
self.config.as_mut().registers.do_write(addr, bytes) | ||
} | ||
|
||
#[inline] | ||
fn do_read(&mut self, addr: u8, bytes: &mut [u8]) -> Result<(), Error> { | ||
self.config.as_mut().registers.do_read(addr, bytes) | ||
} | ||
|
||
#[inline] | ||
fn do_write_read(&mut self, addr: u8, bytes: &[u8], buffer: &mut [u8]) -> Result<(), Error> { | ||
self.config | ||
.as_mut() | ||
.registers | ||
.do_write_read(addr, bytes, buffer) | ||
} | ||
#[inline] | ||
fn cmd_stop(&mut self) { | ||
self.config.as_mut().registers.cmd_stop() | ||
} | ||
|
||
/// Reconfigure the I2C peripheral. | ||
/// | ||
/// Calling this method will temporarily disable the SERCOM peripheral, as | ||
/// some registers are enable-protected. This may interrupt any ongoing | ||
/// transactions. | ||
/// | ||
/// ``` | ||
/// use atsamd_hal::sercom::i2c::I2c; | ||
/// i2c.reconfigure(|c| c.set_run_in_standby(false)); | ||
/// ``` | ||
#[inline] | ||
pub fn reconfigure<F>(&mut self, update: F) | ||
where | ||
F: FnOnce(&mut ClientSpecificConfig<C>), | ||
{ | ||
self.config.as_mut().registers.enable_peripheral(false); | ||
update(self.config.as_mut()); | ||
self.config.as_mut().registers.enable_peripheral(true); | ||
} | ||
|
||
/// Disable the I2C peripheral and return the underlying [`Config`] | ||
#[inline] | ||
pub fn disable(self) -> C { | ||
let mut config = self.config; | ||
config.as_mut().registers.disable(); | ||
config | ||
} | ||
} | ||
|
||
impl<P: PadSet> AsRef<ClientConfig<P>> for I2cClient<ClientConfig<P>> { | ||
#[inline] | ||
fn as_ref(&self) -> &ClientConfig<P> { | ||
self.config.as_ref() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
use super::{I2cClient, InactiveTimeout, PadSet, Registers}; | ||
use crate::{ | ||
sercom::{Sercom, APB_CLK_CTRL}, | ||
time::Hertz, | ||
typelevel::{Is, Sealed}, | ||
}; | ||
|
||
/// A configurable, disabled I2C peripheral | ||
/// | ||
/// This `struct` represents a configurable I2C peripheral in its disabled | ||
/// state. It is generic over the set of [`Pads`]. | ||
/// Upon creation, the [`Config`] takes ownership of the | ||
/// [`Sercom`] and resets it, returning it configured as an I2C peripheral | ||
/// with a default configuration in Master mode. | ||
/// | ||
/// [`Config`] uses a builder-pattern API to configure the peripheral, | ||
/// culminating in a call to [`enable`], which consumes the [`Config`] and | ||
/// returns an enabled [`I2c`]. | ||
/// | ||
/// [`enable`]: Config::enable | ||
/// [`Pads`]: super::Pads | ||
pub struct ClientConfig<P> | ||
where | ||
P: PadSet, | ||
{ | ||
pub(super) registers: Registers<P::Sercom>, | ||
pads: P, | ||
freq: Hertz, | ||
} | ||
|
||
impl<P: PadSet> ClientConfig<P> { | ||
/// Create a new [`Config`] in the default configuration. | ||
#[inline] | ||
fn default(sercom: P::Sercom, pads: P, freq: impl Into<Hertz>) -> Self { | ||
let mut registers = Registers::new(sercom); | ||
registers.swrst(); | ||
registers.set_op_mode(); | ||
Self { | ||
registers, | ||
pads, | ||
freq: freq.into(), | ||
} | ||
} | ||
|
||
/// Create a new [`Config`] in the default configuration | ||
/// | ||
/// This function will enable the corresponding APB clock, reset the | ||
/// [`Sercom`] peripheral, and return a [`Config`] in the default | ||
/// configuration. The only available operating mode is currently Master. | ||
/// | ||
/// Note that [`Config`] takes ownership of both the | ||
/// PAC [`Sercom`] struct as well as the [`Pads`](super::Pads). | ||
/// | ||
/// Users must configure GCLK manually. The `freq` parameter represents the | ||
/// GCLK frequency for this [`Sercom`] instance. | ||
#[inline] | ||
pub fn new( | ||
apb_clk_ctrl: &APB_CLK_CTRL, | ||
mut sercom: P::Sercom, | ||
pads: P, | ||
freq: impl Into<Hertz>, | ||
) -> Self { | ||
sercom.enable_apb_clock(apb_clk_ctrl); | ||
Self::default(sercom, pads, freq) | ||
} | ||
} | ||
|
||
impl<P: PadSet> ClientConfig<P> { | ||
/// Obtain a reference to the PAC `SERCOM` struct | ||
/// | ||
/// # Safety | ||
/// | ||
/// Directly accessing the `SERCOM` could break the invariants of the | ||
/// type-level tracking in this module, so it is unsafe. | ||
#[inline] | ||
pub unsafe fn sercom(&self) -> &P::Sercom { | ||
&self.registers.sercom | ||
} | ||
|
||
/// Trigger the [`Sercom`]'s SWRST and return a [`Config`] in the | ||
/// default configuration. | ||
#[inline] | ||
pub fn reset(self) -> Self { | ||
Self::default(self.registers.sercom, self.pads, self.freq) | ||
} | ||
|
||
/// Consume the [`Config`], reset the peripheral, and return the | ||
/// [`Sercom`] and [`Pads`](super::Pads) | ||
#[inline] | ||
pub fn free(mut self) -> (P::Sercom, P) { | ||
self.registers.swrst(); | ||
(self.registers.free(), self.pads) | ||
} | ||
|
||
/// Run in standby mode (builder pattern version) | ||
/// | ||
/// When set, the I2C peripheral will run in standby mode. See the | ||
/// datasheet for more details. | ||
#[inline] | ||
pub fn run_in_standby(mut self, set: bool) -> Self { | ||
self.set_run_in_standby(set); | ||
self | ||
} | ||
|
||
/// Run in standby mode (setter version) | ||
/// | ||
/// When set, the I2C peripheral will run in standby mode. See the | ||
/// datasheet for more details. | ||
#[inline] | ||
pub fn set_run_in_standby(&mut self, set: bool) { | ||
self.registers.set_run_in_standby(set); | ||
} | ||
|
||
/// Get the current run in standby mode | ||
#[inline] | ||
pub fn get_run_in_standby(&self) -> bool { | ||
self.registers.get_run_in_standby() | ||
} | ||
|
||
/// Enable the I2C peripheral | ||
/// | ||
/// I2C transactions are not possible until the peripheral is enabled. | ||
#[inline] | ||
pub fn enable(mut self) -> I2cClient<Self> | ||
where | ||
Self: ClientAnyConfig, | ||
{ | ||
self.registers.enable(); | ||
|
||
I2cClient { config: self } | ||
} | ||
} | ||
|
||
//============================================================================= | ||
// AnyConfig | ||
//============================================================================= | ||
|
||
/// Type class for all possible [`Config`] types | ||
/// | ||
/// This trait uses the [`AnyKind`] trait pattern to create a [type class] for | ||
/// [`Config`] types. See the [`AnyKind`] documentation for more details on the | ||
/// pattern. | ||
/// | ||
/// In addition to the normal, [`AnyKind`] associated types. This trait also | ||
/// copies the [`Sercom`] type, to make it easier | ||
/// to apply bounds to these types at the next level of abstraction. | ||
/// | ||
/// [`AnyKind`]: crate::typelevel#anykind-trait-pattern | ||
/// [type class]: crate::typelevel#type-classes | ||
pub trait ClientAnyConfig: Is<Type = ClientSpecificConfig<Self>> { | ||
type Sercom: Sercom; | ||
type Pads: PadSet<Sercom = Self::Sercom>; | ||
} | ||
|
||
/// Type alias to recover the specific [`Config`] type from an implementation of | ||
/// [`AnyConfig`] | ||
pub type ClientSpecificConfig<C> = ClientConfig<<C as ClientAnyConfig>::Pads>; | ||
|
||
/// Type alias to recover the specific [`Sercom`] type from an implementation of | ||
/// [`AnyConfig`] | ||
pub type ConfigSercom<C> = <C as ClientAnyConfig>::Sercom; | ||
|
||
impl<P: PadSet> Sealed for ClientConfig<P> {} | ||
|
||
impl<P: PadSet> ClientAnyConfig for ClientConfig<P> { | ||
type Sercom = P::Sercom; | ||
type Pads = P; | ||
} | ||
|
||
impl<P: PadSet> AsRef<Self> for ClientConfig<P> { | ||
#[inline] | ||
fn as_ref(&self) -> &Self { | ||
self | ||
} | ||
} | ||
|
||
impl<P: PadSet> AsMut<Self> for ClientConfig<P> { | ||
#[inline] | ||
fn as_mut(&mut self) -> &mut Self { | ||
self | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
use bitflags::bitflags; | ||
use modular_bitfield::specifiers::{B1, B5}; | ||
use modular_bitfield::*; | ||
|
||
bitflags! { | ||
/// Interrupt bitflags for I2C client transactions | ||
/// | ||
/// The available interrupt flags are `PREC`, `AMATCH`, `DRDY`, and `ERROR`. The binary format of | ||
/// the underlying bits exactly matches the INTFLAG bits. | ||
pub struct ClientFlags: u8 { | ||
/// Stop received interrupt | ||
const PREC = 0x01; | ||
/// Address match interrupt | ||
const AMATCH = 0x02; | ||
/// Data ready interrupt | ||
const DRDY = 0x08; | ||
/// Error interrupt | ||
const ERROR = 0x80; | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would try to standardize what terminology we use: master/slave vs host/client. Personally I don't care either way, with maybe a slight preference towards whatever the datasheet uses.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for bringing this up, I meant to ask. The datasheet does use host/client, so I can go ahead with that.