Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/target
embedded-examples/*/target
**/*.rs.bk
Cargo.lock

Expand Down
8 changes: 4 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ exclude = [
]

[dependencies]
embedded-hal = "0.2"
nb = "0.1"
display-interface = { git = "https://github.com/therealprof/display-interface.git", commit = "9b915f93e8665f56b47f11d4fe28c92c1db514bb" } # 0.5
maybe-async-cfg = "0.2"

[dependencies.itertools]
version = "0.7"
default-features = false

[features]
default = ["std"]
std = []
default = ["async"]
async = []
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
![Newhaven OLED display showing Ferris and the Rust logo](ferris-on-nhd.jpg)

Pure Rust driver for the SSD1322 OLED display chip, for use with
[embedded-hal](https://crates.io/crates/embedded-hal).
[display-interface](https://crates.io/crates/display-interface).

## Description

This driver is intended to work on embedded platforms using the `embedded-hal`
trait library. It is `no_std`, contains no added `unsafe`, and does not require
an allocator. The initial release supports the 4-wire SPI interface.
an allocator. Both sync and `async` implementations are provided.

Because the SSD1322 supports displays as large as 480x128 @ 4bpp, the primary
API uses a `Region` abstraction to allow writing a stream of pixel data from an
Expand All @@ -31,6 +31,8 @@ would consume a colossal (for a μC) 30kiB of RAM.
inspiration.
[japaric/embedded-hal](https://github.com/japaric/embedded-hal) for making
dealing with embedded hardware easy, safe, and enjoyable.
[therealprof/display_interface](https://github.com/therealprof/display-interface)
for an abstract display interface.

## License

Expand Down
117 changes: 0 additions & 117 deletions embedded-examples/init_stm32f30x.rs

This file was deleted.

6 changes: 6 additions & 0 deletions embedded-examples/nrf52-graphics-embassy/.cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
# replace nRF82810_xxAA with your chip as listed in `probe-rs chip list`
runner = "probe-rs run --chip nRF52840_xxAA"

[build]
target = "thumbv7em-none-eabi"
28 changes: 28 additions & 0 deletions embedded-examples/nrf52-graphics-embassy/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
[package]
name = "ssd1322-examples"
license = "MIT OR Apache-2.0"
edition = "2018"
publish = false

[dependencies]
embassy-sync = "0.7.2"
embassy-time = "0.5"
embassy-executor = { version = "0.9.0", features = ["arch-cortex-m", "executor-thread", "executor-interrupt"] }
embassy-nrf = { version = "0.8.0", features = ["nrf52840", "time-driver-rtc1", "time", "nfc-pins-as-gpio"] }

ssd1322 = { path = "../.." }

embedded-hal = "1.0"
embedded-hal-bus = { version = "0.3", features = ["async"] }
embedded-graphics = "0.8"

cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] }
cortex-m-rt = "0.7.0"
panic-probe = { version = "1.0.0" }
display-interface-spi = "0.5.0"

[profile.release]
debug = 2

[package.metadata.embassy]
build = [{ target = "thumbv7em-none-eabi" }]
34 changes: 34 additions & 0 deletions embedded-examples/nrf52-graphics-embassy/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//! This build script copies the `memory.x` file from the crate root into
//! a directory where the linker can always find it at build time.
//! For many projects this is optional, as the linker always searches the
//! project root directory -- wherever `Cargo.toml` is. However, if you
//! are using a workspace or have a more complicated build setup, this
//! build script becomes required. Additionally, by requesting that
//! Cargo re-run the build script whenever `memory.x` is changed,
//! updating `memory.x` ensures a rebuild of the application with the
//! new memory settings.

use std::env;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;

fn main() {
// Put `memory.x` in our output directory and ensure it's
// on the linker search path.
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());

// By default, Cargo will re-run a build script whenever
// any file in the project changes. By specifying `memory.x`
// here, we ensure the build script is only re-run when
// `memory.x` is changed.
println!("cargo:rerun-if-changed=memory.x");

println!("cargo:rustc-link-arg-bins=--nmagic");
println!("cargo:rustc-link-arg-bins=-Tlink.x");
}
11 changes: 11 additions & 0 deletions embedded-examples/nrf52-graphics-embassy/memory.x
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
MEMORY
{

FLASH (rx) : ORIGIN = 0x26000 , LENGTH = 0xED000 - 0x26000
RAM (rwx) : ORIGIN = 0x20006000, LENGTH = 0x20040000 - 0x20006000

/*
FLASH : ORIGIN = 0x00000000, LENGTH = 256K
RAM : ORIGIN = 0x20000000, LENGTH = 24K
*/
}
130 changes: 130 additions & 0 deletions embedded-examples/nrf52-graphics-embassy/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
//! Full example code for setting up an SSD1322 display and drawing to it using
//! `embedded_graphics`. This runs on an NRF52840, using a Newhaven Displays NHD-2.7-12864WD*.

#![no_std]
#![no_main]

use embassy_executor::Spawner;
use embassy_nrf::gpio::{Level, Output, OutputDrive};
use embassy_nrf::{bind_interrupts, peripherals, spim};
use embassy_time::Timer;
use embedded_hal_bus::spi as spi_bus;

use display_interface_spi::SPIInterface;
use embedded_graphics::{
framebuffer::{buffer_size, Framebuffer},
pixelcolor::{raw::LittleEndian, Gray4},
prelude::*,
primitives::{Line, PrimitiveStyle},
};
use ssd1322 as oled;

use panic_probe as _;

bind_interrupts!(struct Irqs {
SPIM3 => spim::InterruptHandler<peripherals::SPI3>;
});

#[embassy_executor::main]
async fn main(_spawner: Spawner) {
let p = embassy_nrf::init(Default::default());

let pin_rst = p.P0_05;
let pin_sck = p.P1_12;
let pin_sdo = p.P1_14;
let pin_cs = p.P1_15;
let pin_dc = p.P1_13;
let spi_instance = p.SPI3;

// My dev board has a switchable power regulator, turn that on
Output::new(p.P0_21, Level::High, OutputDrive::Standard).persist();

// Assert the display's /RESET.
let mut out_rst = Output::new(pin_rst, Level::Low, OutputDrive::Standard);
Timer::after_millis(2).await;
out_rst.set_high();
Timer::after_millis(2).await;

// Set up the push-pull outputs for CS and D/C signals.
let out_cs = Output::new(pin_cs, Level::High, OutputDrive::Standard);
let out_dc = Output::new(pin_dc, Level::High, OutputDrive::Standard);

// Set up the SPI master interface.
let mut spi_config = spim::Config::default();
spi_config.frequency = spim::Frequency::M8;
let spi_bus = spim::Spim::new_txonly(spi_instance, Irqs, pin_sck, pin_sdo, spi_config);
let spi_dev = spi_bus::ExclusiveDevice::new(spi_bus, out_cs, embassy_time::Delay).unwrap();

// Wrap all I/O in the `display-interface` SPI implementation.
// This is what the `ssd1322` crate interacts with.
let spi_iface = SPIInterface::new(spi_dev, out_dc);

// Create the Display instance.
let mut display = oled::DisplayAsync::new(
spi_iface,
oled::PixelCoord(256, 64), // double width because of duplicate pixels, see below
oled::PixelCoord(56 * 2, 0),
);

display
.init(
oled::ConfigAsync::new(
oled::ComScanDirection::RowZeroLast,
oled::ComLayout::Progressive,
)
.column_remap(oled::command::ColumnRemap::Reverse)
.clock_fosc_divset(9, 1)
.display_enhancements(true, true)
.contrast_current(0x7f)
.phase_lengths(5, 15)
.precharge_voltage(0x1f)
.com_deselect_voltage(0x04),
)
.await
.unwrap();

let mut fb =
Framebuffer::<Gray4, _, LittleEndian, 128, 64, { buffer_size::<Gray4>(128, 64) }>::new();

let mut i = 0;
let mut shade = 0;
loop {
// fb.clear(Gray4::BLACK).unwrap();

if i > 128 + 64 {
i = (i + 1) % 8;
shade = (shade + 1) % 16;
}

let point = if i < 128 {
Point::new(i, 0)
} else {
Point::new(127, i - 128)
};

Line::new(Point::new(0, 64), point)
.into_styled(PrimitiveStyle::with_stroke(Gray4::new(16 - shade), 1))
.draw(&mut fb)
.unwrap();

i += 8;

// the NHD-2.7-12864WD is a little odd in that each visible pixel is driven as two
// consecutive virtual pixels. This duplicates each nibble to account for that.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this is common, maybe it's worth including support for this? I may open an issue and see if anyone +1's it.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no clue but found this to be rather poorly documented and some other people I've talked to also seem to have found it rather odd, see mastodon thread and monome norns ssd1322 driver code for example.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually doing a web search now that I know what's up it does seem like there might be at least one other manufacturer doing something similar:

https://forum.arduino.cc/t/using-u8glib-with-ssd1322-controller-and-128x64-oled/363454/9

and it seems i'm not the only person that struggled to find documentation for this, so maybe sticking a heads-up in the main library comment would help someone out:

https://www.reddit.com/r/embedded/comments/1n5mjac/cant_get_proper_output_on_ssd1322based_128x64/

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey. I've also stumbled upon this issue while trying to use this library. The workaround of using a double-width Framebuffer does get it to render content on the full display, but that's still not ideal as text and shapes get squished horizontally, so "proper" support would be very much appreciated :)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@supertassu if you take a look at the code sample this comment thread is in, I'm actually using a "normal" framebuffer at the true aspect ratio, and the data is duplicated just while writing it out by modifying the data while iterating with flat_map:

    let pixels = fb.data().iter().flat_map(|n| {
        let upper = n & 0xf0;
        let lower = n & 0x0f;
        [upper | (upper >> 4), lower | (lower << 4)]
    });

let pixels = fb.data().iter().flat_map(|n| {
let upper = n & 0xf0;
let lower = n & 0x0f;
[upper | (upper >> 4), lower | (lower << 4)]
});

// send framebuffer to display: get a region and send the packed pixel data.
display
.region(oled::PixelCoord(0, 0), oled::PixelCoord(256, 64))
.unwrap()
.draw_packed(pixels)
.await
.unwrap();

Timer::after_millis(1).await;
}
}
Loading