├── .cargo └── config.toml ├── .github ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── doc ├── SSD1306.pdf └── SSD1306B_rev1.1.pdf ├── examples ├── async_i2c_spi.rs ├── async_terminal_i2c.rs ├── bmp_i2c.rs ├── dvd.bmp ├── graphics.rs ├── graphics_i2c.rs ├── graphics_i2c_128x32.rs ├── graphics_i2c_72x40.rs ├── image_i2c.rs ├── noise_i2c.rs ├── pixelsquare.rs ├── rotation_i2c.rs ├── rtic_brightness.rs ├── rtic_dvd.rs ├── rust.bmp ├── rust.png ├── rust.raw ├── terminal_i2c.rs └── text_i2c.rs ├── readme_banner.jpg ├── release.toml ├── rustfmt.nightly.toml └── src ├── brightness.rs ├── command.rs ├── error.rs ├── i2c_interface.rs ├── lib.rs ├── mode ├── buffered_graphics.rs ├── mod.rs └── terminal.rs ├── prelude.rs ├── rotation.rs ├── size.rs └── test_helpers.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.'cfg(all(target_arch = "arm", target_os = "none"))'] 2 | runner = [ "probe-rs", "run", "--chip", "STM32F103C8" ] 3 | rustflags = [ 4 | "-C", "link-arg=-Tlink.x", 5 | "-C", "link-arg=--nmagic", 6 | "-C", "link-arg=-Tdefmt.x", 7 | ] 8 | 9 | [build] 10 | target = "thumbv7m-none-eabi" 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | - Version of `ssd1306` in use (if applicable): [version here] 2 | - MCU/other hardware in use: [hardware description here] 3 | - Display resolution and interface: [SPI/I2C], [128x64] 4 | 5 | ## Description of the problem/feature request/other 6 | 7 | [description here] 8 | 9 | ## Test case (if applicable) 10 | 11 | ```rust 12 | // Failing test case demonstrating the issue here 13 | ``` 14 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Hi! Thank you for helping out with SSD1306 development! Please: 2 | 3 | - [ ] Check that you've added documentation to any new methods 4 | - [ ] Rebase from `master` if you're not already up to date 5 | - [ ] Add or modify an example if there are changes to the public API 6 | - [ ] Add a `CHANGELOG.md` entry in the **Unreleased** section under the appropriate heading (**Added**, **Fixed**, **Changed**, etc) 7 | - [ ] Run `rustfmt` on the project with `cargo fmt --all` - CI will not pass without this step 8 | - [ ] Check that your branch is up to date with master and that CI is passing once the PR is opened 9 | 10 | ## PR description 11 | 12 | [add your PR description here] 13 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Build and test 2 | on: 3 | push: 4 | branches-ignore: 5 | - '*.tmp' 6 | tags: 7 | - '*' 8 | pull_request: 9 | workflow_dispatch: 10 | 11 | jobs: 12 | fmt: 13 | name: fmt 14 | runs-on: "ubuntu-latest" 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: dtolnay/rust-toolchain@1.84 18 | with: 19 | components: rustfmt 20 | - run: cargo fmt --all -- --check 21 | 22 | test: 23 | name: test 24 | runs-on: "ubuntu-latest" 25 | steps: 26 | - uses: actions/checkout@v4 27 | - uses: dtolnay/rust-toolchain@1.84 28 | - run: cargo test --lib --target x86_64-unknown-linux-gnu 29 | - run: cargo test --doc --target x86_64-unknown-linux-gnu 30 | 31 | test-msrv: 32 | name: build with MSRV 33 | runs-on: "ubuntu-latest" 34 | steps: 35 | - uses: actions/checkout@v4 36 | - uses: dtolnay/rust-toolchain@master 37 | with: 38 | toolchain: 1.75 39 | - run: cargo build --lib --target x86_64-unknown-linux-gnu 40 | - run: cargo build --lib --target x86_64-unknown-linux-gnu --features async 41 | - run: cargo doc --target x86_64-unknown-linux-gnu 42 | - run: cargo doc --target x86_64-unknown-linux-gnu --features async 43 | 44 | build: 45 | strategy: 46 | fail-fast: false 47 | matrix: 48 | target: 49 | - arm-unknown-linux-gnueabi 50 | # Raspberry Pi 2, 3, etc 51 | - armv7-unknown-linux-gnueabihf 52 | # Linux 53 | - x86_64-unknown-linux-gnu 54 | - x86_64-unknown-linux-musl 55 | # Bare metal 56 | - thumbv6m-none-eabi 57 | include: 58 | - target: thumbv7em-none-eabi 59 | examples: true 60 | - target: thumbv7em-none-eabihf 61 | examples: true 62 | - target: thumbv7m-none-eabi 63 | examples: true 64 | name: ${{matrix.target}} 65 | runs-on: ubuntu-latest 66 | steps: 67 | - uses: actions/checkout@v4 68 | - uses: dtolnay/rust-toolchain@1.84 69 | with: 70 | components: rustfmt 71 | - run: rustup target add ${{matrix.target}} 72 | - run: cargo build --target ${{matrix.target}} --all-features --release 73 | - if: ${{ matrix.examples }} 74 | run: cargo build --target ${{matrix.target}} --examples --release 75 | - if: ${{ matrix.examples }} 76 | run: cargo build --target ${{matrix.target}} --all-features --examples --release 77 | - run: cargo doc --all-features --target ${{matrix.target }} 78 | 79 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | [`ssd1306`](https://crates.io/crates/ssd1306) is a no-std driver written in Rust for the popular 4 | SSD1306 monochrome OLED display. 5 | 6 | 7 | 8 | ## [Unreleased] - ReleaseDate 9 | ## [0.10.0] - 2025-03-22 10 | ### Changed 11 | - Added `DisplaySize64x32` to the prelude 12 | - Update `embedded-hal-bus` dependency to 0.3.0 13 | - Update examples 14 | 15 | ### Fixed 16 | - Switch to resolver version 2. This fixes compilation issues when the `async` feature is enabled. 17 | - Parentheses for expression in mode/terminal.rs (see [precedence](https://rust-lang.github.io/rust-clippy/master/index.html#precedence)) in mode/terminal.rs 18 | - Switch iterator in `write_str` in mode/terminal.rs from last() to next_back (see [double_ended_iterator_last](https://rust-lang.github.io/rust-clippy/master/index.html#double_ended_iterator_last)) 19 | - If feature `async` is enabled, the `embedded_hal_async::i2c::I2c` is used instead of `embedded_hal::i2c::I2c` so the I2C can be shared 20 | - Update dependencies for `embassy-executor` to 0.7.0 21 | - Remove `embassy-executor` feature "integrated-timer". See https://github.com/embassy-rs/embassy/blob/main/embassy-executor/CHANGELOG.md 22 | - Switch `embassy-executor` from git to crates.io 23 | - Update dependencies for `embassy-stm32` to 0.2.0 24 | - Switch `embassy-stm32` from git to crates.io 25 | - Update dependencies for `embassy-time` to 0.4.0 26 | - Switch `embassy-time` from git to crates.io 27 | - Update dependencies for `tinybmp` to 0.5.0 28 | - Update root-toolchain to 1.84 for github workflow in ci.yml 29 | 30 | ## [0.9.0] - 2024-08-30 31 | 32 | - Updated dependencies for `embedded-hal` 1.0.0. 33 | - Switch examples to embassy STM32 PAC which implements `embedded-hal` 1.0.0 traits. 34 | - Add an asynchronous interface, enabled via the `async` feature. 35 | - **(breaking)** Increased MSRV to 1.75.0 36 | 37 | ### Added 38 | 39 | - [#203](https://github.com/jamwaffles/ssd1306/pull/203) Added `Ssd1306::release(self)` to release the contained i2c interface. 40 | 41 | ### Changed 42 | 43 | - [#212](https://github.com/rust-embedded-community/ssd1306/pull/212) Switch 44 | from circleci to github actions. Adjust urls now repository is hosted on 45 | rust-embedded-community. Update code and config for modern rust and tools 46 | 47 | ## [0.8.4] - 2023-10-27 48 | 49 | ### Fixed 50 | 51 | - [#201](https://github.com/jamwaffles/ssd1306/pull/201) Fixed `BufferedGraphicsMode::clear(On)` such that it fills all pixels with `On`, not only some. 52 | 53 | ## [0.8.3] - 2023-10-09 54 | 55 | ### Changed 56 | 57 | - [#195](https://github.com/jamwaffles/ssd1306/pull/195) Changed `BasicMode::clear` to clear in 58 | small batches instead of one big write. This drops RAM requirement by ~900b and fixes issues on 59 | MCUs with less than 1Kb of RAM. 60 | - [#195](https://github.com/jamwaffles/ssd1306/pull/195) Changed `TerminalMode` to use lookup by 61 | ASCII code instead of per-character match when searching for glyph. This may save up to 3.5Kb of 62 | compiled code on AVR MCUs. 63 | 64 | ## [0.8.2] - 2023-09-29 65 | 66 | ### Fixed 67 | 68 | - [#197](https://github.com/jamwaffles/ssd1306/pull/197) Fixed terminal mode panic and wrapping 69 | behaviour for rotated displays. 70 | 71 | ## [0.8.1] - 2023-08-18 72 | 73 | ### Added 74 | 75 | - [#190](https://github.com/jamwaffles/ssd1306/pull/190) Added `Ssd1306::set_invert` to invert the 76 | screen pixels 77 | 78 | ## [0.8.0] - 2023-06-01 79 | 80 | ### Added 81 | 82 | - [#183](https://github.com/jamwaffles/ssd1306/pull/183) `Brightness::custom()` is now publicly 83 | available. 84 | 85 | ### Fixed 86 | 87 | - [#177](https://github.com/jamwaffles/ssd1306/pull/177) Fixed a few spelling mistakes. 88 | 89 | ### Changed 90 | 91 | - **(breaking)** [#184](https://github.com/jamwaffles/ssd1306/pull/184) Increased MSRV to 1.61.0 92 | - **(breaking)** [#179](https://github.com/jamwaffles/ssd1306/pull/179) Changed `Ssd1306::reset` 93 | signature. 94 | - [#181](https://github.com/jamwaffles/ssd1306/pull/181) Update embedded-graphics-core dependency to 95 | 0.4 96 | - **(breaking)** [#185](https://github.com/jamwaffles/ssd1306/pull/185) The inherent 97 | `BufferedGraphicsMode::clear` has been renamed to `clear_buffer`. 98 | - [#185](https://github.com/jamwaffles/ssd1306/pull/185) Some methods no longer require 99 | `DI: WriteOnlyDataCommand`. 100 | 101 | ## [0.7.1] - 2022-08-15 102 | 103 | ### Added 104 | 105 | - [#161](https://github.com/jamwaffles/ssd1306/pull/161) Added a `set_mirror` method to enable or 106 | disable display mirroring. 107 | - [#166](https://github.com/jamwaffles/ssd1306/pull/166) Added `DisplaySize` configuration for 64x32 108 | displays. 109 | 110 | ## [0.7.0] - 2021-07-08 111 | 112 | ### Changed 113 | 114 | - **(breaking)** [#158](https://github.com/jamwaffles/ssd1306/pull/158) Migrate away from 115 | `generic-array` to a solution using const generics. This raises the crate MSRV to 1.51. 116 | 117 | ## [0.6.0] - 2021-06-22 118 | 119 | ### Changed 120 | 121 | - **(breaking)** [#156](https://github.com/jamwaffles/ssd1306/pull/156) Migrate from 122 | `embedded-graphics` to `embedded-graphics-core`. 123 | - **(breaking)** [#150](https://github.com/jamwaffles/ssd1306/pull/150) 124 | `BufferedGraphicsMode::set_pixel` now accepts a `bool` instead of a `u8` for the pixel color 125 | value. 126 | - **(breaking)** [#150](https://github.com/jamwaffles/ssd1306/pull/150) `display_on` is now called 127 | `set_display_on`. 128 | - **(breaking)** [#150](https://github.com/jamwaffles/ssd1306/pull/150) `TerminalMode::get_position` 129 | is now called `position` to conform with Rust API guidelines. 130 | - **(breaking)** [#150](https://github.com/jamwaffles/ssd1306/pull/150) Refactor the crate API to be 131 | more versatile and to make code clearer to understand. 132 | 133 | A graphics mode initialisation now looks like this: 134 | 135 | ```rust 136 | use ssd1306::{prelude::*, I2CDisplayInterface, Ssd1306}; 137 | 138 | let interface = I2CDisplayInterface::new(i2c); 139 | 140 | let mut display = Ssd1306::new(interface, DisplaySize128x64, DisplayRotation::Rotate0) 141 | .into_buffered_graphics_mode(); 142 | 143 | display.init().unwrap(); 144 | ``` 145 | 146 | ## [0.5.2] - 2021-04-19 147 | 148 | - [#145](https://github.com/jamwaffles/ssd1306/pull/145) Fixed rotation for 96x16 and 72x40 149 | displays. 150 | - [#147](https://github.com/jamwaffles/ssd1306/pull/147) Fixed display rotation in terminal mode. 151 | 152 | ## [0.5.1] - 2021-01-09 153 | 154 | ## [0.5.0] - 2020-12-21 155 | 156 | ## [0.4.2] - 2020-12-15 (yanked) 157 | 158 | ### Changed 159 | 160 | - **(breaking)** [#139](https://github.com/jamwaffles/ssd1306/pull/139) Removed default display size 161 | type parameters. 162 | 163 | ### Fixed 164 | 165 | - [#141](https://github.com/jamwaffles/ssd1306/pull/141) 72x40 displays can now be set to higher 166 | brightnesses, matching other sizes. 167 | 168 | ## [0.4.1] - 2020-12-01 169 | 170 | ### Changed 171 | 172 | - [#137](https://github.com/jamwaffles/ssd1306/pull/137) Replaced `TerminalMode` font with a new, 173 | more consistent one. This now uses the `zxpix` font from 174 | . 175 | 176 | ## [0.4.0] - 2020-08-03 177 | 178 | ### Added 179 | 180 | - [#121](https://github.com/jamwaffles/ssd1306/pull/121) Added brightness control with the 181 | `set_brightness()` method. 182 | - [#118](https://github.com/jamwaffles/ssd1306/pull/118) `DisplayModeTrait::into_properties()` new 183 | method that consumes the driver and returns the `DisplayProperties` 184 | 185 | ### Changed 186 | 187 | - **(breaking)** [#129](https://github.com/jamwaffles/ssd1306/pull/129) `TerminalMode::set_rotation` 188 | now resets the cursor position 189 | - **(breaking)** [#125](https://github.com/jamwaffles/ssd1306/pull/125) Redesigned display size 190 | handling. 191 | - **(breaking)** [#126](https://github.com/jamwaffles/ssd1306/pull/126) Moved `reset` method to 192 | `DisplayModeTrait`. If the prelude is not used, add either `use ssd1306::prelude::*` or 193 | `ssd1306::mode::displaymode::DisplayModeTrait` to your imports. 194 | - **(breaking)** [#119](https://github.com/jamwaffles/ssd1306/pull/119) Remove `DisplayMode` and 195 | `RawMode` 196 | - [#120](https://github.com/jamwaffles/ssd1306/pull/120) Update to v0.4 197 | [`display-interface`](https://crates.io/crates/display-interface) 198 | - **(breaking)** [#118](https://github.com/jamwaffles/ssd1306/pull/118) Change `release` method to 199 | return the display interface instead of the `DisplayProperties`. 200 | - **(breaking)** [#116](https://github.com/jamwaffles/ssd1306/pull/116) Replace custom I2C and SPI 201 | interfaces by generic [`display-interface`](https://crates.io/crates/display-interface) 202 | - **(breaking)** [#113](https://github.com/jamwaffles/ssd1306/pull/113) Removed public 203 | `send_bounded_data` from DisplayInterface and implementations 204 | 205 | ### Fixed 206 | 207 | - [#129](https://github.com/jamwaffles/ssd1306/pull/129) Fixed `Rotate90` and `Rotate270` rotation 208 | modes for `TerminalMode` 209 | 210 | ## [0.3.1] - 2020-03-21 211 | 212 | ### Fixed 213 | 214 | - Fix docs.rs build config 215 | 216 | ## [0.3.0] - 2020-03-20 217 | 218 | ### Fixed 219 | 220 | - [#111](https://github.com/jamwaffles/ssd1306/pull/111) Fix TerminalMode offset for smaller 221 | displays. 222 | 223 | ### Added 224 | 225 | - [#111](https://github.com/jamwaffles/ssd1306/pull/111) Add support for modules with a 64x48px 226 | display size. 227 | 228 | ### Changed 229 | 230 | - **(breaking)** [#112](https://github.com/jamwaffles/ssd1306/pull/112) Upgrade to embedded-graphics 231 | 0.6.0 232 | - [#107](https://github.com/jamwaffles/ssd1306/pull/107) Migrate from Travis to CircleCI 233 | - [#105](https://github.com/jamwaffles/ssd1306/pull/105) Reduce flash usage by around 400 bytes by 234 | replacing some internal `unwrap()`s with `as` coercions. 235 | - [#106](https://github.com/jamwaffles/ssd1306/pull/106) Optimise internals by using iterators to 236 | elide bounds checks. Should also speed up `GraphicsMode` (and `embedded-graphics` operations) with 237 | a cleaned-up `set_pixel`. 238 | - [#108](https://github.com/jamwaffles/ssd1306/pull/108) Add an example using 239 | `DisplayProperties.draw()` to send a raw buffer of random bytes to the display over I2C. 240 | 241 | ### Added 242 | 243 | - [#110](https://github.com/jamwaffles/ssd1306/pull/110) Add an animated image example `rtfm_dvd` 244 | using [RTFM](https://crates.io/crates/cortex-m-rtfm) 245 | 246 | ## [0.3.0-alpha.4] - 2020-02-07 247 | 248 | ### Added 249 | 250 | - [#101](https://github.com/jamwaffles/ssd1306/pull/101) Add support for modules with a 72x40px 251 | display size. These are often advertised as 70x40px displays which are likely the same hardware. 252 | An example is also added - `graphics_i2c_72x40`. 253 | 254 | ### Fixed 255 | 256 | - Fix docs.rs build by targeting `x86_64-unknown-linux-gnu` 257 | 258 | ### Changed 259 | 260 | - **(breaking)** Upgrade embedded-graphics from `0.6.0-alpha.2` to version `0.6.0-alpha.3` 261 | - [#106](https://github.com/jamwaffles/ssd1306/pull/106) Switch out some `for` loops for iterators 262 | internally to speed up data transfers and reduce code size in `--release` mode. 263 | 264 | ## [0.3.0-alpha.3] - 2020-02-03 265 | 266 | ### Changed 267 | 268 | - [#97](https://github.com/jamwaffles/ssd1306/pull/97) Use the new `Triangle` primitive from 269 | Embedded Graphics 0.6.0-alpha.2 in the three SSD1306 `graphics*.rs` examples 270 | - [#103](https://github.com/jamwaffles/ssd1306/pull/103) Pin embedded-graphics version to 271 | 0.6.0-alpha.2 272 | 273 | ## [0.3.0-alpha.2] 274 | 275 | ### Fixed 276 | 277 | - [#80](https://github.com/jamwaffles/ssd1306/pull/80) `TerminalMode` now has a cursor. Newlines 278 | (`\n`) and carriage returns (`\r`) are now supported. 279 | 280 | ### Changed 281 | 282 | - [#94](https://github.com/jamwaffles/ssd1306/pull/94) Programs that only change some parts of the 283 | display should now run much faster. The driver keeps track of changed pixels and only sends a 284 | bounding box of updated pixels instead of updating the entire display every time. 285 | 286 | ## [0.3.0-alpha.1] 287 | 288 | ### Changed 289 | 290 | - **(breaking)** Upgraded to Embedded Graphics 0.6.0-alpha.2 291 | 292 | ## 0.2.6 293 | 294 | ### Added 295 | 296 | - Added a changelog! 297 | - Display power control (#86) - call `.display_on(true)` or `.display_on(false)` to turn on or off 298 | the display respectively. 299 | 300 | ### Fixed 301 | 302 | - Doc examples are now tested in CI (#89) 303 | 304 | ### Changed 305 | 306 | - Builder docs clarify the order of method calls (#89) 307 | 308 | 309 | [Unreleased]: https://github.com/rust-embedded-community/ssd1306/compare/v0.10.0...HEAD 310 | [0.10.0]: https://github.com/rust-embedded-community/ssd1306/compare/v0.9.0...v0.10.0 311 | [0.9.0]: https://github.com/jamwaffles/ssd1306/compare/v0.8.4...v0.9.0 312 | [0.8.4]: https://github.com/jamwaffles/ssd1306/compare/v0.8.3...v0.8.4 313 | 314 | [0.8.3]: https://github.com/jamwaffles/ssd1306/compare/v0.8.2...v0.8.3 315 | [0.8.2]: https://github.com/jamwaffles/ssd1306/compare/v0.8.1...v0.8.2 316 | [0.8.1]: https://github.com/jamwaffles/ssd1306/compare/v0.8.0...v0.8.1 317 | [0.8.0]: https://github.com/jamwaffles/ssd1306/compare/v0.7.1...v0.8.0 318 | [0.7.1]: https://github.com/jamwaffles/ssd1306/compare/v0.7.0...v0.7.1 319 | [0.7.0]: https://github.com/jamwaffles/ssd1306/compare/v0.6.0...v0.7.0 320 | [0.6.0]: https://github.com/jamwaffles/ssd1306/compare/v0.5.2...v0.6.0 321 | [0.5.2]: https://github.com/jamwaffles/ssd1306/compare/v0.5.1...v0.5.2 322 | [0.5.1]: https://github.com/jamwaffles/ssd1306/compare/v0.5.0...v0.5.1 323 | [0.5.0]: https://github.com/jamwaffles/ssd1306/compare/v0.4.2...v0.5.0 324 | [0.4.2]: https://github.com/jamwaffles/ssd1306/compare/v0.4.1...v0.4.2 325 | [0.4.1]: https://github.com/jamwaffles/ssd1306/compare/v0.4.0...v0.4.1 326 | [0.4.0]: https://github.com/jamwaffles/ssd1306/compare/v0.3.1...v0.4.0 327 | [0.3.1]: https://github.com/jamwaffles/ssd1306/compare/v0.3.0...v0.3.1 328 | [0.3.0]: https://github.com/jamwaffles/ssd1306/compare/v0.3.0-alpha.4...v0.3.0 329 | [0.3.0-alpha.4]: https://github.com/jamwaffles/ssd1306/compare/v0.3.0-alpha.3...v0.3.0-alpha.4 330 | [0.3.0-alpha.3]: https://github.com/jamwaffles/ssd1306/compare/v0.3.0-alpha.2...v0.3.0-alpha.3 331 | [0.3.0-alpha.2]: https://github.com/jamwaffles/ssd1306/compare/v0.3.0-alpha.1...v0.3.0-alpha.2 332 | [0.3.0-alpha.1]: https://github.com/jamwaffles/ssd1306/compare/0.2.5...v0.3.0-alpha.1 333 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["James Waples "] 3 | categories = ["embedded", "no-std"] 4 | description = "I2C/SPI driver for the SSD1306 OLED display controller" 5 | documentation = "https://docs.rs/ssd1306" 6 | keywords = ["no-std", "ssd1306", "oled", "embedded", "embedded-hal-driver"] 7 | license = "MIT OR Apache-2.0" 8 | name = "ssd1306" 9 | readme = "README.md" 10 | repository = "https://github.com/rust-embedded-community/ssd1306" 11 | version = "0.10.0" 12 | edition = "2021" 13 | exclude = ["build.rs", "build.sh", "memory.x", "doc", "*.jpg", "*.png", "*.bmp"] 14 | rust-version = "1.75.0" 15 | resolver = "2" 16 | 17 | [package.metadata.docs.rs] 18 | targets = ["thumbv7m-none-eabi", "thumbv7em-none-eabihf"] 19 | all-features = true 20 | 21 | [dependencies] 22 | embedded-hal = "1.0.0" 23 | display-interface = "0.5.0" 24 | display-interface-i2c = "0.5.0" 25 | display-interface-spi = "0.5.0" 26 | embedded-graphics-core = { version = "0.4.0", optional = true } 27 | embedded-hal-async = { version = "1.0.0", optional = true } 28 | maybe-async-cfg = "=0.2.4" 29 | 30 | [dev-dependencies] 31 | embedded-graphics = "0.8.0" 32 | 33 | [target.'cfg(target_arch="arm")'.dev-dependencies] 34 | cortex-m = { version = "0.7.2", features = ["critical-section-single-core"] } 35 | cortex-m-rt = "0.7.3" 36 | cortex-m-rtic = "1.1.4" 37 | defmt = "0.3.6" 38 | defmt-rtt = "0.4.0" 39 | panic-probe = { version = "0.3.1", features = ["print-defmt"] } 40 | # Used to load BMP images in various examples 41 | tinybmp = "0.6.0" 42 | # Used by the noise_i2c examples 43 | rand = { version = "0.8.4", default-features = false, features = ["small_rng"] } 44 | embassy-embedded-hal = { version = "0.3.0", default-features = false } 45 | embassy-executor = { version = "0.7.0", features = [ 46 | "arch-cortex-m", 47 | "executor-thread", 48 | "defmt", 49 | ] } 50 | embassy-stm32 = { version = "0.2.0", features = [ 51 | "stm32f103c8", 52 | "memory-x", 53 | "defmt", 54 | "exti", 55 | "time-driver-tim3", 56 | "unstable-pac", 57 | ] } 58 | embassy-time = { version = "0.4.0" } 59 | embassy-futures = "0.1.1" 60 | embedded-hal-bus = { version = "0.3.0", features = ["async"] } 61 | 62 | [features] 63 | default = ["graphics"] 64 | graphics = ["embedded-graphics-core"] 65 | async = ["dep:embedded-hal-async"] 66 | 67 | [[example]] 68 | name = "async_i2c_spi" 69 | required-features = ["async"] 70 | 71 | [[example]] 72 | name = "async_terminal_i2c" 73 | required-features = ["async"] 74 | 75 | [profile.dev] 76 | opt-level = "s" 77 | codegen-units = 1 78 | incremental = false 79 | 80 | [profile.release] 81 | codegen-units = 1 82 | debug = true 83 | lto = true 84 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 James Waples 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SSD1306 driver 2 | 3 | [![Build Status](https://github.com/rust-embedded-community/ssd1306/actions/workflows/ci.yml/badge.svg)](https://github.com/rust-embedded-community/ssd1306/actions/workflows/ci.yml) 4 | [![Crates.io](https://img.shields.io/crates/v/ssd1306.svg)](https://crates.io/crates/ssd1306) 5 | [![Docs.rs](https://docs.rs/ssd1306/badge.svg)](https://docs.rs/ssd1306) 6 | 7 | [![CRIUS display showing the Rust logo](readme_banner.jpg?raw=true)](examples/image_i2c.rs) 8 | 9 | I2C and SPI (4 wire) driver for the SSD1306 OLED display. 10 | 11 | ## [Documentation](https://docs.rs/ssd1306) 12 | 13 | ## [Changelog](CHANGELOG.md) 14 | 15 | ## [Examples](examples) 16 | 17 | This crate uses [`probe-run`](https://crates.io/crates/probe-run) to run the examples. Once set up, 18 | it should be as simple as `cargo run --example --release`. 19 | 20 | From [`examples/image_i2c.rs`](examples/image_i2c.rs): 21 | 22 | ```rust 23 | #![no_std] 24 | #![no_main] 25 | 26 | use cortex_m_rt::{entry, exception, ExceptionFrame}; 27 | use embedded_graphics::{ 28 | image::{Image, ImageRaw}, 29 | pixelcolor::BinaryColor, 30 | prelude::*, 31 | }; 32 | use panic_halt as _; 33 | use ssd1306::{prelude::*, I2CDisplayInterface, Ssd1306}; 34 | use stm32f1xx_hal::{ 35 | i2c::{BlockingI2c, DutyCycle, Mode}, 36 | prelude::*, 37 | stm32, 38 | }; 39 | 40 | #[entry] 41 | fn main() -> ! { 42 | let dp = stm32::Peripherals::take().unwrap(); 43 | 44 | let mut flash = dp.FLASH.constrain(); 45 | let mut rcc = dp.RCC.constrain(); 46 | 47 | let clocks = rcc.cfgr.freeze(&mut flash.acr); 48 | 49 | let mut afio = dp.AFIO.constrain(&mut rcc.apb2); 50 | 51 | let mut gpiob = dp.GPIOB.split(&mut rcc.apb2); 52 | 53 | let scl = gpiob.pb8.into_alternate_open_drain(&mut gpiob.crh); 54 | let sda = gpiob.pb9.into_alternate_open_drain(&mut gpiob.crh); 55 | 56 | let i2c = BlockingI2c::i2c1( 57 | dp.I2C1, 58 | (scl, sda), 59 | &mut afio.mapr, 60 | Mode::Fast { 61 | frequency: 400_000.hz(), 62 | duty_cycle: DutyCycle::Ratio2to1, 63 | }, 64 | clocks, 65 | &mut rcc.apb1, 66 | 1000, 67 | 10, 68 | 1000, 69 | 1000, 70 | ); 71 | 72 | let interface = I2CDisplayInterface::new(i2c); 73 | let mut display = Ssd1306::new(interface, DisplaySize128x64, DisplayRotation::Rotate0) 74 | .into_buffered_graphics_mode(); 75 | display.init().unwrap(); 76 | 77 | let raw: ImageRaw = ImageRaw::new(include_bytes!("./rust.raw"), 64); 78 | 79 | let im = Image::new(&raw, Point::new(32, 0)); 80 | 81 | im.draw(&mut display).unwrap(); 82 | 83 | display.flush().unwrap(); 84 | 85 | loop {} 86 | } 87 | 88 | #[exception] 89 | fn HardFault(ef: &ExceptionFrame) -> ! { 90 | panic!("{:#?}", ef); 91 | } 92 | ``` 93 | 94 | ## License 95 | 96 | Licensed under either of 97 | 98 | - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or 99 | http://www.apache.org/licenses/LICENSE-2.0) 100 | - MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 101 | 102 | at your option. 103 | 104 | ### Contribution 105 | 106 | Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the 107 | work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any 108 | additional terms or conditions. 109 | -------------------------------------------------------------------------------- /doc/SSD1306.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-embedded-community/ssd1306/8e45cb20d2d60ee8c35b166893629027610cfbb8/doc/SSD1306.pdf -------------------------------------------------------------------------------- /doc/SSD1306B_rev1.1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-embedded-community/ssd1306/8e45cb20d2d60ee8c35b166893629027610cfbb8/doc/SSD1306B_rev1.1.pdf -------------------------------------------------------------------------------- /examples/async_i2c_spi.rs: -------------------------------------------------------------------------------- 1 | //! Draw a 1 bit per pixel black and white rust logo to 128x64 SSD1306 displays over both I2C 2 | //! and SPI. This uses async an approach to transfer to i2c and spi simultaniously using DMA 3 | //! 4 | //! This example is for the STM32F103 "Blue Pill" board using I2C1 and SPI1. 5 | //! 6 | //! Wiring connections are as follows for a CRIUS-branded display: 7 | //! 8 | //! ``` 9 | //! I2c Display -> Blue Pill 10 | //! GND -> GND 11 | //! +5V -> VCC 12 | //! SDA -> PB7 13 | //! SCL -> PB6 14 | //! 15 | //! SPI display -> Blue Pill 16 | //! GND -> GND 17 | //! 3V3 -> VCC 18 | //! PA5 -> SCL (D0) 19 | //! PA7 -> SDA (D1) 20 | //! PB0 -> RST 21 | //! PB1 -> D/C 22 | //! PB10 -> CS 23 | //! ``` 24 | //! 25 | //! Run on a Blue Pill with `cargo run --example async_i2c_spi`. 26 | 27 | #![no_std] 28 | #![no_main] 29 | 30 | use defmt_rtt as _; 31 | use embassy_executor::Spawner; 32 | use embassy_futures::join::join; 33 | use embassy_stm32::{bind_interrupts, gpio, i2c, peripherals, spi::Spi, time::Hertz, Config}; 34 | use embedded_graphics::{ 35 | image::{Image, ImageRaw}, 36 | pixelcolor::BinaryColor, 37 | prelude::*, 38 | }; 39 | use panic_probe as _; 40 | use ssd1306::{prelude::*, I2CDisplayInterface, Ssd1306Async}; 41 | 42 | bind_interrupts!(struct Irqs { 43 | I2C1_EV => i2c::EventInterruptHandler; 44 | I2C1_ER => i2c::ErrorInterruptHandler; 45 | }); 46 | 47 | #[embassy_executor::main] 48 | async fn main(_spawner: Spawner) { 49 | let mut config: Config = Default::default(); 50 | config.rcc.hse = Some(embassy_stm32::rcc::Hse { 51 | freq: Hertz::mhz(8), 52 | mode: embassy_stm32::rcc::HseMode::Oscillator, 53 | }); 54 | config.rcc.sys = embassy_stm32::rcc::Sysclk::PLL1_P; 55 | config.rcc.pll = Some(embassy_stm32::rcc::Pll { 56 | src: embassy_stm32::rcc::PllSource::HSE, 57 | prediv: embassy_stm32::rcc::PllPreDiv::DIV1, 58 | mul: embassy_stm32::rcc::PllMul::MUL9, // 8 * 9 = 72Mhz 59 | }); 60 | 61 | // Scale down to 36Mhz (maximum allowed) 62 | config.rcc.apb1_pre = embassy_stm32::rcc::APBPrescaler::DIV2; 63 | let p = embassy_stm32::init(config); 64 | 65 | // I2C 66 | let i2c = embassy_stm32::i2c::I2c::new( 67 | p.I2C1, 68 | p.PB6, 69 | p.PB7, 70 | Irqs, 71 | p.DMA1_CH6, 72 | p.DMA1_CH7, 73 | // According to the datasheet the stm32f1xx only supports up to 400khz, but 1mhz seems to 74 | // work just fine. WHen having issues try changing this to Hertz::khz(400). 75 | Hertz::mhz(1), 76 | Default::default(), 77 | ); 78 | 79 | let interface = I2CDisplayInterface::new(i2c); 80 | let mut display_i2c = Ssd1306Async::new(interface, DisplaySize128x64, DisplayRotation::Rotate0) 81 | .into_buffered_graphics_mode(); 82 | 83 | // SPI 84 | let spi = Spi::new_txonly(p.SPI1, p.PA5, p.PA7, p.DMA1_CH3, Default::default()); 85 | 86 | let mut rst = gpio::Output::new(p.PB0, gpio::Level::Low, gpio::Speed::Low); 87 | let dc = gpio::Output::new(p.PB1, gpio::Level::Low, gpio::Speed::Low); 88 | let cs = gpio::Output::new(p.PB10, gpio::Level::Low, gpio::Speed::Low); 89 | let spi = embedded_hal_bus::spi::ExclusiveDevice::new_no_delay(spi, cs).unwrap(); 90 | 91 | let interface = SPIInterface::new(spi, dc); 92 | let mut display_spi = Ssd1306Async::new(interface, DisplaySize128x64, DisplayRotation::Rotate0) 93 | .into_buffered_graphics_mode(); 94 | 95 | // Init and reset both displays as needed 96 | join( 97 | async { 98 | display_i2c.init().await.unwrap(); 99 | }, 100 | async { 101 | display_spi 102 | .reset(&mut rst, &mut embassy_time::Delay {}) 103 | .await 104 | .unwrap(); 105 | display_spi.init().await.unwrap(); 106 | }, 107 | ) 108 | .await; 109 | 110 | let raw: ImageRaw = ImageRaw::new(include_bytes!("./rust.raw"), 64); 111 | 112 | for i in (0..=64).chain((0..64).rev()).cycle() { 113 | let top_left = Point::new(i, 0); 114 | let im = Image::new(&raw, top_left); 115 | 116 | im.draw(&mut display_i2c).unwrap(); 117 | im.draw(&mut display_spi).unwrap(); 118 | 119 | join(async { display_i2c.flush().await.unwrap() }, async { 120 | display_spi.flush().await.unwrap() 121 | }) 122 | .await; 123 | 124 | display_i2c.clear(BinaryColor::Off).unwrap(); 125 | display_spi.clear(BinaryColor::Off).unwrap(); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /examples/async_terminal_i2c.rs: -------------------------------------------------------------------------------- 1 | //! Endlessly fill the screen with characters from the alphabet 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 -> PB7 12 | //! (green) SCL -> PB6 13 | //! ``` 14 | //! 15 | //! Run on a Blue Pill with `cargo run --example async_terminal_i2c`. 16 | 17 | #![no_std] 18 | #![no_main] 19 | 20 | use defmt_rtt as _; 21 | use embassy_executor::Spawner; 22 | use embassy_stm32::{bind_interrupts, i2c, peripherals, time::Hertz}; 23 | use panic_probe as _; 24 | use ssd1306::{prelude::*, I2CDisplayInterface, Ssd1306Async}; 25 | 26 | bind_interrupts!(struct Irqs { 27 | I2C1_EV => i2c::EventInterruptHandler; 28 | I2C1_ER => i2c::ErrorInterruptHandler; 29 | }); 30 | 31 | #[embassy_executor::main] 32 | async fn main(_spawner: Spawner) { 33 | let p = embassy_stm32::init(Default::default()); 34 | let i2c = embassy_stm32::i2c::I2c::new( 35 | p.I2C1, 36 | p.PB6, 37 | p.PB7, 38 | Irqs, 39 | p.DMA1_CH6, 40 | p.DMA1_CH7, 41 | Hertz::khz(400), 42 | Default::default(), 43 | ); 44 | 45 | let interface = I2CDisplayInterface::new(i2c); 46 | let mut display = Ssd1306Async::new(interface, DisplaySize128x64, DisplayRotation::Rotate0) 47 | .into_terminal_mode(); 48 | display.init().await.unwrap(); 49 | let _ = display.clear().await; 50 | 51 | /* Endless loop */ 52 | loop { 53 | for c in 97..123 { 54 | let _ = display 55 | .write_str(unsafe { core::str::from_utf8_unchecked(&[c]) }) 56 | .await; 57 | } 58 | for c in 65..91 { 59 | let _ = display 60 | .write_str(unsafe { core::str::from_utf8_unchecked(&[c]) }) 61 | .await; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /examples/bmp_i2c.rs: -------------------------------------------------------------------------------- 1 | //! Draw an RGB565 BMP image onto the display by converting the `Rgb565` pixel color type to 2 | //! `BinaryColor` using a simple threshold where any pixel with a value greater than zero is treated 3 | //! as "on". 4 | //! 5 | //! Note that the `bmp` feature for `embedded-graphics` must be turned on. 6 | //! 7 | //! This example is for the STM32F103 "Blue Pill" board using I2C1. 8 | //! 9 | //! Wiring connections are as follows for a CRIUS-branded display: 10 | //! 11 | //! ``` 12 | //! Display -> Blue Pill 13 | //! (black) GND -> GND 14 | //! (red) +5V -> VCC 15 | //! (yellow) SDA -> PB7 16 | //! (green) SCL -> PB6 17 | //! ``` 18 | //! 19 | //! Run on a Blue Pill with `cargo run --example image_i2c`. 20 | 21 | #![no_std] 22 | #![no_main] 23 | 24 | use cortex_m::asm::nop; 25 | use cortex_m_rt::entry; 26 | use defmt_rtt as _; 27 | use embassy_stm32::time::Hertz; 28 | #[cfg(feature = "async")] 29 | use embassy_stm32::{bind_interrupts, i2c, peripherals}; 30 | use embedded_graphics::{image::Image, pixelcolor::Rgb565, prelude::*}; 31 | use panic_probe as _; 32 | use ssd1306::{prelude::*, I2CDisplayInterface, Ssd1306}; 33 | 34 | use tinybmp::Bmp; 35 | 36 | #[entry] 37 | fn main() -> ! { 38 | let p: embassy_stm32::Peripherals = embassy_stm32::init(Default::default()); 39 | #[cfg(feature = "async")] 40 | bind_interrupts!(struct Irqs { 41 | I2C1_EV => i2c::EventInterruptHandler; 42 | I2C1_ER => i2c::ErrorInterruptHandler; 43 | }); 44 | 45 | #[cfg(feature = "async")] 46 | let i2c = embassy_stm32::i2c::I2c::new( 47 | p.I2C1, 48 | p.PB6, 49 | p.PB7, 50 | Irqs, 51 | p.DMA1_CH6, 52 | p.DMA1_CH7, 53 | Hertz::khz(400), 54 | Default::default(), 55 | ); 56 | 57 | #[cfg(not(feature = "async"))] 58 | let i2c = embassy_stm32::i2c::I2c::new_blocking( 59 | p.I2C1, 60 | p.PB6, 61 | p.PB7, 62 | Hertz::khz(400), 63 | Default::default(), 64 | ); 65 | let interface = I2CDisplayInterface::new(i2c); 66 | 67 | let mut display = Ssd1306::new(interface, DisplaySize128x64, DisplayRotation::Rotate0) 68 | .into_buffered_graphics_mode(); 69 | display.init().unwrap(); 70 | 71 | let bmp = Bmp::from_slice(include_bytes!("./rust.bmp")).expect("Failed to load BMP image"); 72 | 73 | // The image is an RGB565 encoded BMP, so specifying the type as `Image>` will read 74 | // the pixels correctly 75 | let im: Image> = Image::new(&bmp, Point::new(32, 0)); 76 | 77 | // We use the `color_converted` method here to automatically convert the RGB565 image data into 78 | // BinaryColor values. 79 | im.draw(&mut display.color_converted()).unwrap(); 80 | 81 | display.flush().unwrap(); 82 | 83 | loop { 84 | nop() 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /examples/dvd.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-embedded-community/ssd1306/8e45cb20d2d60ee8c35b166893629027610cfbb8/examples/dvd.bmp -------------------------------------------------------------------------------- /examples/graphics.rs: -------------------------------------------------------------------------------- 1 | //! Draw a square, circle and triangle on the screen using the embedded_graphics library over a 4 2 | //! wire SPI interface. 3 | //! 4 | //! This example is for the STM32F103 "Blue Pill" board using a 4 wire interface to the display on 5 | //! SPI1. 6 | //! 7 | //! Wiring connections are as follows 8 | //! 9 | //! ``` 10 | //! GND -> GND 11 | //! 3V3 -> VCC 12 | //! PA5 -> SCL (D0) 13 | //! PA7 -> SDA (D1) 14 | //! PB0 -> RST 15 | //! PB1 -> D/C 16 | //! PB10 -> CS 17 | //! ``` 18 | //! 19 | //! Run on a Blue Pill with `cargo run --example graphics`. 20 | 21 | #![no_std] 22 | #![no_main] 23 | 24 | use cortex_m::asm::nop; 25 | use cortex_m_rt::entry; 26 | use defmt_rtt as _; 27 | use embassy_stm32::{ 28 | gpio, 29 | spi::{self, Spi}, 30 | time::Hertz, 31 | }; 32 | use embedded_graphics::{ 33 | pixelcolor::BinaryColor, 34 | prelude::*, 35 | primitives::{Circle, PrimitiveStyleBuilder, Rectangle, Triangle}, 36 | }; 37 | use panic_probe as _; 38 | use ssd1306::{prelude::*, Ssd1306}; 39 | 40 | #[entry] 41 | fn main() -> ! { 42 | let p = embassy_stm32::init(Default::default()); 43 | let mut config = spi::Config::default(); 44 | config.frequency = Hertz::mhz(8); 45 | let spi = Spi::new_blocking_txonly(p.SPI1, p.PA5, p.PA7, config); 46 | 47 | let mut rst = gpio::Output::new(p.PB0, gpio::Level::Low, gpio::Speed::Low); 48 | let dc = gpio::Output::new(p.PB1, gpio::Level::Low, gpio::Speed::Low); 49 | let cs = gpio::Output::new(p.PB10, gpio::Level::Low, gpio::Speed::Low); 50 | let spi = embedded_hal_bus::spi::ExclusiveDevice::new_no_delay(spi, cs).unwrap(); 51 | 52 | let interface = display_interface_spi::SPIInterface::new(spi, dc); 53 | let mut display = Ssd1306::new(interface, DisplaySize128x64, DisplayRotation::Rotate0) 54 | .into_buffered_graphics_mode(); 55 | 56 | display 57 | .reset(&mut rst, &mut embassy_time::Delay {}) 58 | .unwrap(); 59 | display.init().unwrap(); 60 | 61 | let yoffset = 20; 62 | 63 | let style = PrimitiveStyleBuilder::new() 64 | .stroke_width(1) 65 | .stroke_color(BinaryColor::On) 66 | .build(); 67 | 68 | // screen outline 69 | // default display size is 128x64 if you don't pass a _DisplaySize_ 70 | // enum to the _Builder_ struct 71 | Rectangle::new(Point::new(0, 0), Size::new(127, 63)) 72 | .into_styled(style) 73 | .draw(&mut display) 74 | .unwrap(); 75 | 76 | // triangle 77 | Triangle::new( 78 | Point::new(16, 16 + yoffset), 79 | Point::new(16 + 16, 16 + yoffset), 80 | Point::new(16 + 8, yoffset), 81 | ) 82 | .into_styled(style) 83 | .draw(&mut display) 84 | .unwrap(); 85 | 86 | // square 87 | Rectangle::new(Point::new(52, yoffset), Size::new_equal(16)) 88 | .into_styled(style) 89 | .draw(&mut display) 90 | .unwrap(); 91 | 92 | // circle 93 | Circle::new(Point::new(88, yoffset), 16) 94 | .into_styled(style) 95 | .draw(&mut display) 96 | .unwrap(); 97 | 98 | display.flush().unwrap(); 99 | 100 | loop { 101 | nop() 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /examples/graphics_i2c.rs: -------------------------------------------------------------------------------- 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 -> PB7 12 | //! (green) SCL -> PB6 13 | //! ``` 14 | //! 15 | //! Run on a Blue Pill with `cargo run --example graphics_i2c`. 16 | 17 | #![no_std] 18 | #![no_main] 19 | 20 | use cortex_m::asm::nop; 21 | use cortex_m_rt::entry; 22 | use defmt_rtt as _; 23 | use embassy_stm32::time::Hertz; 24 | #[cfg(feature = "async")] 25 | use embassy_stm32::{bind_interrupts, i2c, peripherals}; 26 | use embedded_graphics::{ 27 | pixelcolor::BinaryColor, 28 | prelude::*, 29 | primitives::{Circle, PrimitiveStyleBuilder, Rectangle, Triangle}, 30 | }; 31 | use panic_probe as _; 32 | use ssd1306::{prelude::*, I2CDisplayInterface, Ssd1306}; 33 | 34 | #[entry] 35 | fn main() -> ! { 36 | let p = embassy_stm32::init(Default::default()); 37 | #[cfg(feature = "async")] 38 | bind_interrupts!(struct Irqs { 39 | I2C1_EV => i2c::EventInterruptHandler; 40 | I2C1_ER => i2c::ErrorInterruptHandler; 41 | }); 42 | 43 | #[cfg(feature = "async")] 44 | let i2c = embassy_stm32::i2c::I2c::new( 45 | p.I2C1, 46 | p.PB6, 47 | p.PB7, 48 | Irqs, 49 | p.DMA1_CH6, 50 | p.DMA1_CH7, 51 | Hertz::khz(400), 52 | Default::default(), 53 | ); 54 | 55 | #[cfg(not(feature = "async"))] 56 | let i2c = embassy_stm32::i2c::I2c::new_blocking( 57 | p.I2C1, 58 | p.PB6, 59 | p.PB7, 60 | Hertz::khz(400), 61 | Default::default(), 62 | ); 63 | 64 | let interface = I2CDisplayInterface::new(i2c); 65 | let mut display = Ssd1306::new(interface, DisplaySize128x64, DisplayRotation::Rotate0) 66 | .into_buffered_graphics_mode(); 67 | display.init().unwrap(); 68 | 69 | let yoffset = 20; 70 | 71 | let style = PrimitiveStyleBuilder::new() 72 | .stroke_width(1) 73 | .stroke_color(BinaryColor::On) 74 | .build(); 75 | 76 | // screen outline 77 | // default display size is 128x64 if you don't pass a _DisplaySize_ 78 | // enum to the _Builder_ struct 79 | Rectangle::new(Point::new(0, 0), Size::new(127, 63)) 80 | .into_styled(style) 81 | .draw(&mut display) 82 | .unwrap(); 83 | 84 | // triangle 85 | Triangle::new( 86 | Point::new(16, 16 + yoffset), 87 | Point::new(16 + 16, 16 + yoffset), 88 | Point::new(16 + 8, yoffset), 89 | ) 90 | .into_styled(style) 91 | .draw(&mut display) 92 | .unwrap(); 93 | 94 | // square 95 | Rectangle::new(Point::new(52, yoffset), Size::new_equal(16)) 96 | .into_styled(style) 97 | .draw(&mut display) 98 | .unwrap(); 99 | 100 | // circle 101 | Circle::new(Point::new(88, yoffset), 16) 102 | .into_styled(style) 103 | .draw(&mut display) 104 | .unwrap(); 105 | 106 | display.flush().unwrap(); 107 | 108 | loop { 109 | nop() 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /examples/graphics_i2c_128x32.rs: -------------------------------------------------------------------------------- 1 | //! Draw a square, circle and triangle on a 128x32px display. 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 -> PB7 12 | //! (green) SCL -> PB6 13 | //! ``` 14 | //! 15 | //! Run on a Blue Pill with `cargo run --example graphics_i2c_128x32`. 16 | 17 | #![no_std] 18 | #![no_main] 19 | 20 | use cortex_m::asm::nop; 21 | use cortex_m_rt::entry; 22 | use defmt_rtt as _; 23 | use embassy_stm32::time::Hertz; 24 | #[cfg(feature = "async")] 25 | use embassy_stm32::{bind_interrupts, i2c, peripherals}; 26 | use embedded_graphics::{ 27 | pixelcolor::BinaryColor, 28 | prelude::*, 29 | primitives::{Circle, PrimitiveStyleBuilder, Rectangle, Triangle}, 30 | }; 31 | use panic_probe as _; 32 | use ssd1306::{prelude::*, I2CDisplayInterface, Ssd1306}; 33 | 34 | #[entry] 35 | fn main() -> ! { 36 | let p = embassy_stm32::init(Default::default()); 37 | #[cfg(feature = "async")] 38 | bind_interrupts!(struct Irqs { 39 | I2C1_EV => i2c::EventInterruptHandler; 40 | I2C1_ER => i2c::ErrorInterruptHandler; 41 | }); 42 | 43 | #[cfg(feature = "async")] 44 | let i2c = embassy_stm32::i2c::I2c::new( 45 | p.I2C1, 46 | p.PB6, 47 | p.PB7, 48 | Irqs, 49 | p.DMA1_CH6, 50 | p.DMA1_CH7, 51 | Hertz::khz(400), 52 | Default::default(), 53 | ); 54 | 55 | #[cfg(not(feature = "async"))] 56 | let i2c = embassy_stm32::i2c::I2c::new_blocking( 57 | p.I2C1, 58 | p.PB6, 59 | p.PB7, 60 | Hertz::khz(400), 61 | Default::default(), 62 | ); 63 | 64 | let interface = I2CDisplayInterface::new(i2c); 65 | let mut display = Ssd1306::new(interface, DisplaySize128x32, DisplayRotation::Rotate0) 66 | .into_buffered_graphics_mode(); 67 | display.init().unwrap(); 68 | 69 | let yoffset = 8; 70 | 71 | let style = PrimitiveStyleBuilder::new() 72 | .stroke_width(1) 73 | .stroke_color(BinaryColor::On) 74 | .build(); 75 | 76 | // screen outline 77 | // default display size is 128x64 if you don't pass a _DisplaySize_ 78 | // enum to the _Builder_ struct 79 | Rectangle::new(Point::new(0, 0), Size::new(127, 31)) 80 | .into_styled(style) 81 | .draw(&mut display) 82 | .unwrap(); 83 | 84 | // triangle 85 | Triangle::new( 86 | Point::new(16, 16 + yoffset), 87 | Point::new(16 + 16, 16 + yoffset), 88 | Point::new(16 + 8, yoffset), 89 | ) 90 | .into_styled(style) 91 | .draw(&mut display) 92 | .unwrap(); 93 | 94 | // square 95 | Rectangle::new(Point::new(52, yoffset), Size::new_equal(16)) 96 | .into_styled(style) 97 | .draw(&mut display) 98 | .unwrap(); 99 | 100 | // circle 101 | Circle::new(Point::new(88, yoffset), 16) 102 | .into_styled(style) 103 | .draw(&mut display) 104 | .unwrap(); 105 | 106 | display.flush().unwrap(); 107 | 108 | loop { 109 | nop() 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /examples/graphics_i2c_72x40.rs: -------------------------------------------------------------------------------- 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 -> PB7 12 | //! (green) SCL -> PB6 13 | //! ``` 14 | //! 15 | //! Run on a Blue Pill with `cargo run --example graphics_i2c`. 16 | 17 | #![no_std] 18 | #![no_main] 19 | 20 | use cortex_m::asm::nop; 21 | use cortex_m_rt::entry; 22 | use defmt_rtt as _; 23 | use embassy_stm32::time::Hertz; 24 | #[cfg(feature = "async")] 25 | use embassy_stm32::{bind_interrupts, i2c, peripherals}; 26 | use embedded_graphics::{ 27 | pixelcolor::BinaryColor, 28 | prelude::*, 29 | primitives::{Circle, PrimitiveStyleBuilder, Rectangle, Triangle}, 30 | }; 31 | use panic_probe as _; 32 | use ssd1306::{prelude::*, I2CDisplayInterface, Ssd1306}; 33 | 34 | #[entry] 35 | fn main() -> ! { 36 | let p = embassy_stm32::init(Default::default()); 37 | #[cfg(feature = "async")] 38 | bind_interrupts!(struct Irqs { 39 | I2C1_EV => i2c::EventInterruptHandler; 40 | I2C1_ER => i2c::ErrorInterruptHandler; 41 | }); 42 | 43 | #[cfg(feature = "async")] 44 | let i2c = embassy_stm32::i2c::I2c::new( 45 | p.I2C1, 46 | p.PB6, 47 | p.PB7, 48 | Irqs, 49 | p.DMA1_CH6, 50 | p.DMA1_CH7, 51 | Hertz::khz(400), 52 | Default::default(), 53 | ); 54 | 55 | #[cfg(not(feature = "async"))] 56 | let i2c = embassy_stm32::i2c::I2c::new_blocking( 57 | p.I2C1, 58 | p.PB6, 59 | p.PB7, 60 | Hertz::khz(400), 61 | Default::default(), 62 | ); 63 | 64 | let interface = I2CDisplayInterface::new(i2c); 65 | let mut display = Ssd1306::new(interface, DisplaySize72x40, DisplayRotation::Rotate0) 66 | .into_buffered_graphics_mode(); 67 | display.init().unwrap(); 68 | 69 | let size = 10; 70 | let offset = Point::new(10, (42 / 2) - (size / 2) - 1); 71 | let spacing = size + 10; 72 | 73 | let style = PrimitiveStyleBuilder::new() 74 | .stroke_width(1) 75 | .stroke_color(BinaryColor::On) 76 | .build(); 77 | 78 | // screen outline 79 | // default display size is 128x64 if you don't pass a _DisplaySize_ 80 | // enum to the _Builder_ struct 81 | Rectangle::with_corners(Point::new(0, 0), Point::new(71, 39)) 82 | .into_styled(style) 83 | .draw(&mut display) 84 | .unwrap(); 85 | 86 | // Triangle 87 | Triangle::new( 88 | Point::new(0, size), 89 | Point::new(size / 2, 0), 90 | Point::new(size, size), 91 | ) 92 | .translate(offset) 93 | .into_styled(style) 94 | .draw(&mut display) 95 | .unwrap(); 96 | 97 | // Move over to next position 98 | let offset = offset + Point::new(spacing, 0); 99 | 100 | // Draw a square 101 | Rectangle::new(Point::new(0, 0), Size::new_equal(size as u32)) 102 | .translate(offset) 103 | .into_styled(style) 104 | .draw(&mut display) 105 | .unwrap(); 106 | 107 | // Move over a bit more 108 | let offset = offset + Point::new(spacing, 0); 109 | 110 | // Circle 111 | Circle::new(Point::zero(), size as u32) 112 | .translate(offset) 113 | .into_styled(style) 114 | .draw(&mut display) 115 | .unwrap(); 116 | 117 | display.flush().unwrap(); 118 | 119 | loop { 120 | nop() 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /examples/image_i2c.rs: -------------------------------------------------------------------------------- 1 | //! Draw a 1 bit per pixel black and white image. On a 128x64 SSD1306 display over I2C. 2 | //! 3 | //! Image was created with ImageMagick: 4 | //! 5 | //! ```bash 6 | //! convert rust.png -depth 1 gray:rust.raw 7 | //! ``` 8 | //! 9 | //! This example is for the STM32F103 "Blue Pill" board using I2C1. 10 | //! 11 | //! Wiring connections are as follows for a CRIUS-branded display: 12 | //! 13 | //! ``` 14 | //! Display -> Blue Pill 15 | //! (black) GND -> GND 16 | //! (red) +5V -> VCC 17 | //! (yellow) SDA -> PB7 18 | //! (green) SCL -> PB6 19 | //! ``` 20 | //! 21 | //! Run on a Blue Pill with `cargo run --example image_i2c`. 22 | 23 | #![no_std] 24 | #![no_main] 25 | 26 | use cortex_m::asm::nop; 27 | use cortex_m_rt::entry; 28 | use defmt_rtt as _; 29 | #[cfg(feature = "async")] 30 | use embassy_stm32::{bind_interrupts, i2c, peripherals}; 31 | 32 | use embassy_stm32::time::Hertz; 33 | use embedded_graphics::{ 34 | image::{Image, ImageRaw}, 35 | pixelcolor::BinaryColor, 36 | prelude::*, 37 | }; 38 | use panic_probe as _; 39 | use ssd1306::{prelude::*, I2CDisplayInterface, Ssd1306}; 40 | 41 | #[entry] 42 | fn main() -> ! { 43 | let p = embassy_stm32::init(Default::default()); 44 | #[cfg(feature = "async")] 45 | bind_interrupts!(struct Irqs { 46 | I2C1_EV => i2c::EventInterruptHandler; 47 | I2C1_ER => i2c::ErrorInterruptHandler; 48 | }); 49 | 50 | #[cfg(feature = "async")] 51 | let i2c = embassy_stm32::i2c::I2c::new( 52 | p.I2C1, 53 | p.PB6, 54 | p.PB7, 55 | Irqs, 56 | p.DMA1_CH6, 57 | p.DMA1_CH7, 58 | Hertz::khz(400), 59 | Default::default(), 60 | ); 61 | 62 | #[cfg(not(feature = "async"))] 63 | let i2c = embassy_stm32::i2c::I2c::new_blocking( 64 | p.I2C1, 65 | p.PB6, 66 | p.PB7, 67 | Hertz::khz(400), 68 | Default::default(), 69 | ); 70 | 71 | let interface = I2CDisplayInterface::new(i2c); 72 | let mut display = Ssd1306::new(interface, DisplaySize128x64, DisplayRotation::Rotate0) 73 | .into_buffered_graphics_mode(); 74 | display.init().unwrap(); 75 | 76 | let raw: ImageRaw = ImageRaw::new(include_bytes!("./rust.raw"), 64); 77 | 78 | let im = Image::new(&raw, Point::new(32, 0)); 79 | 80 | im.draw(&mut display).unwrap(); 81 | 82 | display.flush().unwrap(); 83 | 84 | loop { 85 | nop() 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /examples/noise_i2c.rs: -------------------------------------------------------------------------------- 1 | //! Send random raw data to the display, emulating an old untuned TV. This example retrieves the 2 | //! underlying display properties struct and allows calling of the low-level `draw()` method, 3 | //! sending a 1024 byte buffer straight to the display. 4 | //! 5 | //! This example is for the STM32F103 "Blue Pill" board using I2C1. 6 | //! 7 | //! Wiring connections are as follows for a CRIUS-branded display: 8 | //! 9 | //! ``` 10 | //! Display -> Blue Pill 11 | //! (black) GND -> GND 12 | //! (red) +5V -> VCC 13 | //! (yellow) SDA -> PB7 14 | //! (green) SCL -> PB6 15 | //! ``` 16 | //! 17 | //! Run on a Blue Pill with `cargo run --example noise_i2c`. Best results when using `--release`. 18 | 19 | #![no_std] 20 | #![no_main] 21 | 22 | use cortex_m_rt::entry; 23 | use defmt_rtt as _; 24 | #[cfg(feature = "async")] 25 | use embassy_stm32::{bind_interrupts, i2c, peripherals}; 26 | 27 | use embassy_stm32::time::Hertz; 28 | use panic_probe as _; 29 | use rand::prelude::*; 30 | use ssd1306::{prelude::*, I2CDisplayInterface, Ssd1306}; 31 | 32 | #[entry] 33 | fn main() -> ! { 34 | let p = embassy_stm32::init(Default::default()); 35 | #[cfg(feature = "async")] 36 | bind_interrupts!(struct Irqs { 37 | I2C1_EV => i2c::EventInterruptHandler; 38 | I2C1_ER => i2c::ErrorInterruptHandler; 39 | }); 40 | 41 | #[cfg(feature = "async")] 42 | let i2c = embassy_stm32::i2c::I2c::new( 43 | p.I2C1, 44 | p.PB6, 45 | p.PB7, 46 | Irqs, 47 | p.DMA1_CH6, 48 | p.DMA1_CH7, 49 | Hertz::khz(400), 50 | Default::default(), 51 | ); 52 | 53 | #[cfg(not(feature = "async"))] 54 | let i2c = embassy_stm32::i2c::I2c::new_blocking( 55 | p.I2C1, 56 | p.PB6, 57 | p.PB7, 58 | Hertz::khz(400), 59 | Default::default(), 60 | ); 61 | 62 | let interface = I2CDisplayInterface::new(i2c); 63 | let mut display = Ssd1306::new(interface, DisplaySize128x64, DisplayRotation::Rotate0); 64 | display.init().unwrap(); 65 | 66 | let mut buf = [0x00u8; 1024]; 67 | 68 | let mut rng = SmallRng::seed_from_u64(0xdead_beef_cafe_d00d); 69 | 70 | loop { 71 | rng.fill_bytes(&mut buf); 72 | 73 | display.draw(&buf).unwrap(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /examples/pixelsquare.rs: -------------------------------------------------------------------------------- 1 | //! This example draws a small square one pixel at a time in the top left corner of the display 2 | //! 3 | //! You will probably want to use the [`embedded_graphics`](https://crates.io/crates/embedded-graphics) crate to do more complex drawing. 4 | //! 5 | //! This example is for the STM32F103 "Blue Pill" board using a 4 wire interface to the display on 6 | //! SPI1. 7 | //! 8 | //! Wiring connections are as follows 9 | //! 10 | //! ``` 11 | //! GND -> GND 12 | //! 3V3 -> VCC 13 | //! PA5 -> SCL (D0) 14 | //! PA7 -> SDA (D1) 15 | //! PB0 -> RST 16 | //! PB1 -> D/C 17 | //! PB10 -> CS 18 | //! ``` 19 | //! 20 | //! Run on a Blue Pill with `cargo run --example pixelsquare`. 21 | 22 | #![no_std] 23 | #![no_main] 24 | 25 | use cortex_m::asm::nop; 26 | use cortex_m_rt::entry; 27 | use defmt_rtt as _; 28 | use embassy_stm32::{ 29 | gpio, 30 | spi::{self, Spi}, 31 | time::Hertz, 32 | }; 33 | use panic_probe as _; 34 | use ssd1306::{prelude::*, Ssd1306}; 35 | 36 | #[entry] 37 | fn main() -> ! { 38 | let p = embassy_stm32::init(Default::default()); 39 | let mut config = spi::Config::default(); 40 | config.frequency = Hertz::mhz(8); 41 | let spi = Spi::new_blocking_txonly(p.SPI1, p.PA5, p.PA7, config); 42 | 43 | let mut rst = gpio::Output::new(p.PB0, gpio::Level::Low, gpio::Speed::Low); 44 | let dc = gpio::Output::new(p.PB1, gpio::Level::Low, gpio::Speed::Low); 45 | let cs = gpio::Output::new(p.PB10, gpio::Level::Low, gpio::Speed::Low); 46 | let spi = embedded_hal_bus::spi::ExclusiveDevice::new_no_delay(spi, cs).unwrap(); 47 | 48 | let interface = display_interface_spi::SPIInterface::new(spi, dc); 49 | let mut display = Ssd1306::new(interface, DisplaySize128x64, DisplayRotation::Rotate0) 50 | .into_buffered_graphics_mode(); 51 | 52 | display 53 | .reset(&mut rst, &mut embassy_time::Delay {}) 54 | .unwrap(); 55 | display.init().unwrap(); 56 | 57 | // Top side 58 | display.set_pixel(0, 0, true); 59 | display.set_pixel(1, 0, true); 60 | display.set_pixel(2, 0, true); 61 | display.set_pixel(3, 0, true); 62 | 63 | // Right side 64 | display.set_pixel(3, 0, true); 65 | display.set_pixel(3, 1, true); 66 | display.set_pixel(3, 2, true); 67 | display.set_pixel(3, 3, true); 68 | 69 | // Bottom side 70 | display.set_pixel(0, 3, true); 71 | display.set_pixel(1, 3, true); 72 | display.set_pixel(2, 3, true); 73 | display.set_pixel(3, 3, true); 74 | 75 | // Left side 76 | display.set_pixel(0, 0, true); 77 | display.set_pixel(0, 1, true); 78 | display.set_pixel(0, 2, true); 79 | display.set_pixel(0, 3, true); 80 | 81 | display.flush().unwrap(); 82 | 83 | loop { 84 | nop() 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /examples/rotation_i2c.rs: -------------------------------------------------------------------------------- 1 | //! Draw the Rust logo centered on a 90 degree rotated 128x64px display 2 | //! 3 | //! Image was created with ImageMagick: 4 | //! 5 | //! ```bash 6 | //! convert rust.png -depth 1 gray:rust.raw 7 | //! ``` 8 | //! 9 | //! This example is for the STM32F103 "Blue Pill" board using I2C1. 10 | //! 11 | //! Wiring connections are as follows for a CRIUS-branded display: 12 | //! 13 | //! ``` 14 | //! Display -> Blue Pill 15 | //! (black) GND -> GND 16 | //! (red) +5V -> VCC 17 | //! (yellow) SDA -> PB7 18 | //! (green) SCL -> PB6 19 | //! ``` 20 | //! 21 | //! Run on a Blue Pill with `cargo run --example rotation_i2c`. 22 | 23 | #![no_std] 24 | #![no_main] 25 | 26 | use cortex_m::asm::nop; 27 | use cortex_m_rt::entry; 28 | use defmt_rtt as _; 29 | use embassy_stm32::time::Hertz; 30 | #[cfg(feature = "async")] 31 | use embassy_stm32::{bind_interrupts, i2c, peripherals}; 32 | use embedded_graphics::{ 33 | image::{Image, ImageRaw}, 34 | pixelcolor::BinaryColor, 35 | prelude::*, 36 | }; 37 | use panic_probe as _; 38 | use ssd1306::{prelude::*, I2CDisplayInterface, Ssd1306}; 39 | 40 | #[entry] 41 | fn main() -> ! { 42 | let p = embassy_stm32::init(Default::default()); 43 | #[cfg(feature = "async")] 44 | bind_interrupts!(struct Irqs { 45 | I2C1_EV => i2c::EventInterruptHandler; 46 | I2C1_ER => i2c::ErrorInterruptHandler; 47 | }); 48 | 49 | #[cfg(feature = "async")] 50 | let i2c = embassy_stm32::i2c::I2c::new( 51 | p.I2C1, 52 | p.PB6, 53 | p.PB7, 54 | Irqs, 55 | p.DMA1_CH6, 56 | p.DMA1_CH7, 57 | Hertz::khz(400), 58 | Default::default(), 59 | ); 60 | 61 | #[cfg(not(feature = "async"))] 62 | let i2c = embassy_stm32::i2c::I2c::new_blocking( 63 | p.I2C1, 64 | p.PB6, 65 | p.PB7, 66 | Hertz::khz(400), 67 | Default::default(), 68 | ); 69 | 70 | let interface = I2CDisplayInterface::new(i2c); 71 | let mut display = Ssd1306::new(interface, DisplaySize128x64, DisplayRotation::Rotate90) 72 | .into_buffered_graphics_mode(); 73 | display.init().unwrap(); 74 | 75 | // Contrived example to test builder and instance methods. Sets rotation to 270 degress 76 | // or 90 degress counterclockwise 77 | display.set_rotation(DisplayRotation::Rotate270).unwrap(); 78 | 79 | let (w, h) = display.dimensions(); 80 | 81 | let raw: ImageRaw = ImageRaw::new(include_bytes!("./rust.raw"), 64); 82 | 83 | let im = Image::new( 84 | &raw, 85 | Point::new(w as i32 / 2 - 64 / 2, h as i32 / 2 - 64 / 2), 86 | ); 87 | 88 | im.draw(&mut display).unwrap(); 89 | 90 | display.flush().unwrap(); 91 | 92 | loop { 93 | nop() 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /examples/rtic_brightness.rs: -------------------------------------------------------------------------------- 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 | pub mod pac { 10 | pub use embassy_stm32::pac::Interrupt as interrupt; 11 | pub use embassy_stm32::pac::*; 12 | } 13 | 14 | #[rtic::app(device = crate::pac, peripherals= false, dispatchers = [EXTI0])] 15 | mod app { 16 | use defmt_rtt as _; 17 | use display_interface_spi::SPIInterface; 18 | use embassy_stm32::{ 19 | gpio, 20 | mode::Blocking, 21 | spi::{self, Spi}, 22 | time::Hertz, 23 | timer::low_level::Timer, 24 | Config, 25 | }; 26 | use embedded_graphics::{ 27 | geometry::Point, 28 | image::Image, 29 | pixelcolor::{BinaryColor, Rgb565}, 30 | prelude::*, 31 | primitives::{PrimitiveStyle, Rectangle}, 32 | }; 33 | use panic_probe as _; 34 | use ssd1306::{mode::BufferedGraphicsMode, prelude::*, Ssd1306}; 35 | use tinybmp::Bmp; 36 | 37 | type Display = Ssd1306< 38 | SPIInterface< 39 | embedded_hal_bus::spi::ExclusiveDevice< 40 | Spi<'static, Blocking>, 41 | gpio::Output<'static>, 42 | embedded_hal_bus::spi::NoDelay, 43 | >, 44 | gpio::Output<'static>, 45 | >, 46 | DisplaySize128x64, 47 | BufferedGraphicsMode, 48 | >; 49 | 50 | #[shared] 51 | struct SharedResources {} 52 | 53 | #[local] 54 | struct Resources { 55 | display: Display, 56 | timer: Timer<'static, embassy_stm32::peripherals::TIM1>, 57 | top_left: Point, 58 | velocity: Point, 59 | bmp: Bmp, 60 | brightness: Brightness, 61 | } 62 | 63 | #[init] 64 | fn init(_cx: init::Context) -> (SharedResources, Resources, init::Monotonics) { 65 | let mut config: Config = Default::default(); 66 | config.rcc.hse = Some(embassy_stm32::rcc::Hse { 67 | freq: Hertz::mhz(8), 68 | mode: embassy_stm32::rcc::HseMode::Oscillator, 69 | }); 70 | config.rcc.sys = embassy_stm32::rcc::Sysclk::PLL1_P; 71 | config.rcc.pll = Some(embassy_stm32::rcc::Pll { 72 | src: embassy_stm32::rcc::PllSource::HSE, 73 | prediv: embassy_stm32::rcc::PllPreDiv::DIV1, 74 | mul: embassy_stm32::rcc::PllMul::MUL9, // 8 * 9 = 72Mhz 75 | }); 76 | // Scale down to 36Mhz (maximum allowed) 77 | config.rcc.apb1_pre = embassy_stm32::rcc::APBPrescaler::DIV2; 78 | 79 | let p = embassy_stm32::init(config); 80 | let mut config = spi::Config::default(); 81 | config.frequency = Hertz::mhz(8); 82 | let spi = Spi::new_blocking_txonly(p.SPI1, p.PA5, p.PA7, config); 83 | 84 | let mut rst = gpio::Output::new(p.PB0, gpio::Level::Low, gpio::Speed::Low); 85 | let dc = gpio::Output::new(p.PB1, gpio::Level::Low, gpio::Speed::Low); 86 | let cs = gpio::Output::new(p.PB10, gpio::Level::Low, gpio::Speed::Low); 87 | let spi = embedded_hal_bus::spi::ExclusiveDevice::new_no_delay(spi, cs).unwrap(); 88 | 89 | let interface = display_interface_spi::SPIInterface::new(spi, dc); 90 | let mut display = Ssd1306::new(interface, DisplaySize128x64, DisplayRotation::Rotate180) 91 | .into_buffered_graphics_mode(); 92 | 93 | display 94 | .reset(&mut rst, &mut embassy_time::Delay {}) 95 | .unwrap(); 96 | display.init().unwrap(); 97 | // Forget the RST pin to keep the display out of reset 98 | core::mem::forget(rst); 99 | 100 | // Update framerate 101 | let timer = Timer::new(p.TIM1); 102 | timer.set_frequency(Hertz(20)); // 20 FPS 103 | timer.enable_update_interrupt(true); 104 | timer.start(); 105 | 106 | let bmp = Bmp::from_slice(include_bytes!("dvd.bmp")).unwrap(); 107 | 108 | // Init the static resources to use them later through RTIC 109 | ( 110 | SharedResources {}, 111 | Resources { 112 | timer, 113 | display, 114 | top_left: Point::new(5, 3), 115 | velocity: Point::new(1, 1), 116 | bmp, 117 | brightness: Brightness::default(), 118 | }, 119 | init::Monotonics(), 120 | ) 121 | } 122 | 123 | #[task(binds = TIM1_UP, local = [display, top_left, velocity, timer, bmp, brightness])] 124 | fn update(cx: update::Context) { 125 | let update::LocalResources { 126 | display, 127 | top_left, 128 | velocity, 129 | timer, 130 | bmp, 131 | brightness, 132 | .. 133 | } = cx.local; 134 | 135 | let bottom_right = *top_left + bmp.bounding_box().size; 136 | 137 | // Erase previous image position with a filled black rectangle 138 | Rectangle::with_corners(*top_left, bottom_right) 139 | .into_styled(PrimitiveStyle::with_fill(BinaryColor::Off)) 140 | .draw(display) 141 | .unwrap(); 142 | 143 | // Check if the image collided with a screen edge 144 | { 145 | let mut collision = false; 146 | if bottom_right.x > display.size().width as i32 || top_left.x < 0 { 147 | velocity.x = -velocity.x; 148 | collision = true; 149 | } 150 | 151 | if bottom_right.y > display.size().height as i32 || top_left.y < 0 { 152 | velocity.y = -velocity.y; 153 | collision = true; 154 | } 155 | 156 | if collision { 157 | // Change the brightness 158 | *brightness = match *brightness { 159 | Brightness::DIMMEST => Brightness::DIM, 160 | Brightness::DIM => Brightness::NORMAL, 161 | Brightness::NORMAL => Brightness::BRIGHT, 162 | Brightness::BRIGHT => Brightness::BRIGHTEST, 163 | Brightness::BRIGHTEST => Brightness::DIMMEST, // restart cycle 164 | _ => Brightness::NORMAL, // Brightness is not an enum, cover rest of patterns 165 | }; 166 | 167 | // Send the new brightness value to the display 168 | display.set_brightness(*brightness).unwrap(); 169 | } 170 | } 171 | 172 | // Move the image 173 | *top_left += *velocity; 174 | 175 | // Draw image at new position 176 | Image::new(bmp, *top_left) 177 | .draw(&mut display.color_converted()) 178 | .unwrap(); 179 | 180 | // Write changes to the display 181 | display.flush().unwrap(); 182 | 183 | // Clears the update flag 184 | timer.clear_update_interrupt(); 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /examples/rtic_dvd.rs: -------------------------------------------------------------------------------- 1 | //! Bounce a DVD player logo around the screen 2 | //! 3 | //! Like this, but with no color changing: https://bouncingdvdlogo.com/ 4 | //! 5 | //! For best results, run with the `--release` flag. 6 | 7 | #![no_std] 8 | #![no_main] 9 | 10 | pub mod pac { 11 | pub use embassy_stm32::pac::Interrupt as interrupt; 12 | pub use embassy_stm32::pac::*; 13 | } 14 | 15 | #[rtic::app(device = crate::pac, peripherals= false, dispatchers = [EXTI0])] 16 | mod app { 17 | use defmt_rtt as _; 18 | use display_interface_spi::SPIInterface; 19 | use embassy_stm32::{ 20 | gpio, 21 | mode::Blocking, 22 | spi::{self, Spi}, 23 | time::Hertz, 24 | timer::low_level::Timer, 25 | Config, 26 | }; 27 | use embedded_graphics::{ 28 | geometry::Point, 29 | image::Image, 30 | pixelcolor::{BinaryColor, Rgb565}, 31 | prelude::*, 32 | primitives::{PrimitiveStyle, Rectangle}, 33 | }; 34 | use panic_probe as _; 35 | use ssd1306::{mode::BufferedGraphicsMode, prelude::*, Ssd1306}; 36 | use tinybmp::Bmp; 37 | 38 | type Display = Ssd1306< 39 | SPIInterface< 40 | embedded_hal_bus::spi::ExclusiveDevice< 41 | Spi<'static, Blocking>, 42 | gpio::Output<'static>, 43 | embedded_hal_bus::spi::NoDelay, 44 | >, 45 | gpio::Output<'static>, 46 | >, 47 | DisplaySize128x64, 48 | BufferedGraphicsMode, 49 | >; 50 | 51 | #[shared] 52 | struct SharedResources {} 53 | 54 | #[local] 55 | struct Resources { 56 | display: Display, 57 | timer: Timer<'static, embassy_stm32::peripherals::TIM1>, 58 | top_left: Point, 59 | velocity: Point, 60 | bmp: Bmp, 61 | } 62 | 63 | #[init] 64 | fn init(_cx: init::Context) -> (SharedResources, Resources, init::Monotonics) { 65 | let mut config: Config = Default::default(); 66 | config.rcc.hse = Some(embassy_stm32::rcc::Hse { 67 | freq: Hertz::mhz(8), 68 | mode: embassy_stm32::rcc::HseMode::Oscillator, 69 | }); 70 | config.rcc.sys = embassy_stm32::rcc::Sysclk::PLL1_P; 71 | config.rcc.pll = Some(embassy_stm32::rcc::Pll { 72 | src: embassy_stm32::rcc::PllSource::HSE, 73 | prediv: embassy_stm32::rcc::PllPreDiv::DIV1, 74 | mul: embassy_stm32::rcc::PllMul::MUL9, // 8 * 9 = 72Mhz 75 | }); 76 | // Scale down to 36Mhz (maximum allowed) 77 | config.rcc.apb1_pre = embassy_stm32::rcc::APBPrescaler::DIV2; 78 | 79 | let p = embassy_stm32::init(config); 80 | let mut config = spi::Config::default(); 81 | config.frequency = Hertz::mhz(8); 82 | let spi = Spi::new_blocking_txonly(p.SPI1, p.PA5, p.PA7, config); 83 | 84 | let mut rst = gpio::Output::new(p.PB0, gpio::Level::Low, gpio::Speed::Low); 85 | let dc = gpio::Output::new(p.PB1, gpio::Level::Low, gpio::Speed::Low); 86 | let cs = gpio::Output::new(p.PB10, gpio::Level::Low, gpio::Speed::Low); 87 | let spi = embedded_hal_bus::spi::ExclusiveDevice::new_no_delay(spi, cs).unwrap(); 88 | 89 | let interface = display_interface_spi::SPIInterface::new(spi, dc); 90 | let mut display = Ssd1306::new(interface, DisplaySize128x64, DisplayRotation::Rotate180) 91 | .into_buffered_graphics_mode(); 92 | 93 | display 94 | .reset(&mut rst, &mut embassy_time::Delay {}) 95 | .unwrap(); 96 | display.init().unwrap(); 97 | 98 | // Forget the RST pin to keep the display out of reset 99 | core::mem::forget(rst); 100 | 101 | // Update framerate 102 | let timer = Timer::new(p.TIM1); 103 | timer.set_frequency(Hertz(20)); // 20 FPS 104 | timer.enable_update_interrupt(true); 105 | timer.start(); 106 | 107 | let bmp = Bmp::from_slice(include_bytes!("dvd.bmp")).unwrap(); 108 | 109 | // Init the static resources to use them later through RTIC 110 | ( 111 | SharedResources {}, 112 | Resources { 113 | timer, 114 | display, 115 | top_left: Point::new(5, 3), 116 | velocity: Point::new(1, 1), 117 | bmp, 118 | }, 119 | init::Monotonics(), 120 | ) 121 | } 122 | 123 | #[task(binds = TIM1_UP, local = [display, top_left, velocity, timer, bmp ])] 124 | fn update(cx: update::Context) { 125 | let update::LocalResources { 126 | display, 127 | top_left, 128 | velocity, 129 | timer, 130 | bmp, 131 | .. 132 | } = cx.local; 133 | 134 | let bottom_right = *top_left + bmp.bounding_box().size; 135 | 136 | // Erase previous image position with a filled black rectangle 137 | Rectangle::with_corners(*top_left, bottom_right) 138 | .into_styled(PrimitiveStyle::with_fill(BinaryColor::Off)) 139 | .draw(display) 140 | .unwrap(); 141 | 142 | // Check if the image collided with a screen edge 143 | { 144 | if bottom_right.x > display.size().width as i32 || top_left.x < 0 { 145 | velocity.x = -velocity.x; 146 | } 147 | 148 | if bottom_right.y > display.size().height as i32 || top_left.y < 0 { 149 | velocity.y = -velocity.y; 150 | } 151 | } 152 | 153 | // Move the image 154 | *top_left += *velocity; 155 | 156 | // Draw image at new position 157 | Image::new(bmp, *top_left) 158 | .draw(&mut display.color_converted()) 159 | .unwrap(); 160 | 161 | // Write changes to the display 162 | display.flush().unwrap(); 163 | 164 | // Clears the update flag 165 | timer.clear_update_interrupt(); 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /examples/rust.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-embedded-community/ssd1306/8e45cb20d2d60ee8c35b166893629027610cfbb8/examples/rust.bmp -------------------------------------------------------------------------------- /examples/rust.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-embedded-community/ssd1306/8e45cb20d2d60ee8c35b166893629027610cfbb8/examples/rust.png -------------------------------------------------------------------------------- /examples/rust.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-embedded-community/ssd1306/8e45cb20d2d60ee8c35b166893629027610cfbb8/examples/rust.raw -------------------------------------------------------------------------------- /examples/terminal_i2c.rs: -------------------------------------------------------------------------------- 1 | //! Endlessly fill the screen with characters from the alphabet 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 -> PB7 12 | //! (green) SCL -> PB6 13 | //! ``` 14 | //! 15 | //! Run on a Blue Pill with `cargo run --example terminal_i2c`. 16 | 17 | #![no_std] 18 | #![no_main] 19 | 20 | use core::fmt::Write; 21 | use cortex_m_rt::entry; 22 | use defmt_rtt as _; 23 | use embassy_stm32::time::Hertz; 24 | #[cfg(feature = "async")] 25 | use embassy_stm32::{bind_interrupts, i2c, peripherals}; 26 | use panic_probe as _; 27 | use ssd1306::{prelude::*, I2CDisplayInterface, Ssd1306}; 28 | 29 | #[entry] 30 | fn main() -> ! { 31 | let p = embassy_stm32::init(Default::default()); 32 | #[cfg(feature = "async")] 33 | bind_interrupts!(struct Irqs { 34 | I2C1_EV => i2c::EventInterruptHandler; 35 | I2C1_ER => i2c::ErrorInterruptHandler; 36 | }); 37 | 38 | #[cfg(feature = "async")] 39 | let i2c = embassy_stm32::i2c::I2c::new( 40 | p.I2C1, 41 | p.PB6, 42 | p.PB7, 43 | Irqs, 44 | p.DMA1_CH6, 45 | p.DMA1_CH7, 46 | Hertz::khz(400), 47 | Default::default(), 48 | ); 49 | 50 | #[cfg(not(feature = "async"))] 51 | let i2c = embassy_stm32::i2c::I2c::new_blocking( 52 | p.I2C1, 53 | p.PB6, 54 | p.PB7, 55 | Hertz::khz(400), 56 | Default::default(), 57 | ); 58 | 59 | let interface = I2CDisplayInterface::new(i2c); 60 | let mut display = 61 | Ssd1306::new(interface, DisplaySize128x64, DisplayRotation::Rotate0).into_terminal_mode(); 62 | display.init().unwrap(); 63 | let _ = display.clear(); 64 | 65 | /* Endless loop */ 66 | loop { 67 | for c in 97..123 { 68 | let _ = display.write_str(unsafe { core::str::from_utf8_unchecked(&[c]) }); 69 | } 70 | for c in 65..91 { 71 | let _ = display.write_str(unsafe { core::str::from_utf8_unchecked(&[c]) }); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /examples/text_i2c.rs: -------------------------------------------------------------------------------- 1 | //! Print "Hello world!" with "Hello rust!" underneath. Uses the `embedded_graphics` crate to draw 2 | //! the text with a 6x10 pixel font. 3 | //! 4 | //! This example is for the STM32F103 "Blue Pill" board using I2C1. 5 | //! 6 | //! Wiring connections are as follows for a CRIUS-branded display: 7 | //! 8 | //! ``` 9 | //! Display -> Blue Pill 10 | //! (black) GND -> GND 11 | //! (red) +5V -> VCC 12 | //! (yellow) SDA -> PB7 13 | //! (green) SCL -> PB6 14 | //! ``` 15 | //! 16 | //! Run on a Blue Pill with `cargo run --example text_i2c`. 17 | 18 | #![no_std] 19 | #![no_main] 20 | 21 | use cortex_m::asm::nop; 22 | use cortex_m_rt::entry; 23 | use defmt_rtt as _; 24 | use embassy_stm32::time::Hertz; 25 | #[cfg(feature = "async")] 26 | use embassy_stm32::{bind_interrupts, i2c, peripherals}; 27 | use embedded_graphics::{ 28 | mono_font::{ascii::FONT_6X10, MonoTextStyleBuilder}, 29 | pixelcolor::BinaryColor, 30 | prelude::*, 31 | text::{Baseline, Text}, 32 | }; 33 | use panic_probe as _; 34 | use ssd1306::{prelude::*, I2CDisplayInterface, Ssd1306}; 35 | 36 | #[entry] 37 | fn main() -> ! { 38 | let p = embassy_stm32::init(Default::default()); 39 | #[cfg(feature = "async")] 40 | bind_interrupts!(struct Irqs { 41 | I2C1_EV => i2c::EventInterruptHandler; 42 | I2C1_ER => i2c::ErrorInterruptHandler; 43 | }); 44 | 45 | #[cfg(feature = "async")] 46 | let i2c = embassy_stm32::i2c::I2c::new( 47 | p.I2C1, 48 | p.PB6, 49 | p.PB7, 50 | Irqs, 51 | p.DMA1_CH6, 52 | p.DMA1_CH7, 53 | Hertz::khz(400), 54 | Default::default(), 55 | ); 56 | 57 | #[cfg(not(feature = "async"))] 58 | let i2c = embassy_stm32::i2c::I2c::new_blocking( 59 | p.I2C1, 60 | p.PB6, 61 | p.PB7, 62 | Hertz::khz(400), 63 | Default::default(), 64 | ); 65 | 66 | let interface = I2CDisplayInterface::new(i2c); 67 | let mut display = Ssd1306::new(interface, DisplaySize128x64, DisplayRotation::Rotate0) 68 | .into_buffered_graphics_mode(); 69 | display.init().unwrap(); 70 | 71 | let text_style = MonoTextStyleBuilder::new() 72 | .font(&FONT_6X10) 73 | .text_color(BinaryColor::On) 74 | .build(); 75 | 76 | Text::with_baseline("Hello world!", Point::zero(), text_style, Baseline::Top) 77 | .draw(&mut display) 78 | .unwrap(); 79 | 80 | Text::with_baseline("Hello Rust!", Point::new(0, 16), text_style, Baseline::Top) 81 | .draw(&mut display) 82 | .unwrap(); 83 | 84 | display.flush().unwrap(); 85 | loop { 86 | nop() 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /readme_banner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-embedded-community/ssd1306/8e45cb20d2d60ee8c35b166893629027610cfbb8/readme_banner.jpg -------------------------------------------------------------------------------- /release.toml: -------------------------------------------------------------------------------- 1 | pre-release-replacements = [ 2 | {file="CHANGELOG.md", search="[Uu]nreleased", replace="{{version}}"}, 3 | {file="CHANGELOG.md", search="\\.\\.\\.HEAD", replace="...{{tag_name}}"}, 4 | {file="CHANGELOG.md", search="ReleaseDate", replace="{{date}}"}, 5 | {file="CHANGELOG.md", search="", replace="\n\n## [Unreleased] - ReleaseDate"}, 6 | {file="CHANGELOG.md", search="", replace="\n[Unreleased]: https://github.com/rust-embedded-community/ssd1306/compare/{{tag_name}}...HEAD"}, 7 | ] 8 | tag-message = "Release {{crate_name}} {{version}}" 9 | -------------------------------------------------------------------------------- /rustfmt.nightly.toml: -------------------------------------------------------------------------------- 1 | # Run with `cargo +nightly fmt -- --config-path ./rustfmt.nightly.toml` 2 | # This config only works in nightly. It can be used to clean up doc comments without having to do it 3 | # manually. 4 | 5 | format_code_in_doc_comments = true 6 | merge_imports = true 7 | -------------------------------------------------------------------------------- /src/brightness.rs: -------------------------------------------------------------------------------- 1 | //! Display brightness 2 | 3 | /// Struct that holds display brightness 4 | #[derive(Debug, 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 | pub const fn custom(precharge: u8, contrast: u8) -> Self { 43 | debug_assert!( 44 | 0 < precharge && precharge <= 15, 45 | "Precharge value must be between 1 and 15" 46 | ); 47 | 48 | Self { 49 | precharge, 50 | contrast, 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/command.rs: -------------------------------------------------------------------------------- 1 | //! Display commands. 2 | 3 | // Shamefully taken from https://github.com/EdgewaterDevelopment/rust-ssd1306 4 | 5 | #[cfg(feature = "async")] 6 | use display_interface::AsyncWriteOnlyDataCommand; 7 | use display_interface::{DataFormat::U8, DisplayError, WriteOnlyDataCommand}; 8 | 9 | /// SSD1306 Commands 10 | #[maybe_async_cfg::maybe(sync(keep_self), async(feature = "async"))] 11 | #[derive(Debug, Copy, Clone)] 12 | pub enum Command { 13 | /// Set contrast. Higher number is higher contrast. Default = 0x7F 14 | Contrast(u8), 15 | /// Turn entire display on. If set, all pixels will 16 | /// be set to on, if not, the value in memory will be used. 17 | AllOn(bool), 18 | /// Invert display. 19 | Invert(bool), 20 | /// Turn display on or off. 21 | DisplayOn(bool), 22 | /// Set up horizontal scrolling. 23 | /// Values are scroll direction, start page, end page, 24 | /// and number of frames per step. 25 | HScrollSetup(HScrollDir, Page, Page, NFrames), 26 | /// Set up horizontal + vertical scrolling. 27 | /// Values are scroll direction, start page, end page, 28 | /// number of frames per step, and vertical scrolling offset. 29 | /// Scrolling offset may be from 0-63 30 | VHScrollSetup(VHScrollDir, Page, Page, NFrames, u8), 31 | /// Enable scrolling 32 | EnableScroll(bool), 33 | /// Setup vertical scroll area. 34 | /// Values are number of rows above scroll area (0-63) 35 | /// and number of rows of scrolling. (0-64) 36 | VScrollArea(u8, u8), 37 | /// Set the lower nibble of the column start address 38 | /// register for Page addressing mode, using the lower 39 | /// 4 bits given. 40 | /// This is only for page addressing mode 41 | LowerColStart(u8), 42 | /// Set the upper nibble of the column start address 43 | /// register for Page addressing mode, using the lower 44 | /// 4 bits given. 45 | /// This is only for page addressing mode 46 | UpperColStart(u8), 47 | /// Set the column start address register for Page addressing mode. 48 | /// Combines LowerColStart and UpperColStart 49 | /// This is only for page addressing mode 50 | ColStart(u8), 51 | /// Set addressing mode 52 | AddressMode(AddrMode), 53 | /// Setup column start and end address 54 | /// values range from 0-127 55 | /// This is only for horizontal or vertical addressing mode 56 | ColumnAddress(u8, u8), 57 | /// Setup page start and end address 58 | /// This is only for horizontal or vertical addressing mode 59 | PageAddress(Page, Page), 60 | /// Set GDDRAM page start address for Page addressing mode 61 | PageStart(Page), 62 | /// Set display start line from 0-63 63 | StartLine(u8), 64 | /// Reverse columns from 127-0 65 | SegmentRemap(bool), 66 | /// Set multiplex ratio from 15-63 (MUX-1) 67 | Multiplex(u8), 68 | /// Scan from COM[n-1] to COM0 (where N is mux ratio) 69 | ReverseComDir(bool), 70 | /// Set vertical shift 71 | DisplayOffset(u8), 72 | /// Setup com hardware configuration 73 | /// First value indicates sequential (false) or alternative (true) 74 | /// pin configuration. Second value disables (false) or enables (true) 75 | /// left/right remap. 76 | ComPinConfig(bool, bool), 77 | /// Set up display clock. 78 | /// First value is oscillator frequency, increasing with higher value 79 | /// Second value is divide ratio - 1 80 | DisplayClockDiv(u8, u8), 81 | /// Set up phase 1 and 2 of precharge period. Each value must be in the range 1 - 15. 82 | PreChargePeriod(u8, u8), 83 | /// Set Vcomh Deselect level 84 | VcomhDeselect(VcomhLevel), 85 | /// NOOP 86 | Noop, 87 | /// Enable charge pump 88 | ChargePump(bool), 89 | /// Select external or internal I REF. Only for 72 x 40 display with SSD1306B driver 90 | InternalIref(bool, bool), 91 | } 92 | 93 | #[maybe_async_cfg::maybe( 94 | sync(keep_self), 95 | async( 96 | feature = "async", 97 | idents(WriteOnlyDataCommand(async = "AsyncWriteOnlyDataCommand")) 98 | ) 99 | )] 100 | impl Command { 101 | /// Send command to SSD1306 102 | pub async fn send(self, iface: &mut DI) -> Result<(), DisplayError> 103 | where 104 | DI: WriteOnlyDataCommand, 105 | { 106 | match self { 107 | Command::Contrast(val) => Self::send_commands(iface, &[0x81, val]).await, 108 | Command::AllOn(on) => Self::send_commands(iface, &[0xA4 | (on as u8)]).await, 109 | Command::Invert(inv) => Self::send_commands(iface, &[0xA6 | (inv as u8)]).await, 110 | Command::DisplayOn(on) => Self::send_commands(iface, &[0xAE | (on as u8)]).await, 111 | Command::HScrollSetup(dir, start, end, rate) => { 112 | Self::send_commands( 113 | iface, 114 | &[ 115 | 0x26 | (dir as u8), 116 | 0, 117 | start as u8, 118 | rate as u8, 119 | end as u8, 120 | 0, 121 | 0xFF, 122 | ], 123 | ) 124 | .await 125 | } 126 | Command::VHScrollSetup(dir, start, end, rate, offset) => { 127 | Self::send_commands( 128 | iface, 129 | &[ 130 | 0x28 | (dir as u8), 131 | 0, 132 | start as u8, 133 | rate as u8, 134 | end as u8, 135 | offset, 136 | ], 137 | ) 138 | .await 139 | } 140 | Command::EnableScroll(en) => Self::send_commands(iface, &[0x2E | (en as u8)]).await, 141 | Command::VScrollArea(above, lines) => { 142 | Self::send_commands(iface, &[0xA3, above, lines]).await 143 | } 144 | Command::LowerColStart(addr) => Self::send_commands(iface, &[0xF & addr]).await, 145 | Command::UpperColStart(addr) => { 146 | Self::send_commands(iface, &[0x10 | (0xF & addr)]).await 147 | } 148 | Command::ColStart(addr) => { 149 | Self::send_commands(iface, &[0xF & addr, 0x10 | (0xF & (addr >> 4))]).await 150 | } 151 | Command::AddressMode(mode) => Self::send_commands(iface, &[0x20, mode as u8]).await, 152 | Command::ColumnAddress(start, end) => { 153 | Self::send_commands(iface, &[0x21, start, end]).await 154 | } 155 | Command::PageAddress(start, end) => { 156 | Self::send_commands(iface, &[0x22, start as u8, end as u8]).await 157 | } 158 | Command::PageStart(page) => Self::send_commands(iface, &[0xB0 | (page as u8)]).await, 159 | Command::StartLine(line) => Self::send_commands(iface, &[0x40 | (0x3F & line)]).await, 160 | Command::SegmentRemap(remap) => { 161 | Self::send_commands(iface, &[0xA0 | (remap as u8)]).await 162 | } 163 | Command::Multiplex(ratio) => Self::send_commands(iface, &[0xA8, ratio]).await, 164 | Command::ReverseComDir(rev) => { 165 | Self::send_commands(iface, &[0xC0 | ((rev as u8) << 3)]).await 166 | } 167 | Command::DisplayOffset(offset) => Self::send_commands(iface, &[0xD3, offset]).await, 168 | Command::ComPinConfig(alt, lr) => { 169 | Self::send_commands(iface, &[0xDA, 0x2 | ((alt as u8) << 4) | ((lr as u8) << 5)]) 170 | .await 171 | } 172 | Command::DisplayClockDiv(fosc, div) => { 173 | Self::send_commands(iface, &[0xD5, ((0xF & fosc) << 4) | (0xF & div)]).await 174 | } 175 | Command::PreChargePeriod(phase1, phase2) => { 176 | Self::send_commands(iface, &[0xD9, ((0xF & phase2) << 4) | (0xF & phase1)]).await 177 | } 178 | Command::VcomhDeselect(level) => { 179 | Self::send_commands(iface, &[0xDB, (level as u8) << 4]).await 180 | } 181 | Command::Noop => Self::send_commands(iface, &[0xE3]).await, 182 | Command::ChargePump(en) => { 183 | Self::send_commands(iface, &[0x8D, 0x10 | ((en as u8) << 2)]).await 184 | } 185 | Command::InternalIref(en, current) => { 186 | Self::send_commands(iface, &[0xAD, ((current as u8) << 5) | ((en as u8) << 4)]) 187 | .await 188 | } 189 | } 190 | } 191 | 192 | async fn send_commands(iface: &mut DI, data: &[u8]) -> Result<(), DisplayError> 193 | where 194 | DI: WriteOnlyDataCommand, 195 | { 196 | iface.send_commands(U8(data)).await 197 | } 198 | } 199 | 200 | /// Horizontal Scroll Direction 201 | #[derive(Debug, Clone, Copy)] 202 | #[allow(dead_code)] 203 | pub enum HScrollDir { 204 | /// Left to right 205 | LeftToRight = 0, 206 | /// Right to left 207 | RightToLeft = 1, 208 | } 209 | 210 | /// Vertical and horizontal scroll dir 211 | #[derive(Debug, Clone, Copy)] 212 | #[allow(dead_code)] 213 | pub enum VHScrollDir { 214 | /// Vertical and right horizontal 215 | VerticalRight = 0b01, 216 | /// Vertical and left horizontal 217 | VerticalLeft = 0b10, 218 | } 219 | 220 | /// Display page 221 | #[derive(Debug, Clone, Copy)] 222 | pub enum Page { 223 | /// Page 0 224 | Page0 = 0b0000, 225 | /// Page 1 226 | Page1 = 0b0001, 227 | /// Page 2 228 | Page2 = 0b0010, 229 | /// Page 3 230 | Page3 = 0b0011, 231 | /// Page 4 232 | Page4 = 0b0100, 233 | /// Page 5 234 | Page5 = 0b0101, 235 | /// Page 6 236 | Page6 = 0b0110, 237 | /// Page 7 238 | Page7 = 0b0111, 239 | /// Page 8 240 | Page8 = 0b1000, 241 | /// Page 9 242 | Page9 = 0b1001, 243 | /// Page 10 244 | Page10 = 0b1010, 245 | /// Page 11 246 | Page11 = 0b1011, 247 | /// Page 12 248 | Page12 = 0b1100, 249 | /// Page 13 250 | Page13 = 0b1101, 251 | /// Page 14 252 | Page14 = 0b1110, 253 | /// Page 15 254 | Page15 = 0b1111, 255 | } 256 | 257 | impl From for Page { 258 | fn from(val: u8) -> Page { 259 | match val / 8 { 260 | 0 => Page::Page0, 261 | 1 => Page::Page1, 262 | 2 => Page::Page2, 263 | 3 => Page::Page3, 264 | 4 => Page::Page4, 265 | 5 => Page::Page5, 266 | 6 => Page::Page6, 267 | 7 => Page::Page7, 268 | 8 => Page::Page8, 269 | 9 => Page::Page9, 270 | 10 => Page::Page10, 271 | 11 => Page::Page11, 272 | 12 => Page::Page12, 273 | 13 => Page::Page13, 274 | 14 => Page::Page14, 275 | 15 => Page::Page15, 276 | _ => panic!("Page too high"), 277 | } 278 | } 279 | } 280 | 281 | /// Frame interval 282 | #[derive(Debug, Clone, Copy)] 283 | #[allow(dead_code)] 284 | pub enum NFrames { 285 | /// 2 Frames 286 | F2 = 0b111, 287 | /// 3 Frames 288 | F3 = 0b100, 289 | /// 4 Frames 290 | F4 = 0b101, 291 | /// 5 Frames 292 | F5 = 0b000, 293 | /// 25 Frames 294 | F25 = 0b110, 295 | /// 64 Frames 296 | F64 = 0b001, 297 | /// 128 Frames 298 | F128 = 0b010, 299 | /// 256 Frames 300 | F256 = 0b011, 301 | } 302 | 303 | /// Address mode 304 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 305 | #[allow(dead_code)] 306 | pub enum AddrMode { 307 | /// Horizontal mode 308 | Horizontal = 0b00, 309 | /// Vertical mode 310 | Vertical = 0b01, 311 | /// Page mode (default) 312 | Page = 0b10, 313 | } 314 | 315 | /// Vcomh Deselect level 316 | #[derive(Debug, Clone, Copy)] 317 | #[allow(dead_code)] 318 | pub enum VcomhLevel { 319 | /// 0.65 * Vcc 320 | V065 = 0b001, 321 | /// 0.77 * Vcc 322 | V077 = 0b010, 323 | /// 0.83 * Vcc 324 | V083 = 0b011, 325 | /// Auto 326 | Auto = 0b100, 327 | } 328 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | /// Errors in this crate 2 | #[derive(Debug)] 3 | pub enum Error { 4 | /// Communication error 5 | Comm(CommE), 6 | /// Pin setting error 7 | Pin(PinE), 8 | } 9 | -------------------------------------------------------------------------------- /src/i2c_interface.rs: -------------------------------------------------------------------------------- 1 | //! I2C interface factory 2 | 3 | use display_interface_i2c::I2CInterface; 4 | 5 | /// Helper struct to create preconfigured I2C interfaces for the display. 6 | #[derive(Debug, Copy, Clone)] 7 | pub struct I2CDisplayInterface(()); 8 | 9 | impl I2CDisplayInterface { 10 | /// Create new builder with a default I2C address of 0x3C 11 | #[cfg(not(feature = "async"))] 12 | #[allow(clippy::new_ret_no_self)] 13 | // pub fn with_i2c(i2c: I) -> I2CInterface // alternative, but breaking change 14 | pub fn new(i2c: I) -> I2CInterface 15 | where 16 | I: embedded_hal::i2c::I2c, 17 | { 18 | Self::new_custom_address(i2c, 0x3C) 19 | } 20 | #[cfg(feature = "async")] 21 | #[allow(clippy::new_ret_no_self)] 22 | /// Create a new async I2C interface with the address 0x3C 23 | pub fn new(i2c: I) -> I2CInterface 24 | where 25 | I: embedded_hal_async::i2c::I2c, 26 | { 27 | Self::new_custom_address(i2c, 0x3C) 28 | } 29 | 30 | #[cfg(not(feature = "async"))] 31 | /// Create a new I2C interface with the alternate address 0x3D as specified in the datasheet. 32 | pub fn new_alternate_address(i2c: I) -> I2CInterface 33 | where 34 | I: embedded_hal::i2c::I2c, 35 | { 36 | Self::new_custom_address(i2c, 0x3D) 37 | } 38 | 39 | #[cfg(feature = "async")] 40 | /// Create a new async I2C interface with the alternate address 0x3D as specified in the datasheet. 41 | pub fn new_alternate_address(i2c: I) -> I2CInterface 42 | where 43 | I: embedded_hal_async::i2c::I2c, 44 | { 45 | Self::new_custom_address(i2c, 0x3D) 46 | } 47 | 48 | #[cfg(not(feature = "async"))] 49 | /// Create a new I2C interface with a custom address. 50 | pub fn new_custom_address(i2c: I, address: u8) -> I2CInterface 51 | where 52 | I: embedded_hal::i2c::I2c, 53 | { 54 | I2CInterface::new(i2c, address, 0x40) 55 | } 56 | #[cfg(feature = "async")] 57 | /// Create a new async I2C interface with a custom address. 58 | pub fn new_custom_address(i2c: I, address: u8) -> I2CInterface 59 | where 60 | I: embedded_hal_async::i2c::I2c, 61 | { 62 | I2CInterface::new(i2c, address, 0x40) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! SSD1306 OLED display driver. 2 | //! 3 | //! This crate provides a driver interface to the popular SSD1306 monochrome OLED display driver. It 4 | //! supports I2C and SPI via the [`display_interface`](https://docs.rs/display_interface) crate. 5 | //! 6 | //! The main driver is created using [`Ssd1306::new`] which accepts an interface instance, display 7 | //! size, rotation and mode. The following display modes are supported: 8 | //! 9 | //! - [`BasicMode`] - A simple mode with lower level methods available. 10 | //! - [`BufferedGraphicsMode`] - A framebuffered mode with additional methods and integration with 11 | //! [embedded-graphics](https://docs.rs/embedded-graphics). 12 | //! - [`TerminalMode`] - A bufferless mode supporting drawing text to the display, as well as 13 | //! setting cursor positions like a simple terminal. 14 | //! 15 | //! # Examples 16 | //! 17 | //! Examples can be found in [the examples/ 18 | //! folder](https://github.com/rust-embedded-community/ssd1306/blob/master/examples) 19 | //! 20 | //! ## Draw some text to the display 21 | //! 22 | //! Uses [`BufferedGraphicsMode`] and [embedded_graphics](https://docs.rs/embedded-graphics). [See 23 | //! the complete example 24 | //! here](https://github.com/rust-embedded-community/ssd1306/blob/master/examples/text_i2c.rs). 25 | //! 26 | //! ```rust 27 | //! # use ssd1306::test_helpers::I2cStub; 28 | //! # let i2c = I2cStub; 29 | //! use embedded_graphics::{ 30 | //! mono_font::{ascii::FONT_6X10, MonoTextStyleBuilder}, 31 | //! pixelcolor::BinaryColor, 32 | //! prelude::*, 33 | //! text::{Baseline, Text}, 34 | //! }; 35 | //! use ssd1306::{mode::BufferedGraphicsMode, prelude::*, I2CDisplayInterface, Ssd1306}; 36 | //! 37 | //! let interface = I2CDisplayInterface::new(i2c); 38 | //! let mut display = Ssd1306::new( 39 | //! interface, 40 | //! DisplaySize128x64, 41 | //! DisplayRotation::Rotate0, 42 | //! ).into_buffered_graphics_mode(); 43 | //! display.init().unwrap(); 44 | //! 45 | //! let text_style = MonoTextStyleBuilder::new() 46 | //! .font(&FONT_6X10) 47 | //! .text_color(BinaryColor::On) 48 | //! .build(); 49 | //! 50 | //! Text::with_baseline("Hello world!", Point::zero(), text_style, Baseline::Top) 51 | //! .draw(&mut display) 52 | //! .unwrap(); 53 | //! 54 | //! Text::with_baseline("Hello Rust!", Point::new(0, 16), text_style, Baseline::Top) 55 | //! .draw(&mut display) 56 | //! .unwrap(); 57 | //! 58 | //! display.flush().unwrap(); 59 | //! ``` 60 | //! 61 | //! ## Write text to the display without a framebuffer 62 | //! 63 | //! Uses [`TerminalMode`]. [See the complete example 64 | //! here](https://github.com/rust-embedded-community/ssd1306/blob/master/examples/terminal_i2c.rs). 65 | //! 66 | //! ```rust 67 | //! # use ssd1306::test_helpers::I2cStub; 68 | //! # let i2c = I2cStub; 69 | //! use core::fmt::Write; 70 | //! use ssd1306::{mode::TerminalMode, prelude::*, I2CDisplayInterface, Ssd1306}; 71 | //! 72 | //! let interface = I2CDisplayInterface::new(i2c); 73 | //! 74 | //! let mut display = Ssd1306::new( 75 | //! interface, 76 | //! DisplaySize128x64, 77 | //! DisplayRotation::Rotate0, 78 | //! ).into_terminal_mode(); 79 | //! display.init().unwrap(); 80 | //! display.clear().unwrap(); 81 | //! 82 | //! // Spam some characters to the display 83 | //! for c in 97..123 { 84 | //! let _ = display.write_str(unsafe { core::str::from_utf8_unchecked(&[c]) }); 85 | //! } 86 | //! for c in 65..91 { 87 | //! let _ = display.write_str(unsafe { core::str::from_utf8_unchecked(&[c]) }); 88 | //! } 89 | //! 90 | //! // The `write!()` macro is also supported 91 | //! write!(display, "Hello, {}", "world"); 92 | //! ``` 93 | //! 94 | //! [featureset]: https://github.com/rust-embedded-community/embedded-graphics#features 95 | //! [`BufferedGraphicsMode`]: crate::mode::BufferedGraphicsMode 96 | //! [`TerminalMode`]: crate::mode::TerminalMode 97 | 98 | #![no_std] 99 | #![deny(missing_debug_implementations)] 100 | #![deny(missing_docs)] 101 | #![deny(warnings)] 102 | #![deny(missing_copy_implementations)] 103 | #![deny(trivial_casts)] 104 | #![deny(trivial_numeric_casts)] 105 | #![deny(unsafe_code)] 106 | #![deny(unstable_features)] 107 | #![deny(unused_import_braces)] 108 | #![deny(unused_qualifications)] 109 | #![deny(rustdoc::broken_intra_doc_links)] 110 | #![allow(async_fn_in_trait)] 111 | 112 | mod brightness; 113 | pub mod command; 114 | mod error; 115 | mod i2c_interface; 116 | pub mod mode; 117 | pub mod prelude; 118 | pub mod rotation; 119 | pub mod size; 120 | #[doc(hidden)] 121 | pub mod test_helpers; 122 | 123 | use core::convert::Infallible; 124 | 125 | pub use crate::i2c_interface::I2CDisplayInterface; 126 | use crate::mode::BasicMode; 127 | use brightness::Brightness; 128 | #[cfg(feature = "async")] 129 | use command::CommandAsync; 130 | use command::{AddrMode, Command, VcomhLevel}; 131 | #[cfg(feature = "async")] 132 | use display_interface::AsyncWriteOnlyDataCommand; 133 | use display_interface::{DataFormat::U8, DisplayError, WriteOnlyDataCommand}; 134 | use embedded_hal::{delay::DelayNs, digital::OutputPin}; 135 | #[cfg(feature = "async")] 136 | use embedded_hal_async::delay::DelayNs as DelayNsAsync; 137 | use error::Error; 138 | use mode::{BufferedGraphicsMode, TerminalMode}; 139 | #[cfg(feature = "async")] 140 | use mode::{BufferedGraphicsModeAsync, TerminalModeAsync}; 141 | use rotation::DisplayRotation; 142 | use size::DisplaySize; 143 | #[cfg(feature = "async")] 144 | use size::DisplaySizeAsync; 145 | 146 | /// SSD1306 driver. 147 | /// 148 | /// Note that some methods are only available when the display is configured in a certain [`mode`]. 149 | #[maybe_async_cfg::maybe(sync(keep_self), async(feature = "async"))] 150 | #[derive(Copy, Clone, Debug)] 151 | pub struct Ssd1306 { 152 | interface: DI, 153 | mode: MODE, 154 | size: SIZE, 155 | addr_mode: AddrMode, 156 | rotation: DisplayRotation, 157 | } 158 | 159 | #[maybe_async_cfg::maybe( 160 | sync(keep_self,), 161 | async(feature = "async", idents(DisplaySize(async = "DisplaySizeAsync"))) 162 | )] 163 | impl Ssd1306 164 | where 165 | SIZE: DisplaySize, 166 | { 167 | /// Create a basic SSD1306 interface. 168 | /// 169 | /// Use the `into_*_mode` methods to enable more functionality. 170 | pub fn new(interface: DI, size: SIZE, rotation: DisplayRotation) -> Self { 171 | Self { 172 | interface, 173 | size, 174 | addr_mode: AddrMode::Page, 175 | mode: BasicMode, 176 | rotation, 177 | } 178 | } 179 | } 180 | 181 | #[maybe_async_cfg::maybe( 182 | sync(keep_self,), 183 | async( 184 | feature = "async", 185 | idents( 186 | DisplaySize(async = "DisplaySizeAsync"), 187 | BufferedGraphicsMode(async = "BufferedGraphicsModeAsync"), 188 | TerminalMode(async = "TerminalModeAsync"), 189 | ) 190 | ) 191 | )] 192 | impl Ssd1306 193 | where 194 | SIZE: DisplaySize, 195 | { 196 | /// Convert the display into another interface mode. 197 | fn into_mode(self, mode: MODE2) -> Ssd1306 { 198 | Ssd1306 { 199 | mode, 200 | addr_mode: self.addr_mode, 201 | interface: self.interface, 202 | size: self.size, 203 | rotation: self.rotation, 204 | } 205 | } 206 | 207 | /// Convert the display into a buffered graphics mode, supporting 208 | /// [embedded-graphics](https://crates.io/crates/embedded-graphics). 209 | /// 210 | /// See [`BufferedGraphicsMode`] for more information. 211 | pub fn into_buffered_graphics_mode(self) -> Ssd1306> { 212 | self.into_mode(BufferedGraphicsMode::new()) 213 | } 214 | 215 | /// Convert the display into a text-only, terminal-like mode. 216 | /// 217 | /// See [`TerminalMode`] for more information. 218 | pub fn into_terminal_mode(self) -> Ssd1306 { 219 | self.into_mode(TerminalMode::new()) 220 | } 221 | } 222 | 223 | #[maybe_async_cfg::maybe( 224 | sync(keep_self), 225 | async( 226 | feature = "async", 227 | idents( 228 | Command(async = "CommandAsync"), 229 | DisplaySize(async = "DisplaySizeAsync"), 230 | WriteOnlyDataCommand(async = "AsyncWriteOnlyDataCommand"), 231 | ) 232 | ) 233 | )] 234 | impl Ssd1306 235 | where 236 | DI: WriteOnlyDataCommand, 237 | SIZE: DisplaySize, 238 | { 239 | /// Initialise the display in one of the available addressing modes. 240 | pub async fn init_with_addr_mode(&mut self, mode: AddrMode) -> Result<(), DisplayError> { 241 | let rotation = self.rotation; 242 | 243 | Command::DisplayOn(false).send(&mut self.interface).await?; 244 | Command::DisplayClockDiv(0x8, 0x0) 245 | .send(&mut self.interface) 246 | .await?; 247 | Command::Multiplex(SIZE::HEIGHT - 1) 248 | .send(&mut self.interface) 249 | .await?; 250 | Command::DisplayOffset(0).send(&mut self.interface).await?; 251 | Command::StartLine(0).send(&mut self.interface).await?; 252 | // TODO: Ability to turn charge pump on/off 253 | Command::ChargePump(true).send(&mut self.interface).await?; 254 | Command::AddressMode(mode).send(&mut self.interface).await?; 255 | 256 | self.size.configure(&mut self.interface).await?; 257 | self.set_rotation(rotation).await?; 258 | 259 | self.set_brightness(Brightness::default()).await?; 260 | Command::VcomhDeselect(VcomhLevel::Auto) 261 | .send(&mut self.interface) 262 | .await?; 263 | Command::AllOn(false).send(&mut self.interface).await?; 264 | Command::Invert(false).send(&mut self.interface).await?; 265 | Command::EnableScroll(false) 266 | .send(&mut self.interface) 267 | .await?; 268 | Command::DisplayOn(true).send(&mut self.interface).await?; 269 | 270 | self.addr_mode = mode; 271 | 272 | Ok(()) 273 | } 274 | 275 | /// Change the addressing mode 276 | pub async fn set_addr_mode(&mut self, mode: AddrMode) -> Result<(), DisplayError> { 277 | Command::AddressMode(mode).send(&mut self.interface).await?; 278 | self.addr_mode = mode; 279 | Ok(()) 280 | } 281 | 282 | /// Send the data to the display for drawing at the current position in the framebuffer 283 | /// and advance the position accordingly. Cf. `set_draw_area` to modify the affected area by 284 | /// this method. 285 | /// 286 | /// This method takes advantage of a bounding box for faster writes. 287 | pub async fn bounded_draw( 288 | &mut self, 289 | buffer: &[u8], 290 | disp_width: usize, 291 | upper_left: (u8, u8), 292 | lower_right: (u8, u8), 293 | ) -> Result<(), DisplayError> { 294 | Self::flush_buffer_chunks( 295 | &mut self.interface, 296 | buffer, 297 | disp_width, 298 | upper_left, 299 | lower_right, 300 | ) 301 | .await 302 | } 303 | 304 | /// Send a raw buffer to the display. 305 | pub async fn draw(&mut self, buffer: &[u8]) -> Result<(), DisplayError> { 306 | self.interface.send_data(U8(buffer)).await 307 | } 308 | 309 | /// Get display dimensions, taking into account the current rotation of the display 310 | /// 311 | /// ```rust 312 | /// # use ssd1306::test_helpers::StubInterface; 313 | /// # let interface = StubInterface; 314 | /// use ssd1306::{mode::TerminalMode, prelude::*, Ssd1306}; 315 | /// 316 | /// let mut display = Ssd1306::new( 317 | /// interface, 318 | /// DisplaySize128x64, 319 | /// DisplayRotation::Rotate0, 320 | /// ).into_terminal_mode(); 321 | /// assert_eq!(display.dimensions(), (128, 64)); 322 | /// 323 | /// # let interface = StubInterface; 324 | /// let mut rotated_display = Ssd1306::new( 325 | /// interface, 326 | /// DisplaySize128x64, 327 | /// DisplayRotation::Rotate90, 328 | /// ).into_terminal_mode(); 329 | /// assert_eq!(rotated_display.dimensions(), (64, 128)); 330 | /// ``` 331 | pub fn dimensions(&self) -> (u8, u8) { 332 | match self.rotation { 333 | DisplayRotation::Rotate0 | DisplayRotation::Rotate180 => (SIZE::WIDTH, SIZE::HEIGHT), 334 | DisplayRotation::Rotate90 | DisplayRotation::Rotate270 => (SIZE::HEIGHT, SIZE::WIDTH), 335 | } 336 | } 337 | 338 | /// Get the display rotation. 339 | pub fn rotation(&self) -> DisplayRotation { 340 | self.rotation 341 | } 342 | 343 | /// Set the display rotation. 344 | pub async fn set_rotation(&mut self, rotation: DisplayRotation) -> Result<(), DisplayError> { 345 | self.rotation = rotation; 346 | 347 | match rotation { 348 | DisplayRotation::Rotate0 => { 349 | Command::SegmentRemap(true) 350 | .send(&mut self.interface) 351 | .await?; 352 | Command::ReverseComDir(true) 353 | .send(&mut self.interface) 354 | .await?; 355 | } 356 | DisplayRotation::Rotate90 => { 357 | Command::SegmentRemap(false) 358 | .send(&mut self.interface) 359 | .await?; 360 | Command::ReverseComDir(true) 361 | .send(&mut self.interface) 362 | .await?; 363 | } 364 | DisplayRotation::Rotate180 => { 365 | Command::SegmentRemap(false) 366 | .send(&mut self.interface) 367 | .await?; 368 | Command::ReverseComDir(false) 369 | .send(&mut self.interface) 370 | .await?; 371 | } 372 | DisplayRotation::Rotate270 => { 373 | Command::SegmentRemap(true) 374 | .send(&mut self.interface) 375 | .await?; 376 | Command::ReverseComDir(false) 377 | .send(&mut self.interface) 378 | .await?; 379 | } 380 | }; 381 | 382 | Ok(()) 383 | } 384 | 385 | /// Set mirror enabled/disabled. 386 | pub async fn set_mirror(&mut self, mirror: bool) -> Result<(), DisplayError> { 387 | if mirror { 388 | match self.rotation { 389 | DisplayRotation::Rotate0 => { 390 | Command::SegmentRemap(false) 391 | .send(&mut self.interface) 392 | .await?; 393 | Command::ReverseComDir(true) 394 | .send(&mut self.interface) 395 | .await?; 396 | } 397 | DisplayRotation::Rotate90 => { 398 | Command::SegmentRemap(false) 399 | .send(&mut self.interface) 400 | .await?; 401 | Command::ReverseComDir(false) 402 | .send(&mut self.interface) 403 | .await?; 404 | } 405 | DisplayRotation::Rotate180 => { 406 | Command::SegmentRemap(true) 407 | .send(&mut self.interface) 408 | .await?; 409 | Command::ReverseComDir(false) 410 | .send(&mut self.interface) 411 | .await?; 412 | } 413 | DisplayRotation::Rotate270 => { 414 | Command::SegmentRemap(true) 415 | .send(&mut self.interface) 416 | .await?; 417 | Command::ReverseComDir(true) 418 | .send(&mut self.interface) 419 | .await?; 420 | } 421 | }; 422 | } else { 423 | self.set_rotation(self.rotation).await?; 424 | } 425 | Ok(()) 426 | } 427 | 428 | /// Change the display brightness. 429 | pub async fn set_brightness(&mut self, brightness: Brightness) -> Result<(), DisplayError> { 430 | Command::PreChargePeriod(1, brightness.precharge) 431 | .send(&mut self.interface) 432 | .await?; 433 | Command::Contrast(brightness.contrast) 434 | .send(&mut self.interface) 435 | .await 436 | } 437 | 438 | /// Turn the display on or off. The display can be drawn to and retains all 439 | /// of its memory even while off. 440 | pub async fn set_display_on(&mut self, on: bool) -> Result<(), DisplayError> { 441 | Command::DisplayOn(on).send(&mut self.interface).await 442 | } 443 | 444 | /// Set the position in the framebuffer of the display limiting where any sent data should be 445 | /// drawn. This method can be used for changing the affected area on the screen as well 446 | /// as (re-)setting the start point of the next `draw` call. 447 | pub async fn set_draw_area( 448 | &mut self, 449 | start: (u8, u8), 450 | end: (u8, u8), 451 | ) -> Result<(), DisplayError> { 452 | Command::ColumnAddress(start.0, end.0.saturating_sub(1)) 453 | .send(&mut self.interface) 454 | .await?; 455 | 456 | if self.addr_mode != AddrMode::Page { 457 | Command::PageAddress(start.1.into(), (end.1.saturating_sub(1)).into()) 458 | .send(&mut self.interface) 459 | .await?; 460 | } 461 | 462 | Ok(()) 463 | } 464 | 465 | /// Set the column address in the framebuffer of the display where any sent data should be 466 | /// drawn. 467 | pub async fn set_column(&mut self, column: u8) -> Result<(), DisplayError> { 468 | Command::ColStart(column).send(&mut self.interface).await 469 | } 470 | 471 | /// Set the page address (row 8px high) in the framebuffer of the display where any sent data 472 | /// should be drawn. 473 | /// 474 | /// Note that the parameter is in pixels, but the page will be set to the start of the 8px 475 | /// row which contains the passed-in row. 476 | pub async fn set_row(&mut self, row: u8) -> Result<(), DisplayError> { 477 | Command::PageStart(row.into()) 478 | .send(&mut self.interface) 479 | .await 480 | } 481 | 482 | /// Set the screen pixel on/off inversion 483 | pub async fn set_invert(&mut self, invert: bool) -> Result<(), DisplayError> { 484 | Command::Invert(invert).send(&mut self.interface).await 485 | } 486 | 487 | async fn flush_buffer_chunks( 488 | interface: &mut DI, 489 | buffer: &[u8], 490 | disp_width: usize, 491 | upper_left: (u8, u8), 492 | lower_right: (u8, u8), 493 | ) -> Result<(), DisplayError> { 494 | // Divide by 8 since each row is actually 8 pixels tall 495 | let num_pages = ((lower_right.1 - upper_left.1) / 8) as usize + 1; 496 | 497 | // Each page is 8 bits tall, so calculate which page number to start at (rounded down) from 498 | // the top of the display 499 | let starting_page = (upper_left.1 / 8) as usize; 500 | 501 | // Calculate start and end X coordinates for each page 502 | let page_lower = upper_left.0 as usize; 503 | let page_upper = lower_right.0 as usize; 504 | 505 | for c in buffer 506 | .chunks(disp_width) 507 | .skip(starting_page) 508 | .take(num_pages) 509 | .map(|s| &s[page_lower..page_upper]) 510 | { 511 | interface.send_data(U8(c)).await? 512 | } 513 | Ok(()) 514 | } 515 | 516 | /// Release the contained interface. 517 | pub fn release(self) -> DI { 518 | self.interface 519 | } 520 | } 521 | 522 | // SPI-only reset 523 | #[maybe_async_cfg::maybe( 524 | sync(keep_self), 525 | async( 526 | feature = "async", 527 | idents( 528 | Command(async = "CommandAsync"), 529 | DisplaySize(async = "DisplaySizeAsync"), 530 | WriteOnlyDataCommand(async = "AsyncWriteOnlyDataCommand"), 531 | DelayNs(async = "DelayNsAsync") 532 | ) 533 | ) 534 | )] 535 | impl Ssd1306 { 536 | /// Reset the display. 537 | pub async fn reset( 538 | &mut self, 539 | rst: &mut RST, 540 | delay: &mut DELAY, 541 | ) -> Result<(), Error> 542 | where 543 | RST: OutputPin, 544 | DELAY: DelayNs, 545 | { 546 | async fn inner_reset(rst: &mut RST, delay: &mut DELAY) -> Result<(), RST::Error> 547 | where 548 | RST: OutputPin, 549 | DELAY: DelayNs, 550 | { 551 | rst.set_high()?; 552 | delay.delay_ms(1).await; 553 | rst.set_low()?; 554 | delay.delay_ms(10).await; 555 | rst.set_high() 556 | } 557 | 558 | inner_reset(rst, delay).await.map_err(Error::Pin) 559 | } 560 | } 561 | -------------------------------------------------------------------------------- /src/mode/buffered_graphics.rs: -------------------------------------------------------------------------------- 1 | //! Buffered graphics mode. 2 | 3 | use crate::{ 4 | command::AddrMode, 5 | rotation::DisplayRotation, 6 | size::{DisplaySize, NewZeroed}, 7 | Ssd1306, 8 | }; 9 | #[cfg(feature = "async")] 10 | use crate::{size::DisplaySizeAsync, Ssd1306Async}; 11 | #[cfg(feature = "async")] 12 | use display_interface::AsyncWriteOnlyDataCommand; 13 | use display_interface::{DisplayError, WriteOnlyDataCommand}; 14 | 15 | /// Buffered graphics mode. 16 | /// 17 | /// This mode keeps a pixel buffer in system memory, up to 1024 bytes for 128x64px displays. This 18 | /// buffer is drawn to by [`set_pixel`](Ssd1306::set_pixel) commands or 19 | /// [`embedded-graphics`](https://docs.rs/embedded-graphics) commands. The display can then be 20 | /// updated using the [`flush`](Ssd1306::flush) method. 21 | #[maybe_async_cfg::maybe( 22 | sync(keep_self), 23 | async(feature = "async", idents(DisplaySize(async = "DisplaySizeAsync"))) 24 | )] 25 | #[derive(Clone, Debug)] 26 | pub struct BufferedGraphicsMode 27 | where 28 | SIZE: DisplaySize, 29 | { 30 | buffer: SIZE::Buffer, 31 | min_x: u8, 32 | max_x: u8, 33 | min_y: u8, 34 | max_y: u8, 35 | } 36 | 37 | #[maybe_async_cfg::maybe( 38 | sync(keep_self), 39 | async(feature = "async", idents(DisplaySize(async = "DisplaySizeAsync"))) 40 | )] 41 | impl BufferedGraphicsMode 42 | where 43 | SIZE: DisplaySize, 44 | { 45 | /// Create a new buffered graphics mode instance. 46 | pub(crate) fn new() -> Self { 47 | Self { 48 | buffer: NewZeroed::new_zeroed(), 49 | min_x: 255, 50 | max_x: 0, 51 | min_y: 255, 52 | max_y: 0, 53 | } 54 | } 55 | } 56 | 57 | #[maybe_async_cfg::maybe( 58 | sync(keep_self), 59 | async( 60 | feature = "async", 61 | idents( 62 | DisplaySize(async = "DisplaySizeAsync"), 63 | DisplayConfig(async = "DisplayConfigAsync"), 64 | WriteOnlyDataCommand(async = "AsyncWriteOnlyDataCommand"), 65 | BufferedGraphicsMode(async = "BufferedGraphicsModeAsync"), 66 | ) 67 | ) 68 | )] 69 | impl DisplayConfig for Ssd1306> 70 | where 71 | DI: WriteOnlyDataCommand, 72 | SIZE: DisplaySize, 73 | { 74 | type Error = DisplayError; 75 | 76 | /// Set the display rotation 77 | /// 78 | /// This method resets the cursor but does not clear the screen. 79 | async fn set_rotation(&mut self, rot: DisplayRotation) -> Result<(), DisplayError> { 80 | self.set_rotation(rot).await 81 | } 82 | 83 | /// Initialise and clear the display in graphics mode. 84 | async fn init(&mut self) -> Result<(), DisplayError> { 85 | self.clear_impl(false); 86 | self.init_with_addr_mode(AddrMode::Horizontal).await 87 | } 88 | } 89 | 90 | #[maybe_async_cfg::maybe( 91 | sync(keep_self), 92 | async( 93 | feature = "async", 94 | idents( 95 | DisplaySize(async = "DisplaySizeAsync"), 96 | WriteOnlyDataCommand(async = "AsyncWriteOnlyDataCommand"), 97 | BufferedGraphicsMode(async = "BufferedGraphicsModeAsync") 98 | ) 99 | ) 100 | )] 101 | impl Ssd1306> 102 | where 103 | DI: WriteOnlyDataCommand, 104 | SIZE: DisplaySize, 105 | { 106 | fn clear_impl(&mut self, value: bool) { 107 | self.mode.buffer.as_mut().fill(if value { 0xff } else { 0 }); 108 | 109 | let (width, height) = self.dimensions(); 110 | self.mode.min_x = 0; 111 | self.mode.max_x = width - 1; 112 | self.mode.min_y = 0; 113 | self.mode.max_y = height - 1; 114 | } 115 | 116 | /// Clear the underlying framebuffer. You need to call `disp.flush()` for any effect on the screen. 117 | pub fn clear_buffer(&mut self) { 118 | self.clear_impl(false); 119 | } 120 | 121 | /// Write out data to a display. 122 | /// 123 | /// This only updates the parts of the display that have changed since the last flush. 124 | pub async fn flush(&mut self) -> Result<(), DisplayError> { 125 | // Nothing to do if no pixels have changed since the last update 126 | if self.mode.max_x < self.mode.min_x || self.mode.max_y < self.mode.min_y { 127 | return Ok(()); 128 | } 129 | 130 | let (width, height) = self.dimensions(); 131 | 132 | // Determine which bytes need to be sent 133 | let disp_min_x = self.mode.min_x; 134 | let disp_min_y = self.mode.min_y; 135 | 136 | let (disp_max_x, disp_max_y) = match self.rotation { 137 | DisplayRotation::Rotate0 | DisplayRotation::Rotate180 => ( 138 | (self.mode.max_x + 1).min(width), 139 | (self.mode.max_y | 7).min(height), 140 | ), 141 | DisplayRotation::Rotate90 | DisplayRotation::Rotate270 => ( 142 | (self.mode.max_x | 7).min(width), 143 | (self.mode.max_y + 1).min(height), 144 | ), 145 | }; 146 | 147 | self.mode.min_x = 255; 148 | self.mode.max_x = 0; 149 | self.mode.min_y = 255; 150 | self.mode.max_y = 0; 151 | 152 | // Tell the display to update only the part that has changed 153 | let offset_x = match self.rotation { 154 | DisplayRotation::Rotate0 | DisplayRotation::Rotate270 => SIZE::OFFSETX, 155 | DisplayRotation::Rotate180 | DisplayRotation::Rotate90 => { 156 | // If segment remapping is flipped, we need to calculate 157 | // the offset from the other edge of the display. 158 | SIZE::DRIVER_COLS - SIZE::WIDTH - SIZE::OFFSETX 159 | } 160 | }; 161 | 162 | match self.rotation { 163 | DisplayRotation::Rotate0 | DisplayRotation::Rotate180 => { 164 | self.set_draw_area( 165 | (disp_min_x + offset_x, disp_min_y + SIZE::OFFSETY), 166 | (disp_max_x + offset_x, disp_max_y + SIZE::OFFSETY), 167 | ) 168 | .await?; 169 | 170 | Self::flush_buffer_chunks( 171 | &mut self.interface, 172 | self.mode.buffer.as_mut(), 173 | width as usize, 174 | (disp_min_x, disp_min_y), 175 | (disp_max_x, disp_max_y), 176 | ) 177 | .await 178 | } 179 | DisplayRotation::Rotate90 | DisplayRotation::Rotate270 => { 180 | self.set_draw_area( 181 | (disp_min_y + offset_x, disp_min_x + SIZE::OFFSETY), 182 | (disp_max_y + offset_x, disp_max_x + SIZE::OFFSETY), 183 | ) 184 | .await?; 185 | 186 | Self::flush_buffer_chunks( 187 | &mut self.interface, 188 | self.mode.buffer.as_mut(), 189 | height as usize, 190 | (disp_min_y, disp_min_x), 191 | (disp_max_y, disp_max_x), 192 | ) 193 | .await 194 | } 195 | } 196 | } 197 | 198 | /// Turn a pixel on or off. A non-zero `value` is treated as on, `0` as off. If the X and Y 199 | /// coordinates are out of the bounds of the display, this method call is a noop. 200 | pub fn set_pixel(&mut self, x: u32, y: u32, value: bool) { 201 | let value = value as u8; 202 | let rotation = self.rotation; 203 | 204 | let (idx, bit) = match rotation { 205 | DisplayRotation::Rotate0 | DisplayRotation::Rotate180 => { 206 | let idx = ((y as usize) / 8 * SIZE::WIDTH as usize) + (x as usize); 207 | let bit = y % 8; 208 | 209 | (idx, bit) 210 | } 211 | DisplayRotation::Rotate90 | DisplayRotation::Rotate270 => { 212 | let idx = ((x as usize) / 8 * SIZE::WIDTH as usize) + (y as usize); 213 | let bit = x % 8; 214 | 215 | (idx, bit) 216 | } 217 | }; 218 | 219 | if let Some(byte) = self.mode.buffer.as_mut().get_mut(idx) { 220 | // Keep track of max and min values 221 | self.mode.min_x = self.mode.min_x.min(x as u8); 222 | self.mode.max_x = self.mode.max_x.max(x as u8); 223 | 224 | self.mode.min_y = self.mode.min_y.min(y as u8); 225 | self.mode.max_y = self.mode.max_y.max(y as u8); 226 | 227 | // Set pixel value in byte 228 | // Ref this comment https://stackoverflow.com/questions/47981/how-do-you-set-clear-and-toggle-a-single-bit#comment46654671_47990 229 | *byte = *byte & !(1 << bit) | (value << bit); 230 | } 231 | } 232 | } 233 | 234 | #[cfg(feature = "graphics")] 235 | use embedded_graphics_core::{ 236 | draw_target::DrawTarget, 237 | geometry::Size, 238 | geometry::{Dimensions, OriginDimensions}, 239 | pixelcolor::BinaryColor, 240 | Pixel, 241 | }; 242 | 243 | use super::DisplayConfig; 244 | #[cfg(feature = "async")] 245 | use super::DisplayConfigAsync; 246 | 247 | #[cfg(feature = "graphics")] 248 | #[maybe_async_cfg::maybe( 249 | sync(keep_self), 250 | async( 251 | feature = "async", 252 | idents( 253 | DisplaySize(async = "DisplaySizeAsync"), 254 | BufferedGraphicsMode(async = "BufferedGraphicsModeAsync"), 255 | WriteOnlyDataCommand(async = "AsyncWriteOnlyDataCommand") 256 | ) 257 | ) 258 | )] 259 | impl DrawTarget for Ssd1306> 260 | where 261 | DI: WriteOnlyDataCommand, 262 | SIZE: DisplaySize, 263 | { 264 | type Color = BinaryColor; 265 | type Error = DisplayError; 266 | 267 | fn draw_iter(&mut self, pixels: I) -> Result<(), Self::Error> 268 | where 269 | I: IntoIterator>, 270 | { 271 | let bb = self.bounding_box(); 272 | 273 | pixels 274 | .into_iter() 275 | .filter(|Pixel(pos, _color)| bb.contains(*pos)) 276 | .for_each(|Pixel(pos, color)| { 277 | self.set_pixel(pos.x as u32, pos.y as u32, color.is_on()); 278 | }); 279 | 280 | Ok(()) 281 | } 282 | 283 | fn clear(&mut self, color: Self::Color) -> Result<(), Self::Error> { 284 | self.clear_impl(color.is_on()); 285 | Ok(()) 286 | } 287 | } 288 | 289 | #[cfg(feature = "graphics")] 290 | #[maybe_async_cfg::maybe( 291 | sync(keep_self,), 292 | async( 293 | feature = "async", 294 | idents( 295 | DisplaySize(async = "DisplaySizeAsync"), 296 | WriteOnlyDataCommand(async = "AsyncWriteOnlyDataCommand"), 297 | BufferedGraphicsMode(async = "BufferedGraphicsModeAsync") 298 | ) 299 | ) 300 | )] 301 | impl OriginDimensions for Ssd1306> 302 | where 303 | DI: WriteOnlyDataCommand, 304 | SIZE: DisplaySize, 305 | { 306 | fn size(&self) -> Size { 307 | let (w, h) = self.dimensions(); 308 | 309 | Size::new(w.into(), h.into()) 310 | } 311 | } 312 | -------------------------------------------------------------------------------- /src/mode/mod.rs: -------------------------------------------------------------------------------- 1 | //! Display modes. 2 | 3 | mod buffered_graphics; 4 | mod terminal; 5 | 6 | use crate::{command::AddrMode, rotation::DisplayRotation, size::DisplaySize, Ssd1306}; 7 | pub use buffered_graphics::*; 8 | use display_interface::{DisplayError, WriteOnlyDataCommand}; 9 | pub use terminal::*; 10 | 11 | /// Common functions to all display modes. 12 | #[maybe_async_cfg::maybe(sync(keep_self,), async(feature = "async"))] 13 | pub trait DisplayConfig { 14 | /// Error. 15 | type Error; 16 | 17 | /// Set display rotation. 18 | async fn set_rotation(&mut self, rotation: DisplayRotation) -> Result<(), Self::Error>; 19 | 20 | /// Initialise and configure the display for the given mode. 21 | async fn init(&mut self) -> Result<(), Self::Error>; 22 | } 23 | 24 | /// A mode with no additional functionality beyond that provided by the base [`Ssd1306`] struct. 25 | #[derive(Debug, Copy, Clone)] 26 | pub struct BasicMode; 27 | 28 | impl Ssd1306 29 | where 30 | DI: WriteOnlyDataCommand, 31 | SIZE: DisplaySize, 32 | { 33 | /// Clear the display. 34 | pub fn clear(&mut self) -> Result<(), DisplayError> { 35 | let old_addr_mode = self.addr_mode; 36 | if old_addr_mode != AddrMode::Horizontal { 37 | self.set_addr_mode(AddrMode::Horizontal)?; 38 | } 39 | 40 | let dim = self.dimensions(); 41 | self.set_draw_area((0, 0), dim)?; 42 | 43 | let num_pixels = dim.0 as u16 * dim.1 as u16; 44 | 45 | const BITS_PER_BYTE: u16 = 8; 46 | const BYTES_PER_BATCH: u16 = 64; 47 | const PIXELS_PER_BATCH: u16 = BITS_PER_BYTE * BYTES_PER_BATCH; 48 | 49 | // Not all screens have number of pixels divisible by 512, so add 1 to cover tail 50 | let num_batches = num_pixels / PIXELS_PER_BATCH + 1; 51 | 52 | for _ in 0..num_batches { 53 | self.draw(&[0; BYTES_PER_BATCH as usize])?; 54 | } 55 | 56 | if old_addr_mode != AddrMode::Horizontal { 57 | self.set_addr_mode(old_addr_mode)?; 58 | } 59 | 60 | Ok(()) 61 | } 62 | } 63 | 64 | impl DisplayConfig for Ssd1306 65 | where 66 | DI: WriteOnlyDataCommand, 67 | SIZE: DisplaySize, 68 | { 69 | type Error = DisplayError; 70 | 71 | /// Set the display rotation. 72 | fn set_rotation(&mut self, rot: DisplayRotation) -> Result<(), DisplayError> { 73 | self.set_rotation(rot) 74 | } 75 | 76 | /// Initialise in horizontal addressing mode. 77 | fn init(&mut self) -> Result<(), DisplayError> { 78 | self.init_with_addr_mode(AddrMode::Horizontal) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/mode/terminal.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "async")] 2 | use crate::mode::DisplayConfigAsync; 3 | use crate::{command::AddrMode, mode::DisplayConfig, rotation::DisplayRotation, size::*, Ssd1306}; 4 | #[cfg(feature = "async")] 5 | use crate::{size::DisplaySizeAsync, Ssd1306Async}; 6 | use core::{cmp::min, fmt}; 7 | #[cfg(feature = "async")] 8 | use display_interface::AsyncWriteOnlyDataCommand; 9 | use display_interface::{DisplayError, WriteOnlyDataCommand}; 10 | 11 | #[maybe_async_cfg::maybe( 12 | sync(keep_self), 13 | async( 14 | feature = "async", 15 | idents( 16 | TerminalDisplaySize(async = "TerminalDisplaySizeAsync"), 17 | DisplaySize(async = "DisplaySizeAsync"), 18 | ) 19 | ) 20 | )] 21 | /// Extends the [`DisplaySize`](crate::size::DisplaySize) trait 22 | /// to include number of characters that can fit on the display. 23 | pub trait TerminalDisplaySize: DisplaySize { 24 | /// The number of characters that can fit on the display at once (w * h / (8 * 8)) 25 | const CHAR_NUM: u8; 26 | } 27 | 28 | #[maybe_async_cfg::maybe( 29 | sync(keep_self), 30 | async( 31 | feature = "async", 32 | keep_self, 33 | idents(TerminalDisplaySize(async = "TerminalDisplaySizeAsync"),) 34 | ) 35 | )] 36 | impl TerminalDisplaySize for DisplaySize128x64 { 37 | const CHAR_NUM: u8 = 128; 38 | } 39 | 40 | #[maybe_async_cfg::maybe( 41 | sync(keep_self), 42 | async( 43 | feature = "async", 44 | keep_self, 45 | idents(TerminalDisplaySize(async = "TerminalDisplaySizeAsync"),) 46 | ) 47 | )] 48 | impl TerminalDisplaySize for DisplaySize128x32 { 49 | const CHAR_NUM: u8 = 64; 50 | } 51 | 52 | #[maybe_async_cfg::maybe( 53 | sync(keep_self), 54 | async( 55 | feature = "async", 56 | keep_self, 57 | idents(TerminalDisplaySize(async = "TerminalDisplaySizeAsync"),) 58 | ) 59 | )] 60 | impl TerminalDisplaySize for DisplaySize96x16 { 61 | const CHAR_NUM: u8 = 24; 62 | } 63 | 64 | #[maybe_async_cfg::maybe( 65 | sync(keep_self), 66 | async( 67 | keep_self, 68 | feature = "async", 69 | idents(TerminalDisplaySize(async = "TerminalDisplaySizeAsync"),) 70 | ) 71 | )] 72 | impl TerminalDisplaySize for DisplaySize72x40 { 73 | const CHAR_NUM: u8 = 45; 74 | } 75 | 76 | #[maybe_async_cfg::maybe( 77 | sync(keep_self), 78 | async( 79 | feature = "async", 80 | keep_self, 81 | idents(TerminalDisplaySize(async = "TerminalDisplaySizeAsync"),) 82 | ) 83 | )] 84 | impl TerminalDisplaySize for DisplaySize64x48 { 85 | const CHAR_NUM: u8 = 48; 86 | } 87 | 88 | /// Contains the new row that the cursor has wrapped around to 89 | struct CursorWrapEvent(u8); 90 | 91 | #[derive(Copy, Clone, Debug)] 92 | struct Cursor { 93 | col: u8, 94 | row: u8, 95 | width: u8, 96 | height: u8, 97 | } 98 | 99 | impl Cursor { 100 | pub fn new(width_pixels: u8, height_pixels: u8) -> Self { 101 | let width = width_pixels / 8; 102 | let height = height_pixels / 8; 103 | Cursor { 104 | col: 0, 105 | row: 0, 106 | width, 107 | height, 108 | } 109 | } 110 | 111 | /// Advances the logical cursor by one character. 112 | /// Returns a value indicating if this caused the cursor to wrap to the next line or the next 113 | /// screen. 114 | pub fn advance(&mut self) -> Option { 115 | self.col = (self.col + 1) % self.width; 116 | if self.col == 0 { 117 | self.row = (self.row + 1) % self.height; 118 | Some(CursorWrapEvent(self.row)) 119 | } else { 120 | None 121 | } 122 | } 123 | 124 | /// Advances the logical cursor to the start of the next line 125 | /// Returns a value indicating the now active line 126 | pub fn advance_line(&mut self) -> CursorWrapEvent { 127 | self.row = (self.row + 1) % self.height; 128 | self.col = 0; 129 | CursorWrapEvent(self.row) 130 | } 131 | 132 | /// Sets the position of the logical cursor arbitrarily. 133 | /// The position will be capped at the maximal possible position. 134 | pub fn set_position(&mut self, col: u8, row: u8) { 135 | self.col = min(col, self.width - 1); 136 | self.row = min(row, self.height - 1); 137 | } 138 | 139 | /// Gets the position of the logical cursor on screen in (col, row) order 140 | pub fn get_position(&self) -> (u8, u8) { 141 | (self.col, self.row) 142 | } 143 | 144 | /// Gets the logical dimensions of the screen in terms of characters, as (width, height) 145 | pub fn get_dimensions(&self) -> (u8, u8) { 146 | (self.width, self.height) 147 | } 148 | } 149 | 150 | /// Errors which can occur when interacting with the terminal mode 151 | #[derive(Clone)] 152 | pub enum TerminalModeError { 153 | /// An error occurred in the underlying interface layer 154 | InterfaceError(DisplayError), 155 | /// The mode was used before it was initialized 156 | Uninitialized, 157 | /// A location was specified outside the bounds of the screen 158 | OutOfBounds, 159 | } 160 | 161 | impl fmt::Debug for TerminalModeError { 162 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { 163 | match self { 164 | Self::InterfaceError(_) => "InterfaceError".fmt(f), 165 | Self::Uninitialized => "Uninitialized".fmt(f), 166 | Self::OutOfBounds => "OutOfBound".fmt(f), 167 | } 168 | } 169 | } 170 | 171 | impl From for TerminalModeError { 172 | fn from(value: DisplayError) -> Self { 173 | TerminalModeError::InterfaceError(value) 174 | } 175 | } 176 | 177 | /// Terminal mode. 178 | #[maybe_async_cfg::maybe( 179 | sync(keep_self), 180 | async(feature = "async", idents(TerminalMode(async = "TerminalModeAsync"))) 181 | )] 182 | #[derive(Debug, Copy, Clone, Default)] 183 | pub struct TerminalMode { 184 | cursor: Option, 185 | } 186 | 187 | #[maybe_async_cfg::maybe( 188 | sync(keep_self), 189 | async(feature = "async", idents(TerminalMode(async = "TerminalModeAsync"))) 190 | )] 191 | impl TerminalMode { 192 | /// Create a new terminal mode config instance. 193 | pub fn new() -> Self { 194 | Self::default() 195 | } 196 | } 197 | 198 | #[maybe_async_cfg::maybe( 199 | sync(keep_self), 200 | async( 201 | feature = "async", 202 | idents( 203 | DisplaySize(async = "DisplaySizeAsync"), 204 | DisplayConfig(async = "DisplayConfigAsync"), 205 | WriteOnlyDataCommand(async = "AsyncWriteOnlyDataCommand"), 206 | TerminalMode(async = "TerminalModeAsync"), 207 | TerminalDisplaySize(async = "TerminalDisplaySizeAsync"), 208 | ) 209 | ) 210 | )] 211 | impl DisplayConfig for Ssd1306 212 | where 213 | DI: WriteOnlyDataCommand, 214 | SIZE: TerminalDisplaySize, 215 | { 216 | type Error = TerminalModeError; 217 | 218 | /// Set the display rotation 219 | /// 220 | /// This method resets the cursor but does not clear the screen. 221 | async fn set_rotation(&mut self, rot: DisplayRotation) -> Result<(), TerminalModeError> { 222 | self.set_rotation(rot).await?; 223 | // Need to reset cursor position, otherwise coordinates can become invalid 224 | self.reset_pos().await 225 | } 226 | 227 | /// Initialise the display in page mode (i.e. a byte walks down a column of 8 pixels) with 228 | /// column 0 on the left and column _(SIZE::Width::U8 - 1)_ on the right, but no automatic line 229 | /// wrapping. 230 | async fn init(&mut self) -> Result<(), TerminalModeError> { 231 | self.init_with_addr_mode(AddrMode::Page).await?; 232 | self.reset_pos().await 233 | } 234 | } 235 | 236 | #[maybe_async_cfg::maybe( 237 | sync(keep_self), 238 | async( 239 | feature = "async", 240 | idents( 241 | DisplaySize(async = "DisplaySizeAsync"), 242 | DisplayConfig(async = "DisplayConfigAsync"), 243 | WriteOnlyDataCommand(async = "AsyncWriteOnlyDataCommand"), 244 | TerminalMode(async = "TerminalModeAsync"), 245 | TerminalDisplaySize(async = "TerminalDisplaySizeAsync"), 246 | ) 247 | ) 248 | )] 249 | impl Ssd1306 250 | where 251 | DI: WriteOnlyDataCommand, 252 | SIZE: TerminalDisplaySize, 253 | { 254 | /// Clear the display and reset the cursor to the top left corner 255 | pub async fn clear(&mut self) -> Result<(), TerminalModeError> { 256 | // Let the chip handle line wrapping so we can fill the screen with blanks faster 257 | self.set_addr_mode(AddrMode::Horizontal).await?; 258 | 259 | let offset_x = match self.rotation() { 260 | DisplayRotation::Rotate0 | DisplayRotation::Rotate270 => SIZE::OFFSETX, 261 | DisplayRotation::Rotate180 | DisplayRotation::Rotate90 => { 262 | // If segment remapping is flipped, we need to calculate 263 | // the offset from the other edge of the display. 264 | SIZE::DRIVER_COLS - SIZE::WIDTH - SIZE::OFFSETX 265 | } 266 | }; 267 | self.set_draw_area( 268 | (offset_x, SIZE::OFFSETY), 269 | (SIZE::WIDTH + offset_x, SIZE::HEIGHT + SIZE::OFFSETY), 270 | ) 271 | .await?; 272 | 273 | // Clear the display 274 | for _ in 0..SIZE::CHAR_NUM { 275 | self.draw(&[0; 8]).await?; 276 | } 277 | 278 | // But for normal operation we manage the line wrapping 279 | self.set_addr_mode(AddrMode::Page).await?; 280 | self.reset_pos().await 281 | } 282 | 283 | /// Print a character to the display 284 | pub async fn print_char(&mut self, c: char) -> Result<(), TerminalModeError> { 285 | match c { 286 | '\n' => { 287 | let CursorWrapEvent(new_line) = self.ensure_cursor()?.advance_line(); 288 | self.set_position(0, new_line).await?; 289 | } 290 | '\r' => { 291 | self.set_column(0).await?; 292 | let (_, cur_line) = self.ensure_cursor()?.get_position(); 293 | self.ensure_cursor()?.set_position(0, cur_line); 294 | } 295 | _ => { 296 | let bitmap = match self.rotation { 297 | DisplayRotation::Rotate0 | DisplayRotation::Rotate180 => { 298 | Self::char_to_bitmap(c) 299 | } 300 | DisplayRotation::Rotate90 | DisplayRotation::Rotate270 => { 301 | let bitmap = Self::char_to_bitmap(c); 302 | Self::rotate_bitmap(bitmap) 303 | } 304 | }; 305 | 306 | self.draw(&bitmap).await?; 307 | 308 | // Increment character counter and potentially wrap line 309 | self.advance_cursor().await?; 310 | } 311 | } 312 | 313 | Ok(()) 314 | } 315 | 316 | /// Get the current cursor position, in character coordinates. 317 | /// This is the (column, row) that the next character will be written to. 318 | pub fn position(&self) -> Result<(u8, u8), TerminalModeError> { 319 | self.mode 320 | .cursor 321 | .as_ref() 322 | .map(Cursor::get_position) 323 | .ok_or(TerminalModeError::Uninitialized) 324 | } 325 | 326 | /// Set the cursor position, in character coordinates. 327 | /// This is the (column, row) that the next character will be written to. 328 | /// If the position is out of bounds, an Err will be returned. 329 | pub async fn set_position(&mut self, column: u8, row: u8) -> Result<(), TerminalModeError> { 330 | let (width, height) = self.ensure_cursor()?.get_dimensions(); 331 | if column >= width || row >= height { 332 | Err(TerminalModeError::OutOfBounds) 333 | } else { 334 | let offset_x = match self.rotation() { 335 | DisplayRotation::Rotate0 | DisplayRotation::Rotate270 => SIZE::OFFSETX, 336 | DisplayRotation::Rotate180 | DisplayRotation::Rotate90 => { 337 | // If segment remapping is flipped, we need to calculate 338 | // the offset from the other edge of the display. 339 | SIZE::DRIVER_COLS - SIZE::WIDTH - SIZE::OFFSETX 340 | } 341 | }; 342 | match self.rotation() { 343 | DisplayRotation::Rotate0 | DisplayRotation::Rotate180 => { 344 | self.set_column(offset_x + column * 8).await?; 345 | self.set_row(SIZE::OFFSETY + row * 8).await?; 346 | } 347 | DisplayRotation::Rotate90 | DisplayRotation::Rotate270 => { 348 | self.set_column(offset_x + row * 8).await?; 349 | self.set_row(SIZE::OFFSETY + column * 8).await?; 350 | } 351 | } 352 | self.ensure_cursor()?.set_position(column, row); 353 | Ok(()) 354 | } 355 | } 356 | 357 | /// Reset the draw area and move pointer to the top left corner 358 | async fn reset_pos(&mut self) -> Result<(), TerminalModeError> { 359 | // Initialise the counter when we know it's valid 360 | let (w, h) = match self.rotation() { 361 | DisplayRotation::Rotate0 | DisplayRotation::Rotate180 => (SIZE::WIDTH, SIZE::HEIGHT), 362 | DisplayRotation::Rotate90 | DisplayRotation::Rotate270 => (SIZE::HEIGHT, SIZE::WIDTH), 363 | }; 364 | self.mode.cursor = Some(Cursor::new(w, h)); 365 | 366 | // Reset cursor position 367 | self.set_position(0, 0).await 368 | } 369 | 370 | /// Advance the cursor, automatically wrapping lines and/or screens if necessary 371 | /// Takes in an already-unwrapped cursor to avoid re-unwrapping 372 | async fn advance_cursor(&mut self) -> Result<(), TerminalModeError> { 373 | let cursor = self.ensure_cursor()?; 374 | 375 | cursor.advance(); 376 | let (c, r) = cursor.get_position(); 377 | self.set_position(c, r).await 378 | } 379 | 380 | fn ensure_cursor(&mut self) -> Result<&mut Cursor, TerminalModeError> { 381 | self.mode 382 | .cursor 383 | .as_mut() 384 | .ok_or(TerminalModeError::Uninitialized) 385 | } 386 | 387 | fn char_to_bitmap(input: char) -> [u8; 8] { 388 | const CHARS: [[u8; 6]; 95] = [ 389 | // ! 390 | [0x00, 0x2f, 0x00, 0x00, 0x00, 0x00], 391 | // " 392 | [0x03, 0x00, 0x03, 0x00, 0x00, 0x00], 393 | // # 394 | [0x12, 0x3f, 0x12, 0x12, 0x3f, 0x12], 395 | // $ 396 | [0x2e, 0x2a, 0x7f, 0x2a, 0x3a, 0x00], 397 | // % 398 | [0x23, 0x13, 0x08, 0x04, 0x32, 0x31], 399 | // & 400 | [0x10, 0x2a, 0x25, 0x2a, 0x10, 0x20], 401 | // ' 402 | [0x02, 0x01, 0x00, 0x00, 0x00, 0x00], 403 | // ( 404 | [0x1e, 0x21, 0x00, 0x00, 0x00, 0x00], 405 | // ) 406 | [0x21, 0x1e, 0x00, 0x00, 0x00, 0x00], 407 | // * 408 | [0x08, 0x2a, 0x1c, 0x2a, 0x08, 0x00], 409 | // + 410 | [0x08, 0x08, 0x3e, 0x08, 0x08, 0x00], 411 | // , 412 | [0x80, 0x60, 0x00, 0x00, 0x00, 0x00], 413 | // - 414 | [0x08, 0x08, 0x08, 0x08, 0x08, 0x00], 415 | // . 416 | [0x30, 0x30, 0x00, 0x00, 0x00, 0x00], 417 | // / 418 | [0x20, 0x10, 0x08, 0x04, 0x02, 0x00], 419 | // 0 420 | [0x1e, 0x31, 0x29, 0x25, 0x23, 0x1e], 421 | // 1 422 | [0x22, 0x21, 0x3f, 0x20, 0x20, 0x20], 423 | // 2 424 | [0x32, 0x29, 0x29, 0x29, 0x29, 0x26], 425 | // 3 426 | [0x12, 0x21, 0x21, 0x25, 0x25, 0x1a], 427 | // 4 428 | [0x18, 0x14, 0x12, 0x3f, 0x10, 0x00], 429 | // 5 430 | [0x17, 0x25, 0x25, 0x25, 0x25, 0x19], 431 | // 6 432 | [0x1e, 0x25, 0x25, 0x25, 0x25, 0x18], 433 | // 7 434 | [0x01, 0x01, 0x31, 0x09, 0x05, 0x03], 435 | // 8 436 | [0x1a, 0x25, 0x25, 0x25, 0x25, 0x1a], 437 | // 9 438 | [0x06, 0x29, 0x29, 0x29, 0x29, 0x1e], 439 | // : 440 | [0x24, 0x00, 0x00, 0x00, 0x00, 0x00], 441 | // ; 442 | [0x80, 0x64, 0x00, 0x00, 0x00, 0x00], 443 | // < 444 | [0x08, 0x14, 0x22, 0x00, 0x00, 0x00], 445 | // = 446 | [0x14, 0x14, 0x14, 0x14, 0x14, 0x00], 447 | // > 448 | [0x22, 0x14, 0x08, 0x00, 0x00, 0x00], 449 | // ? 450 | [0x02, 0x01, 0x01, 0x29, 0x05, 0x02], 451 | // @ 452 | [0x1e, 0x21, 0x2d, 0x2b, 0x2d, 0x0e], 453 | // A 454 | [0x3e, 0x09, 0x09, 0x09, 0x09, 0x3e], 455 | // B 456 | [0x3f, 0x25, 0x25, 0x25, 0x25, 0x1a], 457 | // C 458 | [0x1e, 0x21, 0x21, 0x21, 0x21, 0x12], 459 | // D 460 | [0x3f, 0x21, 0x21, 0x21, 0x12, 0x0c], 461 | // E 462 | [0x3f, 0x25, 0x25, 0x25, 0x25, 0x21], 463 | // F 464 | [0x3f, 0x05, 0x05, 0x05, 0x05, 0x01], 465 | // G 466 | [0x1e, 0x21, 0x21, 0x21, 0x29, 0x1a], 467 | // H 468 | [0x3f, 0x04, 0x04, 0x04, 0x04, 0x3f], 469 | // I 470 | [0x21, 0x21, 0x3f, 0x21, 0x21, 0x00], 471 | // J 472 | [0x10, 0x20, 0x20, 0x20, 0x20, 0x1f], 473 | // K 474 | [0x3f, 0x04, 0x0c, 0x0a, 0x11, 0x20], 475 | // L 476 | [0x3f, 0x20, 0x20, 0x20, 0x20, 0x20], 477 | // M 478 | [0x3f, 0x02, 0x04, 0x04, 0x02, 0x3f], 479 | // N 480 | [0x3f, 0x02, 0x04, 0x08, 0x10, 0x3f], 481 | // O 482 | [0x1e, 0x21, 0x21, 0x21, 0x21, 0x1e], 483 | // P 484 | [0x3f, 0x09, 0x09, 0x09, 0x09, 0x06], 485 | // Q 486 | [0x1e, 0x21, 0x29, 0x31, 0x21, 0x5e], 487 | // R 488 | [0x3f, 0x09, 0x09, 0x09, 0x19, 0x26], 489 | // S 490 | [0x12, 0x25, 0x25, 0x25, 0x25, 0x18], 491 | // T 492 | [0x01, 0x01, 0x01, 0x3f, 0x01, 0x01], 493 | // U 494 | [0x1f, 0x20, 0x20, 0x20, 0x20, 0x1f], 495 | // V 496 | [0x0f, 0x10, 0x20, 0x20, 0x10, 0x0f], 497 | // W 498 | [0x1f, 0x20, 0x10, 0x10, 0x20, 0x1f], 499 | // X 500 | [0x21, 0x12, 0x0c, 0x0c, 0x12, 0x21], 501 | // Y 502 | [0x01, 0x02, 0x3c, 0x02, 0x01, 0x00], 503 | // Z 504 | [0x21, 0x31, 0x29, 0x25, 0x23, 0x21], 505 | // [ 506 | [0x3f, 0x21, 0x00, 0x00, 0x00, 0x00], 507 | // \ 508 | [0x02, 0x04, 0x08, 0x10, 0x20, 0x00], 509 | // ] 510 | [0x21, 0x3f, 0x00, 0x00, 0x00, 0x00], 511 | // ^ 512 | [0x04, 0x02, 0x3f, 0x02, 0x04, 0x00], 513 | // _ 514 | [0x40, 0x40, 0x40, 0x40, 0x40, 0x40], 515 | // ` 516 | [0x01, 0x02, 0x00, 0x00, 0x00, 0x00], 517 | // a 518 | [0x10, 0x2a, 0x2a, 0x2a, 0x3c, 0x00], 519 | // b 520 | [0x3f, 0x24, 0x24, 0x24, 0x18, 0x00], 521 | // c 522 | [0x1c, 0x22, 0x22, 0x22, 0x00, 0x00], 523 | // d 524 | [0x18, 0x24, 0x24, 0x24, 0x3f, 0x00], 525 | // e 526 | [0x1c, 0x2a, 0x2a, 0x2a, 0x24, 0x00], 527 | // f 528 | [0x00, 0x3e, 0x05, 0x01, 0x00, 0x00], 529 | // g 530 | [0x18, 0xa4, 0xa4, 0xa4, 0x7c, 0x00], 531 | // h 532 | [0x3f, 0x04, 0x04, 0x04, 0x38, 0x00], 533 | // i 534 | [0x00, 0x24, 0x3d, 0x20, 0x00, 0x00], 535 | // j 536 | [0x20, 0x40, 0x40, 0x3d, 0x00, 0x00], 537 | // k 538 | [0x3f, 0x0c, 0x12, 0x20, 0x00, 0x00], 539 | // l 540 | [0x1f, 0x20, 0x20, 0x00, 0x00, 0x00], 541 | // m 542 | [0x3e, 0x02, 0x3c, 0x02, 0x3c, 0x00], 543 | // n 544 | [0x3e, 0x02, 0x02, 0x02, 0x3c, 0x00], 545 | // o 546 | [0x1c, 0x22, 0x22, 0x22, 0x1c, 0x00], 547 | // p 548 | [0xfc, 0x24, 0x24, 0x24, 0x18, 0x00], 549 | // q 550 | [0x18, 0x24, 0x24, 0x24, 0xfc, 0x00], 551 | // r 552 | [0x3e, 0x04, 0x02, 0x02, 0x00, 0x00], 553 | // s 554 | [0x24, 0x2a, 0x2a, 0x2a, 0x10, 0x00], 555 | // t 556 | [0x02, 0x1f, 0x22, 0x20, 0x00, 0x00], 557 | // u 558 | [0x1e, 0x20, 0x20, 0x20, 0x1e, 0x00], 559 | // v 560 | [0x06, 0x18, 0x20, 0x18, 0x06, 0x00], 561 | // w 562 | [0x1e, 0x30, 0x1c, 0x30, 0x1e, 0x00], 563 | // x 564 | [0x22, 0x14, 0x08, 0x14, 0x22, 0x00], 565 | // y 566 | [0x1c, 0xa0, 0xa0, 0xa0, 0x7c, 0x00], 567 | // z 568 | [0x22, 0x32, 0x2a, 0x26, 0x22, 0x00], 569 | // { 570 | [0x0c, 0x3f, 0x21, 0x00, 0x00, 0x00], 571 | // | 572 | [0x3f, 0x00, 0x00, 0x00, 0x00, 0x00], 573 | // } 574 | [0x21, 0x3f, 0x0c, 0x00, 0x00, 0x00], 575 | // ~ 576 | [0x02, 0x01, 0x02, 0x01, 0x00, 0x00], 577 | // blank 578 | [0x00, 0x00, 0x00, 0x00, 0x00, 0x00], 579 | ]; 580 | 581 | let g = (input as usize) 582 | .checked_sub(b'!'.into()) 583 | .and_then(|idx| CHARS.get(idx)) 584 | .unwrap_or(&CHARS[CHARS.len() - 1]); 585 | 586 | [0, g[0], g[1], g[2], g[3], g[4], g[5], 0] 587 | } 588 | 589 | fn rotate_bitmap(bitmap: [u8; 8]) -> [u8; 8] { 590 | let mut rotated: [u8; 8] = [0; 8]; 591 | 592 | for (col, source) in bitmap.iter().enumerate() { 593 | // source.msb is the top pixel 594 | for (row, item) in rotated.iter_mut().enumerate() { 595 | let bit = source & (1 << row) != 0; 596 | if bit { 597 | *item |= 1 << col; 598 | } 599 | } 600 | } 601 | 602 | rotated 603 | } 604 | } 605 | 606 | #[cfg(feature = "async")] 607 | impl Ssd1306Async 608 | where 609 | DI: AsyncWriteOnlyDataCommand, 610 | SIZE: TerminalDisplaySizeAsync, 611 | { 612 | /// Write a string slice to the display 613 | pub async fn write_str(&mut self, s: &str) -> Result<(), TerminalModeError> { 614 | for c in s.chars() { 615 | self.print_char(c).await?; 616 | } 617 | Ok(()) 618 | } 619 | } 620 | 621 | impl fmt::Write for Ssd1306 622 | where 623 | DI: WriteOnlyDataCommand, 624 | SIZE: TerminalDisplaySize, 625 | { 626 | fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> { 627 | s.chars().map(move |c| self.print_char(c)).next_back(); 628 | Ok(()) 629 | } 630 | } 631 | -------------------------------------------------------------------------------- /src/prelude.rs: -------------------------------------------------------------------------------- 1 | //! Crate prelude 2 | 3 | pub use display_interface::WriteOnlyDataCommand; 4 | pub use display_interface_i2c::I2CInterface; 5 | pub use display_interface_spi::SPIInterface; 6 | 7 | pub use super::{ 8 | brightness::Brightness, 9 | mode::DisplayConfig, 10 | rotation::DisplayRotation, 11 | size::{ 12 | DisplaySize, DisplaySize128x32, DisplaySize128x64, DisplaySize64x32, DisplaySize64x48, 13 | DisplaySize72x40, DisplaySize96x16, 14 | }, 15 | }; 16 | 17 | #[cfg(feature = "async")] 18 | pub use super::mode::DisplayConfigAsync; 19 | -------------------------------------------------------------------------------- /src/rotation.rs: -------------------------------------------------------------------------------- 1 | //! Display rotation. 2 | 3 | /// Display rotation. 4 | #[derive(Copy, Clone, Debug)] 5 | pub enum DisplayRotation { 6 | /// No rotation, normal display 7 | Rotate0, 8 | /// Rotate by 90 degrees clockwise 9 | Rotate90, 10 | /// Rotate by 180 degrees clockwise 11 | Rotate180, 12 | /// Rotate 270 degrees clockwise 13 | Rotate270, 14 | } 15 | -------------------------------------------------------------------------------- /src/size.rs: -------------------------------------------------------------------------------- 1 | //! Display size. 2 | 3 | use super::command::Command; 4 | #[cfg(feature = "async")] 5 | use super::command::CommandAsync; 6 | #[cfg(feature = "async")] 7 | use display_interface::AsyncWriteOnlyDataCommand; 8 | use display_interface::{DisplayError, WriteOnlyDataCommand}; 9 | 10 | /// Workaround trait, since `Default` is only implemented to arrays up to 32 of size 11 | pub trait NewZeroed { 12 | /// Creates a new value with its memory set to zero 13 | fn new_zeroed() -> Self; 14 | } 15 | 16 | impl NewZeroed for [u8; N] { 17 | fn new_zeroed() -> Self { 18 | [0u8; N] 19 | } 20 | } 21 | 22 | /// Display information. 23 | /// 24 | /// This trait describes information related to a particular display. 25 | /// This includes resolution, offset and framebuffer size. 26 | #[maybe_async_cfg::maybe( 27 | sync(keep_self), 28 | async( 29 | feature = "async", 30 | idents(WriteOnlyDataCommand(async = "AsyncWriteOnlyDataCommand")) 31 | ) 32 | )] 33 | pub trait DisplaySize { 34 | /// Width in pixels 35 | const WIDTH: u8; 36 | 37 | /// Height in pixels 38 | const HEIGHT: u8; 39 | 40 | /// Maximum width supported by the display driver 41 | const DRIVER_COLS: u8 = 128; 42 | 43 | /// Maximum height supported by the display driver 44 | const DRIVER_ROWS: u8 = 64; 45 | 46 | /// Horizontal offset in pixels 47 | const OFFSETX: u8 = 0; 48 | 49 | /// Vertical offset in pixels 50 | const OFFSETY: u8 = 0; 51 | 52 | /// Size of framebuffer. Because the display is monochrome, this is 53 | /// width * height / 8 54 | type Buffer: AsMut<[u8]> + NewZeroed; 55 | 56 | /// Send resolution and model-dependent configuration to the display 57 | /// 58 | /// See [`Command::ComPinConfig`] 59 | /// and [`Command::InternalIref`] 60 | /// for more information 61 | async fn configure(&self, iface: &mut impl WriteOnlyDataCommand) -> Result<(), DisplayError>; 62 | } 63 | 64 | maybe_async_cfg::content! { 65 | #![maybe_async_cfg::default( 66 | idents( 67 | WriteOnlyDataCommand(sync, async = "AsyncWriteOnlyDataCommand"), 68 | Command(sync, async = "CommandAsync"), 69 | DisplaySize(sync, async="DisplaySizeAsync") 70 | ) 71 | )] 72 | 73 | /// Size information for the common 128x64 variants 74 | #[derive(Debug, Copy, Clone)] 75 | pub struct DisplaySize128x64; 76 | #[maybe_async_cfg::maybe(sync(keep_self), async(feature = "async", keep_self))] 77 | impl DisplaySize for DisplaySize128x64 { 78 | const WIDTH: u8 = 128; 79 | const HEIGHT: u8 = 64; 80 | type Buffer = [u8; ::WIDTH as usize * 81 | ::HEIGHT as usize / 8]; 82 | 83 | async fn configure( 84 | &self, 85 | iface: &mut impl WriteOnlyDataCommand, 86 | ) -> Result<(), DisplayError> { 87 | Command::ComPinConfig(true, false).send(iface).await 88 | } 89 | } 90 | 91 | /// Size information for the common 128x32 variants 92 | #[derive(Debug, Copy, Clone)] 93 | pub struct DisplaySize128x32; 94 | #[maybe_async_cfg::maybe(sync(keep_self), async(feature = "async", keep_self))] 95 | impl DisplaySize for DisplaySize128x32 { 96 | const WIDTH: u8 = 128; 97 | const HEIGHT: u8 = 32; 98 | type Buffer = [u8; ::WIDTH as usize * 99 | ::HEIGHT as usize / 8]; 100 | 101 | async fn configure( 102 | &self, 103 | iface: &mut impl WriteOnlyDataCommand, 104 | ) -> Result<(), DisplayError> { 105 | Command::ComPinConfig(false, false).send(iface).await 106 | } 107 | } 108 | 109 | /// Size information for the common 96x16 variants 110 | #[derive(Debug, Copy, Clone)] 111 | pub struct DisplaySize96x16; 112 | #[maybe_async_cfg::maybe(sync(keep_self), async(feature = "async", keep_self))] 113 | impl DisplaySize for DisplaySize96x16 { 114 | const WIDTH: u8 = 96; 115 | const HEIGHT: u8 = 16; 116 | type Buffer = [u8; ::WIDTH as usize * 117 | ::HEIGHT as usize / 8]; 118 | 119 | async fn configure( 120 | &self, 121 | iface: &mut impl WriteOnlyDataCommand, 122 | ) -> Result<(), DisplayError> { 123 | Command::ComPinConfig(false, false).send(iface).await 124 | } 125 | } 126 | 127 | /// Size information for the common 72x40 variants 128 | #[derive(Debug, Copy, Clone)] 129 | pub struct DisplaySize72x40; 130 | #[maybe_async_cfg::maybe(sync(keep_self), async(feature = "async", keep_self))] 131 | impl DisplaySize for DisplaySize72x40 { 132 | const WIDTH: u8 = 72; 133 | const HEIGHT: u8 = 40; 134 | const OFFSETX: u8 = 28; 135 | const OFFSETY: u8 = 0; 136 | type Buffer = [u8; ::WIDTH as usize * 137 | ::HEIGHT as usize / 8]; 138 | 139 | async fn configure( 140 | &self, 141 | iface: &mut impl WriteOnlyDataCommand, 142 | ) -> Result<(), DisplayError> { 143 | Command::ComPinConfig(true, false).send(iface).await?; 144 | Command::InternalIref(true, true).send(iface).await 145 | } 146 | } 147 | 148 | /// Size information for the common 64x48 variants 149 | #[derive(Debug, Copy, Clone)] 150 | pub struct DisplaySize64x48; 151 | #[maybe_async_cfg::maybe(sync(keep_self), async(feature = "async", keep_self))] 152 | impl DisplaySize for DisplaySize64x48 { 153 | const WIDTH: u8 = 64; 154 | const HEIGHT: u8 = 48; 155 | const OFFSETX: u8 = 32; 156 | const OFFSETY: u8 = 0; 157 | type Buffer = [u8; ::WIDTH as usize * 158 | ::HEIGHT as usize / 8]; 159 | 160 | async fn configure( 161 | &self, 162 | iface: &mut impl WriteOnlyDataCommand, 163 | ) -> Result<(), DisplayError> { 164 | Command::ComPinConfig(true, false).send(iface).await 165 | } 166 | } 167 | 168 | /// Size information for the common 64x32 variants 169 | #[derive(Debug, Copy, Clone)] 170 | pub struct DisplaySize64x32; 171 | #[maybe_async_cfg::maybe(sync(keep_self), async(feature = "async", keep_self))] 172 | impl DisplaySize for DisplaySize64x32 { 173 | const WIDTH: u8 = 64; 174 | const HEIGHT: u8 = 32; 175 | const OFFSETX: u8 = 32; 176 | const OFFSETY: u8 = 0; 177 | type Buffer = [u8; ::WIDTH as usize * 178 | ::HEIGHT as usize / 8]; 179 | 180 | async fn configure( 181 | &self, 182 | iface: &mut impl WriteOnlyDataCommand, 183 | ) -> Result<(), DisplayError> { 184 | Command::ComPinConfig(true, false).send(iface).await 185 | } 186 | } 187 | 188 | } // content 189 | -------------------------------------------------------------------------------- /src/test_helpers.rs: -------------------------------------------------------------------------------- 1 | //! Helpers for use in examples and tests 2 | 3 | use display_interface::{DisplayError, WriteOnlyDataCommand}; 4 | use embedded_hal::{ 5 | digital::{ErrorType, OutputPin}, 6 | i2c, 7 | spi::{self, SpiBus}, 8 | }; 9 | 10 | #[derive(PartialEq, Eq, Clone, Debug, Copy)] 11 | pub struct Error {} 12 | 13 | impl embedded_hal::digital::Error for Error { 14 | fn kind(&self) -> embedded_hal::digital::ErrorKind { 15 | embedded_hal::digital::ErrorKind::Other 16 | } 17 | } 18 | 19 | impl i2c::Error for Error { 20 | fn kind(&self) -> i2c::ErrorKind { 21 | i2c::ErrorKind::Other 22 | } 23 | } 24 | 25 | impl spi::Error for Error { 26 | fn kind(&self) -> spi::ErrorKind { 27 | spi::ErrorKind::Other 28 | } 29 | } 30 | 31 | #[allow(dead_code)] 32 | #[derive(Debug, Clone, Copy)] 33 | pub struct SpiStub; 34 | 35 | impl spi::ErrorType for SpiStub { 36 | type Error = Error; 37 | } 38 | 39 | impl SpiBus for SpiStub { 40 | fn read(&mut self, _words: &mut [u8]) -> Result<(), Self::Error> { 41 | todo!() 42 | } 43 | 44 | fn write(&mut self, _words: &[u8]) -> Result<(), Self::Error> { 45 | Ok(()) 46 | } 47 | 48 | fn transfer(&mut self, _read: &mut [u8], _write: &[u8]) -> Result<(), Self::Error> { 49 | Ok(()) 50 | } 51 | 52 | fn transfer_in_place(&mut self, _words: &mut [u8]) -> Result<(), Self::Error> { 53 | todo!() 54 | } 55 | 56 | fn flush(&mut self) -> Result<(), Self::Error> { 57 | todo!() 58 | } 59 | } 60 | 61 | #[allow(dead_code)] 62 | #[derive(Debug, Clone, Copy)] 63 | pub struct I2cStub; 64 | 65 | impl i2c::ErrorType for I2cStub { 66 | type Error = Error; 67 | } 68 | 69 | impl i2c::I2c for I2cStub { 70 | fn transaction( 71 | &mut self, 72 | _address: u8, 73 | _operations: &mut [i2c::Operation<'_>], 74 | ) -> Result<(), Self::Error> { 75 | Ok(()) 76 | } 77 | } 78 | 79 | #[allow(dead_code)] 80 | #[derive(Debug, Clone, Copy)] 81 | pub struct PinStub; 82 | 83 | impl ErrorType for PinStub { 84 | type Error = Error; 85 | } 86 | 87 | impl OutputPin for PinStub { 88 | fn set_low(&mut self) -> Result<(), Self::Error> { 89 | Ok(()) 90 | } 91 | 92 | fn set_high(&mut self) -> Result<(), Self::Error> { 93 | Ok(()) 94 | } 95 | } 96 | 97 | #[allow(dead_code)] 98 | #[derive(Debug, Clone, Copy)] 99 | pub struct StubInterface; 100 | 101 | impl WriteOnlyDataCommand for StubInterface { 102 | fn send_commands( 103 | &mut self, 104 | _cmd: display_interface::DataFormat<'_>, 105 | ) -> Result<(), DisplayError> { 106 | Ok(()) 107 | } 108 | fn send_data(&mut self, _buf: display_interface::DataFormat<'_>) -> Result<(), DisplayError> { 109 | Ok(()) 110 | } 111 | } 112 | --------------------------------------------------------------------------------