MabezDev / ssd1351

A Driver crate for the SSD1351, which drives 128x128 colour displays
16 stars 14 forks source link

So how exactly do I use this? Am I usung the wrong version of embedded-graphics? #18

Closed bentwire closed 6 months ago

bentwire commented 11 months ago

So I initialize the display like this:

        let mut display = ssd1351::display::Display::new(
            iface,
            ssd1351::properties::DisplaySize::Display128x96,
            ssd1351::properties::DisplayRotation::Rotate0,
        );

        display.init().unwrap();
`

But I can't use it:
    let text = Text::with_baseline("Hello world!", Point::zero(), text_style, Baseline::Top)
        .draw(&mut display)
        .unwrap();

` Won't compile:


error[E0277]: the trait bound `ssd1351::display::Display<SpiInterface<rp2040_hal::Spi<rp2040_hal::spi::Enabled, SPI0, (rp2040_hal::gpio::Pin<Gpio7, FunctionSpi, PullDown>, rp2040_hal::gpio::Pin<Gpio4, FunctionSpi, PullDown>, rp2040_hal::gpio::Pin<Gpio6, FunctionSpi, PullDown>), 8>, rp2040_hal::gpio::Pin<Gpio9, FunctionSio<SioOutput>, PullDown>>>: DrawTarget` is not satisfied
   --> src/main.rs:325:19
    |
325 |             .draw(&mut display)
    |              ---- ^^^^^^^^^^^^ the trait `DrawTarget` is not implemented for `ssd1351::display::Display<SpiInterface<rp2040_hal::Spi<rp2040_hal::spi::Enabled, SPI0, (rp2040_hal::gpio::Pin<Gpio7, FunctionSpi, PullDown>, rp2040_hal::gpio::Pin<Gpio4, FunctionSpi, PullDown>, rp2040_hal::gpio::Pin<Gpio6, FunctionSpi, PullDown>), 8>, rp2040_hal::gpio::Pin<Gpio9, FunctionSio<SioOutput>, PullDown>>>`
    |              |
    |              required by a bound introduced by this call
    |
    = help: the following other types implement trait `DrawTarget`:
              mono_font::draw_target::MonoFontDrawTarget<'_, T, mono_font::draw_target::Foreground<<T as DrawTarget>::Color>>
              mono_font::draw_target::MonoFontDrawTarget<'_, T, mono_font::draw_target::Background<<T as DrawTarget>::Color>>
              mono_font::draw_target::MonoFontDrawTarget<'_, T, mono_font::draw_target::Both<<T as DrawTarget>::Color>>
              Clipped<'_, T>
              ColorConverted<'_, T, C>
              Cropped<'_, T>
              embedded_graphics::draw_target::Translated<'_, T>
              Framebuffer<C, RawU1, BO, WIDTH, HEIGHT, N>
            and 10 others
note: required by a bound in `embedded_graphics::Drawable::draw`
   --> /home/mike/.cargo/registry/src/index.crates.io-6f17d22bba15001f/embedded-graphics-core-0.4.0/src/drawable.rs:106:12
    |
104 |     fn draw<D>(&self, target: &mut D) -> Result<Self::Output, D::Error>
    |        ---- required by a bound in this associated function
105 |     where
106 |         D: DrawTarget<Color = Self::Color>;
    |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `Drawable::draw`
