diff --git a/.gitignore b/.gitignore index 143b1ca..4353154 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -/target/ +**/target/ **/*.rs.bk Cargo.lock diff --git a/Cargo.toml b/Cargo.toml index ef5ad22..c3fbb06 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ version = "0.3.0" edition = "2018" [package.metadata.docs.rs] -targets = [ "thumbv7m-none-eabi" ] +targets = ["thumbv7m-none-eabi"] all-features = true [badges] @@ -22,6 +22,7 @@ circle-ci = { repository = "jamwaffles/ssd1331", branch = "master" } [dependencies] embedded-hal = "0.2.3" embedded-graphics-core = { version = "0.3.2", optional = true } +embedded-hal-async = { git = "https://github.com/rust-embedded/embedded-hal" } [dev-dependencies] cortex-m = "0.7.3" @@ -29,11 +30,12 @@ cortex-m-rt = "0.6.11" panic-semihosting = "0.5.3" embedded-graphics = "0.7.1" tinybmp = "0.3.1" -stm32f1xx-hal = { version = "0.7.0", features = [ "rt", "stm32f103" ] } +stm32f1xx-hal = { version = "0.7.0", features = ["rt", "stm32f103"] } [features] default = ["graphics"] graphics = ["embedded-graphics-core"] +embedded-async = [] [profile.dev] codegen-units = 1 diff --git a/README.md b/README.md index 2c4da32..1e6e1d7 100644 --- a/README.md +++ b/README.md @@ -31,10 +31,14 @@ You can also export images directly from The GIMP by saving as `.bmp` and choosi ## [Examples](examples) +Examples are stored in per target directories in ssd1331-examples. cd to your preferred example + +`cd ssd1331-examples/stm32f1-examples/` + This crate uses [`probe-run`](https://crates.io/crates/probe-run) to run the examples. Once set up, it should be as simple as `cargo run --example --release`. `--release` will be required for some examples to reduce FLASH usage. Load a BMP image of the Rust logo and display it in the center of the display. From -[`examples/bmp.rs`](examples/bmp.rs): +[`ssd1331-examples/stm32f1-examples/bmp.rs`](examples/bmp.rs): ```rust #![no_std] @@ -93,7 +97,7 @@ fn main() -> ! { let (w, h) = disp.dimensions(); let bmp = - Bmp::from_slice(include_bytes!("./rust-pride.bmp")).expect("Failed to load BMP image"); + Bmp::from_slice(include_bytes!("../../../assets/rust-pride.bmp")).expect("Failed to load BMP image"); let im: Image> = Image::new(&bmp, Point::zero()); diff --git a/examples/ferris.png b/assets/ferris.png similarity index 100% rename from examples/ferris.png rename to assets/ferris.png diff --git a/examples/ferris.raw b/assets/ferris.raw similarity index 100% rename from examples/ferris.raw rename to assets/ferris.raw diff --git a/examples/rust-pride.bmp b/assets/rust-pride.bmp similarity index 100% rename from examples/rust-pride.bmp rename to assets/rust-pride.bmp diff --git a/examples/rust.png b/assets/rust.png similarity index 100% rename from examples/rust.png rename to assets/rust.png diff --git a/examples/rust.raw b/assets/rust.raw similarity index 100% rename from examples/rust.raw rename to assets/rust.raw diff --git a/build.sh b/build.sh index 5710494..a5670c7 100755 --- a/build.sh +++ b/build.sh @@ -14,19 +14,23 @@ fi cargo fmt --all -- --check -cargo build --target $TARGET --all-features --release +# todo not building all features +cargo build --target $TARGET --release cargo test --lib --target x86_64-unknown-linux-gnu cargo test --doc --target x86_64-unknown-linux-gnu if [ -z $DISABLE_EXAMPLES ]; then - cargo build --target $TARGET --all-features --examples + # todo list of example directories in metadata so other + (cd ssd1331-examples/stm32f1-examples && cargo build --examples) + (cd ssd1331-examples/embassy-nrf && cargo build --examples) fi # Remove stale docs - the linkchecker might miss links to old files if they're not removed cargo clean --doc cargo clean --doc --target $TARGET -cargo doc --all-features --target $TARGET +# todo not building all features +cargo doc --target $TARGET linkchecker target/$TARGET/doc/ssd1331/index.html diff --git a/src/command.rs b/src/command.rs index 7832d33..12bcb6a 100644 --- a/src/command.rs +++ b/src/command.rs @@ -50,6 +50,75 @@ pub enum Command { } impl Command { + /// Send command to SSD1331 + #[cfg(feature = "embedded-async")] + pub async fn send_async( + self, + spi: &mut SPI, + dc: &mut DC, + ) -> Result<(), Error> + where + SPI: embedded_hal_async::spi::SpiBusWrite, + DC: OutputPin, + { + // Transform command into a fixed size array of 7 u8 and the real length for sending + let (data, len) = match self { + Command::Contrast(a, b, c) => ([0x81, a, 0x82, b, 0x83, c, 0], 6), + // TODO: Collapse AllOn and Invert commands into new DisplayMode cmd with enum + Command::AllOn(on) => ([if on { 0xA5 } else { 0xA6 }, 0, 0, 0, 0, 0, 0], 1), + Command::Invert(inv) => ([if inv { 0xA7 } else { 0xA4 }, 0, 0, 0, 0, 0, 0], 1), + Command::DisplayOn(on) => ([0xAE | (on as u8), 0, 0, 0, 0, 0, 0], 1), + Command::ColumnAddress(start, end) => ([0x15, start, end, 0, 0, 0, 0], 3), + Command::RowAddress(start, end) => ([0x75, start, end, 0, 0, 0, 0], 3), + Command::StartLine(line) => ([0xA1, (0x3F & line), 0, 0, 0, 0, 0], 2), + Command::RemapAndColorDepth(hremap, vremap, cmode, addr_inc_mode) => ( + [ + 0xA0, + 0x20 | ((vremap as u8) << 4 + | (hremap as u8) << 1 + | (cmode as u8) << 6 + | (addr_inc_mode as u8)), + 0, + 0, + 0, + 0, + 0, + ], + 2, + ), + Command::Multiplex(ratio) => ([0xA8, ratio, 0, 0, 0, 0, 0], 2), + Command::ReverseComDir(rev) => ([0xC0 | ((rev as u8) << 3), 0, 0, 0, 0, 0, 0], 1), + Command::DisplayOffset(offset) => ([0xA2, offset, 0, 0, 0, 0, 0], 2), + Command::ComPinConfig(alt, lr) => ( + [ + 0xDA, + 0x2 | ((alt as u8) << 4) | ((lr as u8) << 5), + 0, + 0, + 0, + 0, + 0, + ], + 2, + ), + Command::DisplayClockDiv(fosc, div) => { + ([0xB3, ((0xF & fosc) << 4) | (0xF & div), 0, 0, 0, 0, 0], 2) + } + Command::PreChargePeriod(phase1, phase2) => ( + [0x3e, ((0xF & phase2) << 4) | (0xF & phase1), 0, 0, 0, 0, 0], + 2, + ), + Command::VcomhDeselect(level) => ([0xBE, (level as u8) << 1, 0, 0, 0, 0, 0], 2), + Command::Noop => ([0xE3, 0, 0, 0, 0, 0, 0], 1), + }; + + // Command mode. 1 = data, 0 = command + dc.set_low().map_err(Error::Pin)?; + + // Send command over the interface + spi.write(&data[0..len]).await.map_err(Error::Comm) + } + /// Send command to SSD1331 pub fn send( self, diff --git a/src/display.rs b/src/display.rs index 19753a9..aabe339 100644 --- a/src/display.rs +++ b/src/display.rs @@ -39,7 +39,7 @@ const BUF_SIZE: usize = 96 * 64 * 2; /// let dc = Pin; /// /// let mut display = Ssd1331::new(spi, dc, Rotate0); -/// let raw = ImageRawLE::new(include_bytes!("../examples/ferris.raw"), 86); +/// let raw = ImageRawLE::new(include_bytes!("../assets/ferris.raw"), 86); /// /// let image: Image> = Image::new(&raw, Point::zero()); /// @@ -99,6 +99,36 @@ pub struct Ssd1331 { dc: DC, } +#[cfg(feature = "embedded-async")] +impl Ssd1331 +where + SPI: embedded_hal_async::spi::SpiBusWrite, + DC: OutputPin, +{ + /// Send the full framebuffer to the display + /// + /// This resets the draw area the full size of the display + pub async fn flush_async(&mut self) -> Result<(), Error> { + // Ensure the display buffer is at the origin of the display before we send the full frame + // to prevent accidental offsets + // self.set_draw_area((0, 0), (DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1))?; + + Command::ColumnAddress(0, DISPLAY_WIDTH - 1) + .send_async(&mut self.spi, &mut self.dc) + .await?; + Command::RowAddress(0.into(), (DISPLAY_HEIGHT - 1).into()) + .send_async(&mut self.spi, &mut self.dc) + .await?; + + // 1 = data, 0 = command + self.dc.set_high().map_err(Error::Pin)?; + + embedded_hal_async::spi::SpiBusWrite::write(&mut self.spi, &self.buffer) + .await + .map_err(Error::Comm) + } +} + impl Ssd1331 where SPI: hal::blocking::spi::Write, diff --git a/src/lib.rs b/src/lib.rs index 38e072b..53c4060 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -60,7 +60,7 @@ //! //! let (w, h) = display.dimensions(); //! -//! let bmp = Bmp::from_slice(include_bytes!("../examples/rust-pride.bmp")) +//! let bmp = Bmp::from_slice(include_bytes!("../assets/rust-pride.bmp")) //! .expect("Failed to load BMP image"); //! //! let im: Image> = Image::new(&bmp, Point::zero()); diff --git a/ssd1331-examples/embassy-nrf/.cargo/config b/ssd1331-examples/embassy-nrf/.cargo/config new file mode 100644 index 0000000..efe3b32 --- /dev/null +++ b/ssd1331-examples/embassy-nrf/.cargo/config @@ -0,0 +1,8 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +runner = "probe-run --chip nRF52840_xxAA" +rustflags = [ + "-C", "link-arg=-Tlink.x", +] + +[build] +target = "thumbv7em-none-eabihf" diff --git a/ssd1331-examples/embassy-nrf/Cargo.toml b/ssd1331-examples/embassy-nrf/Cargo.toml new file mode 100644 index 0000000..89b631a --- /dev/null +++ b/ssd1331-examples/embassy-nrf/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "embassy-nrf" +version = "0.1.0" +edition = "2021" + +[dependencies] +embedded-hal = "0.2.7" +embedded-hal-async = { version = "0.1.0-alpha.0" } +embedded-graphics-core = { version = "0.3.2", optional = true } +cortex-m = "0.7.3" +cortex-m-rt = "0.6.11" +embedded-graphics = "0.7.1" +tinybmp = "0.3.1" +futures = { version = "0.3.17", default-features = false } +embassy = { version = "0.1.0", git = "https://github.com/embassy-rs/embassy" } +embassy-nrf = { version = "0.1.0", git = "https://github.com/embassy-rs/embassy", features = [ + "nightly", + "unstable-traits", + "nrf52840", + "gpiote", + "time-driver-rtc1" +] } +ssd1331 = { path = "../../", features = ["embedded-async"] } + +[patch.crates-io] +embassy = { git = "https://github.com/embassy-rs/embassy" } +embassy-nrf = { git = "https://github.com/embassy-rs/embassy" } +embassy-macros = { git = "https://github.com/embassy-rs/embassy" } +embedded-hal-async = { git = "https://github.com/rust-embedded/embedded-hal" } +embedded-hal = { git = "https://github.com/rust-embedded/embedded-hal" } + +[profile.dev] +codegen-units = 1 +incremental = false + +[profile.release] +codegen-units = 1 +debug = true +lto = true diff --git a/build.rs b/ssd1331-examples/embassy-nrf/build.rs similarity index 100% rename from build.rs rename to ssd1331-examples/embassy-nrf/build.rs diff --git a/ssd1331-examples/embassy-nrf/examples/bmp.rs b/ssd1331-examples/embassy-nrf/examples/bmp.rs new file mode 100644 index 0000000..ec492ff --- /dev/null +++ b/ssd1331-examples/embassy-nrf/examples/bmp.rs @@ -0,0 +1,113 @@ +//! The rust-toolchain will pull in the correct nightly and target so all you +//! need to run is +//! +//! cargo run --release +//! +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use core::future::pending; +use embassy::interrupt::InterruptExt; +use embassy::time::{Duration, Timer}; +use embassy::util::Forever; +use embassy_nrf::gpio::{self, AnyPin, Level, Output, OutputDrive, Pin}; +use embassy_nrf::{interrupt, spim}; +use embedded_graphics::prelude::*; +use embedded_graphics::{image::Image, pixelcolor::Rgb565}; +use ssd1331::{DisplayRotation, Ssd1331}; +use tinybmp::Bmp; + +// we make a lazily created static +static EXECUTOR: Forever = Forever::new(); + +#[cortex_m_rt::entry] +fn main() -> ! { + // once we hit runtime we create and fill that executor finally + let executor = EXECUTOR.put(embassy::executor::Executor::new()); + + // provides the peripherals from the async first pac if you selected it + let dp = embassy_nrf::init(Default::default()); + + let green = gpio::Output::new( + // degrade just a typesystem hack to forget which pin it is so we can + // call it Anypin and make our function calls more generic + dp.P0_22.degrade(), + gpio::Level::High, + gpio::OutputDrive::Standard, + ); + + // spawn tasks + executor.run(|spawner| { + let _ = spawner.spawn(blinky_task(green)); + let _ = spawner.spawn(display_task()); + }) +} + +#[embassy::task] +async fn blinky_task(mut green: gpio::Output<'static, AnyPin>) { + loop { + green.set_high(); + Timer::after(Duration::from_millis(300)).await; + green.set_low(); + Timer::after(Duration::from_millis(1000)).await; + } +} + +#[embassy::task] +pub async fn display_task() { + // Too lazy to pass all the pins and peripherals we need. + // Safety: Fragile but safe as long as pins and peripherals arent used + // anywhere else + let mut dp = unsafe { ::steal() }; + + let mut spim_irq = interrupt::take!(SPIM3); + spim_irq.set_priority(interrupt::Priority::P4); + + let mut spim_config = spim::Config::default(); + spim_config.frequency = spim::Frequency::M16; + let spim = spim::Spim::new_txonly( + &mut dp.SPI3, + &mut spim_irq, + &mut dp.P0_21, + &mut dp.P0_17, + spim_config, + ); + + let mut rst = Output::new(&mut dp.P0_16, Level::High, OutputDrive::Standard); + let dc = Output::new(&mut dp.P0_15, Level::High, OutputDrive::Standard); + let mut display = Ssd1331::new(spim, dc, DisplayRotation::Rotate0); + Timer::after(Duration::from_millis(1)).await; + rst.set_low(); + Timer::after(Duration::from_millis(1)).await; + rst.set_high(); + display.init().unwrap(); + + let (w, h) = display.dimensions(); + + let bmp = + Bmp::from_slice(include_bytes!("../../../assets/rust-pride.bmp")).expect("Failed to load BMP image"); + + let im: Image> = Image::new(&bmp, Point::zero()); + + // Position image in the center of the display + let moved = im.translate(Point::new( + (w as u32 - bmp.size().width) as i32 / 2, + (h as u32 - bmp.size().height) as i32 / 2, + )); + + moved.draw(&mut display).unwrap(); + + display.flush_async().await.unwrap(); + // display.flush().unwrap(); + + // Block forever so the above drivers don't get dropped + pending::<()>().await; +} + +#[panic_handler] // panicking behavior +fn panic(_: &core::panic::PanicInfo) -> ! { + loop { + cortex_m::asm::bkpt(); + } +} diff --git a/ssd1331-examples/embassy-nrf/memory.x b/ssd1331-examples/embassy-nrf/memory.x new file mode 100644 index 0000000..b86bf59 --- /dev/null +++ b/ssd1331-examples/embassy-nrf/memory.x @@ -0,0 +1,6 @@ +MEMORY +{ + /* NOTE 1 K = 1 KiBi = 1024 bytes */ + FLASH : ORIGIN = 0x00000000, LENGTH = 1024K + RAM : ORIGIN = 0x20000000, LENGTH = 256K +} diff --git a/ssd1331-examples/embassy-nrf/rust-toolchain b/ssd1331-examples/embassy-nrf/rust-toolchain new file mode 100644 index 0000000..05204be --- /dev/null +++ b/ssd1331-examples/embassy-nrf/rust-toolchain @@ -0,0 +1,6 @@ +# Before upgrading check that everything is available on all tier1 targets here: +# https://rust-lang.github.io/rustup-components-history +[toolchain] +channel = "nightly-2022-04-24" +components = [ "rust-src", "rustfmt" ] +targets = ["thumbv7em-none-eabi"] diff --git a/.cargo/config b/ssd1331-examples/stm32f1-examples/.cargo/config similarity index 100% rename from .cargo/config rename to ssd1331-examples/stm32f1-examples/.cargo/config diff --git a/ssd1331-examples/stm32f1-examples/Cargo.toml b/ssd1331-examples/stm32f1-examples/Cargo.toml new file mode 100644 index 0000000..241a2ba --- /dev/null +++ b/ssd1331-examples/stm32f1-examples/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "stm32f1-examples" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +embedded-hal = "0.2.6" +cortex-m = "0.7.3" +cortex-m-rt = "0.6.11" +embedded-graphics = "0.7.1" +tinybmp = "0.3.1" +stm32f1xx-hal = { version = "0.7.0", features = ["rt", "stm32f103"] } +panic-semihosting = "0.5.3" +ssd1331 = { path = "../../" } + +[profile.dev] +codegen-units = 1 +incremental = false + +[profile.release] +codegen-units = 1 +debug = true +lto = true diff --git a/ssd1331-examples/stm32f1-examples/build.rs b/ssd1331-examples/stm32f1-examples/build.rs new file mode 100644 index 0000000..364f9a0 --- /dev/null +++ b/ssd1331-examples/stm32f1-examples/build.rs @@ -0,0 +1,14 @@ +use std::{env, fs::File, io::Write, path::PathBuf}; + +pub fn main() { + // Put the linker script somewhere the linker can find it + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-changed=memory.x"); +} diff --git a/examples/bmp.rs b/ssd1331-examples/stm32f1-examples/examples/bmp.rs similarity index 95% rename from examples/bmp.rs rename to ssd1331-examples/stm32f1-examples/examples/bmp.rs index 0d211c6..82fa4e9 100644 --- a/examples/bmp.rs +++ b/ssd1331-examples/stm32f1-examples/examples/bmp.rs @@ -80,8 +80,8 @@ fn main() -> ! { let (w, h) = display.dimensions(); - let bmp = - Bmp::from_slice(include_bytes!("./rust-pride.bmp")).expect("Failed to load BMP image"); + let bmp = Bmp::from_slice(include_bytes!("../../../assets/rust-pride.bmp")) + .expect("Failed to load BMP image"); let im: Image> = Image::new(&bmp, Point::zero()); diff --git a/examples/graphics.rs b/ssd1331-examples/stm32f1-examples/examples/graphics.rs similarity index 100% rename from examples/graphics.rs rename to ssd1331-examples/stm32f1-examples/examples/graphics.rs diff --git a/examples/image.rs b/ssd1331-examples/stm32f1-examples/examples/image.rs similarity index 96% rename from examples/image.rs rename to ssd1331-examples/stm32f1-examples/examples/image.rs index 5b04f0a..a893ad7 100644 --- a/examples/image.rs +++ b/ssd1331-examples/stm32f1-examples/examples/image.rs @@ -85,7 +85,7 @@ fn main() -> ! { // Loads an 86x64px image encoded in LE (Little Endian) format. This image is a 16BPP image of // the Rust mascot, Ferris. - let im = ImageRawLE::new(include_bytes!("./ferris.raw"), 86); + let im = ImageRawLE::new(include_bytes!("../../../assets/ferris.raw"), 86); Image::new(&im, Point::new((96 - 86) / 2, 0)) .draw(&mut display) diff --git a/examples/pixelsquare.rs b/ssd1331-examples/stm32f1-examples/examples/pixelsquare.rs similarity index 100% rename from examples/pixelsquare.rs rename to ssd1331-examples/stm32f1-examples/examples/pixelsquare.rs diff --git a/examples/rotation.rs b/ssd1331-examples/stm32f1-examples/examples/rotation.rs similarity index 96% rename from examples/rotation.rs rename to ssd1331-examples/stm32f1-examples/examples/rotation.rs index 54009ba..aa69653 100644 --- a/examples/rotation.rs +++ b/ssd1331-examples/stm32f1-examples/examples/rotation.rs @@ -86,7 +86,7 @@ fn main() -> ! { // Load a 1BPP 64x64px image with LE (Little Endian) encoding of the Rust logo, white foreground // black background - let im = ImageRawLE::::new(include_bytes!("./rust.raw"), 64); + let im = ImageRawLE::::new(include_bytes!("../../../assets/rust.raw"), 64); // Use `color_converted` to create a wrapper that converts BinaryColors to Rgb565 colors to send // to the display. diff --git a/examples/text.rs b/ssd1331-examples/stm32f1-examples/examples/text.rs similarity index 100% rename from examples/text.rs rename to ssd1331-examples/stm32f1-examples/examples/text.rs diff --git a/memory.x b/ssd1331-examples/stm32f1-examples/memory.x similarity index 100% rename from memory.x rename to ssd1331-examples/stm32f1-examples/memory.x