Expand description

Use the SERCOM peripheral for I2C communications

Configuring an I2C peripheral occurs in three steps. First, you must create a set of Pads for use by the peripheral. Next, you assemble pieces into a Config struct. After configuring the peripheral, you then enable it, yielding a functional I2c struct. Transactions are performed using the i2c traits from embedded HAL.

Pads

A Sercom uses two Pins as peripheral Pads, but only certain Pin combinations are acceptable. In particular, all Pins must be mapped to the same Sercom, and SDA is always Pad0, while SCL is always Pad1 (see the datasheet). This HAL makes it impossible to use invalid Pin/Pad combinations, and the Pads struct is responsible for enforcing these constraints.

A Pads type takes three or four type parameters, depending on the chip. The first type always specifies the Sercom. On SAMx5x chips, the second type specifies the IoSet. The remaining two, SDA and SCL represent the SDA and SCL pads respectively. A Pad is just a Pin configured in the correct PinMode that implements IsPad. The bsp_pins! macro can be used to define convenient type aliases for Pad types.

use atsamd_hal::gpio::{PA08, PA09, AlternateC};
use atsamd_hal::sercom::{Sercom0, i2c};
use atsamd_hal::typelevel::NoneT;

// SAMx5x-specific imports
use atsamd_hal::sercom::pad::IoSet1;

type Sda = Pin<PA08, AlternateC>;
type Scl = Pin<PA09, AlternateC>;

// SAMD11/SAMD21 version
type Pads = i2c::Pads<Sercom0, Sda, Scl>;
// SAMx5x version
type Pads = i2c::Pads<Sercom0, IoSet1, Sda, Scl>;

Alternatively, you can use the PadsFromIds alias to define a set of Pads in terms of PinIds instead of Pins. This is useful when you don’t have Pin aliases pre-defined.

use atsamd_hal::gpio::{PA08, PA09};
use atsamd_hal::sercom::{Sercom0, i2c};

type Pads = i2c::PadsFromIds<Sercom0, PA08, PA09>;

Instances of Pads are created using the new method.

On SAMD21 and SAMx5x chips, new method automatically convert each pin to the correct PinMode. But for SAMD11 chips, users must manually convert each pin before calling the builder methods. This is a consequence of inherent ambiguities in the SAMD11 SERCOM pad definitions. Specifically, the same PinId can correspond to two different PadNums for the same Sercom.

use atsamd_hal::pac::Peripherals;
use atsamd_hal::gpio::Pins;
use atsamd_hal::sercom::{Sercom0, i2c};

let mut peripherals = Peripherals::take().unwrap();
let pins = Pins::new(peripherals.PORT);
let pads = i2c::Pads::<Sercom0>::new(pins.pa08, pins.pa09);

Config

Next, create a Config struct, which represents the I2C peripheral in its disabled state. A Config is specified with one type parameters, the Pads type.

Upon creation, the Config takes ownership of both the Pads struct and the PAC Sercom struct. It takes a reference to the PM, so that it can enable the APB clock, and it takes a frequency to indicate the GCLK configuration. Users are responsible for correctly configuring the GCLK.

use atsamd_hal::gpio::{PA08, PA09};
use atsamd_hal::sercom::{Sercom0, i2c};

type Pads = i2c::PadsFromIds<Sercom0, PA08, PA09>;
type Config = i2c::Config<Pads>;

let pm = peripherals.PM;
let sercom = peripherals.SERCOM0;
// Configure GCLK for 10 MHz
let freq = 10.mhz();
let config = i2c::Config::new(&pm, sercom, pads, freq);

The Config struct can configure the peripheral in one of two ways:

  • A set of methods is provided to use in a builder pattern: for example baud, run_in_standby, etc. These methods take self and return Self.
  • A set of methods is provided to use as setters: for example set_baud, set_run_in_standby, etc. These methods take &mut self and return nothing.

In any case, the peripheral setup ends with a call to enable, which consumes the Config and returns an enabled I2c peripheral.

let i2c = i2c::Config::new(&pm, sercom, pads, freq)
    .baud(1.mhz())
    .enable();

Alternatively,

let i2c = i2c::Config::new(&mclk, sercom, pads, freq);
    i2c.set_baud(1.mhz());
    let i2c = i2c.enable();

Reading the current configuration

It is possible to read the current configuration by using the getter methods provided: for example get_baud, get_run_in_standby, etc.

I2c

I2c structs can only be created from a Config. They have one type parameter, representing the underlying Config.

Only the I2c struct can actually perform transactions. To do so, use the embedded HAL traits, like i2c::WriteRead, i2c::Read and i2c::Write.

use embedded_hal::blocking::i2c::Write;

i2c.write(0x54, 0x0fe)

Reading the current configuration

The AsRef<Config<P>> trait is implemented for I2c<Config<P>>. This means you can use the get_ methods implemented for Config, since they take an &self argument.

// Assume i2c is a I2c<C<P>>
let baud = i2c.as_ref().get_baud();

Reconfiguring

The reconfigure method gives out an &mut Config reference, which can then use the set_* methods.

use atsamd_hal::sercom::i2c::I2c;
use atsamd_hal::time::*;

// Assume config is a valid Duplex I2C Config struct
let i2c = config.enable();

// Send/receive data...

// Reconfigure I2C peripheral
i2c.reconfigure(|c| c.set_run_in_standby(false));

// Disable I2C peripheral
let config = i2c.disable();

Non-supported features

  • Slave mode is not supported at this time.
  • High-speed mode is not supported.
  • 4-wire mode is not supported.
  • 32-bit extension mode is not supported (SAMx5x). If you need to transfer slices, consider using the DMA methods instead. The dma Cargo feature must be enabled.

Structs

A configurable, disabled I2C peripheral
Interrupt bitflags for I2C transactions
Abstraction over a I2C peripheral, allowing to perform I2C transactions.
Container for a set of SERCOM Pads
Status flags for I2C transactions

Enums

Type representing the current bus state
Errors available for I2C transactions
Inactive timeout configuration

Traits

Type class for all possible Config types
Type-level function to recover the Pad types from a generic set of Pads

Type Definitions

Type alias to recover the specific Sercom type from an implementation of AnyConfig
Define a set of Pads using PinIds instead of Pins
Type alias to recover the specific Config type from an implementation of AnyConfig
Word size for an I2C message