Skip to content

Commit

Permalink
Switch to async I2C
Browse files Browse the repository at this point in the history
  • Loading branch information
zargony committed Aug 27, 2024
1 parent 7df6579 commit 625d838
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 63 deletions.
85 changes: 70 additions & 15 deletions firmware/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 5 additions & 4 deletions firmware/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,13 @@ git2 = "0.19"

[dependencies]
display-interface = "0.5"
embassy-embedded-hal = "0.2"
embassy-executor = "0.5"
embassy-futures = "0.1"
embassy-sync = "0.6"
embassy-time = { version = "0.3", features = ["generic-queue"] }
embedded-graphics = "0.8"
embedded-hal = "1.0"
embedded-hal-async = "1.0"
embedded-hal-bus = { version = "0.2", features = ["async"] }
esp-backtrace = { version = "0.13", features = ["esp32c3", "custom-halt", "panic-handler", "exception-handler", "println"] }
esp-hal = { version = "0.19", features = ["esp32c3", "async"] }
esp-hal-embassy = { version = "0.2", features = ["esp32c3"] }
Expand All @@ -56,6 +56,7 @@ pn532 = "0.4"
static_cell = "2.1"
u8g2-fonts = "0.4"

# SSD1306 driver (>0.8.4) with embedded-hal 1.0 and display-interface 0.5 support
# SSD1306 driver (>0.8.4) with embedded-hal-async 1.0 and display-interface 0.5 support
# See https://github.com/rust-embedded-community/ssd1306/pull/213
ssd1306 = { git = "https://github.com/rust-embedded-community/ssd1306", rev = "53039a806ffc77bbab0ba150d39b2a7aaa3eae03" }
# and https://github.com/rust-embedded-community/ssd1306/pull/214
ssd1306 = { git = "https://github.com/rust-embedded-community/ssd1306", rev = "35ec056c11f2889dd519e554d710a70b37ccf682", features = ["async"] }
42 changes: 21 additions & 21 deletions firmware/src/display.rs
Original file line number Diff line number Diff line change
@@ -1,70 +1,70 @@
use crate::screen::{self, Screen};
use embedded_graphics::pixelcolor::BinaryColor;
use embedded_graphics::prelude::*;
use embedded_hal::i2c::I2c;
use embedded_hal_async::i2c::I2c;
use log::{debug, info};
use ssd1306::mode::{BufferedGraphicsMode, DisplayConfig};
use ssd1306::mode::{BufferedGraphicsModeAsync, DisplayConfigAsync};
use ssd1306::prelude::I2CInterface;
use ssd1306::rotation::DisplayRotation;
use ssd1306::size::DisplaySize128x64;
use ssd1306::Ssd1306;

// The `ssd1306` crate unfortunately doesn't support async yet (though `display-interface`,
// `display-interface-i2c` and `embedded-hal-bus` do), so we can't use async here yet.
// See also https://github.com/rust-embedded-community/ssd1306/pull/189
use ssd1306::Ssd1306Async;

/// Display error
pub type Error = screen::Error<display_interface::DisplayError>;

/// Convenient hardware-agnostic display driver
pub struct Display<I2C> {
driver: Ssd1306<I2CInterface<I2C>, DisplaySize128x64, BufferedGraphicsMode<DisplaySize128x64>>,
driver: Ssd1306Async<
I2CInterface<I2C>,
DisplaySize128x64,
BufferedGraphicsModeAsync<DisplaySize128x64>,
>,
}

