Crate adafruit_seesaw

Source
Expand description

Adafruit Seesaw Logo

crates.io page docs.rs CI

§Introduction

What is Seesaw? From Adafruit’s guide: “Adafruit seesaw is a near-universal converter framework which allows you to add and extend hardware support to any I2C-capable microcontroller or microcomputer. Instead of getting separate I2C GPIO expanders, ADCs, PWM drivers, etc, seesaw can be configured to give a wide range of capabilities.”

This crate aims to be a functionally-equivalent Rust driver for Adafruit’s own C++ driver.

A note on terminology: Adafruit’s Seesaw firmware refers to blocks of device functionality/capabilities as “modules”. e.g. a Seesaw device that has controllable neopixels will have the GPIO module loaded and available in its firmware. Don’t confuse them with Rust modules! This crate exports a module called modules that contains all the modules for the Seesaw devices.

If you want to learn more about modules, this page in the Seesaw guide explains it pretty well.

§Usage

The library follows the patterns of the shared-bus library so that multiple devices can be connected and communicated with without owning the I2C bus.

Communicating with Seesaw devices requires a bus that implements both I2C traits and Delay from embedded-hal.

§#![no_std] (single-threaded)

If you’re communicating with devices within a single thread, use the SeesawRefCell typed struct, which uses the RefCellBus wrapper to enable sharing of the bus across multiple Seesaw devices.

// Setup on an STM32F405
let cp = cortex_m::Peripherals::take().unwrap();
let clocks = dp.RCC.constrain().cfgr.freeze();
let delay = cp.SYST.delay(&clocks);
let i2c = I2c::new(dp.I2C1, (scl, sda), 400.kHz(), &clocks);
let seesaw = SeesawRefCell::new(delay, i2c);
let mut neokeys = NeoKey1x4::new_with_default_addr(seesaw.acquire_driver())
    .init()
    .expect("Failed to start NeoKey1x4");

§std (multi-threaded)

This requires turning on the std feature flag.

For multi-threaded purposes, use the SeesawStdMutex typed struct, which wraps the bus in a std Mutex.

Example usage of using multi-threaded Seesaw in a std context, running on an ESP32-S3:

use adafruit_seesaw::{devices::RotaryEncoder, prelude::*, SeesawStdMutex};
use esp_idf_hal::{
    self,
    delay::Delay,
    gpio::PinDriver,
    i2c::{I2cConfig, I2cDriver},
    peripherals::Peripherals,
    prelude::*,
};
use std::time::Duration;

fn main() -> Result<(), anyhow::Error> {
    esp_idf_hal::sys::link_patches();
    esp_idf_svc::log::EspLogger::initialize_default();

    // System
    let peripherals = Peripherals::take().unwrap();
    let mut i2c_power = PinDriver::output(peripherals.pins.gpio7).unwrap();
    i2c_power.set_low()?;
    std::thread::sleep(Duration::from_millis(333));

    // I2C
    let (sda, scl) = (peripherals.pins.gpio3, peripherals.pins.gpio4);
    let config = I2cConfig::new().baudrate(400.kHz().into());
    let i2c = I2cDriver::new(peripherals.i2c0, sda, scl, &config)?;
    i2c_power.set_high()?;
    std::thread::sleep(Duration::from_millis(333));

    let seesaw: &'static _ = {
        use once_cell::sync::OnceCell;
        static MANAGER: OnceCell<SeesawStdMutex<(Delay, I2cDriver<'_>)>> =
            OnceCell::new();

        match MANAGER.set(SeesawStdMutex::new(Delay::new_default(), i2c)) {
            Ok(_) => MANAGER.get(),
            Err(_) => None,
        }
    }
    .unwrap();

    let _encoder =
        RotaryEncoder::new_with_default_addr(seesaw.acquire_driver())
            .init()
            .expect("Failed to start rotary encoder.");

    loop {
        // Do stuff with rotary encoder
    }
}

§Communicating with a Device

At a minimum, a device implements the SeesawDevice trait which specifies a common constructor function, along with lots of other device-specific information specified as const values:

Product valueConst method on all SeesawDevicesNotes
Default AddressDevice::default_addr()
Hardware IDDevice::hardware_id()This value depends on the host MCU of the device
Product IDDevice::product_id()You can use this value to go to the product page at adafruit.com/product/$product_id

Let’s talk to a NeoKey1x4 using the seesaw manager we created above.

§Using the default address

