Skip to content

Commit cecc6dc

Browse files
authored
Use iterators in interface send methods (#106)
* Use iterators in interface send methods * Optimise set_pixel in graphics mode * Dedupes byte/bit index calculation * Replaces a slice index operation with `get_mut`, eliding bounds checks (I think - the call tree is pretty hairy) * Replaces if/else bit set/clear op with bit ops. See here for asm output: https://godbolt.org/z/YRSanf * Changelog entry
1 parent 9a2916e commit cecc6dc

File tree

4 files changed

+63
-88
lines changed

4 files changed

+63
-88
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
### Changed
1010

1111
- [#105](https://github.com/jamwaffles/ssd1306/pull/105) Reduce flash usage by around 400 bytes by replacing some internal `unwrap()`s with `as` coercions.
12+
- [#106](https://github.com/jamwaffles/ssd1306/pull/106) Optimise internals by using iterators to elide bounds checks. Should also speed up `GraphicsMode` (and `embedded-graphics` operations) with a cleaned-up `set_pixel`.
1213

1314
## [0.3.0-alpha.4] - 2020-02-07
1415

src/interface/i2c.rs

Lines changed: 33 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -50,18 +50,16 @@ where
5050
// 8.1.5.2 5) b) in the datasheet
5151
writebuf[0] = 0x40;
5252

53-
for chunk in buf.chunks(16) {
54-
let chunklen = chunk.len();
53+
buf.chunks(16).try_for_each(|c| {
54+
let chunk_len = c.len();
5555

5656
// Copy over all data from buffer, leaving the data command byte intact
57-
writebuf[1..=chunklen].copy_from_slice(&chunk[0..chunklen]);
57+
writebuf[1..=chunk_len].copy_from_slice(c);
5858

5959
self.i2c
60-
.write(self.addr, &writebuf[..=chunklen])
61-
.map_err(Error::Comm)?;
62-
}
63-
64-
Ok(())
60+
.write(self.addr, &writebuf[0..=chunk_len])
61+
.map_err(Error::Comm)
62+
})
6563
}
6664

6765
fn send_bounded_data(
@@ -76,39 +74,39 @@ where
7674
return Ok(());
7775
}
7876

79-
let mut writebuf: [u8; 17] = [0; 17];
80-
81-
// Divide by 8 since each row is actually 8 pixels tall
82-
let height = ((lower_right.1 - upper_left.1) / 8) as usize;
83-
84-
let starting_page = (upper_left.1 / 8) as usize;
77+
// Write buffer. Writes are sent in chunks of 16 bytes plus DC byte
78+
let mut writebuf: [u8; 17] = [0x0; 17];
8579

8680
// Data mode
8781
// 8.1.5.2 5) b) in the datasheet
8882
writebuf[0] = 0x40;
8983

90-
let mut page_offset = starting_page * disp_width;
91-
92-
for _ in 0..=height {
93-
let start_index = page_offset + upper_left.0 as usize;
94-
let end_index = page_offset + lower_right.0 as usize;
95-
96-
page_offset += disp_width;
97-
98-
let sub_buf = &buf[start_index..end_index];
99-
100-
for chunk in sub_buf.chunks(16) {
101-
let chunklen = chunk.len();
102-
103-
// Copy over all data from buffer, leaving the data command byte intact
104-
writebuf[1..=chunklen].copy_from_slice(&chunk[0..chunklen]);
84+
// Divide by 8 since each row is actually 8 pixels tall
85+
let num_pages = ((lower_right.1 - upper_left.1) / 8) as usize + 1;
10586

106-
self.i2c
107-
.write(self.addr, &writebuf[..=chunklen])
108-
.map_err(Error::Comm)?;
109-
}
110-
}
87+
// Each page is 8 bits tall, so calculate which page number to start at (rounded down) from
88+
// the top of the display
89+
let starting_page = (upper_left.1 / 8) as usize;
11190

112-
Ok(())
91+
// Calculate start and end X coordinates for each page
92+
let page_lower = upper_left.0 as usize;
93+
let page_upper = lower_right.0 as usize;
94+
95+
buf.chunks(disp_width)
96+
.skip(starting_page)
97+
.take(num_pages)
98+
.map(|s| &s[page_lower..page_upper])
99+
.try_for_each(|c| {
100+
c.chunks(16).try_for_each(|c| {
101+
let chunk_len = c.len();
102+
103+
// Copy over all data from buffer, leaving the data command byte intact
104+
writebuf[1..=chunk_len].copy_from_slice(c);
105+
106+
self.i2c
107+
.write(self.addr, &writebuf[0..=chunk_len])
108+
.map_err(Error::Comm)
109+
})
110+
})
113111
}
114112
}