impl<I2C: I2c> Display<I2C> {
/// Create display driver and initialize display hardware
pub fn new(i2c: I2C, addr: u8) -> Result<Self, Error> {
pub async fn new(i2c: I2C) -> Result<Self, Error> {
debug!("Display: Initializing SSD1306...");

// Build SSD1306 driver and switch to buffered graphics mode
let mut driver = Ssd1306::new(
I2CInterface::new(i2c, addr, 0x40),
let mut driver = Ssd1306Async::new(
I2CInterface::new(i2c, 0x3c, 0x40),
DisplaySize128x64,
DisplayRotation::Rotate0,
)
.into_buffered_graphics_mode();

// Initialize and clear display
driver.init()?;
driver.init().await?;
driver.clear(BinaryColor::Off)?;
driver.flush()?;
driver.flush().await?;

info!("Display: SSD1306 initialized");
Ok(Self { driver })
}

/// Turn display off
pub fn turn_off(&mut self) -> Result<(), Error> {
pub async fn turn_off(&mut self) -> Result<(), Error> {
debug!("Display: Power off");
self.driver.set_display_on(false)?;
self.driver.set_display_on(false).await?;
Ok(())
}

/// Clear display
#[allow(dead_code)]
pub fn clear(&mut self) -> Result<(), Error> {
pub async fn clear(&mut self) -> Result<(), Error> {
self.driver.clear(BinaryColor::Off)?;
self.driver.flush()?;
self.driver.set_display_on(true)?;
self.driver.flush().await?;
self.driver.set_display_on(true).await?;
Ok(())
}

/// Show screen
pub fn screen<S: Screen>(&mut self, screen: &S) -> Result<(), Error> {
pub async fn screen<S: Screen>(&mut self, screen: &S) -> Result<(), Error> {
self.driver.clear(BinaryColor::Off)?;
screen.draw(&mut self.driver)?;
self.driver.flush()?;
self.driver.set_display_on(true)?;
self.driver.flush().await?;
self.driver.set_display_on(true).await?;
Ok(())
}
}
19 changes: 10 additions & 9 deletions firmware/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@ mod screen;
mod ui;
mod wifi;

use core::cell::RefCell;
use embassy_embedded_hal::shared_bus::asynch::i2c::I2cDevice;
use embassy_executor::Spawner;
use embedded_hal_bus::i2c::RefCellDevice;
use embassy_sync::{blocking_mutex::raw::NoopRawMutex, mutex::Mutex};
use esp_backtrace as _;
use esp_hal::clock::ClockControl;
use esp_hal::gpio::any_pin::AnyPin;
Expand Down Expand Up @@ -124,23 +124,24 @@ async fn main(_spawner: Spawner) {
info!("Touch 'n Drink {VERSION_STR} ({GIT_SHA_STR})");

// Initialize I2C controller
let i2c = RefCell::new(I2C::new(
let i2c = I2C::new_async(
peripherals.I2C0,
io.pins.gpio9,
io.pins.gpio10,
100.kHz(), // Standard-Mode: 100 kHz, Fast-Mode: 400 kHz
&clocks,
None,
));
);
// Share I2C bus. Since the mcu is single-core and I2C is not used in interrupts, I2C access
// cannot be preempted and we can safely use a NoopMutex for shared access.
let i2c: Mutex<NoopRawMutex, _> = Mutex::new(i2c);

// Initialize display
let display_i2c = RefCellDevice::new(&i2c);
let mut display = match display::Display::new(display_i2c, 0x3c) {
let mut display = match display::Display::new(I2cDevice::new(&i2c)).await {
Ok(disp) => disp,
// Panic on failure since without a display there's no reasonable way to tell the user
Err(err) => panic!("Display initialization failed: {:?}", err),
};
let _ = display.screen(&screen::Splash);
let _ = display.screen(&screen::Splash).await;

// Initialize keypad
let keypad = keypad::Keypad::new(
Expand All @@ -159,7 +160,7 @@ async fn main(_spawner: Spawner) {

// Initialize NFC reader
let nfc_irq = AnyInput::new(io.pins.gpio20, Pull::Up);
let nfc = match nfc::Nfc::new(RefCellDevice::new(&i2c), nfc_irq).await {
let nfc = match nfc::Nfc::new(I2cDevice::new(&i2c), nfc_irq).await {
Ok(nfc) => nfc,
// Panic on failure since an initialization error indicates a serious error
Err(err) => panic!("NFC reader initialization failed: {:?}", err),
Expand Down
12 changes: 7 additions & 5 deletions firmware/src/nfc.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use core::convert::Infallible;
use core::fmt::{self, Debug};
use embassy_time::{with_timeout, Duration, Timer};
use embedded_hal::i2c::I2c;
use embedded_hal_async::digital::Wait;
use embedded_hal_async::i2c::I2c;
use log::{debug, info, warn};
use pn532::i2c::{I2C_ADDRESS, PN532_I2C_READY};
use pn532::requests::{BorrowedRequest, Command, SAMMode};
Expand Down Expand Up @@ -35,10 +35,10 @@ const PN532_TO_HOST: u8 = 0xD5;
pub enum Error {
/// PN532 error (with static interface error type)
#[allow(dead_code)]
Pn532(Pn532Error<embedded_hal::i2c::ErrorKind>),
Pn532(Pn532Error<embedded_hal_async::i2c::ErrorKind>),
}

impl<E: embedded_hal::i2c::Error> From<Pn532Error<E>> for Error {
impl<E: embedded_hal_async::i2c::Error> From<Pn532Error<E>> for Error {
fn from(err: Pn532Error<E>) -> Self {
// Convert generic Pn532Error::InterfaceError(E: embedded_hal::i2c::Error) to non-generic
// Pn532Error::InterfaceError(embedded_hal::i2c::ErrorKind) to avoid generics in this type
Expand Down Expand Up @@ -85,7 +85,7 @@ impl<I2C: I2c, IRQ: Wait<Error = Infallible>> Interface for I2CInterfaceWithIrq<
type Error = I2C::Error;

async fn write(&mut self, frame: &[u8]) -> Result<(), Self::Error> {
self.i2c.write(I2C_ADDRESS, frame)
self.i2c.write(I2C_ADDRESS, frame).await
}

async fn wait_ready(&mut self) -> Result<(), Self::Error> {
Expand All @@ -103,7 +103,9 @@ impl<I2C: I2c, IRQ: Wait<Error = Infallible>> Interface for I2CInterfaceWithIrq<
// self.i2c.transaction(I2C_ADDRESS, &mut [Operation::Read(&mut buf), Operation::Read(frame)])?;
let mut buf = [0; BUFFER_SIZE + 1];
// TODO: I2C communication should be asynchronous as well
self.i2c.read(I2C_ADDRESS, &mut buf[..frame.len() + 1])?;
self.i2c
.read(I2C_ADDRESS, &mut buf[..frame.len() + 1])
.await?;
debug_assert_eq!(buf[0], PN532_I2C_READY, "PN532 read while not ready");
frame.copy_from_slice(&buf[1..frame.len() + 1]);
Ok(())
Expand Down
Loading

0 comments on commit 625d838

Please sign in to comment.