Skip to content

Commit

Permalink
Add support for 72x40px SSD1306 modules (#101)
Browse files Browse the repository at this point in the history
* Add 72x40 display size

* Add copypasta 72x40 demo

* Set COM pin config correctly

The display I have at least uses an alternate row connection mapping as
opposed to sequential. Without this change, the display skips every
second row and makes everything stretched 2x in Y

* Add support for display offsets

Adds offset of (28, 0) as discovered by this Reddit post:

https://www.reddit.com/r/arduino/comments/bup1kf/anyone_got_one_of_these_little_beauties_working/

* Refactor 70x42 example for centered layout

* Add changelog entry

* Fix example

* Update 70x42 example for e-g 0.6.0-alpha.3
  • Loading branch information
jamwaffles authored Feb 7, 2020
1 parent 5be7337 commit b453204
Show file tree
Hide file tree
Showing 6 changed files with 163 additions and 4 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@

## [Unreleased] - ReleaseDate

### Added

- [#101](https://github.com/jamwaffles/ssd1306/pull/101) Add support for modules with a 72x40px display size. These are often advertised as 70x40px displays which are likely the same hardware. An example is also added - `graphics_i2c_72x40`.

### Fixed

- Fix docs.rs build by targeting `x86_64-unknown-linux-gnu`
Expand Down
130 changes: 130 additions & 0 deletions examples/graphics_i2c_72x40.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
//! 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 -> PB9
//! (green) SCL -> PB8
//! ```
//!
//! Run on a Blue Pill with `cargo run --example graphics_i2c`.

#![no_std]
#![no_main]

extern crate cortex_m;
extern crate cortex_m_rt as rt;
extern crate panic_semihosting;
extern crate stm32f1xx_hal as hal;

use cortex_m_rt::{entry, exception, ExceptionFrame};
use embedded_graphics::{
pixelcolor::BinaryColor,
prelude::*,
primitives::{Circle, Rectangle, Triangle},
style::PrimitiveStyleBuilder,
};
use hal::{
i2c::{BlockingI2c, DutyCycle, Mode},
prelude::*,
stm32,
};
use ssd1306::{prelude::*, Builder};

#[entry]
fn main() -> ! {
let dp = stm32::Peripherals::take().unwrap();

let mut flash = dp.FLASH.constrain();
let mut rcc = dp.RCC.constrain();

let clocks = rcc.cfgr.freeze(&mut flash.acr);

let mut afio = dp.AFIO.constrain(&mut rcc.apb2);

let mut gpiob = dp.GPIOB.split(&mut rcc.apb2);

let scl = gpiob.pb8.into_alternate_open_drain(&mut gpiob.crh);
let sda = gpiob.pb9.into_alternate_open_drain(&mut gpiob.crh);

let i2c = BlockingI2c::i2c1(
dp.I2C1,
(scl, sda),
&mut afio.mapr,
Mode::Fast {
frequency: 400_000.hz(),
duty_cycle: DutyCycle::Ratio2to1,
},
clocks,
&mut rcc.apb1,
1000,
10,
1000,
1000,
);

let mut disp: GraphicsMode<_> = Builder::new()
.size(DisplaySize::Display72x40)
.connect_i2c(i2c)
.into();

disp.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::new(Point::new(0, 0), Point::new(71, 39))
.into_styled(style)
.draw(&mut disp);

// Triangle
Triangle::new(
Point::new(0, size),
Point::new(size / 2, 0),
Point::new(size, size),
)
.translate(offset)
.into_styled(style)
.draw(&mut disp);

// Move over to next position
let offset = offset + Point::new(spacing, 0);

// Draw a square
Rectangle::new(Point::new(0, 0), Point::new(size, size))
.translate(offset)
.into_styled(style)
.draw(&mut disp);

// Move over a bit more
let offset = offset + Point::new(spacing, 0);

// Circle
Circle::new(Point::new(size / 2, size / 2), size as u32 / 2)
.translate(offset)
.into_styled(style)
.draw(&mut disp);

disp.flush().unwrap();

loop {}
}

#[exception]
fn HardFault(ef: &ExceptionFrame) -> ! {
panic!("{:#?}", ef);
}
3 changes: 3 additions & 0 deletions src/displaysize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ pub enum DisplaySize {
Display128x32,
/// 96 by 16 pixels
Display96x16,
/// 70 by 42 pixels
Display72x40,
}

impl DisplaySize {
Expand All @@ -20,6 +22,7 @@ impl DisplaySize {
DisplaySize::Display128x64 => (128, 64),
DisplaySize::Display128x32 => (128, 32),
DisplaySize::Display96x16 => (96, 16),
DisplaySize::Display72x40 => (72, 40),
}
}
}
16 changes: 12 additions & 4 deletions src/mode/graphics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,11 +159,17 @@ where
self.min_y = width - 1;
self.max_y = 0;

// Compensate for any offset in the physical display. For example, the 72x40 display has an
// offset of (28, 0) pixels.
let offs = self.properties.display_offset;

// Tell the display to update only the part that has changed
match self.properties.get_rotation() {
DisplayRotation::Rotate0 | DisplayRotation::Rotate180 => {
self.properties
.set_draw_area((disp_min_x, disp_min_y), (disp_max_x, disp_max_y))?;
self.properties.set_draw_area(
(disp_min_x + offs.0, disp_min_y + offs.1),
(disp_max_x + offs.0, disp_max_y + offs.1),
)?;

self.properties.bounded_draw(
&self.buffer,
Expand All @@ -173,8 +179,10 @@ where
)
}
DisplayRotation::Rotate90 | DisplayRotation::Rotate270 => {
self.properties
.set_draw_area((disp_min_y, disp_min_x), (disp_max_y, disp_max_x))?;
self.properties.set_draw_area(
(disp_min_y + offs.1, disp_min_x + offs.0),
(disp_max_y + offs.1, disp_max_x + offs.0),
)?;

self.properties.bounded_draw(
&self.buffer,
Expand Down
4 changes: 4 additions & 0 deletions src/mode/terminal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,10 +176,13 @@ where
pub fn clear(&mut self) -> Result<(), TerminalModeError<DI>> {
let display_size = self.properties.get_size();

// The number of characters that can fit on the display at once (w * h / 8 * 8)
// TODO: Use `display_size.dimensions()`
let numchars = match display_size {
DisplaySize::Display128x64 => 128,
DisplaySize::Display128x32 => 64,
DisplaySize::Display96x16 => 24,
DisplaySize::Display72x40 => 45,
};

// Let the chip handle line wrapping so we can fill the screen with blanks faster
Expand All @@ -191,6 +194,7 @@ where
.set_draw_area((0, 0), (display_width, display_height))
.terminal_err()?;

// Clear the display
for _ in 0..numchars {
self.properties.draw(&[0; 8]).terminal_err()?;
}
Expand Down
10 changes: 10 additions & 0 deletions src/properties.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub struct DisplayProperties<DI> {
iface: DI,
display_size: DisplaySize,
display_rotation: DisplayRotation,
pub(crate) display_offset: (u8, u8),
addr_mode: AddrMode,
}

Expand All @@ -25,10 +26,18 @@ where
display_size: DisplaySize,
display_rotation: DisplayRotation,
) -> DisplayProperties<DI> {
let display_offset = match display_size {
DisplaySize::Display128x64 => (0, 0),
DisplaySize::Display128x32 => (0, 0),
DisplaySize::Display96x16 => (0, 0),
DisplaySize::Display72x40 => (28, 0),
};

DisplayProperties {
iface,
display_size,
display_rotation,
display_offset,
addr_mode: AddrMode::Page, // reset value
}
}
Expand Down Expand Up @@ -61,6 +70,7 @@ where
DisplaySize::Display128x32 => Command::ComPinConfig(false, false).send(&mut self.iface),
DisplaySize::Display128x64 => Command::ComPinConfig(true, false).send(&mut self.iface),
DisplaySize::Display96x16 => Command::ComPinConfig(false, false).send(&mut self.iface),
DisplaySize::Display72x40 => Command::ComPinConfig(true, false).send(&mut self.iface),
}?;

Command::Contrast(0x8F).send(&mut self.iface)?;
Expand Down

0 comments on commit b453204

Please sign in to comment.