let neokeys = NeoKey1x4::new_with_default_addr(seesaw.acquire_driver());

§Using a custom address

let neokeys = NeoKey1x4::new(0x00, seesaw.acquire_driver());

§Initializing

Devices that implement SeesawDevice also implmement SeesawDeviceInit, which defines a device-specific init function for setting up a device’s hardware functionality. The intention is to run a set of sensible defaults so you don’t have to remember to do it yourself.

let neokeys = NeoKey1x4::new_with_default_addr(seesaw.acquire_driver())
    .init()
    .expect("Failed to initialize NeoKey1x4");

For instance, the init function for our Neokey1x4 does the following:

  • Resets the device
  • Reads & verifies the device hardware ID
  • Enables the on-device neopixels
  • Enables the on-device buttons

Calling init is of course optional, but without it you’ll have to handle initialization yourself.

§Predefined Devices

The crate comes with a few predefined devices that you can use. Their documentation is available here.

DeviceProduct IDMCUNotes
ArcadeButton1x45296ATTiny8x7
NeoKey1x44980SAMD09
NeoSlider5295ATTiny8x7
NeoTrellis3954SAMD09Example demo video neotrellis_ripples.rs
NeoRotary45752ATTiny8x7
RotaryEncoder4991SAMD09

§Creating Your Own Devices

So far, this library only implements a few Seesaw devices (i.e., the ones that I currently own). You can define your own device using the seesaw_device! macro and then configuring its modules using their respective traits.

Let’s assume you have some future Adafruit Neokey-esque device that has 6 buttons and 6 neopixels.

You call the seesaw_device! macro with information about the device:

seesaw_device! {
    name: Neokey2x3,
    hardware_id: HardwareId::_,
    product_id: _,
    default_addr: _
}

Then implement the module traits for its various capabilities:

impl<D: Driver> GpioModule<D> for Neokey2x3<D> {}
impl<D: Driver> NeopixelModule<D> for Neokey2x3<D> {
    type Color = Neokey2x3Color;

    const N_LEDS: usize = 6;
    const PIN: u8 = _;
}

The last thing you might want to do is implmeent the SeesawDeviceInit trait to handle the device intialization:

impl<D: Driver> SeesawDeviceInit<D> for Neokey2x3<D> {
    fn init(mut self) -> Result<Self, Self::Error> {
        self.reset_and_verify_seesaw()
            .and_then(|_| self.enable_neopixel())
            .and_then(|_| self.enable_button_pins())
            .map(|_| self)
    }
}

Now you can use the new device as you would any other:

let neokeys = NeoKey2x3::new_with_default_addr(seesaw.acquire_driver())
    .init()
    .expect("Failed to initialize NeoKey2x3");

§Implementation Progress

Seesaw ModuleImplemented
ADC
EEPROM⬜️
Encoder
GPIO
Keypad
Neopixel
Sercom0⬜️
Spectrum⬜️
Status
Timer
Touch⬜️

§Known Issues

These issues are based solely on my own experience testing this crate on my own devices. Any confirmation of similar or contrary experience is much appreciated.

§.version() returns an incorrect product ID for some devices

The .version() function–a function of the Status module–returns incorrect dates and product IDs for some devices. I’m not too concerned about the date, but product ID matters in the case of identifying a device.

Works forReturns
NeoKey1x4SeesawVersion { id: 4980, year: 2036, month: 5, day: 5 }
NeoRotary4SeesawVersion { id: 5752, year: 2023, month: 6, day: 27 }
NeoSliderSeesawVersion { id: 5295, year: 2021, month: 11, day: 16 }
RotaryEncoderSeesawVersion { id: 4991, year: 2035, month: 5, day: 5 }
Does not work forReturnsShould be
NeoTrellisSeesawVersion { id: 0, year: 2050, month: 10, day: 4 }3954
Neopixel DriverSeesawVersion { id: 5742, year: 2023, month: 5, day: 20 }5766

§License

adafruit-seesaw is licensed under either of

  • Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
  • MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)

at your option.


Not affiliated with, nor officially supported by Adafruit.

Re-exports§

pub use rgb;

Modules§

bus
devices
modules
prelude

Macros§

seesaw_device

Structs§

Seesaw
The owner of the driver from which new seesaw devices can be created

Enums§

SeesawError

Traits§

Driver
Blanket trait for anything that implements I2C and a delay
DriverExt

Type Aliases§

SeesawRefCell
SeesawStdMutex