src/interface/spi.rs

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -57,22 +57,20 @@ where
5757
self.dc.set_high().map_err(Error::Pin)?;
5858

5959
// Divide by 8 since each row is actually 8 pixels tall
60-
let height = ((lower_right.1 - upper_left.1) / 8) as usize;
60+
let num_pages = ((lower_right.1 - upper_left.1) / 8) as usize + 1;
6161

62+
// Each page is 8 bits tall, so calculate which page number to start at (rounded down) from
63+
// the top of the display
6264
let starting_page = (upper_left.1 / 8) as usize;
6365

64-
let mut page_offset = starting_page * disp_width;
66+
// Calculate start and end X coordinates for each page
67+
let page_lower = upper_left.0 as usize;
68+
let page_upper = lower_right.0 as usize;
6569

66-
for _ in 0..=height {
67-
let start_index = page_offset + upper_left.0 as usize;
68-
let end_index = page_offset + lower_right.0 as usize;
69-
let sub_buf = &buf[start_index..end_index];
70-
71-
page_offset += disp_width;
72-
73-
self.spi.write(&sub_buf).map_err(Error::Comm)?;
74-
}
75-
76-
Ok(())
70+
buf.chunks(disp_width)
71+
.skip(starting_page)
72+
.take(num_pages)
73+
.map(|s| &s[page_lower..page_upper])
74+
.try_for_each(|c| self.spi.write(&c).map_err(Error::Comm))
7775
}
7876
}

src/mode/graphics.rs

Lines changed: 18 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -200,54 +200,32 @@ where
200200
let (display_width, _) = self.properties.get_size().dimensions();
201201
let display_rotation = self.properties.get_rotation();
202202

203-
let idx = match display_rotation {
203+
let (idx, bit) = match display_rotation {
204204
DisplayRotation::Rotate0 | DisplayRotation::Rotate180 => {
205-
if x >= display_width as u32 {
206-
return;
207-
}
208-
((y as usize) / 8 * display_width as usize) + (x as usize)
209-
}
210-
211-
DisplayRotation::Rotate90 | DisplayRotation::Rotate270 => {
212-
if y >= display_width as u32 {
213-
return;
214-
}
215-
((x as usize) / 8 * display_width as usize) + (y as usize)
216-
}
217-
};
218-
219-
if idx >= self.buffer.len() {
220-
return;
221-
}
222-
223-
// Keep track of max and min values
224-
self.min_x = self.min_x.min(x as u8);
225-
self.max_x = self.max_x.max(x as u8);
205+
let idx = ((y as usize) / 8 * display_width as usize) + (x as usize);
206+
let bit = y % 8;
226207

227-
self.min_y = self.min_y.min(y as u8);
228-
self.max_y = self.max_y.max(y as u8);
229-
230-
let (byte, bit) = match display_rotation {
231-
DisplayRotation::Rotate0 | DisplayRotation::Rotate180 => {
232-
let byte =
233-
&mut self.buffer[((y as usize) / 8 * display_width as usize) + (x as usize)];
234-
let bit = 1 << (y % 8);
235-
236-
(byte, bit)
208+
(idx, bit)
237209
}
238210
DisplayRotation::Rotate90 | DisplayRotation::Rotate270 => {
239-
let byte =
240-
&mut self.buffer[((x as usize) / 8 * display_width as usize) + (y as usize)];
241-
let bit = 1 << (x % 8);
211+
let idx = ((x as usize) / 8 * display_width as usize) + (y as usize);
212+
let bit = x % 8;
242213

243-
(byte, bit)
214+
(idx, bit)
244215
}
245216
};
246217

247-
if value == 0 {
248-
*byte &= !bit;
249-
} else {
250-
*byte |= bit;
218+
if let Some(byte) = self.buffer.get_mut(idx) {
219+
// Keep track of max and min values
220+
self.min_x = self.min_x.min(x as u8);
221+
self.max_x = self.max_x.max(x as u8);
222+
223+
self.min_y = self.min_y.min(y as u8);
224+
self.max_y = self.max_y.max(y as u8);
225+
226+
// Set pixel value in byte
227+
// Ref this comment https://stackoverflow.com/questions/47981/how-do-you-set-clear-and-toggle-a-single-bit#comment46654671_47990
228+
*byte = *byte & !(1 << bit) | (value << bit)
251229
}
252230
}
253231

0 commit comments

Comments
 (0)