Skip to content

Commit b453204

Browse files
authored
Add support for 72x40px SSD1306 modules (#101)
* 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
1 parent 5be7337 commit b453204

File tree

6 files changed

+163
-4
lines changed

6 files changed

+163
-4
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66

77
## [Unreleased] - ReleaseDate
88

9+
### Added
10+
11+
- [#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`.
12+
913
### Fixed
1014

1115
- Fix docs.rs build by targeting `x86_64-unknown-linux-gnu`

examples/graphics_i2c_72x40.rs

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
//! Draw a square, circle and triangle on the screen using the `embedded_graphics` crate.
2+
//!
3+
//! This example is for the STM32F103 "Blue Pill" board using I2C1.
4+
//!
5+
//! Wiring connections are as follows for a CRIUS-branded display:
6+
//!
7+
//! ```
8+
//! Display -> Blue Pill
9+
//! (black) GND -> GND
10+
//! (red) +5V -> VCC
11+
//! (yellow) SDA -> PB9
12+
//! (green) SCL -> PB8
13+
//! ```
14+
//!
15+
//! Run on a Blue Pill with `cargo run --example graphics_i2c`.
16+
17+
#![no_std]
18+
#![no_main]
19+
20+
extern crate cortex_m;
21+
extern crate cortex_m_rt as rt;
22+
extern crate panic_semihosting;
23+
extern crate stm32f1xx_hal as hal;
24+
25+
use cortex_m_rt::{entry, exception, ExceptionFrame};
26+
use embedded_graphics::{
27+
pixelcolor::BinaryColor,
28+
prelude::*,
29+
primitives::{Circle, Rectangle, Triangle},
30+
style::PrimitiveStyleBuilder,
31+
};
32+
use hal::{
33+
i2c::{BlockingI2c, DutyCycle, Mode},
34+
prelude::*,
35+
stm32,
36+
};
37+
use ssd1306::{prelude::*, Builder};
38+
39+
#[entry]
40+
fn main() -> ! {
41+
let dp = stm32::Peripherals::take().unwrap();
42+
43+
let mut flash = dp.FLASH.constrain();
44+
let mut rcc = dp.RCC.constrain();
45+
46+
let clocks = rcc.cfgr.freeze(&mut flash.acr);
47+
48+
let mut afio = dp.AFIO.constrain(&mut rcc.apb2);
49+
50+
let mut gpiob = dp.GPIOB.split(&mut rcc.apb2);
51+
52+
let scl = gpiob.pb8.into_alternate_open_drain(&mut gpiob.crh);
53+
let sda = gpiob.pb9.into_alternate_open_drain(&mut gpiob.crh);
54+
55+
let i2c = BlockingI2c::i2c1(
56+
dp.I2C1,
57+
(scl, sda),
58+
&mut afio.mapr,
59+
Mode::Fast {
60+
frequency: 400_000.hz(),
61+
duty_cycle: DutyCycle::Ratio2to1,
62+
},
63+
clocks,
64+
&mut rcc.apb1,
65+
1000,
66+
10,
67+
1000,
68+
1000,
69+
);
70+
71+
let mut disp: GraphicsMode<_> = Builder::new()
72+
.size(DisplaySize::Display72x40)
73+
.connect_i2c(i2c)
74+
.into();
75+
76+
disp.init().unwrap();
77+
78+
let size = 10;
79+
let offset = Point::new(10, (42 / 2) - (size / 2) - 1);
80+
let spacing = size + 10;
81+
82+
let style = PrimitiveStyleBuilder::new()
83+
.stroke_width(1)
84+
.stroke_color(BinaryColor::On)
85+
.build();
86+
87+
// screen outline
88+
// default display size is 128x64 if you don't pass a _DisplaySize_
89+
// enum to the _Builder_ struct
90+
Rectangle::new(Point::new(0, 0), Point::new(71, 39))
91+
.into_styled(style)
92+
.draw(&mut disp);
93+
94+
// Triangle
95+
Triangle::new(
96+
Point::new(0, size),
97+
Point::new(size / 2, 0),
98+
Point::new(size, size),
99+
)
100+
.translate(offset)
101+
.into_styled(style)
102+
.draw(&mut disp);
103+
104+
// Move over to next position
105+
let offset = offset + Point::new(spacing, 0);
106+
107+
// Draw a square
108+
Rectangle::new(Point::new(0, 0), Point::new(size, size))
109+
.translate(offset)
110+
.into_styled(style)
111+
.draw(&mut disp);
112+
113+
// Move over a bit more
114+
let offset = offset + Point::new(spacing, 0);
115+
116+
// Circle
117+
Circle::new(Point::new(size / 2, size / 2), size as u32 / 2)
118+
.translate(offset)
119+
.into_styled(style)
120+
.draw(&mut disp);
121+
122+
disp.flush().unwrap();
123+
124+
loop {}
125+
}
126+
127+
#[exception]
128+
fn HardFault(ef: &ExceptionFrame) -> ! {
129+
panic!("{:#?}", ef);
130+
}

src/displaysize.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ pub enum DisplaySize {
1010
Display128x32,
1111
/// 96 by 16 pixels
1212
Display96x16,
13+
/// 70 by 42 pixels
14+
Display72x40,
1315
}
1416

1517
impl DisplaySize {
@@ -20,6 +22,7 @@ impl DisplaySize {
2022
DisplaySize::Display128x64 => (128, 64),
2123
DisplaySize::Display128x32 => (128, 32),
2224
DisplaySize::Display96x16 => (96, 16),
25+
DisplaySize::Display72x40 => (72, 40),
2326
}
2427
}
2528
}

src/mode/graphics.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -159,11 +159,17 @@ where
159159
self.min_y = width - 1;
160160
self.max_y = 0;
161161

162+
// Compensate for any offset in the physical display. For example, the 72x40 display has an
163+
// offset of (28, 0) pixels.
164+
let offs = self.properties.display_offset;
165+
162166
// Tell the display to update only the part that has changed
163167
match self.properties.get_rotation() {
164168
DisplayRotation::Rotate0 | DisplayRotation::Rotate180 => {
165-
self.properties
166-
.set_draw_area((disp_min_x, disp_min_y), (disp_max_x, disp_max_y))?;
169+
self.properties.set_draw_area(
170+
(disp_min_x + offs.0, disp_min_y + offs.1),
171+
(disp_max_x + offs.0, disp_max_y + offs.1),
172+
)?;
167173

168174
self.properties.bounded_draw(
169175
&self.buffer,
@@ -173,8 +179,10 @@ where
173179
)
174180
}
175181
DisplayRotation::Rotate90 | DisplayRotation::Rotate270 => {
176-
self.properties
177-
.set_draw_area((disp_min_y, disp_min_x), (disp_max_y, disp_max_x))?;
182+
self.properties.set_draw_area(
183+
(disp_min_y + offs.1, disp_min_x + offs.0),
184+
(disp_max_y + offs.1, disp_max_x + offs.0),
185+
)?;
178186

179187
self.properties.bounded_draw(
180188
&self.buffer,

src/mode/terminal.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,10 +176,13 @@ where
176176
pub fn clear(&mut self) -> Result<(), TerminalModeError<DI>> {
177177
let display_size = self.properties.get_size();
178178

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

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

197+
// Clear the display
194198
for _ in 0..numchars {
195199
self.properties.draw(&[0; 8]).terminal_err()?;
196200
}

src/properties.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ pub struct DisplayProperties<DI> {
1212
iface: DI,
1313
display_size: DisplaySize,
1414
display_rotation: DisplayRotation,
15+
pub(crate) display_offset: (u8, u8),
1516
addr_mode: AddrMode,
1617
}
1718

@@ -25,10 +26,18 @@ where
2526
display_size: DisplaySize,
2627
display_rotation: DisplayRotation,
2728
) -> DisplayProperties<DI> {
29+
let display_offset = match display_size {
30+
DisplaySize::Display128x64 => (0, 0),
31+
DisplaySize::Display128x32 => (0, 0),
32+
DisplaySize::Display96x16 => (0, 0),
33+
DisplaySize::Display72x40 => (28, 0),
34+
};
35+
2836
DisplayProperties {
2937
iface,
3038
display_size,
3139
display_rotation,
40+
display_offset,
3241
addr_mode: AddrMode::Page, // reset value
3342
}
3443
}
@@ -61,6 +70,7 @@ where
6170
DisplaySize::Display128x32 => Command::ComPinConfig(false, false).send(&mut self.iface),
6271
DisplaySize::Display128x64 => Command::ComPinConfig(true, false).send(&mut self.iface),
6372
DisplaySize::Display96x16 => Command::ComPinConfig(false, false).send(&mut self.iface),
73+
DisplaySize::Display72x40 => Command::ComPinConfig(true, false).send(&mut self.iface),
6474
}?;
6575

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

0 commit comments

Comments
 (0)