`

How do I use the driver????
tmbull commented 11 months ago

@bentwire I ran into the same issue. I was able to work around it by creating my own "wrapper" around the GraphicsMode type:

type Display = GraphicsMode<SpiInterface<Spi<stm32f4xx_hal::pac::SPI1>, stm32f4xx_hal::gpio::Pin<'A', 8, stm32f4xx_hal::gpio::Output>>>;

struct MyDisplay {
    display: Display,
}

impl MyDisplay {
    /// Draw a `Pixel` that has a color defined as `Rgb565`.
    fn draw_pixel(&mut self, pixel: Pixel<Rgb565>) -> Result<(), <MyDisplay as DrawTarget>::Error> {
        let Pixel(coord, color) = pixel;

        // Place an (x, y) pixel at the right index in the framebuffer. If the pixel coordinates
        // are out of bounds (negative or greater than (127, 127)), this operation will be a
        // noop.
        if let Ok((x @ 0..=127, y @ 0..=127)) = coord.try_into() {
            self.display.set_pixel(x, y, color.into_storage())
        }

        Ok(())
    }
}

impl Dimensions for MyDisplay {
    fn bounding_box(&self) -> Rectangle {
        let (width, height) = self.display.get_dimensions();

        Rectangle::new(Point::new(0, 0), Size::new(width as u32, height as u32))
    }
}

impl DrawTarget for MyDisplay {
    type Color = Rgb565;
    type Error = core::convert::Infallible;

    fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error> where I: IntoIterator<Item=Pixel<Self::Color>> {
        for pixel in pixels {
            self.draw_pixel(pixel)?
        }

        Ok(())
    }
}

#[entry]
fn main() -> ! {
    // Init peripherals and stuff ...

    let spi = Spi::new(
        dp.SPI1,
        (sck, NoPin::new(), mosi),
        SSD1351_SPI_MODE,
        20.MHz(),
        &clocks,
    );

    static mut BUFFER: [u8; 128 * 128 * 2] = [0; 128 * 128 * 2];
    let mut display: Display = Builder::new().with_rotation(DisplayRotation::Rotate270).connect_spi(spi, dc, &mut BUFFER).into();
    display.reset(&mut rst, &mut delay).unwrap();
    display.init().unwrap();

    let mut myDisplay = MyDisplay { display };

    let text = Text::with_baseline("Hello world!", Point::zero(), text_style, Baseline::Top)
        .draw(&mut myDisplay)
        .unwrap();

   loop {}

You would need to adjust types a bit since I'm using an STM32 rather than RP2040. In the end, I think this is probably unnecessary. I'm thinking that the error is a bit of a red herring and the problem is something other than the trait bound issue, but I'm still a rust noob so I wasn't able to determine the root cause of the error.

aguynamedben commented 10 months ago

This took me forever to figure out, but here's how I got it working on the STM32F3 DISCOVERY. Thank you for sharing your workaround @tmbull. 🙏 If I keep using this library maybe I'll try to sort it out. It seems like GraphicsMode is designed to mostly work with embedded-graphics, but there's some issue with the types/traits not lining up quite right?

I'm not sure these types are exactly correct, particularly the integers in the type such as stm32f3xx_hal::gpio::U<6>,, but... it works!

main.rs

#![deny(unsafe_code)]
#![no_std]
#![no_main]

#[cfg(not(debug_assertions))]
use panic_abort as _;
#[cfg(debug_assertions)]
use panic_semihosting as _;

use core::convert::TryInto;

use embedded_graphics::{
    mono_font::{ascii::FONT_6X10, MonoTextStyle},
    pixelcolor::Rgb565,
    prelude::*,
    primitives::Rectangle,
    text::{Baseline, Text},
};
use embedded_time::rate::*;

use cortex_m::asm;
use cortex_m_rt::entry;
use cortex_m_semihosting::hprintln;

use ssd1351::{builder::Builder, properties::DisplayRotation};
use stm32f3xx_hal::pac;
use stm32f3xx_hal::prelude::*;
use stm32f3xx_hal::spi::Spi;

#[entry]
fn main() -> ! {
    hprintln!("Let's go!");

    let cp = cortex_m::Peripherals::take().unwrap();
    let dp = pac::Peripherals::take().unwrap();

    let mut flash = dp.FLASH.constrain();
    let mut rcc = dp.RCC.constrain();
    let mut gpioe = dp.GPIOE.split(&mut rcc.ahb);
    let mut gpioa = dp.GPIOA.split(&mut rcc.ahb);

    let clocks = rcc
        .cfgr
        .use_hse(8.MHz())
        .sysclk(48.MHz())
        .pclk1(24.MHz())
        .freeze(&mut flash.acr);

    // Configure pins for LEDs

    let mut onboard_led = gpioe
        .pe13
        .into_push_pull_output(&mut gpioe.moder, &mut gpioe.otyper);
    let mut external_led = gpioe
        .pe9
        .into_push_pull_output(&mut gpioe.moder, &mut gpioe.otyper);

    // Configure SPI + OLED Screen (ssd1351)

    let sck =
        gpioa
            .pa5
            .into_af_push_pull::<5>(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.afrl);
    let miso =
        gpioa
            .pa6
            .into_af_push_pull::<5>(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.afrl);
    let mosi =
        gpioa
            .pa7
            .into_af_push_pull::<5>(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.afrl);
    let dc = gpioa
        .pa3
        .into_push_pull_output(&mut gpioa.moder, &mut gpioa.otyper);
    let mut rst = gpioa
        .pa2
        .into_push_pull_output(&mut gpioa.moder, &mut gpioa.otyper);
    let _cs = gpioa
        .pa4
        .into_push_pull_output(&mut gpioa.moder, &mut gpioa.otyper);

    let spi = Spi::new(dp.SPI1, (sck, miso, mosi), 1.MHz(), clocks, &mut rcc.apb2);

    let mut display: Display = Builder::new()
        .with_rotation(DisplayRotation::Rotate270)
        .connect_spi(spi, dc)
        .into();
    let mut delay = stm32f3xx_hal::delay::Delay::new(cp.SYST, clocks);
    display.reset(&mut rst, &mut delay).unwrap();
    display.init().unwrap();

    let mut my_display = MyDisplay { display };

    let text_style = MonoTextStyle::new(&FONT_6X10, Rgb565::WHITE);
    let _text = Text::with_baseline(
        "Hello Derek! I hope \nyou eat a lot of\nturkey this week!",
        Point::zero(),
        text_style,
        Baseline::Top,
    )
    .draw(&mut my_display)
    .unwrap();

    loop {
        onboard_led.toggle().unwrap();
        external_led.toggle().unwrap();
        asm::delay(2_000_000);
    }
}

//
// HACKS
//
// To get ssd1351 driver to play nicely with embedded-graphics, see
// https://github.com/MabezDev/ssd1351/issues/18
//

type Display = ssd1351::mode::GraphicsMode<
    ssd1351::interface::spi::SpiInterface<
        Spi<
            pac::SPI1,
            (
                stm32f3xx_hal::gpio::Pin<
                    stm32f3xx_hal::gpio::Gpioa,
                    stm32f3xx_hal::gpio::U<5>,
                    stm32f3xx_hal::gpio::Alternate<stm32f3xx_hal::gpio::PushPull, 5>,
                >,
                stm32f3xx_hal::gpio::Pin<
                    stm32f3xx_hal::gpio::Gpioa,
                    stm32f3xx_hal::gpio::U<6>,
                    stm32f3xx_hal::gpio::Alternate<stm32f3xx_hal::gpio::PushPull, 5>,
                >,
                stm32f3xx_hal::gpio::Pin<
                    stm32f3xx_hal::gpio::Gpioa,
                    stm32f3xx_hal::gpio::U<7>,
                    stm32f3xx_hal::gpio::Alternate<stm32f3xx_hal::gpio::PushPull, 5>,
                >,
            ),
            u8,
        >,
        stm32f3xx_hal::gpio::Pin<
            stm32f3xx_hal::gpio::Gpioa,
            stm32f3xx_hal::gpio::U<3>,
            stm32f3xx_hal::gpio::Output<stm32f3xx_hal::gpio::PushPull>,
        >,
    >,
>;

struct MyDisplay {
    display: Display,
}

impl MyDisplay {
    /// Draw a `Pixel` that has a color defined as `Rgb565`.
    fn draw_pixel(&mut self, pixel: Pixel<Rgb565>) -> Result<(), <MyDisplay as DrawTarget>::Error> {
        let Pixel(coord, color) = pixel;

        // Place an (x, y) pixel at the right index in the framebuffer. If the pixel coordinates
        // are out of bounds (negative or greater than (127, 127)), this operation will be a
        // noop.
        if let Ok((x @ 0..=127, y @ 0..=127)) = coord.try_into() {
            self.display.set_pixel(x, y, color.into_storage())
        }

        Ok(())
    }
}

impl Dimensions for MyDisplay {
    fn bounding_box(&self) -> Rectangle {
        let (width, height) = self.display.get_dimensions();

        Rectangle::new(Point::new(0, 0), Size::new(width as u32, height as u32))
    }
}

impl DrawTarget for MyDisplay {
    type Color = Rgb565;
    type Error = core::convert::Infallible;

    fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
    where
        I: IntoIterator<Item = Pixel<Self::Color>>,
    {
        for pixel in pixels {
            self.draw_pixel(pixel)?
        }

        Ok(())
    }
}

//
// (End HACKS)
//

Cargo.toml

[package]
authors = ["Ben Standefer <benstandefer@gmail.com>"]
edition = "2018"
readme = "README.md"
name = "np1"
version = "0.1.0"

[dependencies]
cortex-m = { version = "0.7.7", features = ["critical-section-single-core"] }
cortex-m-rt = { version = "0.7.3", features = ["device"] }
cortex-m-semihosting = "0.5.0"
embedded-graphics = "0.8.1"
embedded-time = "0.12.1"
panic-halt = "0.2.0"
panic-semihosting = { version = "0.6.0", features = ["exit"] }
ssd1351 = "0.4.2"
stm32f3xx-hal = { version = "0.9.2", features = ["ld", "rt", "stm32f303xc"] }

# Uncomment for the panic example.
# panic-itm = "0.4.1"

# Uncomment for the allocator example.
# alloc-cortex-m = "0.4.0"

# Uncomment for the device example.
# Update `memory.x`, set target to `thumbv7em-none-eabihf` in `.cargo/config`,
# and then use `cargo build --examples device` to build it.
# [dependencies.stm32f3]
# features = ["stm32f303", "rt"]
# version = "0.7.1"

# this lets you use `cargo fix`!
[[bin]]
name = "np1"
test = false
bench = false

[profile.release]
codegen-units = 1 # better optimizations
debug = true      # symbols are nice and they don't increase the size on Flash
lto = true        # better optimizations
Orange-Murker commented 8 months ago

Hi. I have added embedded-hal v1.0.0 support and that seems to have fixed the issue. Can you try the git version of this crate if your HAL already supports e-h v1.0.0 to see if that resolved the issue for you?

You can add it like this:

ssd1351 = { git = "https://github.com/MabezDev/ssd1351.git" }
rbomze commented 6 months ago

The following could save you some time and headaches ;):

Don't forget to activate the graphics feature. No need for custom wrappers as seen above.

Cargo.toml

...
[dependencies]
ssd1351 = { version = "0.5.0", features = ["graphics"] }
...

The following is specific for embassy-rs with an esp32: it did cost me plenty of time to figure out that after initializing SPI you get an SPIBus, not an SPIDevice. Something similar to the following is necessary :

let spi_bus = Spi::new(peripherals.SPI2,5000u32.kHz(),SpiMode::Mode0, &clocks)
    .with_sck(sclk)
    .with_mosi(mosi);
 let spi_dev = ExclusiveDevice::new(spi_bus, cs, embedded_hal_bus::spi::NoDelay);
 let di = SPIInterface::new(spi_dev, dc);
 let display = ssd1351::display::Display::new(di, DisplaySize::Display128x128, DisplayRotation::Rotate0);
Orange-Murker commented 6 months ago

Thank you for confirming that it works with embassy and e-h-v1.0. I will probably add some code examples to the repository later on.