feat: add display UI with splash screen, dashboard, and RSSI sparkline
Implement full SSD1306 OLED display support: - Splash screen on boot (1.5s) with project name, version, board - Dashboard showing radio state, config, counters, RSSI/SNR - RSSI history sparkline (64-sample ring buffer, 2px-wide bars) - DisplayOn/DisplayOff host commands routed via dedicated channel - Pure render functions against generic DrawTarget (no hardware coupling) - Display on/off control bypasses radio task entirely Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
17624 files changed
+4564928
-38
No files matched your search
+127
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+86
@@ -0,0 +1,86 @@
|
||||
//! Draw an RGB565 BMP image onto the display by converting the `Rgb565` pixel color type to
|
||||
//! `BinaryColor` using a simple threshold where any pixel with a value greater than zero is treated
|
||||
//! as "on".
|
||||
//!
|
||||
//! Note that the `bmp` feature for `embedded-graphics` must be turned on.
|
||||
//!
|
||||
//! 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 image_i2c`.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use cortex_m::asm::nop;
|
||||
use cortex_m_rt::entry;
|
||||
use defmt_rtt as _;
|
||||
use embassy_stm32::time::Hertz;
|
||||
#[cfg(feature = "async")]
|
||||
use embassy_stm32::{bind_interrupts, i2c, peripherals};
|
||||
use embedded_graphics::{image::Image, pixelcolor::Rgb565, prelude::*};
|
||||
use panic_probe as _;
|
||||
use ssd1306::{prelude::*, I2CDisplayInterface, Ssd1306};
|
||||
|
||||
use tinybmp::Bmp;
|
||||
|
||||
#[entry]
|
||||
fn main() -> ! {
|
||||
let p: embassy_stm32::Peripherals = embassy_stm32::init(Default::default());
|
||||
#[cfg(feature = "async")]
|
||||
bind_interrupts!(struct Irqs {
|
||||
I2C1_EV => i2c::EventInterruptHandler<peripherals::I2C1>;
|
||||
I2C1_ER => i2c::ErrorInterruptHandler<peripherals::I2C1>;
|
||||
});
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
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(),
|
||||
);
|
||||
|
||||
#[cfg(not(feature = "async"))]
|
||||
let i2c = embassy_stm32::i2c::I2c::new_blocking(
|
||||
p.I2C1,
|
||||
p.PB6,
|
||||
p.PB7,
|
||||
Hertz::khz(400),
|
||||
Default::default(),
|
||||
);
|
||||
let interface = I2CDisplayInterface::new(i2c);
|
||||
|
||||
let mut display = Ssd1306::new(interface, DisplaySize128x64, DisplayRotation::Rotate0)
|
||||
.into_buffered_graphics_mode();
|
||||
display.init().unwrap();
|
||||
|
||||
let bmp = Bmp::from_slice(include_bytes!("./rust.bmp")).expect("Failed to load BMP image");
|
||||
|
||||
// The image is an RGB565 encoded BMP, so specifying the type as `Image<Bmp<Rgb565>>` will read
|
||||
// the pixels correctly
|
||||
let im: Image<Bmp<Rgb565>> = Image::new(&bmp, Point::new(32, 0));
|
||||
|
||||
// We use the `color_converted` method here to automatically convert the RGB565 image data into
|
||||
// BinaryColor values.
|
||||
im.draw(&mut display.color_converted()).unwrap();
|
||||
|
||||
display.flush().unwrap();
|
||||
|
||||
loop {
|
||||
nop()
|
||||
}
|
||||
}
|
||||
Vendored
+103
@@ -0,0 +1,103 @@
|
||||
//! Draw a square, circle and triangle on the screen using the embedded_graphics library over a 4
|
||||
//! wire SPI interface.
|
||||
//!
|
||||
//! This example is for the STM32F103 "Blue Pill" board using a 4 wire interface to the display on
|
||||
//! SPI1.
|
||||
//!
|
||||
//! Wiring connections are as follows
|
||||
//!
|
||||
//! ```
|
||||
//! 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 graphics`.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use cortex_m::asm::nop;
|
||||
use cortex_m_rt::entry;
|
||||
use defmt_rtt as _;
|
||||
use embassy_stm32::{
|
||||
gpio,
|
||||
spi::{self, Spi},
|
||||
time::Hertz,
|
||||
};
|
||||
use embedded_graphics::{
|
||||
pixelcolor::BinaryColor,
|
||||
prelude::*,
|
||||
primitives::{Circle, PrimitiveStyleBuilder, Rectangle, Triangle},
|
||||
};
|
||||
use panic_probe as _;
|
||||
use ssd1306::{prelude::*, Ssd1306};
|
||||
|
||||
#[entry]
|
||||
fn main() -> ! {
|
||||
let p = embassy_stm32::init(Default::default());
|
||||
let mut config = spi::Config::default();
|
||||
config.frequency = Hertz::mhz(8);
|
||||
let spi = Spi::new_blocking_txonly(p.SPI1, p.PA5, p.PA7, config);
|
||||
|
||||
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 = display_interface_spi::SPIInterface::new(spi, dc);
|
||||
let mut display = Ssd1306::new(interface, DisplaySize128x64, DisplayRotation::Rotate0)
|
||||
.into_buffered_graphics_mode();
|
||||
|
||||
display
|
||||
.reset(&mut rst, &mut embassy_time::Delay {})
|
||||
.unwrap();
|
||||
display.init().unwrap();
|
||||
|
||||
let yoffset = 20;
|
||||
|
||||
let style = PrimitiveStyleBuilder::new()
|
||||
.stroke_width(1)
|
||||
.stroke_color(BinaryColor::On)
|
||||
.build();
|
||||
|
||||
// screen outline
|
||||
// default display size is 128x64 if you don't pass a _DisplaySize_
|
||||
// enum to the _Builder_ struct
|
||||
Rectangle::new(Point::new(0, 0), Size::new(127, 63))
|
||||
.into_styled(style)
|
||||
.draw(&mut display)
|
||||
.unwrap();
|
||||
|
||||
// triangle
|
||||
Triangle::new(
|
||||
Point::new(16, 16 + yoffset),
|
||||
Point::new(16 + 16, 16 + yoffset),
|
||||
Point::new(16 + 8, yoffset),
|
||||
)
|
||||
.into_styled(style)
|
||||
.draw(&mut display)
|
||||
.unwrap();
|
||||
|
||||
// square
|
||||
Rectangle::new(Point::new(52, yoffset), Size::new_equal(16))
|
||||
.into_styled(style)
|
||||
.draw(&mut display)
|
||||
.unwrap();
|
||||
|
||||
// circle
|
||||
Circle::new(Point::new(88, yoffset), 16)
|
||||
.into_styled(style)
|
||||
.draw(&mut display)
|
||||
.unwrap();
|
||||
|
||||
display.flush().unwrap();
|
||||
|
||||
loop {
|
||||
nop()
|
||||
}
|
||||
}
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
//! Draw a square, circle and triangle on the screen using the `embedded_graphics` crate.
|
||||
//!
|
||||
//! 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 graphics_i2c`.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use cortex_m::asm::nop;
|
||||
use cortex_m_rt::entry;
|
||||
use defmt_rtt as _;
|
||||
use embassy_stm32::time::Hertz;
|
||||
#[cfg(feature = "async")]
|
||||
use embassy_stm32::{bind_interrupts, i2c, peripherals};
|
||||
use embedded_graphics::{
|
||||
pixelcolor::BinaryColor,
|
||||
prelude::*,
|
||||
primitives::{Circle, PrimitiveStyleBuilder, Rectangle, Triangle},
|
||||
};
|
||||
use panic_probe as _;
|
||||
use ssd1306::{prelude::*, I2CDisplayInterface, Ssd1306};
|
||||
|
||||
#[entry]
|
||||
fn main() -> ! {
|
||||
let p = embassy_stm32::init(Default::default());
|
||||
#[cfg(feature = "async")]
|
||||
bind_interrupts!(struct Irqs {
|
||||
I2C1_EV => i2c::EventInterruptHandler<peripherals::I2C1>;
|
||||
I2C1_ER => i2c::ErrorInterruptHandler<peripherals::I2C1>;
|
||||
});
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
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(),
|
||||
);
|
||||
|
||||
#[cfg(not(feature = "async"))]
|
||||
let i2c = embassy_stm32::i2c::I2c::new_blocking(
|
||||
p.I2C1,
|
||||
p.PB6,
|
||||
p.PB7,
|
||||
Hertz::khz(400),
|
||||
Default::default(),
|
||||
);
|
||||
|
||||
let interface = I2CDisplayInterface::new(i2c);
|
||||
let mut display = Ssd1306::new(interface, DisplaySize128x64, DisplayRotation::Rotate0)
|
||||
.into_buffered_graphics_mode();
|
||||
display.init().unwrap();
|
||||
|
||||
let yoffset = 20;
|
||||
|
||||
let style = PrimitiveStyleBuilder::new()
|
||||
.stroke_width(1)
|
||||
.stroke_color(BinaryColor::On)
|
||||
.build();
|
||||
|
||||
// screen outline
|
||||
// default display size is 128x64 if you don't pass a _DisplaySize_
|
||||
// enum to the _Builder_ struct
|
||||
Rectangle::new(Point::new(0, 0), Size::new(127, 63))
|
||||
.into_styled(style)
|
||||
.draw(&mut display)
|
||||
.unwrap();
|
||||
|
||||
// triangle
|
||||
Triangle::new(
|
||||
Point::new(16, 16 + yoffset),
|
||||
Point::new(16 + 16, 16 + yoffset),
|
||||
Point::new(16 + 8, yoffset),
|
||||
)
|
||||
.into_styled(style)
|
||||
.draw(&mut display)
|
||||
.unwrap();
|
||||
|
||||
// square
|
||||
Rectangle::new(Point::new(52, yoffset), Size::new_equal(16))
|
||||
.into_styled(style)
|
||||
.draw(&mut display)
|
||||
.unwrap();
|
||||
|
||||
// circle
|
||||
Circle::new(Point::new(88, yoffset), 16)
|
||||
.into_styled(style)
|
||||
.draw(&mut display)
|
||||
.unwrap();
|
||||
|
||||
display.flush().unwrap();
|
||||
|
||||
loop {
|
||||
nop()
|
||||
}
|
||||
}
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
//! Draw a square, circle and triangle on a 128x32px display.
|
||||
//!
|
||||
//! 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 graphics_i2c_128x32`.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use cortex_m::asm::nop;
|
||||
use cortex_m_rt::entry;
|
||||
use defmt_rtt as _;
|
||||
use embassy_stm32::time::Hertz;
|
||||
#[cfg(feature = "async")]
|
||||
use embassy_stm32::{bind_interrupts, i2c, peripherals};
|
||||
use embedded_graphics::{
|
||||
pixelcolor::BinaryColor,
|
||||
prelude::*,
|
||||
primitives::{Circle, PrimitiveStyleBuilder, Rectangle, Triangle},
|
||||
};
|
||||
use panic_probe as _;
|
||||
use ssd1306::{prelude::*, I2CDisplayInterface, Ssd1306};
|
||||
|
||||
#[entry]
|
||||
fn main() -> ! {
|
||||
let p = embassy_stm32::init(Default::default());
|
||||
#[cfg(feature = "async")]
|
||||
bind_interrupts!(struct Irqs {
|
||||
I2C1_EV => i2c::EventInterruptHandler<peripherals::I2C1>;
|
||||
I2C1_ER => i2c::ErrorInterruptHandler<peripherals::I2C1>;
|
||||
});
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
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(),
|
||||
);
|
||||
|
||||
#[cfg(not(feature = "async"))]
|
||||
let i2c = embassy_stm32::i2c::I2c::new_blocking(
|
||||
p.I2C1,
|
||||
p.PB6,
|
||||
p.PB7,
|
||||
Hertz::khz(400),
|
||||
Default::default(),
|
||||
);
|
||||
|
||||
let interface = I2CDisplayInterface::new(i2c);
|
||||
let mut display = Ssd1306::new(interface, DisplaySize128x32, DisplayRotation::Rotate0)
|
||||
.into_buffered_graphics_mode();
|
||||
display.init().unwrap();
|
||||
|
||||
let yoffset = 8;
|
||||
|
||||
let style = PrimitiveStyleBuilder::new()
|
||||
.stroke_width(1)
|
||||
.stroke_color(BinaryColor::On)
|
||||
.build();
|
||||
|
||||
// screen outline
|
||||
// default display size is 128x64 if you don't pass a _DisplaySize_
|
||||
// enum to the _Builder_ struct
|
||||
Rectangle::new(Point::new(0, 0), Size::new(127, 31))
|
||||
.into_styled(style)
|
||||
.draw(&mut display)
|
||||
.unwrap();
|
||||
|
||||
// triangle
|
||||
Triangle::new(
|
||||
Point::new(16, 16 + yoffset),
|
||||
Point::new(16 + 16, 16 + yoffset),
|
||||
Point::new(16 + 8, yoffset),
|
||||
)
|
||||
.into_styled(style)
|
||||
.draw(&mut display)
|
||||
.unwrap();
|
||||
|
||||
// square
|
||||
Rectangle::new(Point::new(52, yoffset), Size::new_equal(16))
|
||||
.into_styled(style)
|
||||
.draw(&mut display)
|
||||
.unwrap();
|
||||
|
||||
// circle
|
||||
Circle::new(Point::new(88, yoffset), 16)
|
||||
.into_styled(style)
|
||||
.draw(&mut display)
|
||||
.unwrap();
|
||||
|
||||
display.flush().unwrap();
|
||||
|
||||
loop {
|
||||
nop()
|
||||
}
|
||||
}
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
//! Draw a square, circle and triangle on the screen using the `embedded_graphics` crate.
|
||||
//!
|
||||
//! 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 graphics_i2c`.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use cortex_m::asm::nop;
|
||||
use cortex_m_rt::entry;
|
||||
use defmt_rtt as _;
|
||||
use embassy_stm32::time::Hertz;
|
||||
#[cfg(feature = "async")]
|
||||
use embassy_stm32::{bind_interrupts, i2c, peripherals};
|
||||
use embedded_graphics::{
|
||||
pixelcolor::BinaryColor,
|
||||
prelude::*,
|
||||
primitives::{Circle, PrimitiveStyleBuilder, Rectangle, Triangle},
|
||||
};
|
||||
use panic_probe as _;
|
||||
use ssd1306::{prelude::*, I2CDisplayInterface, Ssd1306};
|
||||
|
||||
#[entry]
|
||||
fn main() -> ! {
|
||||
let p = embassy_stm32::init(Default::default());
|
||||
#[cfg(feature = "async")]
|
||||
bind_interrupts!(struct Irqs {
|
||||
I2C1_EV => i2c::EventInterruptHandler<peripherals::I2C1>;
|
||||
I2C1_ER => i2c::ErrorInterruptHandler<peripherals::I2C1>;
|
||||
});
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
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(),
|
||||
);
|
||||
|
||||
#[cfg(not(feature = "async"))]
|
||||
let i2c = embassy_stm32::i2c::I2c::new_blocking(
|
||||
p.I2C1,
|
||||
p.PB6,
|
||||
p.PB7,
|
||||
Hertz::khz(400),
|
||||
Default::default(),
|
||||
);
|
||||
|
||||
let interface = I2CDisplayInterface::new(i2c);
|
||||
let mut display = Ssd1306::new(interface, DisplaySize72x40, DisplayRotation::Rotate0)
|
||||
.into_buffered_graphics_mode();
|
||||
display.init().unwrap();
|
||||
|
||||
let size = 10;
|
||||
let offset = Point::new(10, (42 / 2) - (size / 2) - 1);
|
||||
let spacing = size + 10;
|
||||
|
||||
let style = PrimitiveStyleBuilder::new()
|
||||
.stroke_width(1)
|
||||
.stroke_color(BinaryColor::On)
|
||||
.build();
|
||||
|
||||
// screen outline
|
||||
// default display size is 128x64 if you don't pass a _DisplaySize_
|
||||
// enum to the _Builder_ struct
|
||||
Rectangle::with_corners(Point::new(0, 0), Point::new(71, 39))
|
||||
.into_styled(style)
|
||||
.draw(&mut display)
|
||||
.unwrap();
|
||||
|
||||
// Triangle
|
||||
Triangle::new(
|
||||
Point::new(0, size),
|
||||
Point::new(size / 2, 0),
|
||||
Point::new(size, size),
|
||||
)
|
||||
.translate(offset)
|
||||
.into_styled(style)
|
||||
.draw(&mut display)
|
||||
.unwrap();
|
||||
|
||||
// Move over to next position
|
||||
let offset = offset + Point::new(spacing, 0);
|
||||
|
||||
// Draw a square
|
||||
Rectangle::new(Point::new(0, 0), Size::new_equal(size as u32))
|
||||
.translate(offset)
|
||||
.into_styled(style)
|
||||
.draw(&mut display)
|
||||
.unwrap();
|
||||
|
||||
// Move over a bit more
|
||||
let offset = offset + Point::new(spacing, 0);
|
||||
|
||||
// Circle
|
||||
Circle::new(Point::zero(), size as u32)
|
||||
.translate(offset)
|
||||
.into_styled(style)
|
||||
.draw(&mut display)
|
||||
.unwrap();
|
||||
|
||||
display.flush().unwrap();
|
||||
|
||||
loop {
|
||||
nop()
|
||||
}
|
||||
}
|
||||
Vendored
+87
@@ -0,0 +1,87 @@
|
||||
//! Draw a 1 bit per pixel black and white image. On a 128x64 SSD1306 display over I2C.
|
||||
//!
|
||||
//! Image was created with ImageMagick:
|
||||
//!
|
||||
//! ```bash
|
||||
//! convert rust.png -depth 1 gray:rust.raw
|
||||
//! ```
|
||||
//!
|
||||
//! 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 image_i2c`.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use cortex_m::asm::nop;
|
||||
use cortex_m_rt::entry;
|
||||
use defmt_rtt as _;
|
||||
#[cfg(feature = "async")]
|
||||
use embassy_stm32::{bind_interrupts, i2c, peripherals};
|
||||
|
||||
use embassy_stm32::time::Hertz;
|
||||
use embedded_graphics::{
|
||||
image::{Image, ImageRaw},
|
||||
pixelcolor::BinaryColor,
|
||||
prelude::*,
|
||||
};
|
||||
use panic_probe as _;
|
||||
use ssd1306::{prelude::*, I2CDisplayInterface, Ssd1306};
|
||||
|
||||
#[entry]
|
||||
fn main() -> ! {
|
||||
let p = embassy_stm32::init(Default::default());
|
||||
#[cfg(feature = "async")]
|
||||
bind_interrupts!(struct Irqs {
|
||||
I2C1_EV => i2c::EventInterruptHandler<peripherals::I2C1>;
|
||||
I2C1_ER => i2c::ErrorInterruptHandler<peripherals::I2C1>;
|
||||
});
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
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(),
|
||||
);
|
||||
|
||||
#[cfg(not(feature = "async"))]
|
||||
let i2c = embassy_stm32::i2c::I2c::new_blocking(
|
||||
p.I2C1,
|
||||
p.PB6,
|
||||
p.PB7,
|
||||
Hertz::khz(400),
|
||||
Default::default(),
|
||||
);
|
||||
|
||||
let interface = I2CDisplayInterface::new(i2c);
|
||||
let mut display = Ssd1306::new(interface, DisplaySize128x64, DisplayRotation::Rotate0)
|
||||
.into_buffered_graphics_mode();
|
||||
display.init().unwrap();
|
||||
|
||||
let raw: ImageRaw<BinaryColor> = ImageRaw::new(include_bytes!("./rust.raw"), 64);
|
||||
|
||||
let im = Image::new(&raw, Point::new(32, 0));
|
||||
|
||||
im.draw(&mut display).unwrap();
|
||||
|
||||
display.flush().unwrap();
|
||||
|
||||
loop {
|
||||
nop()
|
||||
}
|
||||
}
|
||||
Vendored
+75
@@ -0,0 +1,75 @@
|
||||
//! Send random raw data to the display, emulating an old untuned TV. This example retrieves the
|
||||
//! underlying display properties struct and allows calling of the low-level `draw()` method,
|
||||
//! sending a 1024 byte buffer straight to the display.
|
||||
//!
|
||||
//! 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 noise_i2c`. Best results when using `--release`.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use cortex_m_rt::entry;
|
||||
use defmt_rtt as _;
|
||||
#[cfg(feature = "async")]
|
||||
use embassy_stm32::{bind_interrupts, i2c, peripherals};
|
||||
|
||||
use embassy_stm32::time::Hertz;
|
||||
use panic_probe as _;
|
||||
use rand::prelude::*;
|
||||
use ssd1306::{prelude::*, I2CDisplayInterface, Ssd1306};
|
||||
|
||||
#[entry]
|
||||
fn main() -> ! {
|
||||
let p = embassy_stm32::init(Default::default());
|
||||
#[cfg(feature = "async")]
|
||||
bind_interrupts!(struct Irqs {
|
||||
I2C1_EV => i2c::EventInterruptHandler<peripherals::I2C1>;
|
||||
I2C1_ER => i2c::ErrorInterruptHandler<peripherals::I2C1>;
|
||||
});
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
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(),
|
||||
);
|
||||
|
||||
#[cfg(not(feature = "async"))]
|
||||
let i2c = embassy_stm32::i2c::I2c::new_blocking(
|
||||
p.I2C1,
|
||||
p.PB6,
|
||||
p.PB7,
|
||||
Hertz::khz(400),
|
||||
Default::default(),
|
||||
);
|
||||
|
||||
let interface = I2CDisplayInterface::new(i2c);
|
||||
let mut display = Ssd1306::new(interface, DisplaySize128x64, DisplayRotation::Rotate0);
|
||||
display.init().unwrap();
|
||||
|
||||
let mut buf = [0x00u8; 1024];
|
||||
|
||||
let mut rng = SmallRng::seed_from_u64(0xdead_beef_cafe_d00d);
|
||||
|
||||
loop {
|
||||
rng.fill_bytes(&mut buf);
|
||||
|
||||
display.draw(&buf).unwrap();
|
||||
}
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
//! This example draws a small square one pixel at a time in the top left corner of the display
|
||||
//!
|
||||
//! You will probably want to use the [`embedded_graphics`](https://crates.io/crates/embedded-graphics) crate to do more complex drawing.
|
||||
//!
|
||||
//! This example is for the STM32F103 "Blue Pill" board using a 4 wire interface to the display on
|
||||
//! SPI1.
|
||||
//!
|
||||
//! Wiring connections are as follows
|
||||
//!
|
||||
//! ```
|
||||
//! 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 pixelsquare`.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use cortex_m::asm::nop;
|
||||
use cortex_m_rt::entry;
|
||||
use defmt_rtt as _;
|
||||
use embassy_stm32::{
|
||||
gpio,
|
||||
spi::{self, Spi},
|
||||
time::Hertz,
|
||||
};
|
||||
use panic_probe as _;
|
||||
use ssd1306::{prelude::*, Ssd1306};
|
||||
|
||||
#[entry]
|
||||
fn main() -> ! {
|
||||
let p = embassy_stm32::init(Default::default());
|
||||
let mut config = spi::Config::default();
|
||||
config.frequency = Hertz::mhz(8);
|
||||
let spi = Spi::new_blocking_txonly(p.SPI1, p.PA5, p.PA7, config);
|
||||
|
||||
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 = display_interface_spi::SPIInterface::new(spi, dc);
|
||||
let mut display = Ssd1306::new(interface, DisplaySize128x64, DisplayRotation::Rotate0)
|
||||
.into_buffered_graphics_mode();
|
||||
|
||||
display
|
||||
.reset(&mut rst, &mut embassy_time::Delay {})
|
||||
.unwrap();
|
||||
display.init().unwrap();
|
||||
|
||||
// Top side
|
||||
display.set_pixel(0, 0, true);
|
||||
display.set_pixel(1, 0, true);
|
||||
display.set_pixel(2, 0, true);
|
||||
display.set_pixel(3, 0, true);
|
||||
|
||||
// Right side
|
||||
display.set_pixel(3, 0, true);
|
||||
display.set_pixel(3, 1, true);
|
||||
display.set_pixel(3, 2, true);
|
||||
display.set_pixel(3, 3, true);
|
||||
|
||||
// Bottom side
|
||||
display.set_pixel(0, 3, true);
|
||||
display.set_pixel(1, 3, true);
|
||||
display.set_pixel(2, 3, true);
|
||||
display.set_pixel(3, 3, true);
|
||||
|
||||
// Left side
|
||||
display.set_pixel(0, 0, true);
|
||||
display.set_pixel(0, 1, true);
|
||||
display.set_pixel(0, 2, true);
|
||||
display.set_pixel(0, 3, true);
|
||||
|
||||
display.flush().unwrap();
|
||||
|
||||
loop {
|
||||
nop()
|
||||
}
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
//! Draw the Rust logo centered on a 90 degree rotated 128x64px display
|
||||
//!
|
||||
//! Image was created with ImageMagick:
|
||||
//!
|
||||
//! ```bash
|
||||
//! convert rust.png -depth 1 gray:rust.raw
|
||||
//! ```
|
||||
//!
|
||||
//! 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 rotation_i2c`.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use cortex_m::asm::nop;
|
||||
use cortex_m_rt::entry;
|
||||
use defmt_rtt as _;
|
||||
use embassy_stm32::time::Hertz;
|
||||
#[cfg(feature = "async")]
|
||||
use embassy_stm32::{bind_interrupts, i2c, peripherals};
|
||||
use embedded_graphics::{
|
||||
image::{Image, ImageRaw},
|
||||
pixelcolor::BinaryColor,
|
||||
prelude::*,
|
||||
};
|
||||
use panic_probe as _;
|
||||
use ssd1306::{prelude::*, I2CDisplayInterface, Ssd1306};
|
||||
|
||||
#[entry]
|
||||
fn main() -> ! {
|
||||
let p = embassy_stm32::init(Default::default());
|
||||
#[cfg(feature = "async")]
|
||||
bind_interrupts!(struct Irqs {
|
||||
I2C1_EV => i2c::EventInterruptHandler<peripherals::I2C1>;
|
||||
I2C1_ER => i2c::ErrorInterruptHandler<peripherals::I2C1>;
|
||||
});
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
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(),
|
||||
);
|
||||
|
||||
#[cfg(not(feature = "async"))]
|
||||
let i2c = embassy_stm32::i2c::I2c::new_blocking(
|
||||
p.I2C1,
|
||||
p.PB6,
|
||||
p.PB7,
|
||||
Hertz::khz(400),
|
||||
Default::default(),
|
||||
);
|
||||
|
||||
let interface = I2CDisplayInterface::new(i2c);
|
||||
let mut display = Ssd1306::new(interface, DisplaySize128x64, DisplayRotation::Rotate90)
|
||||
.into_buffered_graphics_mode();
|
||||
display.init().unwrap();
|
||||
|
||||
// Contrived example to test builder and instance methods. Sets rotation to 270 degress
|
||||
// or 90 degress counterclockwise
|
||||
display.set_rotation(DisplayRotation::Rotate270).unwrap();
|
||||
|
||||
let (w, h) = display.dimensions();
|
||||
|
||||
let raw: ImageRaw<BinaryColor> = ImageRaw::new(include_bytes!("./rust.raw"), 64);
|
||||
|
||||
let im = Image::new(
|
||||
&raw,
|
||||
Point::new(w as i32 / 2 - 64 / 2, h as i32 / 2 - 64 / 2),
|
||||
);
|
||||
|
||||
im.draw(&mut display).unwrap();
|
||||
|
||||
display.flush().unwrap();
|
||||
|
||||
loop {
|
||||
nop()
|
||||
}
|
||||
}
|
||||
+186
@@ -0,0 +1,186 @@
|
||||
//! Cycle the screen brightness through 5 predefined brightness levels when the "DVD" logo hits one
|
||||
//! of the sides of the display.
|
||||
//!
|
||||
//! For best results, run with the `--release` flag.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
pub mod pac {
|
||||
pub use embassy_stm32::pac::Interrupt as interrupt;
|
||||
pub use embassy_stm32::pac::*;
|
||||
}
|
||||
|
||||
#[rtic::app(device = crate::pac, peripherals= false, dispatchers = [EXTI0])]
|
||||
mod app {
|
||||
use defmt_rtt as _;
|
||||
use display_interface_spi::SPIInterface;
|
||||
use embassy_stm32::{
|
||||
gpio,
|
||||
mode::Blocking,
|
||||
spi::{self, Spi},
|
||||
time::Hertz,
|
||||
timer::low_level::Timer,
|
||||
Config,
|
||||
};
|
||||
use embedded_graphics::{
|
||||
geometry::Point,
|
||||
image::Image,
|
||||
pixelcolor::{BinaryColor, Rgb565},
|
||||
prelude::*,
|
||||
primitives::{PrimitiveStyle, Rectangle},
|
||||
};
|
||||
use panic_probe as _;
|
||||
use ssd1306::{mode::BufferedGraphicsMode, prelude::*, Ssd1306};
|
||||
use tinybmp::Bmp;
|
||||
|
||||
type Display = Ssd1306<
|
||||
SPIInterface<
|
||||
embedded_hal_bus::spi::ExclusiveDevice<
|
||||
Spi<'static, Blocking>,
|
||||
gpio::Output<'static>,
|
||||
embedded_hal_bus::spi::NoDelay,
|
||||
>,
|
||||
gpio::Output<'static>,
|
||||
>,
|
||||
DisplaySize128x64,
|
||||
BufferedGraphicsMode<DisplaySize128x64>,
|
||||
>;
|
||||
|
||||
#[shared]
|
||||
struct SharedResources {}
|
||||
|
||||
#[local]
|
||||
struct Resources {
|
||||
display: Display,
|
||||
timer: Timer<'static, embassy_stm32::peripherals::TIM1>,
|
||||
top_left: Point,
|
||||
velocity: Point,
|
||||
bmp: Bmp<Rgb565, 'static>,
|
||||
brightness: Brightness,
|
||||
}
|
||||
|
||||
#[init]
|
||||
fn init(_cx: init::Context) -> (SharedResources, Resources, init::Monotonics) {
|
||||
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);
|
||||
let mut config = spi::Config::default();
|
||||
config.frequency = Hertz::mhz(8);
|
||||
let spi = Spi::new_blocking_txonly(p.SPI1, p.PA5, p.PA7, config);
|
||||
|
||||
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 = display_interface_spi::SPIInterface::new(spi, dc);
|
||||
let mut display = Ssd1306::new(interface, DisplaySize128x64, DisplayRotation::Rotate180)
|
||||
.into_buffered_graphics_mode();
|
||||
|
||||
display
|
||||
.reset(&mut rst, &mut embassy_time::Delay {})
|
||||
.unwrap();
|
||||
display.init().unwrap();
|
||||
// Forget the RST pin to keep the display out of reset
|
||||
core::mem::forget(rst);
|
||||
|
||||
// Update framerate
|
||||
let timer = Timer::new(p.TIM1);
|
||||
timer.set_frequency(Hertz(20)); // 20 FPS
|
||||
timer.enable_update_interrupt(true);
|
||||
timer.start();
|
||||
|
||||
let bmp = Bmp::from_slice(include_bytes!("dvd.bmp")).unwrap();
|
||||
|
||||
// Init the static resources to use them later through RTIC
|
||||
(
|
||||
SharedResources {},
|
||||
Resources {
|
||||
timer,
|
||||
display,
|
||||
top_left: Point::new(5, 3),
|
||||
velocity: Point::new(1, 1),
|
||||
bmp,
|
||||
brightness: Brightness::default(),
|
||||
},
|
||||
init::Monotonics(),
|
||||
)
|
||||
}
|
||||
|
||||
#[task(binds = TIM1_UP, local = [display, top_left, velocity, timer, bmp, brightness])]
|
||||
fn update(cx: update::Context) {
|
||||
let update::LocalResources {
|
||||
display,
|
||||
top_left,
|
||||
velocity,
|
||||
timer,
|
||||
bmp,
|
||||
brightness,
|
||||
..
|
||||
} = cx.local;
|
||||
|
||||
let bottom_right = *top_left + bmp.bounding_box().size;
|
||||
|
||||
// Erase previous image position with a filled black rectangle
|
||||
Rectangle::with_corners(*top_left, bottom_right)
|
||||
.into_styled(PrimitiveStyle::with_fill(BinaryColor::Off))
|
||||
.draw(display)
|
||||
.unwrap();
|
||||
|
||||
// Check if the image collided with a screen edge
|
||||
{
|
||||
let mut collision = false;
|
||||
if bottom_right.x > display.size().width as i32 || top_left.x < 0 {
|
||||
velocity.x = -velocity.x;
|
||||
collision = true;
|
||||
}
|
||||
|
||||
if bottom_right.y > display.size().height as i32 || top_left.y < 0 {
|
||||
velocity.y = -velocity.y;
|
||||
collision = true;
|
||||
}
|
||||
|
||||
if collision {
|
||||
// Change the brightness
|
||||
*brightness = match *brightness {
|
||||
Brightness::DIMMEST => Brightness::DIM,
|
||||
Brightness::DIM => Brightness::NORMAL,
|
||||
Brightness::NORMAL => Brightness::BRIGHT,
|
||||
Brightness::BRIGHT => Brightness::BRIGHTEST,
|
||||
Brightness::BRIGHTEST => Brightness::DIMMEST, // restart cycle
|
||||
_ => Brightness::NORMAL, // Brightness is not an enum, cover rest of patterns
|
||||
};
|
||||
|
||||
// Send the new brightness value to the display
|
||||
display.set_brightness(*brightness).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
// Move the image
|
||||
*top_left += *velocity;
|
||||
|
||||
// Draw image at new position
|
||||
Image::new(bmp, *top_left)
|
||||
.draw(&mut display.color_converted())
|
||||
.unwrap();
|
||||
|
||||
// Write changes to the display
|
||||
display.flush().unwrap();
|
||||
|
||||
// Clears the update flag
|
||||
timer.clear_update_interrupt();
|
||||
}
|
||||
}
|
||||
Vendored
+167
@@ -0,0 +1,167 @@
|
||||
//! Bounce a DVD player logo around the screen
|
||||
//!
|
||||
//! Like this, but with no color changing: https://bouncingdvdlogo.com/
|
||||
//!
|
||||
//! For best results, run with the `--release` flag.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
pub mod pac {
|
||||
pub use embassy_stm32::pac::Interrupt as interrupt;
|
||||
pub use embassy_stm32::pac::*;
|
||||
}
|
||||
|
||||
#[rtic::app(device = crate::pac, peripherals= false, dispatchers = [EXTI0])]
|
||||
mod app {
|
||||
use defmt_rtt as _;
|
||||
use display_interface_spi::SPIInterface;
|
||||
use embassy_stm32::{
|
||||
gpio,
|
||||
mode::Blocking,
|
||||
spi::{self, Spi},
|
||||
time::Hertz,
|
||||
timer::low_level::Timer,
|
||||
Config,
|
||||
};
|
||||
use embedded_graphics::{
|
||||
geometry::Point,
|
||||
image::Image,
|
||||
pixelcolor::{BinaryColor, Rgb565},
|
||||
prelude::*,
|
||||
primitives::{PrimitiveStyle, Rectangle},
|
||||
};
|
||||
use panic_probe as _;
|
||||
use ssd1306::{mode::BufferedGraphicsMode, prelude::*, Ssd1306};
|
||||
use tinybmp::Bmp;
|
||||
|
||||
type Display = Ssd1306<
|
||||
SPIInterface<
|
||||
embedded_hal_bus::spi::ExclusiveDevice<
|
||||
Spi<'static, Blocking>,
|
||||
gpio::Output<'static>,
|
||||
embedded_hal_bus::spi::NoDelay,
|
||||
>,
|
||||
gpio::Output<'static>,
|
||||
>,
|
||||
DisplaySize128x64,
|
||||
BufferedGraphicsMode<DisplaySize128x64>,
|
||||
>;
|
||||
|
||||
#[shared]
|
||||
struct SharedResources {}
|
||||
|
||||
#[local]
|
||||
struct Resources {
|
||||
display: Display,
|
||||
timer: Timer<'static, embassy_stm32::peripherals::TIM1>,
|
||||
top_left: Point,
|
||||
velocity: Point,
|
||||
bmp: Bmp<Rgb565, 'static>,
|
||||
}
|
||||
|
||||
#[init]
|
||||
fn init(_cx: init::Context) -> (SharedResources, Resources, init::Monotonics) {
|
||||
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);
|
||||
let mut config = spi::Config::default();
|
||||
config.frequency = Hertz::mhz(8);
|
||||
let spi = Spi::new_blocking_txonly(p.SPI1, p.PA5, p.PA7, config);
|
||||
|
||||
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 = display_interface_spi::SPIInterface::new(spi, dc);
|
||||
let mut display = Ssd1306::new(interface, DisplaySize128x64, DisplayRotation::Rotate180)
|
||||
.into_buffered_graphics_mode();
|
||||
|
||||
display
|
||||
.reset(&mut rst, &mut embassy_time::Delay {})
|
||||
.unwrap();
|
||||
display.init().unwrap();
|
||||
|
||||
// Forget the RST pin to keep the display out of reset
|
||||
core::mem::forget(rst);
|
||||
|
||||
// Update framerate
|
||||
let timer = Timer::new(p.TIM1);
|
||||
timer.set_frequency(Hertz(20)); // 20 FPS
|
||||
timer.enable_update_interrupt(true);
|
||||
timer.start();
|
||||
|
||||
let bmp = Bmp::from_slice(include_bytes!("dvd.bmp")).unwrap();
|
||||
|
||||
// Init the static resources to use them later through RTIC
|
||||
(
|
||||
SharedResources {},
|
||||
Resources {
|
||||
timer,
|
||||
display,
|
||||
top_left: Point::new(5, 3),
|
||||
velocity: Point::new(1, 1),
|
||||
bmp,
|
||||
},
|
||||
init::Monotonics(),
|
||||
)
|
||||
}
|
||||
|
||||
#[task(binds = TIM1_UP, local = [display, top_left, velocity, timer, bmp ])]
|
||||
fn update(cx: update::Context) {
|
||||
let update::LocalResources {
|
||||
display,
|
||||
top_left,
|
||||
velocity,
|
||||
timer,
|
||||
bmp,
|
||||
..
|
||||
} = cx.local;
|
||||
|
||||
let bottom_right = *top_left + bmp.bounding_box().size;
|
||||
|
||||
// Erase previous image position with a filled black rectangle
|
||||
Rectangle::with_corners(*top_left, bottom_right)
|
||||
.into_styled(PrimitiveStyle::with_fill(BinaryColor::Off))
|
||||
.draw(display)
|
||||
.unwrap();
|
||||
|
||||
// Check if the image collided with a screen edge
|
||||
{
|
||||
if bottom_right.x > display.size().width as i32 || top_left.x < 0 {
|
||||
velocity.x = -velocity.x;
|
||||
}
|
||||
|
||||
if bottom_right.y > display.size().height as i32 || top_left.y < 0 {
|
||||
velocity.y = -velocity.y;
|
||||
}
|
||||
}
|
||||
|
||||
// Move the image
|
||||
*top_left += *velocity;
|
||||
|
||||
// Draw image at new position
|
||||
Image::new(bmp, *top_left)
|
||||
.draw(&mut display.color_converted())
|
||||
.unwrap();
|
||||
|
||||
// Write changes to the display
|
||||
display.flush().unwrap();
|
||||
|
||||
// Clears the update flag
|
||||
timer.clear_update_interrupt();
|
||||
}
|
||||
}
|
||||
Vendored
BIN
Binary file not shown.
+74
@@ -0,0 +1,74 @@
|
||||
//! 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 terminal_i2c`.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use core::fmt::Write;
|
||||
use cortex_m_rt::entry;
|
||||
use defmt_rtt as _;
|
||||
use embassy_stm32::time::Hertz;
|
||||
#[cfg(feature = "async")]
|
||||
use embassy_stm32::{bind_interrupts, i2c, peripherals};
|
||||
use panic_probe as _;
|
||||
use ssd1306::{prelude::*, I2CDisplayInterface, Ssd1306};
|
||||
|
||||
#[entry]
|
||||
fn main() -> ! {
|
||||
let p = embassy_stm32::init(Default::default());
|
||||
#[cfg(feature = "async")]
|
||||
bind_interrupts!(struct Irqs {
|
||||
I2C1_EV => i2c::EventInterruptHandler<peripherals::I2C1>;
|
||||
I2C1_ER => i2c::ErrorInterruptHandler<peripherals::I2C1>;
|
||||
});
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
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(),
|
||||
);
|
||||
|
||||
#[cfg(not(feature = "async"))]
|
||||
let i2c = embassy_stm32::i2c::I2c::new_blocking(
|
||||
p.I2C1,
|
||||
p.PB6,
|
||||
p.PB7,
|
||||
Hertz::khz(400),
|
||||
Default::default(),
|
||||
);
|
||||
|
||||
let interface = I2CDisplayInterface::new(i2c);
|
||||
let mut display =
|
||||
Ssd1306::new(interface, DisplaySize128x64, DisplayRotation::Rotate0).into_terminal_mode();
|
||||
display.init().unwrap();
|
||||
let _ = display.clear();
|
||||
|
||||
/* Endless loop */
|
||||
loop {
|
||||
for c in 97..123 {
|
||||
let _ = display.write_str(unsafe { core::str::from_utf8_unchecked(&[c]) });
|
||||
}
|
||||
for c in 65..91 {
|
||||
let _ = display.write_str(unsafe { core::str::from_utf8_unchecked(&[c]) });
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+88
@@ -0,0 +1,88 @@
|
||||
//! Print "Hello world!" with "Hello rust!" underneath. Uses the `embedded_graphics` crate to draw
|
||||
//! the text with a 6x10 pixel font.
|
||||
//!
|
||||
//! 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 text_i2c`.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use cortex_m::asm::nop;
|
||||
use cortex_m_rt::entry;
|
||||
use defmt_rtt as _;
|
||||
use embassy_stm32::time::Hertz;
|
||||
#[cfg(feature = "async")]
|
||||
use embassy_stm32::{bind_interrupts, i2c, peripherals};
|
||||
use embedded_graphics::{
|
||||
mono_font::{ascii::FONT_6X10, MonoTextStyleBuilder},
|
||||
pixelcolor::BinaryColor,
|
||||
prelude::*,
|
||||
text::{Baseline, Text},
|
||||
};
|
||||
use panic_probe as _;
|
||||
use ssd1306::{prelude::*, I2CDisplayInterface, Ssd1306};
|
||||
|
||||
#[entry]
|
||||
fn main() -> ! {
|
||||
let p = embassy_stm32::init(Default::default());
|
||||
#[cfg(feature = "async")]
|
||||
bind_interrupts!(struct Irqs {
|
||||
I2C1_EV => i2c::EventInterruptHandler<peripherals::I2C1>;
|
||||
I2C1_ER => i2c::ErrorInterruptHandler<peripherals::I2C1>;
|
||||
});
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
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(),
|
||||
);
|
||||
|
||||
#[cfg(not(feature = "async"))]
|
||||
let i2c = embassy_stm32::i2c::I2c::new_blocking(
|
||||
p.I2C1,
|
||||
p.PB6,
|
||||
p.PB7,
|
||||
Hertz::khz(400),
|
||||
Default::default(),
|
||||
);
|
||||
|
||||
let interface = I2CDisplayInterface::new(i2c);
|
||||
let mut display = Ssd1306::new(interface, DisplaySize128x64, DisplayRotation::Rotate0)
|
||||
.into_buffered_graphics_mode();
|
||||
display.init().unwrap();
|
||||
|
||||
let text_style = MonoTextStyleBuilder::new()
|
||||
.font(&FONT_6X10)
|
||||
.text_color(BinaryColor::On)
|
||||
.build();
|
||||
|
||||
Text::with_baseline("Hello world!", Point::zero(), text_style, Baseline::Top)
|
||||
.draw(&mut display)
|
||||
.unwrap();
|
||||
|
||||
Text::with_baseline("Hello Rust!", Point::new(0, 16), text_style, Baseline::Top)
|
||||
.draw(&mut display)
|
||||
.unwrap();
|
||||
|
||||
display.flush().unwrap();
|
||||
loop {
|
||||
nop()
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user