Skip to content

Commit

Permalink
Add async support
Browse files Browse the repository at this point in the history
use maybe_async_cfg to add an async interface as well, enabled by the
"async" feature. When enabled an Ssd1306Async struct is added with the
same api as Ssd1306, only async.
  • Loading branch information
sjoerdsimons authored and eldruin committed Aug 29, 2024
1 parent dbeb781 commit ad2f001
Show file tree
Hide file tree
Showing 12 changed files with 752 additions and 184 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ jobs:
with:
toolchain: 1.75
- run: cargo build --lib --target x86_64-unknown-linux-gnu
- run: cargo build --lib --target x86_64-unknown-linux-gnu --features async
- run: cargo doc --target x86_64-unknown-linux-gnu
- run: cargo doc --target x86_64-unknown-linux-gnu --features async

build:
strategy:
Expand Down Expand Up @@ -68,6 +70,8 @@ jobs:
components: rustfmt
- run: rustup target add ${{matrix.target}}
- run: cargo build --target ${{matrix.target}} --all-features --release
- if: ${{ matrix.examples }}
run: cargo build --target ${{matrix.target}} --examples --release
- if: ${{ matrix.examples }}
run: cargo build --target ${{matrix.target}} --all-features --examples --release
- run: cargo doc --all-features --target ${{matrix.target }}
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ SSD1306 monochrome OLED display.

- Updated dependencies for `embedded-hal` 1.0.0.
- Switch examples to embassy STM32 PAC which implements `embedded-hal` 1.0.0 traits.
- Add an asynchronous interface, enabled via the `async` feature.
- **(breaking)** Increased MSRV to 1.75.0

### Added
Expand Down
17 changes: 16 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,16 @@ rust-version = "1.75.0"

[package.metadata.docs.rs]
targets = [ "thumbv7m-none-eabi", "thumbv7em-none-eabihf" ]
all-features = true

[dependencies]
embedded-hal = "1.0.0"
display-interface = "0.5.0"
display-interface-i2c = "0.5.0"
display-interface-spi = "0.5.0"
embedded-graphics-core = { version = "0.4.0", optional = true }
embedded-hal-async = { version = "1.0.0", optional = true }
maybe-async-cfg = "0.2.4"

[dev-dependencies]
embedded-graphics = "0.8.0"
Expand All @@ -37,13 +40,25 @@ panic-probe = { version = "0.3.1", features = ["print-defmt"] }
tinybmp = "0.5.0"
# Used by the noise_i2c examples
rand = { version = "0.8.4", default-features = false, features = [ "small_rng" ] }
embassy-embedded-hal = { version = "0.2.0", default-features=false }
embassy-executor = { version = "0.6.0", git = "https://github.com/embassy-rs/embassy", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] }
embassy-stm32 = { version = "0.1.0", git = "https://github.com/embassy-rs/embassy", features = [ "stm32f103c8", "memory-x", "defmt", "exti", "time-driver-tim3" , "unstable-pac"] }
embassy-time = { version = "0.3.1", git = "https://github.com/embassy-rs/embassy" }
embedded-hal-bus = "0.2.0"
embassy-futures = "0.1.1"
embedded-hal-bus = { version = "0.2.0", features = ["async"]}

[features]
default = ["graphics"]
graphics = ["embedded-graphics-core"]
async = [ "dep:embedded-hal-async" ]

[[example]]
name = "async_i2c_spi"
required-features = [ "async" ]

[[example]]
name = "async_terminal_i2c"
required-features = [ "async" ]

[profile.dev]
opt-level="s"
Expand Down
127 changes: 127 additions & 0 deletions examples/async_i2c_spi.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
//! Draw a 1 bit per pixel black and white rust logo to 128x64 SSD1306 displays over both I2C
//! and SPI. This uses async an approach to transfer to i2c and spi simultaniously using DMA
//!
//! This example is for the STM32F103 "Blue Pill" board using I2C1 and SPI1.
//!
//! Wiring connections are as follows for a CRIUS-branded display:
//!
//! ```
//! I2c Display -> Blue Pill
//! GND -> GND
//! +5V -> VCC
//! SDA -> PB7
//! SCL -> PB6
//!
//! SPI display -> Blue Pill
//! GND -> GND
//! 3V3 -> VCC
//! PA5 -> SCL (D0)
//! PA7 -> SDA (D1)
//! PB0 -> RST
//! PB1 -> D/C
//! PB10 -> CS
//! ```
//!
//! Run on a Blue Pill with `cargo run --example async_i2c_spi`.

#![no_std]
#![no_main]

use defmt_rtt as _;
use embassy_executor::Spawner;
use embassy_futures::join::join;
use embassy_stm32::{bind_interrupts, gpio, i2c, peripherals, spi::Spi, time::Hertz, Config};
use embedded_graphics::{
image::{Image, ImageRaw},
pixelcolor::BinaryColor,
prelude::*,
};
use panic_probe as _;
use ssd1306::{prelude::*, I2CDisplayInterface, Ssd1306Async};

bind_interrupts!(struct Irqs {
I2C1_EV => i2c::EventInterruptHandler<peripherals::I2C1>;
I2C1_ER => i2c::ErrorInterruptHandler<peripherals::I2C1>;
});

