Skip to content

Commit

Permalink
Optimise BasicMode::clear RAM usage and optimize size of Ssd1306::cha…
Browse files Browse the repository at this point in the history
…r_to_bitmap (#195)
  • Loading branch information
alexey-medvedchikov authored Oct 9, 2023
1 parent e0eaca9 commit fddf721
Show file tree
Hide file tree
Showing 4 changed files with 284 additions and 164 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ SSD1306 monochrome OLED display.

## [Unreleased] - ReleaseDate

### Changed

- [#195](https://github.com/jamwaffles/ssd1306/pull/195) Changed `BasicMode::clear` to clear in small batches instead of
one big write. This drops RAM requirement by ~900b and fixes issues on MCUs with less than 1Kb of RAM.
- [#195](https://github.com/jamwaffles/ssd1306/pull/195) Changed `TerminalMode` to use lookup by ASCII code instead of
per-character match when searching for glyph. This may save up to 3.5Kb of compiled code on AVR MCUs.

## [0.8.2] - 2023-09-29

### Fixed
Expand Down
115 changes: 53 additions & 62 deletions src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,14 +95,14 @@ impl Command {
where
DI: WriteOnlyDataCommand,
{
// Transform command into a fixed size array of 7 u8 and the real length for sending
let (data, len) = match self {
Command::Contrast(val) => ([0x81, val, 0, 0, 0, 0, 0], 2),
Command::AllOn(on) => ([0xA4 | (on as u8), 0, 0, 0, 0, 0, 0], 1),
Command::Invert(inv) => ([0xA6 | (inv as u8), 0, 0, 0, 0, 0, 0], 1),
Command::DisplayOn(on) => ([0xAE | (on as u8), 0, 0, 0, 0, 0, 0], 1),
Command::HScrollSetup(dir, start, end, rate) => (
[
match self {
Command::Contrast(val) => Self::send_commands(iface, &[0x81, val]),
Command::AllOn(on) => Self::send_commands(iface, &[0xA4 | (on as u8)]),
Command::Invert(inv) => Self::send_commands(iface, &[0xA6 | (inv as u8)]),
Command::DisplayOn(on) => Self::send_commands(iface, &[0xAE | (on as u8)]),
Command::HScrollSetup(dir, start, end, rate) => Self::send_commands(
iface,
&[
0x26 | (dir as u8),
0,
start as u8,
Expand All @@ -111,72 +111,63 @@ impl Command {
0,
0xFF,
],
7,
),
Command::VHScrollSetup(dir, start, end, rate, offset) => (
[
Command::VHScrollSetup(dir, start, end, rate, offset) => Self::send_commands(
iface,
&[
0x28 | (dir as u8),
0,
start as u8,
rate as u8,
end as u8,
offset,
0,
],
6,
),
Command::EnableScroll(en) => ([0x2E | (en as u8), 0, 0, 0, 0, 0, 0], 1),
Command::VScrollArea(above, lines) => ([0xA3, above, lines, 0, 0, 0, 0], 3),
Command::LowerColStart(addr) => ([0xF & addr, 0, 0, 0, 0, 0, 0], 1),
Command::UpperColStart(addr) => ([0x10 | (0xF & addr), 0, 0, 0, 0, 0, 0], 1),
Command::ColStart(addr) => ([0xF & addr, 0x10 | (0xF & (addr >> 4)), 0, 0, 0, 0, 0], 2),
Command::AddressMode(mode) => ([0x20, mode as u8, 0, 0, 0, 0, 0], 2),
Command::ColumnAddress(start, end) => ([0x21, start, end, 0, 0, 0, 0], 3),
Command::PageAddress(start, end) => ([0x22, start as u8, end as u8, 0, 0, 0, 0], 3),
Command::PageStart(page) => ([0xB0 | (page as u8), 0, 0, 0, 0, 0, 0], 1),
Command::StartLine(line) => ([0x40 | (0x3F & line), 0, 0, 0, 0, 0, 0], 1),
Command::SegmentRemap(remap) => ([0xA0 | (remap as u8), 0, 0, 0, 0, 0, 0], 1),
Command::Multiplex(ratio) => ([0xA8, ratio, 0, 0, 0, 0, 0], 2),
Command::ReverseComDir(rev) => ([0xC0 | ((rev as u8) << 3), 0, 0, 0, 0, 0, 0], 1),
Command::DisplayOffset(offset) => ([0xD3, offset, 0, 0, 0, 0, 0], 2),
Command::ComPinConfig(alt, lr) => (
[
0xDA,
0x2 | ((alt as u8) << 4) | ((lr as u8) << 5),
0,
0,
0,
0,
0,
],
2,
),
Command::EnableScroll(en) => Self::send_commands(iface, &[0x2E | (en as u8)]),
Command::VScrollArea(above, lines) => Self::send_commands(iface, &[0xA3, above, lines]),
Command::LowerColStart(addr) => Self::send_commands(iface, &[0xF & addr]),
Command::UpperColStart(addr) => Self::send_commands(iface, &[0x10 | (0xF & addr)]),
Command::ColStart(addr) => {
Self::send_commands(iface, &[0xF & addr, 0x10 | (0xF & (addr >> 4))])
}
Command::AddressMode(mode) => Self::send_commands(iface, &[0x20, mode as u8]),
Command::ColumnAddress(start, end) => Self::send_commands(iface, &[0x21, start, end]),
Command::PageAddress(start, end) => {
Self::send_commands(iface, &[0x22, start as u8, end as u8])
}
Command::PageStart(page) => Self::send_commands(iface, &[0xB0 | (page as u8)]),
Command::StartLine(line) => Self::send_commands(iface, &[0x40 | (0x3F & line)]),
Command::SegmentRemap(remap) => Self::send_commands(iface, &[0xA0 | (remap as u8)]),
Command::Multiplex(ratio) => Self::send_commands(iface, &[0xA8, ratio]),
Command::ReverseComDir(rev) => Self::send_commands(iface, &[0xC0 | ((rev as u8) << 3)]),
Command::DisplayOffset(offset) => Self::send_commands(iface, &[0xD3, offset]),
Command::ComPinConfig(alt, lr) => {
Self::send_commands(iface, &[0xDA, 0x2 | ((alt as u8) << 4) | ((lr as u8) << 5)])
}
Command::DisplayClockDiv(fosc, div) => {
([0xD5, ((0xF & fosc) << 4) | (0xF & div), 0, 0, 0, 0, 0], 2)
Self::send_commands(iface, &[0xD5, ((0xF & fosc) << 4) | (0xF & div)])
}
Command::PreChargePeriod(phase1, phase2) => (
[0xD9, ((0xF & phase2) << 4) | (0xF & phase1), 0, 0, 0, 0, 0],
2,
),
Command::VcomhDeselect(level) => ([0xDB, (level as u8) << 4, 0, 0, 0, 0, 0], 2),
Command::Noop => ([0xE3, 0, 0, 0, 0, 0, 0], 1),
Command::ChargePump(en) => ([0x8D, 0x10 | ((en as u8) << 2), 0, 0, 0, 0, 0], 2),
Command::InternalIref(en, current) => (
[
0xAD,
((current as u8) << 5) | ((en as u8) << 4),
0,
0,
0,
0,
0,
],
2,
),
};
Command::PreChargePeriod(phase1, phase2) => {
Self::send_commands(iface, &[0xD9, ((0xF & phase2) << 4) | (0xF & phase1)])
}
Command::VcomhDeselect(level) => {
Self::send_commands(iface, &[0xDB, (level as u8) << 4])
}
Command::Noop => Self::send_commands(iface, &[0xE3]),
Command::ChargePump(en) => {
Self::send_commands(iface, &[0x8D, 0x10 | ((en as u8) << 2)])
}
Command::InternalIref(en, current) => {
Self::send_commands(iface, &[0xAD, ((current as u8) << 5) | ((en as u8) << 4)])
}
}
}

// Send command over the interface
iface.send_commands(U8(&data[0..len]))
fn send_commands<DI>(iface: &mut DI, data: &[u8]) -> Result<(), DisplayError>
where
DI: WriteOnlyDataCommand,
{
iface.send_commands(U8(data))
}
}

Expand Down
29 changes: 25 additions & 4 deletions src/mode/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,32 @@ where
{
/// Clear the display.
pub fn clear(&mut self) -> Result<(), DisplayError> {
self.set_draw_area((0, 0), self.dimensions())?;
let old_addr_mode = self.addr_mode;
if old_addr_mode != AddrMode::Horizontal {
self.set_addr_mode(AddrMode::Horizontal)?;
}

// TODO: If const generics allows this, replace `1024` with computed W x H value for current
// `SIZE`.
self.draw(&[0u8; 1024])
let dim = self.dimensions();
self.set_draw_area((0, 0), dim)?;

let num_pixels = dim.0 as u16 * dim.1 as u16;

const BITS_PER_BYTE: u16 = 8;
const BYTES_PER_BATCH: u16 = 64;
const PIXELS_PER_BATCH: u16 = BITS_PER_BYTE * BYTES_PER_BATCH;

// Not all screens have number of pixels divisible by 512, so add 1 to cover tail
let num_batches = num_pixels / PIXELS_PER_BATCH + 1;

for _ in 0..num_batches {
self.draw(&[0; BYTES_PER_BATCH as usize])?;
}

if old_addr_mode != AddrMode::Horizontal {
self.set_addr_mode(old_addr_mode)?;
}

Ok(())
}
}

Expand Down
Loading

0 comments on commit fddf721

Please sign in to comment.