Skip to content

Commit ad2f001

Browse files
sjoerdsimonseldruin
authored andcommitted
Add async support
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.
1 parent dbeb781 commit ad2f001

File tree

12 files changed

+752
-184
lines changed

12 files changed

+752
-184
lines changed

.github/workflows/ci.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@ jobs:
3737
with:
3838
toolchain: 1.75
3939
- run: cargo build --lib --target x86_64-unknown-linux-gnu
40+
- run: cargo build --lib --target x86_64-unknown-linux-gnu --features async
4041
- run: cargo doc --target x86_64-unknown-linux-gnu
42+
- run: cargo doc --target x86_64-unknown-linux-gnu --features async
4143

4244
build:
4345
strategy:
@@ -68,6 +70,8 @@ jobs:
6870
components: rustfmt
6971
- run: rustup target add ${{matrix.target}}
7072
- run: cargo build --target ${{matrix.target}} --all-features --release
73+
- if: ${{ matrix.examples }}
74+
run: cargo build --target ${{matrix.target}} --examples --release
7175
- if: ${{ matrix.examples }}
7276
run: cargo build --target ${{matrix.target}} --all-features --examples --release
7377
- run: cargo doc --all-features --target ${{matrix.target }}

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ SSD1306 monochrome OLED display.
99

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

1415
### Added

Cargo.toml

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,16 @@ rust-version = "1.75.0"
1515

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

1920
[dependencies]
2021
embedded-hal = "1.0.0"
2122
display-interface = "0.5.0"
2223
display-interface-i2c = "0.5.0"
2324
display-interface-spi = "0.5.0"
2425
embedded-graphics-core = { version = "0.4.0", optional = true }
26+
embedded-hal-async = { version = "1.0.0", optional = true }
27+
maybe-async-cfg = "0.2.4"
2528

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

4450
[features]
4551
default = ["graphics"]
4652
graphics = ["embedded-graphics-core"]
53+
async = [ "dep:embedded-hal-async" ]
54+
55+
[[example]]
56+
name = "async_i2c_spi"
57+
required-features = [ "async" ]
58+
59+
[[example]]
60+
name = "async_terminal_i2c"
61+
required-features = [ "async" ]
4762

4863
[profile.dev]
4964
opt-level="s"

examples/async_i2c_spi.rs

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
//! Draw a 1 bit per pixel black and white rust logo to 128x64 SSD1306 displays over both I2C
2+
//! and SPI. This uses async an approach to transfer to i2c and spi simultaniously using DMA
3+
//!
4+
//! This example is for the STM32F103 "Blue Pill" board using I2C1 and SPI1.
5+
//!
6+
//! Wiring connections are as follows for a CRIUS-branded display:
7+
//!
8+
//! ```
9+
//! I2c Display -> Blue Pill
10+
//! GND -> GND
11+
//! +5V -> VCC
12+
//! SDA -> PB7
13+
//! SCL -> PB6
14+
//!
15+
//! SPI display -> Blue Pill
16+
//! GND -> GND
17+
//! 3V3 -> VCC
18+
//! PA5 -> SCL (D0)
19+
//! PA7 -> SDA (D1)
20+
//! PB0 -> RST
21+
//! PB1 -> D/C
22+
//! PB10 -> CS
23+
//! ```
24+
//!
25+
//! Run on a Blue Pill with `cargo run --example async_i2c_spi`.
26+
27+
#![no_std]
28+
#![no_main]
29+
30+
use defmt_rtt as _;
31+
use embassy_executor::Spawner;
32+
use embassy_futures::join::join;
33+
use embassy_stm32::{bind_interrupts, gpio, i2c, peripherals, spi::Spi, time::Hertz, Config};
34+
use embedded_graphics::{
35+
image::{Image, ImageRaw},
36+
pixelcolor::BinaryColor,
37+
prelude::*,
38+
};
39+
use panic_probe as _;
40+
use ssd1306::{prelude::*, I2CDisplayInterface, Ssd1306Async};
41+
42+
bind_interrupts!(struct Irqs {
43+
I2C1_EV => i2c::EventInterruptHandler<peripherals::I2C1>;
44+
I2C1_ER => i2c::ErrorInterruptHandler<peripherals::I2C1>;
45+
});
46+
47+
#[embassy_executor::main]
48+
async fn main(_spawner: Spawner) {
49+
let mut config: Config = Default::default();
50+
config.rcc.hse = Some(embassy_stm32::rcc::Hse {
51+
freq: Hertz::mhz(8),
52+
mode: embassy_stm32::rcc::HseMode::Oscillator,
53+
});
54+
config.rcc.sys = embassy_stm32::rcc::Sysclk::PLL1_P;
55+
config.rcc.pll = Some(embassy_stm32::rcc::Pll {
56+
src: embassy_stm32::rcc::PllSource::HSE,
57+
prediv: embassy_stm32::rcc::PllPreDiv::DIV1,
58+
mul: embassy_stm32::rcc::PllMul::MUL9, // 8 * 9 = 72Mhz
59+
});
60+
61+
// Scale down to 36Mhz (maximum allowed)
62+
config.rcc.apb1_pre = embassy_stm32::rcc::APBPrescaler::DIV2;
63+
let p = embassy_stm32::init(config);
64+
65+
// I2C
66+
let i2c = embassy_stm32::i2c::I2c::new(
67+
p.I2C1,
68+
p.PB6,
69+
p.PB7,
70+
Irqs,
71+
p.DMA1_CH6,
72+
p.DMA1_CH7,
73+
// According to the datasheet the stm32f1xx only supports up to 400khz, but 1mhz seems to
74+
// work just fine. WHen having issues try changing this to Hertz::khz(400).
75+
Hertz::mhz(1),
76+
Default::default(),
77+
);
78+
79+
let interface = I2CDisplayInterface::new(i2c);
80+
let mut display_i2c = Ssd1306Async::new(interface, DisplaySize128x64, DisplayRotation::Rotate0)
81+
.into_buffered_graphics_mode();
82+
83+
// SPI
84+
let spi = Spi::new_txonly(p.SPI1, p.PA5, p.PA7, p.DMA1_CH3, Default::default());
85+
86+
let mut rst = gpio::Output::new(p.PB0, gpio::Level::Low, gpio::Speed::Low);
87+
let dc = gpio::Output::new(p.PB1, gpio::Level::Low, gpio::Speed::Low);
88+
let cs = gpio::Output::new(p.PB10, gpio::Level::Low, gpio::Speed::Low);
89+
let spi = embedded_hal_bus::spi::ExclusiveDevice::new_no_delay(spi, cs).unwrap();
90+
91+
let interface = SPIInterface::new(spi, dc);
92+
let mut display_spi = Ssd1306Async::new(interface, DisplaySize128x64, DisplayRotation::Rotate0)
93+
.into_buffered_graphics_mode();
94+
95+
// Init and reset both displays as needed
96+
join(
97+
async {
98+
display_i2c.init().await.unwrap();
99+
},
100+
async {
101+
display_spi
102+
.reset(&mut rst, &mut embassy_time::Delay {})
103+
.await
104+
.unwrap();
105+
display_spi.init().await.unwrap();
106+
},
107+
)
108+
.await;
109+
110+
let raw: ImageRaw<BinaryColor> = ImageRaw::new(include_bytes!("./rust.raw"), 64);
111+
112+
for i in (0..=64).chain((0..64).rev()).cycle() {
113+
let top_left = Point::new(i, 0);
114+
let im = Image::new(&raw, top_left);
115+
116+
im.draw(&mut display_i2c).unwrap();
117+
im.draw(&mut display_spi).unwrap();
118+
119+
join(async { display_i2c.flush().await.unwrap() }, async {
120+
display_spi.flush().await.unwrap()
121+
})
122+
.await;
123+
124+
display_i2c.clear(BinaryColor::Off).unwrap();
125+
display_spi.clear(BinaryColor::Off).unwrap();
126+
}
127+
}

