Skip to content

Commit f5aa1d7

Browse files
bugadaniDániel Bugajamwaffles
authored
Add support for setting brightness (#121)
* Fix comment of PreChargePeriod * Messy implementation of brightness change * Add CHANGELOG entry * Commit suggested changes Co-authored-by: James Waples <[email protected]> * Remove change_property * Fix alignment and comments * Implement Default and use it when initializing * Run cargo fmt * Add example * Add a note to precharge, assert its value before sending command * Implement suggested changes Co-authored-by: James Waples <[email protected]> * Update src/command.rs, fix comment Co-authored-by: James Waples <[email protected]> * Run cargo fmt * Also rename method where used... Co-authored-by: Dániel Buga <[email protected]> Co-authored-by: James Waples <[email protected]>
1 parent 95772ff commit f5aa1d7

File tree

9 files changed

+270
-4
lines changed

9 files changed

+270
-4
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
### Added
1010

11+
- [#121](https://github.com/jamwaffles/ssd1306/pull/121) Added brightness control with the `set_brightness()` method.
1112
- [#118](https://github.com/jamwaffles/ssd1306/pull/118) `DisplayModeTrait::into_properties()` new method that consumes the driver and returns the `DisplayProperties`
1213

1314
### Changed

examples/rtfm_brightness.rs

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
//! Cycle the screen brightness through 5 predefined brightness levels when the "DVD" logo hits one
2+
//! of the sides of the display.
3+
//!
4+
//! For best results, run with the `--release` flag.
5+
6+
#![no_std]
7+
#![no_main]
8+
9+
use core::convert::TryFrom;
10+
use display_interface_spi::SPIInterfaceNoCS;
11+
use embedded_graphics::{
12+
geometry::Point, image::Image, pixelcolor::BinaryColor, prelude::*, primitives::Rectangle,
13+
};
14+
use panic_halt as _;
15+
use rtfm::app;
16+
use ssd1306::{prelude::*, Builder};
17+
use stm32f1xx_hal::{
18+
delay::Delay,
19+
gpio,
20+
pac::{self, SPI1},
21+
prelude::*,
22+
spi::{self, Mode, Phase, Polarity, Spi},
23+
timer::{CountDownTimer, Event, Timer},
24+
};
25+
use tinybmp::Bmp;
26+
27+
type Display = ssd1306::mode::graphics::GraphicsMode<
28+
SPIInterfaceNoCS<
29+
spi::Spi<
30+
SPI1,
31+
spi::Spi1NoRemap,
32+
(
33+
gpio::gpioa::PA5<gpio::Alternate<gpio::PushPull>>,
34+
gpio::gpioa::PA6<gpio::Input<gpio::Floating>>,
35+
gpio::gpioa::PA7<gpio::Alternate<gpio::PushPull>>,
36+
),
37+
>,
38+
gpio::gpiob::PB1<gpio::Output<gpio::PushPull>>,
39+
>,
40+
>;
41+
42+
#[app(device = stm32f1xx_hal::pac, peripherals = true)]
43+
const APP: () = {
44+
struct Resources {
45+
display: Display,
46+
timer: CountDownTimer<pac::TIM1>,
47+
top_left: Point,
48+
velocity: Point,
49+
bmp: Bmp<'static>,
50+
brightness: Brightness,
51+
}
52+
53+
#[init]
54+
fn init(cx: init::Context) -> init::LateResources {
55+
let dp = cx.device;
56+
let core = cx.core;
57+
58+
let mut flash = dp.FLASH.constrain();
59+
let mut rcc = dp.RCC.constrain();
60+
61+
let clocks = rcc
62+
.cfgr
63+
.use_hse(8.mhz())
64+
.sysclk(72.mhz())
65+
.pclk1(36.mhz())
66+
.freeze(&mut flash.acr);
67+
68+
let mut afio = dp.AFIO.constrain(&mut rcc.apb2);
69+
70+
let mut gpiob = dp.GPIOB.split(&mut rcc.apb2);
71+
let mut gpioa = dp.GPIOA.split(&mut rcc.apb2);
72+
73+
// SPI1
74+
let sck = gpioa.pa5.into_alternate_push_pull(&mut gpioa.crl);
75+
let miso = gpioa.pa6;
76+
let mosi = gpioa.pa7.into_alternate_push_pull(&mut gpioa.crl);
77+
78+
let mut delay = Delay::new(core.SYST, clocks);
79+
80+
let mut rst = gpiob.pb0.into_push_pull_output(&mut gpiob.crl);
81+
let dc = gpiob.pb1.into_push_pull_output(&mut gpiob.crl);
82+
83+
let spi = Spi::spi1(
84+
dp.SPI1,
85+
(sck, miso, mosi),
86+
&mut afio.mapr,
87+
Mode {
88+
polarity: Polarity::IdleLow,
89+
phase: Phase::CaptureOnFirstTransition,
90+
},
91+
8.mhz(),
92+
clocks,
93+
&mut rcc.apb2,
94+
);
95+
96+
let interface = display_interface_spi::SPIInterfaceNoCS::new(spi, dc);
97+
let mut display: GraphicsMode<_> = Builder::new()
98+
.with_rotation(DisplayRotation::Rotate180)
99+
.connect(interface)
100+
.into();
101+
102+
display.reset(&mut rst, &mut delay).unwrap();
103+
display.init().unwrap();
104+
105+
// Update framerate
106+
let fps = 20;
107+
108+
let mut timer = Timer::tim1(dp.TIM1, &clocks, &mut rcc.apb2).start_count_down(fps.hz());
109+
110+
timer.listen(Event::Update);
111+
112+
let bmp = Bmp::from_slice(include_bytes!("dvd.bmp")).unwrap();
113+
114+
// Init the static resources to use them later through RTFM
115+
init::LateResources {
116+
timer,
117+
display,
118+
top_left: Point::new(5, 3),
119+
velocity: Point::new(1, 1),
120+
bmp,
121+
brightness: Brightness::default(),
122+
}
123+
}
124+
125+
#[task(binds = TIM1_UP, resources = [display, top_left, velocity, timer, bmp, brightness])]
126+
fn update(cx: update::Context) {
127+
let update::Resources {
128+
display,
129+
top_left,
130+
velocity,
131+
timer,
132+
bmp,
133+
brightness,
134+
..
135+
} = cx.resources;
136+
137+
let bottom_right = *top_left + Point::try_from(bmp.dimensions()).unwrap();
138+
139+
// Erase previous image position with a filled black rectangle
140+
Rectangle::new(*top_left, bottom_right)
141+
.into_styled(embedded_graphics::primitive_style!(
142+
fill_color = BinaryColor::Off
143+
))
144+
.draw(display)
145+
.unwrap();
146+
147+
// Check if the image collided with a screen edge
148+
{
149+
let mut collision = false;
150+
if bottom_right.x > display.size().width as i32 || top_left.x < 0 {
151+
velocity.x = -velocity.x;
152+
collision = true;
153+
}
154+
155+
if bottom_right.y > display.size().height as i32 || top_left.y < 0 {
156+
velocity.y = -velocity.y;
157+
collision = true;
158+
}
159+
160+
if collision {
161+
// Change the brightness
162+
*brightness = match *brightness {
163+
Brightness::DIMMEST => Brightness::DIM,
164+
Brightness::DIM => Brightness::NORMAL,
165+
Brightness::NORMAL => Brightness::BRIGHT,
166+
Brightness::BRIGHT => Brightness::BRIGHTEST,
167+
Brightness::BRIGHTEST => Brightness::DIMMEST, // restart cycle
168+
_ => Brightness::NORMAL, // Brightness is not an enum, cover rest of patterns
169+
};
170+
171+
// Send the new brightness value to the display
172+
display.set_brightness(*brightness).unwrap();
173+
}
174+
}
175+
176+
// Move the image
177+
*top_left += *velocity;
178+
179+
// Draw image at new position
180+
Image::new(bmp, *top_left).draw(display).unwrap();
181+
182+
// Write changes to the display
183+
display.flush().unwrap();
184+
185+
// Clears the update flag
186+
timer.clear_update_interrupt_flag();
187+
}
188+
189+
extern "C" {
190+
fn EXTI0();
191+
}
192+
};

src/brightness.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
//! Display brightness
2+
3+
/// Struct that holds display brightness
4+
#[derive(Copy, Clone, PartialEq, Eq)]
5+
pub struct Brightness {
6+
pub(crate) precharge: u8,
7+
pub(crate) contrast: u8,
8+
}
9+
10+
impl Default for Brightness {
11+
fn default() -> Self {
12+
Brightness::NORMAL
13+
}
14+
}
15+
16+
impl Brightness {
17+
/// The dimmest predefined brightness level
18+
pub const DIMMEST: Brightness = Brightness::custom(0x1, 0x00);
19+
20+
/// A dim predefined brightness level
21+
pub const DIM: Brightness = Brightness::custom(0x2, 0x2F);
22+
23+
/// A medium predefined brightness level
24+
pub const NORMAL: Brightness = Brightness::custom(0x2, 0x5F);
25+
26+
/// A bright predefined brightness level
27+
pub const BRIGHT: Brightness = Brightness::custom(0x2, 0x9F);
28+
29+
/// The brightest predefined brightness level
30+
pub const BRIGHTEST: Brightness = Brightness::custom(0x2, 0xFF);
31+
32+
/// Create a Brightness object from a precharge period and contrast pair.
33+
///
34+
/// `precharge` sets the `phase 2` argument of the `0xD9 Set Pre-Charge Period` command and must
35+
/// be must be between 1 and 15.
36+
/// The effects of this parameter are hardware dependent. For the common 128x64 displays, values
37+
/// 1 and 2 result in different brightness levels, values above 2 behave the same was as 2.
38+
/// See section 10.1.17 of the SSD1306 datasheet for more information.
39+
///
40+
/// `contrast` sets the value used in the `0x81 Set Contrast Control` command and must be
41+
/// between 0 and 255. See section 10.1.7 of the SSD1306 datasheet for more information.
42+
const fn custom(precharge: u8, contrast: u8) -> Self {
43+
Self {
44+
precharge,
45+
contrast,
46+
}
47+
}
48+
}

src/command.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ pub enum Command {
7676
/// First value is oscillator frequency, increasing with higher value
7777
/// Second value is divide ratio - 1
7878
DisplayClockDiv(u8, u8),
79-
/// Set up phase 1 and 2 of precharge period. each value is from 0-63
79+
/// Set up phase 1 and 2 of precharge period. Each value must be in the range 1 - 15.
8080
PreChargePeriod(u8, u8),
8181
/// Set Vcomh Deselect level
8282
VcomhDeselect(VcomhLevel),

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ pub enum Error<CommE, PinE> {
142142

143143
extern crate embedded_hal as hal;
144144

145+
pub mod brightness;
145146
pub mod builder;
146147
mod command;
147148
pub mod displayrotation;

src/mode/graphics.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
use display_interface::{DisplayError, WriteOnlyDataCommand};
5959

6060
use crate::{
61-
displayrotation::DisplayRotation, mode::displaymode::DisplayModeTrait,
61+
brightness::Brightness, displayrotation::DisplayRotation, mode::displaymode::DisplayModeTrait,
6262
properties::DisplayProperties,
6363
};
6464

@@ -228,6 +228,11 @@ where
228228
pub fn display_on(&mut self, on: bool) -> Result<(), DisplayError> {
229229
self.properties.display_on(on)
230230
}
231+
232+
/// Change the display brightness.
233+
pub fn set_brightness(&mut self, brightness: Brightness) -> Result<(), DisplayError> {
234+
self.properties.set_brightness(brightness)
235+
}
231236
}
232237

233238
#[cfg(feature = "graphics")]

src/mode/terminal.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
use display_interface::{DisplayError, WriteOnlyDataCommand};
2929

3030
use crate::{
31+
brightness::Brightness,
3132
command::AddrMode,
3233
displayrotation::DisplayRotation,
3334
displaysize::DisplaySize,
@@ -257,6 +258,11 @@ where
257258
self.properties.display_on(on).terminal_err()
258259
}
259260

261+
/// Change the display brightness.
262+
pub fn set_brightness(&mut self, brightness: Brightness) -> Result<(), TerminalModeError> {
263+
self.properties.set_brightness(brightness).terminal_err()
264+
}
265+
260266
/// Get the current cursor position, in character coordinates.
261267
/// This is the (column, row) that the next character will be written to.
262268
pub fn get_position(&self) -> Result<(u8, u8), TerminalModeError> {

src/prelude.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ pub use display_interface_i2c::I2CInterface;
55
pub use display_interface_spi::{SPIInterface, SPIInterfaceNoCS};
66

77
pub use super::{
8+
brightness::Brightness,
89
displayrotation::DisplayRotation,
910
displaysize::DisplaySize,
1011
mode::{displaymode::DisplayModeTrait, GraphicsMode, TerminalMode},

src/properties.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
33
use crate::mode::displaymode::DisplayModeTrait;
44
use crate::{
5+
brightness::Brightness,
56
command::{AddrMode, Command, VcomhLevel},
67
displayrotation::DisplayRotation,
78
displaysize::DisplaySize,
@@ -83,8 +84,7 @@ where
8384
DisplaySize::Display64x48 => Command::ComPinConfig(true, false).send(&mut self.iface),
8485
}?;
8586

86-
Command::Contrast(0x8F).send(&mut self.iface)?;
87-
Command::PreChargePeriod(0x1, 0xF).send(&mut self.iface)?;
87+
self.set_brightness(Brightness::default())?;
8888
Command::VcomhDeselect(VcomhLevel::Auto).send(&mut self.iface)?;
8989
Command::AllOn(false).send(&mut self.iface)?;
9090
Command::Invert(false).send(&mut self.iface)?;
@@ -251,6 +251,18 @@ where
251251
Command::DisplayOn(on).send(&mut self.iface)
252252
}
253253

254+
/// Change the display brightness.
255+
pub fn set_brightness(&mut self, brightness: Brightness) -> Result<(), DisplayError> {
256+
// Should be moved to Brightness::new once conditions can be used in const functions
257+
debug_assert!(
258+
0 < brightness.precharge && brightness.precharge <= 15,
259+
"Precharge value must be between 1 and 15"
260+
);
261+
262+
Command::PreChargePeriod(1, brightness.precharge).send(&mut self.iface)?;
263+
Command::Contrast(brightness.contrast).send(&mut self.iface)
264+
}
265+
254266
/// Change into any mode implementing DisplayModeTrait
255267
pub fn into<NMODE: DisplayModeTrait<DI>>(self) -> NMODE
256268
where

0 commit comments

Comments
 (0)