atsamd_hal/sercom/i2c.rs
1//! Use the SERCOM peripheral for I2C communications
2//!
3//! Configuring an I2C peripheral occurs in three steps. First, you must create
4//! a set of [`Pads`] for use by the peripheral. Next, you assemble pieces into
5//! a [`Config`] struct. After configuring the peripheral, you then [`enable`]
6//! it, yielding a functional [`I2c`] struct. Transactions are performed using
7//! traits from embedded-hal ([v1.0](crate::ehal) and blocking traits from
8//! [v0.2](`crate::ehal_02`)).
9//!
10//! # [`Pads`]
11//!
12//! A [`Sercom`] uses two [`Pin`]s as peripheral [`Pad`]s, but only certain
13//! [`Pin`] combinations are acceptable. All [`Pin`]s must be mapped to the same
14//! [`Sercom`], and SDA is always [`Pad0`], while SCL is always [`Pad1`] (see
15//! the datasheet). SAMx5x chips have an additional constraint that [`Pin`]s
16//! must be in the same [`IoSet`]. This HAL makes it impossible to use invalid
17//! [`Pin`]/[`Pad`] combinations, and the [`Pads`] struct is responsible for
18//! enforcing these constraints.
19//!
20//! A [`Pads`] type takes three type parameters. The first type specifies the
21//! [`Sercom`], `SDA` and `SCL` represent the SDA and SCL pads respectively. A
22//! [`Pad`] is just a [`Pin`] configured in the correct [`PinMode`] that
23//! implements [`IsPad`]. The [`bsp_pins!`](crate::bsp_pins) macro can be used
24//! to define convenient type aliases for [`Pad`] types.
25//!
26//! ```no_run
27//! use atsamd_hal::gpio::{PA08, PA09, AlternateC};
28//! use atsamd_hal::sercom::{Sercom0, i2c};
29//! use atsamd_hal::typelevel::NoneT;
30//!
31//! type Sda = Pin<PA08, AlternateC>;
32//! type Scl = Pin<PA09, AlternateC>;
33//!
34//! type Pads = i2c::Pads<Sercom0, Sda, Scl>;
35//! ```
36//!
37//! Alternatively, you can use the [`PadsFromIds`] alias to define a set of
38//! `Pads` in terms of [`PinId`]s instead of [`Pin`]s. This is useful when you
39//! don't have [`Pin`] aliases pre-defined.
40//!
41//! ```no_run
42//! use atsamd_hal::gpio::{PA08, PA09};
43//! use atsamd_hal::sercom::{Sercom0, i2c};
44//!
45//! type Pads = i2c::PadsFromIds<Sercom0, PA08, PA09>;
46//! ```
47//!
48//! Instances of [`Pads`] are created using the [`new`](Pads::new) method.
49//!
50//! On SAMD21 and SAMx5x chips, [`new`](Pads::new) method automatically convert
51//! each pin to the correct [`PinMode`]. But for SAMD11 chips, users must
52//! manually convert each pin before calling the builder methods. This is a
53//! consequence of inherent ambiguities in the SAMD11 SERCOM pad definitions.
54//! Specifically, the same [`PinId`] can correspond to two different [`PadNum`]s
55//! for the *same* `Sercom`.
56//!
57//! ```no_run
58//! use atsamd_hal::pac::Peripherals;
59//! use atsamd_hal::gpio::Pins;
60//! use atsamd_hal::sercom::{Sercom0, i2c};
61//!
62//! let mut peripherals = Peripherals::take().unwrap();
63//! let pins = Pins::new(peripherals.PORT);
64//! let pads = i2c::Pads::<Sercom0>::new(pins.pa08, pins.pa09);
65//! ```
66//!
67//! # [`Config`]
68//!
69//! Next, create a [`Config`] struct, which represents the I2C peripheral in its
70//! disabled state. A [`Config`] is specified with one type parameters, the
71//! [`Pads`] type.
72//!
73//! Upon creation, the [`Config`] takes ownership of both the [`Pads`] struct
74//! and the PAC [`Sercom`] struct. It takes a reference to the PM, so that it
75//! can enable the APB clock, and it takes a frequency to indicate the GCLK
76//! configuration. Users are responsible for correctly configuring the GCLK.
77//!
78//! ```no_run
79//! use atsamd_hal::gpio::{PA08, PA09};
80//! use atsamd_hal::sercom::{Sercom0, i2c};
81//!
82//! type Pads = i2c::PadsFromIds<Sercom0, PA08, PA09>;
83//! type Config = i2c::Config<Pads>;
84//!
85//! let pm = peripherals.PM;
86//! let sercom = peripherals.SERCOM0;
87//! // Configure GCLK for 10 MHz
88//! let freq = 10.mhz();
89//! let config = i2c::Config::new(&pm, sercom, pads, freq);
90//! ```
91//!
92//! The [`Config`] struct can configure the peripheral in one of two ways:
93//!
94//! * A set of methods is provided to use in a builder pattern: for example
95//! [`baud`](Config::baud), [`run_in_standby`](Config::run_in_standby), etc.
96//! These methods take `self` and return `Self`.
97//! * A set of methods is provided to use as setters: for example
98//! [`set_baud`](Config::set_baud),
99//! [`set_run_in_standby`](Config::set_run_in_standby), etc. These methods
100//! take `&mut self` and return nothing.
101//!
102//! In any case, the peripheral setup ends with a call to [`enable`], which
103//! consumes the [`Config`] and returns an enabled [`I2c`] peripheral.
104//!
105//! ```no_run
106//! let i2c = i2c::Config::new(&pm, sercom, pads, freq)
107//! .baud(1.mhz())
108//! .enable();
109//! ```
110//!
111//! Alternatively,
112//!
113//! ```no_run
114//! let i2c = i2c::Config::new(&mclk, sercom, pads, freq);
115//! i2c.set_baud(1.mhz());
116//! let i2c = i2c.enable();
117//! ```
118//!
119//! ## Reading the current configuration
120//!
121//! It is possible to read the current configuration by using the getter methods
122//! provided: for example [`get_baud`](Config::get_baud),
123//! [`get_run_in_standby`](Config::get_run_in_standby), etc.
124//!
125//! # [`I2c`]
126//!
127//! [`I2c`] structs can only be created from a [`Config`]. They have one type
128//! parameter, representing the underlying [`Config`].
129//!
130//! Only the [`I2c`] struct can actually perform transactions. To do so, use the
131//! [`embedded_hal::i2c::I2c`] trait.
132//!
133//! ```
134//! use embedded_hal::i2c::I2c;
135//!
136//! i2c.write(0x54, 0x0fe).unwrap();
137//! ```
138//!
139//! # Reading the current configuration
140//!
141//! The `AsRef<Config<P>>` trait is implemented for `I2c<Config<P>>`. This means
142//! you can use the `get_` methods implemented for `Config`, since they take an
143//! `&self` argument.
144//!
145//! ```no_run
146//! // Assume i2c is a I2c<C<P>>
147//! let baud = i2c.as_ref().get_baud();
148//! ```
149//!
150//! # Reconfiguring
151//!
152//! The [`reconfigure`] method gives out an `&mut Config` reference, which can
153//! then use the `set_*` methods.
154//!
155//! ```no_run
156//! use atsamd_hal::sercom::i2c::I2c;
157//!
158//! // Assume config is a valid Duplex I2C Config struct
159//! let i2c = config.enable();
160//!
161//! // Send/receive data...
162//!
163//! // Reconfigure I2C peripheral
164//! i2c.reconfigure(|c| c.set_run_in_standby(false));
165//!
166//! // Disable I2C peripheral
167//! let config = i2c.disable();
168//! ```
169//!
170//! # Non-supported features
171//!
172//! * Slave mode is not supported at this time.
173//! * High-speed mode is not supported.
174//! * 4-wire mode is not supported.
175//! * 32-bit extension mode is not supported (SAMx5x). If you need to transfer
176//! slices, consider using the DMA methods instead <span class="stab
177//! portability" title="Available on crate feature `dma`
178//! only"><code>dma</code></span>.
179//!
180//! # Using I2C with DMA <span class="stab portability" title="Available on crate feature `dma` only"><code>dma</code></span>
181//!
182//! This HAL includes support for DMA-enabled I2C transfers. Use
183//! [`I2c::with_dma_channel`] to attach a DMA channel to the [`I2c`] struct. A
184//! DMA-enabled [`I2c`] implements the blocking
185//! [`embedded_hal::i2c::I2c`](crate::ehal::i2c::I2c) trait, which can be used
186//! to perform I2C transfers which are fast, continuous and low jitter, even if
187//! they are preemped by a higher priority interrupt.
188//!
189//!
190//! ```no_run
191//! use atsamd_hal::dmac::channel::{AnyChannel, Ready};
192//! use atsand_hal::sercom::i2c::{I2c, AnyConfig, Error};
193//! use atsamd_hal::embedded_hal::i2c::I2c;
194//! fn i2c_write_with_dma<A: AnyConfig, C: AnyChannel<Status = Ready>>(i2c: I2c<A>, channel: C, bytes: &[u8]) -> Result<(), Error>{
195//! // Attach a DMA channel
196//! let i2c = i2c.with_dma_channel(channel);
197//! i2c.write(0x54, bytes)?;
198//! }
199//! ```
200//!
201//! ## Limitations of using DMA with I2C
202//!
203//! * The I2C peripheral only supports continuous DMA read/writes of up to 255
204//! bytes. Trying to read/write with a transfer of 256 bytes or more will
205//! result in a panic. This also applies to using [`I2c::transaction`] with
206//! adjacent write/read operations of the same type; the total number of bytes
207//! across all adjacent operations must not exceed 256. If you need continuous
208//! transfers of 256 bytes or more, use the non-DMA [`I2c`] implementations.
209//!
210//! * When using [`I2c::transaction`] or [`I2c::write_read`], the
211//! [`embedded_hal::i2c::I2c`] specification mandates that a REPEATED START
212//! (instead of a STOP+START) is sent between transactions of a different type
213//! (read/write). Unfortunately, in DMA mode, the hardware is only capable of
214//! sending STOP+START. If you absolutely need repeated starts, the only
215//! workaround is to use the I2C without DMA.
216//!
217//! * Using [`I2c::transaction`] consumes significantly more memory than the
218//! other methods provided by [`embedded_hal::i2c::I2c`] (at least 256 bytes
219//! extra).
220//!
221//! * When using [`I2c::transaction`], up to 17 adjacent operations of the same
222//! type can be continuously handled by DMA without CPU intervention. If you
223//! need more than 17 adjacent operations of the same type, the transfer will
224//! reverted to using the byte-by-byte (non-DMA) implementation.
225//!
226//! All these limitations also apply to I2C transfers in async mode when using
227//! DMA. They do not apply to I2C transfers in async mode when not using DMA.
228//!
229//! # `async` operation <span class="stab portability" title="Available on crate feature `async` only"><code>async</code></span>
230//!
231//! An [`I2c`] can be used for `async` operations. Configuring an [`I2c`] in
232//! async mode is relatively simple:
233//!
234//! * Bind the corresponding `SERCOM` interrupt source to the SPI
235//! [`InterruptHandler`] (refer to the module-level [`async_hal`]
236//! documentation for more information).
237//! * Turn a previously configured [`I2c`] into an [`I2cFuture`] by calling
238//! [`I2c::into_future`]
239//! * Optionally, add a DMA channel by using [`I2cFuture::with_dma_channel`].
240//! The API is exactly the same whether a DMA channel is used or not.
241//! * Use the provided async methods for reading or writing to the I2C
242//! peripheral. [`I2cFuture`] implements [`embedded_hal_async::i2c::I2c`].
243//!
244//! `I2cFuture` implements `AsRef<I2c>` and `AsMut<I2c>` so that it can be
245//! reconfigured using the regular [`I2c`] methods.
246//!
247//! ## Considerations when using `async` [`I2c`] with DMA <span class="stab portability" title="Available on crate feature `async` only"><code>async</code></span> <span class="stab portability" title="Available on crate feature `dma` only"><code>dma</code></span>
248//!
249//! * An [`I2c`] struct must be turned into an [`I2cFuture`] by calling
250//! [`I2c::into_future`] before calling `with_dma_channel`. The DMA channel
251//! itself must also be configured in async mode by using
252//! [`DmaController::into_future`](crate::dmac::DmaController::into_future).
253//! If a DMA channel is added to the [`I2c`] struct before it is turned into
254//! an [`I2cFuture`], it will not be able to use DMA in async mode.
255//!
256//! ```
257//! // This will work
258//! let i2c = i2c.into_future().with_dma_channel(channel);
259//!
260//! // This won't
261//! let i2c = i2c.with_dma_channel(channel).into_future();
262//! ```
263//!
264//! ### Safety considerations
265//!
266//! In `async` mode, an I2C+DMA transfer does not require `'static` source and
267//! destination buffers. This, in theory, makes its use `unsafe`. However it is
268//! marked as safe for better ergonomics, and to enable the implementation of
269//! the [`embedded_hal_async::i2c::I2c`] trait.
270//!
271//! This means that, as an user, you **must** ensure that the [`Future`]s
272//! returned by the [`embedded_hal_async::i2c::I2c`] methods may never be
273//! forgotten through [`forget`] or by wrapping them with a [`ManuallyDrop`].
274//!
275//! The returned futures implement [`Drop`] and will automatically stop any
276//! ongoing transfers; this guarantees that the memory occupied by the
277//! now-dropped buffers may not be corrupted by running transfers.
278//!
279//! This means that using functions like [`futures::select_biased`] to implement
280//! timeouts is safe; transfers will be safely cancelled if the timeout expires.
281//!
282//! This also means that should you [`forget`] this [`Future`] after its first
283//! [`poll`] call, the transfer will keep running, ruining the now-reclaimed
284//! memory, as well as the rest of your day.
285//!
286//! * `await`ing is fine: the [`Future`] will run to completion.
287//! * Dropping an incomplete transfer is also fine. Dropping can happen, for
288//! example, if the transfer doesn't complete before a timeout expires.
289//! * Dropping an incomplete transfer *without running its destructor* is
290//! **unsound** and will trigger undefined behavior.
291//!
292//! ```ignore
293//! async fn always_ready() {}
294//!
295//! let mut buffer = [0x00; 10];
296//!
297//! // This is completely safe
298//! i2c.read(&mut buffer).await?;
299//!
300//! // This is also safe: we launch a transfer, which is then immediately cancelled
301//! futures::select_biased! {
302//! _ = i2c.read(&mut buffer)?,
303//! _ = always_ready(),
304//! }
305//!
306//! // This, while contrived, is also safe.
307//! {
308//! use core::future::Future;
309//!
310//! let future = i2c.read(&mut buffer);
311//! futures::pin_mut!(future);
312//! // Assume ctx is a `core::task::Context` given out by the executor.
313//! // The future is polled, therefore starting the transfer
314//! future.as_mut().poll(ctx);
315//!
316//! // Future is dropped here - transfer is cancelled.
317//! }
318//!
319//! // DANGER: This is an example of undefined behavior
320//! {
321//! use core::future::Future;
322//! use core::ops::DerefMut;
323//!
324//! let future = core::mem::ManuallyDrop::new(i2c.read(&mut buffer));
325//! futures::pin_mut!(future);
326//! // To actually make this example compile, we would need to wrap the returned
327//! // future from `i2c.read()` in a newtype that implements Future, because we
328//! // can't actually call as_mut() without being able to name the type we want
329//! // to deref to.
330//! let future_ref: &mut SomeNewTypeFuture = &mut future.as_mut();
331//! future.as_mut().poll(ctx);
332//!
333//! // Future is NOT dropped here - transfer is not cancelled, resulting un UB.
334//! }
335//! ```
336//!
337//! As you can see, unsoundness is relatively hard to come by - however, caution
338//! should still be exercised.
339//!
340//! [`enable`]: Config::enable
341//! [`disable`]: I2c::disable
342//! [`reconfigure`]: I2c::reconfigure
343//! [`bsp_pins`]: crate::bsp_pins
344//! [`Sercom`]: crate::sercom::Sercom
345//! [`Pad0`]: crate::sercom::pad::Pad0
346//! [`Pad1`]: crate::sercom::pad::Pad1
347//! [`Pad`]: crate::sercom::pad::Pad
348//! [`IoSet`]: crate::sercom::pad::IoSet
349//! [`IsPad`]: crate::sercom::pad::IsPad
350//! [`PadNum`]: crate::sercom::pad::PadNum
351//! [`Pin`]: crate::gpio::pin::Pin
352//! [`PinId`]: crate::gpio::pin::PinId
353//! [`PinMode`]: crate::gpio::pin::PinMode
354//! [`embedded_hal::i2c::I2c`]: crate::ehal::i2c::I2c
355//! [`I2c::transaction`]: crate::ehal::i2c::I2c::transaction
356//! [`I2c::write_read`]: crate::ehal::i2c::I2c::write_read
357//! [`async_hal`]: crate::async_hal
358//! [`forget`]: core::mem::forget
359//! [`ManuallyDrop`]: core::mem::ManuallyDrop
360//! [`Future`]: core::future::Future
361//! [`poll`]: core::future::Future::poll
362
363use atsamd_hal_macros::hal_module;
364
365#[hal_module(
366 any("sercom0-d11", "sercom0-d21") => "i2c/pads_thumbv6m.rs",
367 "sercom0-d5x" => "i2c/pads_thumbv7em.rs",
368)]
369mod pads {}
370
371pub use pads::*;
372
373mod reg;
374use reg::Registers;
375
376mod flags;
377pub use flags::*;
378
379mod config;
380pub use config::*;
381
382mod impl_ehal;
383
384#[cfg(feature = "async")]
385mod async_api;
386
387#[cfg(feature = "async")]
388pub use async_api::*;
389
390/// Word size for an I2C message
391pub type Word = u8;
392
393/// Inactive timeout configuration
394#[repr(u8)]
395#[derive(Clone, Copy)]
396pub enum InactiveTimeout {
397 /// Disabled
398 Disabled = 0x0,
399 /// 5-6 SCL cycles (50-60 us @ 100 kHz)
400 Us55 = 0x1,
401 ///10-11 SCL cycles (100-110 us @ 100 kHz)
402 Us105 = 0x2,
403 /// 20-21 SCL cycles (200-210 us @ 100 kHz)
404 Us205 = 0x3,
405}
406
407/// Abstraction over a I2C peripheral, allowing to perform I2C transactions.
408pub struct I2c<C: AnyConfig, D = crate::typelevel::NoneT> {
409 config: C,
410 _dma_channel: D,
411}
412
413impl<C: AnyConfig, D> I2c<C, D> {
414 /// Obtain a pointer to the `DATA` register. Necessary for DMA transfers.
415 #[inline]
416 pub fn data_ptr(&self) -> *mut Word {
417 self.config.as_ref().registers.data_ptr()
418 }
419
420 /// Read the interrupt flags
421 #[inline]
422 pub fn read_flags(&self) -> Flags {
423 self.config.as_ref().registers.read_flags()
424 }
425
426 /// Clear interrupt status flags
427 #[inline]
428 pub fn clear_flags(&mut self, flags: Flags) {
429 self.config.as_mut().registers.clear_flags(flags);
430 }
431
432 /// Enable interrupts for the specified flags.
433 #[inline]
434 pub fn enable_interrupts(&mut self, flags: Flags) {
435 self.config.as_mut().registers.enable_interrupts(flags);
436 }
437
438 /// Disable interrupts for the specified flags.
439 #[inline]
440 pub fn disable_interrupts(&mut self, flags: Flags) {
441 self.config.as_mut().registers.disable_interrupts(flags);
442 }
443
444 /// Read the status flags
445 #[inline]
446 pub fn read_status(&self) -> Status {
447 self.config.as_ref().registers.read_status()
448 }
449
450 /// Clear the status flags
451 #[inline]
452 pub fn clear_status(&mut self, status: Status) {
453 self.config.as_mut().registers.clear_status(status);
454 }
455
456 #[cfg(feature = "dma")]
457 #[inline]
458 pub(super) fn start_dma_write(&mut self, address: u8, xfer_len: u8) {
459 self.config
460 .as_mut()
461 .registers
462 .start_dma_write(address, xfer_len)
463 }
464
465 #[cfg(feature = "dma")]
466 #[inline]
467 pub(super) fn start_dma_read(&mut self, address: u8, xfer_len: u8) {
468 self.config
469 .as_mut()
470 .registers
471 .start_dma_read(address, xfer_len)
472 }
473
474 #[cfg(feature = "dma")]
475 #[inline]
476 pub(super) fn check_bus_status(&self) -> Result<(), Error> {
477 self.config.as_ref().registers.check_bus_status()
478 }
479
480 #[inline]
481 fn do_write(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Error> {
482 self.config.as_mut().registers.do_write(addr, bytes)
483 }
484
485 /// Continue a write operation that was issued before with
486 /// [`do_write`](Self::do_write) or [`continue_write`](Self::continue_write)
487 /// without a repeated start condition in between
488 #[inline]
489 fn continue_write(&mut self, bytes: &[u8]) -> Result<(), Error> {
490 self.config.as_mut().registers.continue_write(bytes)
491 }
492
493 #[inline]
494 fn do_read(&mut self, addr: u8, bytes: &mut [u8]) -> Result<(), Error> {
495 self.config.as_mut().registers.do_read(addr, bytes)
496 }
497
498 /// Continue a read operation that was issued before with
499 /// [`do_read`](Self::do_read) or [`continue_read`](Self::continue_read)
500 /// without a repeated start condition in between
501 #[inline]
502 fn continue_read(&mut self, bytes: &mut [u8]) -> Result<(), Error> {
503 self.config.as_mut().registers.continue_read(bytes)
504 }
505
506 #[inline]
507 fn do_write_read(&mut self, addr: u8, bytes: &[u8], buffer: &mut [u8]) -> Result<(), Error> {
508 self.config
509 .as_mut()
510 .registers
511 .do_write_read(addr, bytes, buffer)
512 }
513 #[inline]
514 fn cmd_stop(&mut self) {
515 self.config.as_mut().registers.cmd_stop()
516 }
517
518 /// Reconfigure the I2C peripheral.
519 ///
520 /// Calling this method will temporarily disable the SERCOM peripheral, as
521 /// some registers are enable-protected. This may interrupt any ongoing
522 /// transactions.
523 ///
524 /// ```
525 /// use atsamd_hal::sercom::i2c::I2c;
526 /// i2c.reconfigure(|c| c.set_run_in_standby(false));
527 /// ```
528 #[inline]
529 pub fn reconfigure<F>(&mut self, update: F)
530 where
531 F: FnOnce(&mut SpecificConfig<C>),
532 {
533 self.config.as_mut().registers.enable_peripheral(false);
534 update(self.config.as_mut());
535 self.config.as_mut().registers.enable_peripheral(true);
536 }
537
538 /// Disable the I2C peripheral and return the underlying [`Config`]
539 #[inline]
540 pub fn disable(self) -> C {
541 let mut config = self.config;
542 config.as_mut().registers.disable();
543 config
544 }
545}
546
547impl<C: AnyConfig> I2c<C> {
548 /// Attach a DMA channel to this [`I2c`]. Its
549 /// [`embedded_hal::i2c::I2c`](crate::ehal::i2c::I2c) implementation will
550 /// use DMA to carry out its transactions.
551 #[cfg(feature = "dma")]
552 #[inline]
553 pub fn with_dma_channel<Chan: crate::dmac::AnyChannel<Status = crate::dmac::Ready>>(
554 self,
555 channel: Chan,
556 ) -> I2c<C, Chan> {
557 I2c {
558 config: self.config,
559 _dma_channel: channel,
560 }
561 }
562}
563
564#[cfg(feature = "dma")]
565impl<C, D, S> I2c<C, D>
566where
567 C: AnyConfig,
568 D: crate::dmac::AnyChannel<Status = S>,
569 S: crate::dmac::ReadyChannel,
570{
571 /// Reclaim the DMA channel. Any subsequent I2C operations will no longer
572 /// use DMA.
573 pub fn take_dma_channel(self) -> (I2c<C, crate::typelevel::NoneT>, D) {
574 (
575 I2c {
576 config: self.config,
577 _dma_channel: crate::typelevel::NoneT,
578 },
579 self._dma_channel,
580 )
581 }
582}
583
584impl<P: PadSet> AsRef<Config<P>> for I2c<Config<P>> {
585 #[inline]
586 fn as_ref(&self) -> &Config<P> {
587 self.config.as_ref()
588 }
589}