Platform-agnostic driver to communicate with devices that implement the Adafruit Seesaw firmware. See the Seesaw guide for more information on the firmware.
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
.
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");
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
}
}
All devices implement the SeesawDevice
trait and have the same constructor function, along with lots of other device-specific information.
Product value | Const method on all SeesawDevice s |
Notes |
---|---|---|
Default Address | Device::default_addr() |
|
Hardware ID | Device::hardware_id() |
This value depends on the host MCU of the device |
Product ID | Device::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.
let neokeys = NeoKey1x4::new_with_default_addr(seesaw.acquire_driver());
let neokeys = NeoKey1x4::new(0x00, seesaw.acquire_driver());
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.
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.
Let's assume you have some future Adafruit Neokey-esque device that has 6 buttons and 6 neopixels.
seesaw_device! {
name: Neokey2x3,
hardware_id: HardwareId::_,
product_id: _,
default_addr: _,
modules: [
GpioModule,
NeopixelModule { num_leds: 6, pin: _ },
]
}
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 NeoKey1x4");
Seesaw Module | Implemented |
---|---|
ADC | ✅ |
EEPROM | ⬜️ |
Encoder | ✅ |
GPIO | ✅ |
Keypad | ⬜️ Pending |
Neopixel | ✅ |
Sercom0 | ⬜️ |
Spectrum | ⬜️ |
Status | ✅ |
Timer | ✅ |
Touch | ⬜️ |
Device | Product ID | MCU | Implemented |
---|---|---|---|
ArcadeButton1x4 | 5296 | ATTiny8x7 | ✅ |
NeoKey1x4 | 4980 | SAMD09 | ✅ |
NeoSlider | 5295 | ATTiny8x7 | ✅ |
NeoTrellis | 3954 | SAMD09 | ⬜️ Pending |
RotaryEncoder | 4991 | SAMD09 | ✅ |
- ⬜️ Ask Adafruit nicely for a list of their products that use the Seesaw firmware
- ⬜️ Setup github actions for CI
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.