examples/async_terminal_i2c.rs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
//! Endlessly fill the screen with characters from the alphabet
2+
//!
3+
//! This example is for the STM32F103 "Blue Pill" board using I2C1.
4+
//!
5+
//! Wiring connections are as follows for a CRIUS-branded display:
6+
//!
7+
//! ```
8+
//! Display -> Blue Pill
9+
//! (black) GND -> GND
10+
//! (red) +5V -> VCC
11+
//! (yellow) SDA -> PB7
12+
//! (green) SCL -> PB6
13+
//! ```
14+
//!
15+
//! Run on a Blue Pill with `cargo run --example async_terminal_i2c`.
16+
17+
#![no_std]
18+
#![no_main]
19+
20+
use defmt_rtt as _;
21+
use embassy_executor::Spawner;
22+
use embassy_stm32::{bind_interrupts, i2c, peripherals, time::Hertz};
23+
use panic_probe as _;
24+
use ssd1306::{prelude::*, I2CDisplayInterface, Ssd1306Async};
25+
26+
bind_interrupts!(struct Irqs {
27+
I2C1_EV => i2c::EventInterruptHandler<peripherals::I2C1>;
28+
I2C1_ER => i2c::ErrorInterruptHandler<peripherals::I2C1>;
29+
});
30+
31+
#[embassy_executor::main]
32+
async fn main(_spawner: Spawner) {
33+
let p = embassy_stm32::init(Default::default());
34+
let i2c = embassy_stm32::i2c::I2c::new(
35+
p.I2C1,
36+
p.PB6,
37+
p.PB7,
38+
Irqs,
39+
p.DMA1_CH6,
40+
p.DMA1_CH7,
41+
Hertz::khz(400),
42+
Default::default(),
43+
);
44+
45+
let interface = I2CDisplayInterface::new(i2c);
46+
let mut display = Ssd1306Async::new(interface, DisplaySize128x64, DisplayRotation::Rotate0)
47+
.into_terminal_mode();
48+
display.init().await.unwrap();
49+
let _ = display.clear().await;
50+
51+
/* Endless loop */
52+
loop {
53+
for c in 97..123 {
54+
let _ = display
55+
.write_str(unsafe { core::str::from_utf8_unchecked(&[c]) })
56+
.await;
57+
}
58+
for c in 65..91 {
59+
let _ = display
60+
.write_str(unsafe { core::str::from_utf8_unchecked(&[c]) })
61+
.await;
62+
}
63+
}
64+
}

0 commit comments

Comments
 (0)