#[embassy_executor::main]
async fn main(_spawner: Spawner) {
let mut config: Config = Default::default();
config.rcc.hse = Some(embassy_stm32::rcc::Hse {
freq: Hertz::mhz(8),
mode: embassy_stm32::rcc::HseMode::Oscillator,
});
config.rcc.sys = embassy_stm32::rcc::Sysclk::PLL1_P;
config.rcc.pll = Some(embassy_stm32::rcc::Pll {
src: embassy_stm32::rcc::PllSource::HSE,
prediv: embassy_stm32::rcc::PllPreDiv::DIV1,
mul: embassy_stm32::rcc::PllMul::MUL9, // 8 * 9 = 72Mhz
});

// Scale down to 36Mhz (maximum allowed)
config.rcc.apb1_pre = embassy_stm32::rcc::APBPrescaler::DIV2;
let p = embassy_stm32::init(config);

// I2C
let i2c = embassy_stm32::i2c::I2c::new(
p.I2C1,
p.PB6,
p.PB7,
Irqs,
p.DMA1_CH6,
p.DMA1_CH7,
// According to the datasheet the stm32f1xx only supports up to 400khz, but 1mhz seems to
// work just fine. WHen having issues try changing this to Hertz::khz(400).
Hertz::mhz(1),
Default::default(),
);

let interface = I2CDisplayInterface::new(i2c);
let mut display_i2c = Ssd1306Async::new(interface, DisplaySize128x64, DisplayRotation::Rotate0)
.into_buffered_graphics_mode();

// SPI
let spi = Spi::new_txonly(p.SPI1, p.PA5, p.PA7, p.DMA1_CH3, Default::default());

let mut rst = gpio::Output::new(p.PB0, gpio::Level::Low, gpio::Speed::Low);
let dc = gpio::Output::new(p.PB1, gpio::Level::Low, gpio::Speed::Low);
let cs = gpio::Output::new(p.PB10, gpio::Level::Low, gpio::Speed::Low);
let spi = embedded_hal_bus::spi::ExclusiveDevice::new_no_delay(spi, cs).unwrap();

let interface = SPIInterface::new(spi, dc);
let mut display_spi = Ssd1306Async::new(interface, DisplaySize128x64, DisplayRotation::Rotate0)
.into_buffered_graphics_mode();

// Init and reset both displays as needed
join(
async {
display_i2c.init().await.unwrap();
},
async {
display_spi
.reset(&mut rst, &mut embassy_time::Delay {})
.await
.unwrap();
display_spi.init().await.unwrap();
},
)
.await;

let raw: ImageRaw<BinaryColor> = ImageRaw::new(include_bytes!("./rust.raw"), 64);

for i in (0..=64).chain((0..64).rev()).cycle() {
let top_left = Point::new(i, 0);
let im = Image::new(&raw, top_left);

im.draw(&mut display_i2c).unwrap();
im.draw(&mut display_spi).unwrap();

join(async { display_i2c.flush().await.unwrap() }, async {
display_spi.flush().await.unwrap()
})
.await;

display_i2c.clear(BinaryColor::Off).unwrap();
display_spi.clear(BinaryColor::Off).unwrap();
}
}
64 changes: 64 additions & 0 deletions examples/async_terminal_i2c.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//! Endlessly fill the screen with characters from the alphabet
//!
//! This example is for the STM32F103 "Blue Pill" board using I2C1.
//!
//! Wiring connections are as follows for a CRIUS-branded display:
//!
//! ```
//! Display -> Blue Pill
//! (black) GND -> GND
//! (red) +5V -> VCC
//! (yellow) SDA -> PB7
//! (green) SCL -> PB6
//! ```
//!
//! Run on a Blue Pill with `cargo run --example async_terminal_i2c`.

#![no_std]
#![no_main]

use defmt_rtt as _;
use embassy_executor::Spawner;
use embassy_stm32::{bind_interrupts, i2c, peripherals, time::Hertz};
use panic_probe as _;
use ssd1306::{prelude::*, I2CDisplayInterface, Ssd1306Async};

bind_interrupts!(struct Irqs {
I2C1_EV => i2c::EventInterruptHandler<peripherals::I2C1>;
I2C1_ER => i2c::ErrorInterruptHandler<peripherals::I2C1>;
});

#[embassy_executor::main]
async fn main(_spawner: Spawner) {
let p = embassy_stm32::init(Default::default());
let i2c = embassy_stm32::i2c::I2c::new(
p.I2C1,
p.PB6,
p.PB7,
Irqs,
p.DMA1_CH6,
p.DMA1_CH7,
Hertz::khz(400),
Default::default(),
);

let interface = I2CDisplayInterface::new(i2c);
let mut display = Ssd1306Async::new(interface, DisplaySize128x64, DisplayRotation::Rotate0)
.into_terminal_mode();
display.init().await.unwrap();
let _ = display.clear().await;

/* Endless loop */
loop {
for c in 97..123 {
let _ = display
.write_str(unsafe { core::str::from_utf8_unchecked(&[c]) })
.await;
}
for c in 65..91 {
let _ = display
.write_str(unsafe { core::str::from_utf8_unchecked(&[c]) })
.await;
}
}
}
Loading

0 comments on commit ad2f001

Please sign in to comment.