firmware for my Touchscreen E-Paper Input Module for Framework Laptop 16
3
fork

Configure Feed

Select the types of activity you want to include in your feed.

Initial commit - add BSP and display drivers

arthomnix 8c92274a

+828
+2
.cargo/config.toml
··· 1 + [build] 2 + target = "thumbv6m-none-eabi"
+21
.gitignore
··· 1 + # Generated by Cargo 2 + # will have compiled files and executables 3 + debug/ 4 + target/ 5 + 6 + # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 + # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 + Cargo.lock 9 + 10 + # These are backup files generated by rustfmt 11 + **/*.rs.bk 12 + 13 + # MSVC Windows builds of rustc generate these, which store debugging information 14 + *.pdb 15 + 16 + # RustRover 17 + # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 18 + # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 19 + # and can be added to the global gitignore or merged into this file. For a more nuclear 20 + # option (not recommended) you can uncomment the following to ignore the entire idea folder. 21 + #.idea/
+8
.idea/.gitignore
··· 1 + # Default ignored files 2 + /shelf/ 3 + /workspace.xml 4 + # Editor-based HTTP Client requests 5 + /httpRequests/ 6 + # Datasource local storage ignored files 7 + /dataSources/ 8 + /dataSources.local.xml
+19
.idea/epd_firmware.iml
··· 1 + <?xml version="1.0" encoding="UTF-8"?> 2 + <module type="EMPTY_MODULE" version="4"> 3 + <component name="NewModuleRootManager"> 4 + <content url="file://$MODULE_DIR$"> 5 + <sourceFolder url="file://$MODULE_DIR$/fw16_epd_bootloader/src" isTestSource="false" /> 6 + <sourceFolder url="file://$MODULE_DIR$/fw16_epd_bsp/src" isTestSource="false" /> 7 + <sourceFolder url="file://$MODULE_DIR$/littlefs2_rp2040/src" isTestSource="false" /> 8 + <sourceFolder url="file://$MODULE_DIR$/fw16-epd-bsp/src" isTestSource="false" /> 9 + <sourceFolder url="file://$MODULE_DIR$/pervasive-spi/src" isTestSource="false" /> 10 + <sourceFolder url="file://$MODULE_DIR$/tp370pgh01/src" isTestSource="false" /> 11 + <sourceFolder url="file://$MODULE_DIR$/fw16-epd-main/src" isTestSource="false" /> 12 + <excludeFolder url="file://$MODULE_DIR$/fw16_epd_bsp/target" /> 13 + <excludeFolder url="file://$MODULE_DIR$/target" /> 14 + <excludeFolder url="file://$MODULE_DIR$/fw16-epd-bsp/target" /> 15 + </content> 16 + <orderEntry type="inheritedJdk" /> 17 + <orderEntry type="sourceFolder" forTests="false" /> 18 + </component> 19 + </module>
+15
.idea/misc.xml
··· 1 + <?xml version="1.0" encoding="UTF-8"?> 2 + <project version="4"> 3 + <component name="DiscordProjectSettings"> 4 + <option name="show" value="ASK" /> 5 + <option name="description" value="" /> 6 + <option name="theme" value="material" /> 7 + <option name="button1Title" value="" /> 8 + <option name="button1Url" value="" /> 9 + <option name="button2Title" value="" /> 10 + <option name="button2Url" value="" /> 11 + </component> 12 + <component name="MarkdownSettingsMigration"> 13 + <option name="stateVersion" value="1" /> 14 + </component> 15 + </project>
+8
.idea/modules.xml
··· 1 + <?xml version="1.0" encoding="UTF-8"?> 2 + <project version="4"> 3 + <component name="ProjectModuleManager"> 4 + <modules> 5 + <module fileurl="file://$PROJECT_DIR$/.idea/epd_firmware.iml" filepath="$PROJECT_DIR$/.idea/epd_firmware.iml" /> 6 + </modules> 7 + </component> 8 + </project>
+6
.idea/vcs.xml
··· 1 + <?xml version="1.0" encoding="UTF-8"?> 2 + <project version="4"> 3 + <component name="VcsDirectoryMappings"> 4 + <mapping directory="" vcs="Git" /> 5 + </component> 6 + </project>
+19
Cargo.toml
··· 1 + [workspace] 2 + resolver = "2" 3 + 4 + members = [ 5 + "fw16-epd-bsp", 6 + "fw16-epd-main", 7 + "pervasive-spi", 8 + "tp370pgh01", 9 + ] 10 + 11 + [workspace.dependencies] 12 + rp2040-hal = "0.11" 13 + cortex-m = "0.7" 14 + cortex-m-rt = "0.7" 15 + embedded-hal = "1.0" 16 + embedded-hal-bus = "0.2" 17 + defmt = "0.3" 18 + defmt-rtt = "0.4" 19 + panic-probe = { version = "0.3", features = ["print-defmt"] }
+22
fw16-epd-bsp/Cargo.toml
··· 1 + [package] 2 + name = "fw16-epd-bsp" 3 + version = "0.1.0" 4 + authors = ["arthomnix"] 5 + edition = "2021" 6 + description = "Board Support Package for arthomnix's touchscreen e-paper input module for the Framework 16" 7 + license = "MIT" 8 + 9 + [dependencies] 10 + cortex-m-rt = { workspace = true, optional = true } 11 + rp2040-hal.workspace = true 12 + rp2040-boot2 = { version = "0.3.0", optional = true } 13 + 14 + [features] 15 + default = ["boot2", "rt", "critical-section-impl", "rom-func-cache", "rom-v2-intrinsics"] 16 + critical-section-impl = ["rp2040-hal/critical-section-impl"] 17 + boot2 = ["rp2040-boot2"] 18 + rt = ["cortex-m-rt", "rp2040-hal/rt"] 19 + rp2040-e5 = ["rp2040-hal/rp2040-e5"] 20 + rom-func-cache = ["rp2040-hal/rom-func-cache"] 21 + disable-intrinsics = ["rp2040-hal/disable-intrinsics"] 22 + rom-v2-intrinsics = ["rp2040-hal/rom-v2-intrinsics"]
+89
fw16-epd-bsp/src/lib.rs
··· 1 + #![no_std] 2 + 3 + pub extern crate rp2040_hal as hal; 4 + 5 + pub use hal::pac; 6 + 7 + #[cfg(feature = "rt")] 8 + pub use rp2040_hal::entry; 9 + 10 + #[cfg(feature = "boot2")] 11 + #[link_section = ".boot2"] 12 + #[no_mangle] 13 + #[used] 14 + pub static BOOT2_FIRMWARE: [u8; 256] = rp2040_boot2::BOOT_LOADER_W25Q080; 15 + 16 + hal::bsp_pins! { 17 + Gpio2 { 18 + name: epd_touch_int, 19 + aliases: { 20 + FunctionSioInput, PullUp: EpdTouchInt 21 + } 22 + }, 23 + 24 + Gpio3 { name: epd_touch_rst }, 25 + 26 + Gpio4 { 27 + name: i2c_sda, 28 + aliases: { 29 + FunctionI2C, PullUp: I2CSda 30 + } 31 + } 32 + 33 + Gpio5 { 34 + name: i2c_scl, 35 + aliases: { 36 + FunctionI2C, PullUp: I2CScl 37 + } 38 + } 39 + 40 + Gpio6 { name: epd_pwr_sw }, 41 + 42 + Gpio8 { 43 + name: epd_busy, 44 + aliases: { 45 + FunctionSioInput, PullUp: EpdBusy 46 + } 47 + }, 48 + 49 + Gpio9 { 50 + name: epd_rst, 51 + aliases: { 52 + FunctionSioOutput, PullNone: EpdReset 53 + } 54 + }, 55 + 56 + Gpio10 { 57 + name: epd_dc, 58 + aliases: { 59 + FunctionSioOutput, PullNone: EpdDc 60 + } 61 + }, 62 + 63 + Gpio11 { 64 + name: spi3_epd_sda, 65 + aliases: { 66 + FunctionSioOutput, PullNone: EpdSdaWrite, 67 + FunctionSioInput, PullNone: EpdSdaRead 68 + } 69 + } 70 + 71 + Gpio13 { 72 + name: spi3_epd_cs, 73 + aliases: { 74 + FunctionSioOutput, PullNone: EpdCs 75 + } 76 + } 77 + 78 + Gpio14 { 79 + name: spi3_epd_sck, 80 + aliases: { 81 + FunctionSioOutput, PullNone: EpdSck, 82 + FunctionPio0, PullNone: EpdSckPio 83 + } 84 + } 85 + 86 + Gpio25 { name: laptop_sleep }, 87 + } 88 + 89 + pub const XOSC_CRYSTAL_FREQ: u32 = 12_000_000;
+14
fw16-epd-main/Cargo.toml
··· 1 + [package] 2 + name = "fw16-epd-main" 3 + version = "0.1.0" 4 + edition = "2021" 5 + 6 + [dependencies] 7 + fw16-epd-bsp = { path = "../fw16-epd-bsp" } 8 + cortex-m.workspace = true 9 + cortex-m-rt.workspace = true 10 + embedded-hal.workspace = true 11 + embedded-hal-bus.workspace = true 12 + defmt.workspace = true 13 + defmt-rtt.workspace = true 14 + panic-probe.workspace = true
+29
fw16-epd-main/src/main.rs
··· 1 + #![no_main] 2 + #![no_std] 3 + 4 + #[allow(unused_imports)] 5 + use panic_probe as _; 6 + #[allow(unused_imports)] 7 + use defmt_rtt as _; 8 + 9 + use fw16_epd_bsp::{entry, hal, pac}; 10 + 11 + #[entry] 12 + fn main() -> ! { 13 + let mut pac = pac::Peripherals::take().unwrap(); 14 + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); 15 + 16 + let clocks = hal::clocks::init_clocks_and_plls( 17 + fw16_epd_bsp::XOSC_CRYSTAL_FREQ, 18 + pac.XOSC, 19 + pac.CLOCKS, 20 + pac.PLL_SYS, 21 + pac.PLL_USB, 22 + &mut pac.RESETS, 23 + &mut watchdog, 24 + ).unwrap(); 25 + 26 + loop { 27 + cortex_m::asm::wfi(); 28 + } 29 + }
+13
pervasive-spi/Cargo.toml
··· 1 + [package] 2 + name = "pervasive-spi" 3 + version = "0.1.0" 4 + edition = "2021" 5 + description = "Bitbang driver for the SPI-derived protocol used by Pervasive Displays e-paper displays" 6 + 7 + [dependencies] 8 + embedded-hal.workspace = true 9 + defmt.workspace = true 10 + rp2040-hal = { workspace = true, optional = true } 11 + 12 + [features] 13 + rp2040 = ["rp2040-hal"]
+182
pervasive-spi/src/lib.rs
··· 1 + //! Bitbanging driver for the SPI-derived protocol used by Pervasive Displays e-paper screens. 2 + 3 + #![no_std] 4 + 5 + #[cfg(feature = "rp2040")] 6 + pub mod rp2040; 7 + 8 + use defmt::trace; 9 + use embedded_hal::delay::DelayNs; 10 + use embedded_hal::digital::{InputPin, OutputPin, PinState}; 11 + 12 + /// A trait for pin types that allows running a closure with the pin configured as an input. 13 + pub trait WithInput { 14 + /// The type of the pin after it has been reconfigured to an input 15 + type Input: InputPin; 16 + 17 + /// Run the provided closure with the pin configured as an input. 18 + fn with_input<R>(&mut self, f: impl Fn(&mut Self::Input) -> R) -> R; 19 + } 20 + 21 + /// A trait for pin types that allows running a closure with the pin configured as an output. 22 + pub trait WithOutput { 23 + /// The type of the pin after it has been reconfigured to an output 24 + type Output: OutputPin; 25 + 26 + /// Run the provided closure with the pin configured as an output. 27 + fn with_output<R>(&mut self, f: impl Fn(&mut Self::Output) -> R) -> R; 28 + } 29 + 30 + /// A trait defining delays to be inserted at specific points in the bitbanging process. 31 + /// 32 + /// `DelayNs` is not used, because the delays involved are likely to be very short (up to and 33 + /// including a single NOP). It is intended that these functions be implemented with NOPs or 34 + /// functions such as `cortex_m::asm::delay`. Some of these delays may not be required on your 35 + /// hardware at all, in which case you can leave the implementation blank. It is recommended to 36 + /// mark implementations as `#[inline(always)]`. 37 + /// 38 + /// An implementation designed for use with the RP2040 with the TP370PGH01 display is provided 39 + /// in the `tp370pgh01` crate (with the `rp2040` feature enabled). 40 + pub trait PervasiveSpiDelays { 41 + fn delay_read_after_sck_high(&self); 42 + fn delay_read_after_sck_low(&self); 43 + fn delay_read_after_byte(&self); 44 + 45 + fn delay_write_after_sda_set(&self); 46 + fn delay_write_after_sck_high(&self); 47 + fn delay_write_after_sck_low(&self); 48 + fn delay_write_after_byte(&self); 49 + } 50 + 51 + /// A bitbanging driver for the SPI-derived protocol used by Pervasive Displays e-paper screens. 52 + pub struct PervasiveSpi<Cs, Sda, Sck, Dc, Delay> { 53 + cs: Cs, 54 + sda: Sda, 55 + sck: Sck, 56 + dc: Dc, 57 + delay: Delay, 58 + } 59 + 60 + impl<Cs, Sda, Sck, Dc, Delay, Error> PervasiveSpi<Cs, Sda, Sck, Dc, Delay> 61 + where 62 + Cs: OutputPin<Error = Error>, 63 + Sda: WithInput + WithOutput, 64 + <Sda as WithInput>::Input: InputPin<Error = Error>, 65 + <Sda as WithOutput>::Output: OutputPin<Error = Error>, 66 + Sck: OutputPin<Error = Error>, 67 + Dc: OutputPin<Error = Error>, 68 + Delay: PervasiveSpiDelays, 69 + { 70 + /// Create a new instance. 71 + /// 72 + /// * `cs`: The chip select pin (output). 73 + /// * `sda`: The SDA pin. The type must implement both the `WithInput` and `WithOutput` traits. 74 + /// An implementation for both of these on the RP2040 is provided if the `rp2040` 75 + /// feature is enabled. For internal reasons, the trait is implemented on `Option<Pin>` 76 + /// instead of `Pin` on the RP2040, but reading and writing will panic if `None` is 77 + /// provided. 78 + /// * `scl`: The SCL pin (output). 79 + /// * `dc`: The D/C (data/command) pin (output). 80 + /// * `delay`: An object implementing `PervasiveSpiDelays`, describing delays to be inserted at 81 + /// particular points in the bitbanging process. 82 + pub fn new(cs: Cs, sda: Sda, sck: Sck, dc: Dc, delay: Delay) -> Self { 83 + Self { 84 + cs, 85 + sda, 86 + sck, 87 + dc, 88 + delay, 89 + } 90 + } 91 + 92 + fn _write(&mut self, data: &[u8], register: bool, reverse_bits: bool) -> Result<(), Error> { 93 + if register { 94 + self.dc.set_low()?; 95 + } else { 96 + self.dc.set_high()?; 97 + } 98 + 99 + for &(mut byte) in data { 100 + self.cs.set_low()?; 101 + 102 + for i in 0..8 { 103 + let bit = if reverse_bits { 104 + byte & (1 << i) 105 + } else { 106 + byte & (1 << (7 - i)) 107 + }; 108 + let state = PinState::from(bit != 0); 109 + 110 + self.sda.with_output(|sda| sda.set_state(state))?; 111 + self.delay.delay_write_after_sda_set(); 112 + self.sck.set_high()?; 113 + self.delay.delay_write_after_sck_high(); 114 + self.sck.set_low()?; 115 + self.delay.delay_write_after_sck_low(); 116 + } 117 + 118 + self.dc.set_high()?; 119 + self.cs.set_high()?; 120 + self.delay.delay_write_after_byte(); 121 + } 122 + 123 + Ok(()) 124 + } 125 + 126 + /// Write the contents of `data` to the display, treating the first bytes in `data` as the 127 + /// register number. The D/C pin will be held low when sending the first byte, and high for 128 + /// the remaining bytes. 129 + pub fn write_register(&mut self, data: &[u8]) -> Result<(), Error> { 130 + self._write(data, true, false) 131 + } 132 + 133 + /// Write the contents of `data` to the display. All bytes in `data` are treated as data, i.e. 134 + /// the D/C pin will remain high for all bytes. 135 + pub fn write_data(&mut self, data: &[u8]) -> Result<(), Error> { 136 + self._write(data, false, false) 137 + } 138 + 139 + /// Write the contents of `data` to the display, reversing the order of the bits in each byte. 140 + /// 141 + /// This is useful for sending image data, as the more common way of encoding raw 1-bit mono 142 + /// images (e.g. with ImageMagick's MONO format) uses the opposite bit order from what Pervasive 143 + /// Displays screens expect. 144 + pub fn write_data_reversed(&mut self, data: &[u8]) -> Result<(), Error> { 145 + self._write(data, false, true) 146 + } 147 + 148 + /// Read data from the display into `buf`. 149 + /// 150 + /// All bytes in `buf` will be filled. It is the caller's responsibility to make sure `buf` is 151 + /// not longer than the number of bytes they want to read. 152 + pub fn read(&mut self, buf: &mut [u8]) -> Result<(), Error> { 153 + for byte in buf.iter_mut() { 154 + self.cs.set_low()?; 155 + 156 + for i in 0..8 { 157 + self.sck.set_high()?; 158 + self.delay.delay_read_after_sck_high(); 159 + *byte |= (self.sda.with_input(|sda| sda.is_high())? as u8) << (7 - i); 160 + self.sck.set_low()?; 161 + self.delay.delay_read_after_sck_low(); 162 + } 163 + 164 + trace!("read byte {}", byte); 165 + 166 + self.cs.set_high()?; 167 + self.delay.delay_read_after_byte(); 168 + } 169 + 170 + Ok(()) 171 + } 172 + 173 + /// Manually set the chip-select pin high. This is intended for performing hard display resets. 174 + pub fn set_cs_high(&mut self) -> Result<(), Error> { 175 + self.cs.set_high() 176 + } 177 + 178 + /// Manually et the chip-select pin low. This is intended for performing hard display resets. 179 + pub fn set_cs_low(&mut self) -> Result<(), Error> { 180 + self.cs.set_low() 181 + } 182 + }
+36
pervasive-spi/src/rp2040.rs
··· 1 + use crate::{WithInput, WithOutput}; 2 + use rp2040_hal::gpio::{ 3 + Function, FunctionSioInput, FunctionSioOutput, Pin, PinId, PullType, ValidFunction, 4 + }; 5 + 6 + impl<I, F, P> WithInput for Option<Pin<I, F, P>> 7 + where 8 + I: PinId + ValidFunction<FunctionSioInput> + ValidFunction<F>, 9 + F: Function, 10 + P: PullType, 11 + { 12 + type Input = Pin<I, FunctionSioInput, P>; 13 + 14 + fn with_input<R>(&mut self, f: impl Fn(&mut Self::Input) -> R) -> R { 15 + let mut input = self.take().unwrap().reconfigure(); 16 + let res = f(&mut input); 17 + self.replace(input.reconfigure()); 18 + res 19 + } 20 + } 21 + 22 + impl<I, F, P> WithOutput for Option<Pin<I, F, P>> 23 + where 24 + I: PinId + ValidFunction<FunctionSioOutput> + ValidFunction<F>, 25 + F: Function, 26 + P: PullType, 27 + { 28 + type Output = Pin<I, FunctionSioOutput, P>; 29 + 30 + fn with_output<R>(&mut self, f: impl Fn(&mut Self::Output) -> R) -> R { 31 + let mut output = self.take().unwrap().reconfigure(); 32 + let res = f(&mut output); 33 + self.replace(output.reconfigure()); 34 + res 35 + } 36 + }
+13
tp370pgh01/Cargo.toml
··· 1 + [package] 2 + name = "tp370pgh01" 3 + version = "0.1.0" 4 + edition = "2021" 5 + 6 + [dependencies] 7 + pervasive-spi = { path = "../pervasive-spi" } 8 + embedded-hal.workspace = true 9 + defmt.workspace = true 10 + cortex-m = { workspace = true, optional = true } 11 + 12 + [features] 13 + rp2040 = ["pervasive-spi/rp2040", "cortex-m"]
+292
tp370pgh01/src/lib.rs
··· 1 + //! Bitbanging driver for the Pervasive Displays TP370PGH01 display. 2 + 3 + #![no_std] 4 + 5 + #[cfg(feature = "rp2040")] 6 + pub mod rp2040; 7 + 8 + use core::error::Error; 9 + use core::fmt::{Debug, Display, Formatter}; 10 + use defmt::{debug, error, trace}; 11 + use embedded_hal::delay::DelayNs; 12 + use embedded_hal::digital::{InputPin, OutputPin}; 13 + use pervasive_spi::{PervasiveSpi, PervasiveSpiDelays, WithInput, WithOutput}; 14 + 15 + /// The horizontal size of the display in pixels. 16 + pub const DIM_X: usize = 240; 17 + /// The vertical size of the display in pixels. 18 + pub const DIM_Y: usize = 416; 19 + /// The number of bytes in an image frame sent to the display, equal to `(DIM_X * DIM_Y) / 8`. 20 + pub const IMAGE_BYTES: usize = (DIM_X * DIM_Y) / 8; 21 + 22 + /// An error that can occur communicating with the display. 23 + pub enum Tp370pgh01Error<GE> { 24 + /// An error occured reading or setting the GPIO pins, 25 + GpioError(GE), 26 + 27 + /// Reading the PSR value from the display's OTP memory failed. 28 + /// 29 + /// This usually indicates that there is a problem with the connection to the display, or 30 + /// not enough delays are being inserted. 31 + ReadPsrInvalid, 32 + } 33 + 34 + impl<GE> From<GE> for Tp370pgh01Error<GE> { 35 + fn from(value: GE) -> Self { 36 + Self::GpioError(value) 37 + } 38 + } 39 + 40 + impl<GE: Error> Debug for Tp370pgh01Error<GE> { 41 + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { 42 + match self { 43 + Self::GpioError(e) => write!(f, "GPIO error: {e:?}"), 44 + Self::ReadPsrInvalid => write!(f, "Could not find PSR in display OTP memory"), 45 + } 46 + } 47 + } 48 + 49 + impl<GE: Error> Display for Tp370pgh01Error<GE> { 50 + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { 51 + match self { 52 + Self::GpioError(e) => write!(f, "GPIO error: {e}"), 53 + Self::ReadPsrInvalid => write!(f, "Could not find PSR in display OTP memory"), 54 + } 55 + } 56 + } 57 + 58 + impl<GE: Error> Error for Tp370pgh01Error<GE> {} 59 + 60 + /// A bitbanging driver for the Pervasive Displays TP370PGH01 display. 61 + pub struct Tp370pgh01<Cs, Sda, Sck, Dc, Busy, Reset, Delay, SpiDelay> { 62 + spi: PervasiveSpi<Cs, Sda, Sck, Dc, SpiDelay>, 63 + busy: Busy, 64 + reset: Reset, 65 + delay: Delay, 66 + } 67 + 68 + impl<Cs, Sda, Sck, Dc, Busy, Reset, Delay, SpiDelay, Error> 69 + Tp370pgh01<Cs, Sda, Sck, Dc, Busy, Reset, Delay, SpiDelay> 70 + where 71 + Cs: OutputPin<Error = Error>, 72 + Sda: WithInput + WithOutput, 73 + <Sda as WithInput>::Input: InputPin<Error = Error>, 74 + <Sda as WithOutput>::Output: OutputPin<Error = Error>, 75 + Sck: OutputPin<Error = Error>, 76 + Dc: OutputPin<Error = Error>, 77 + Busy: InputPin<Error = Error>, 78 + Reset: OutputPin<Error = Error>, 79 + Delay: DelayNs, 80 + SpiDelay: PervasiveSpiDelays, 81 + { 82 + /// Create a new instance of the driver. 83 + /// 84 + /// * `cs` - Chip select pin (output). 85 + /// * `sda` - SDA pin (bidirectional). This must implement both the `WithInput` and `WithOutput` 86 + /// traits from the `pervasive-spi` crate. An implementation for the RP2040 is provided 87 + /// in that crate if the `rp2040` feature is enabled. Note that on the RP2040, the pin 88 + /// must be wrapped in the Option, but all display operations will panic if None is 89 + /// provided. 90 + /// * `sck` - SCK pin (output). 91 + /// * `dc` - D/C (data/command) pin (output). 92 + /// * `reset` - Display reset pin (output). 93 + /// * `delay` - An instance of `DelayNs`, used for timing some display commands such as resets. 94 + /// * `spi_delay` - An instance of `PervasiveSpiDelays` from the `pervasive-spi` crate. This 95 + /// crate provides an implementation for the RP2040 with this display if the 96 + /// `rp2040` feature is activated. 97 + pub fn new( 98 + cs: Cs, 99 + sda: Sda, 100 + sck: Sck, 101 + dc: Dc, 102 + busy: Busy, 103 + reset: Reset, 104 + delay: Delay, 105 + spi_delay: SpiDelay, 106 + ) -> Self { 107 + let spi = PervasiveSpi::new(cs, sda, sck, dc, spi_delay); 108 + Self { 109 + spi, 110 + busy, 111 + reset, 112 + delay, 113 + } 114 + } 115 + 116 + /// Hard-reset the display using the reset pin, then perform a soft reset. 117 + pub fn hard_reset(&mut self) -> Result<(), Tp370pgh01Error<Error>> { 118 + debug!("tp370pgh01: hard resetting display"); 119 + self.spi.set_cs_low()?; 120 + self.reset.set_high()?; 121 + self.delay.delay_ms(5); 122 + self.reset.set_low()?; 123 + self.delay.delay_ms(10); 124 + self.reset.set_high()?; 125 + self.delay.delay_ms(5); 126 + self.spi.set_cs_high()?; 127 + self.soft_reset()?; 128 + debug!("tp370pgh01: hard reset display"); 129 + 130 + Ok(()) 131 + } 132 + 133 + /// Perform a soft reset. 134 + pub fn soft_reset(&mut self) -> Result<(), Tp370pgh01Error<Error>> { 135 + debug!("tp370pgh01: soft resetting display"); 136 + self.spi.write_register(&[0x00, 0x0e])?; 137 + self.delay.delay_ms(5); 138 + debug!("tp370pgh01: soft reset display"); 139 + Ok(()) 140 + } 141 + 142 + fn get_psr(&mut self) -> Result<[u8; 2], Tp370pgh01Error<Error>> { 143 + debug!("tp370pgh01: reading PSR"); 144 + self.spi.write_register(&[0xa2])?; 145 + let mut buf = [0u8; 2]; 146 + self.delay.delay_ms(10); 147 + self.spi.read(&mut buf)?; 148 + 149 + let bank0 = buf[1] == 0xa5; 150 + debug!( 151 + "tp370pgh01: PSR is in bank {}", 152 + if bank0 { '0' } else { '1' } 153 + ); 154 + 155 + let offset_psr: u16 = if bank0 { 0x0fb4 } else { 0x1fb4 }; 156 + let offset_a5: u16 = if bank0 { 0x0000 } else { 0x1000 }; 157 + 158 + if offset_a5 > 0 { 159 + for _ in 1..offset_a5 { 160 + self.spi.read(&mut [0])?; 161 + } 162 + 163 + let mut buf = [0]; 164 + self.spi.read(&mut buf)?; 165 + if buf[0] != 0xa5 { 166 + error!("tp370pgh01: failed to find PSR"); 167 + return Err(Tp370pgh01Error::ReadPsrInvalid); 168 + } 169 + } 170 + 171 + for _ in offset_a5 + 1..offset_psr { 172 + self.spi.read(&mut [0])?; 173 + } 174 + 175 + self.spi.read(&mut buf)?; 176 + debug!("tp370pgh01: found PSR: {} {}", buf[0], buf[1]); 177 + 178 + Ok(buf) 179 + } 180 + 181 + fn busy_wait(&mut self) -> Result<(), Tp370pgh01Error<Error>> { 182 + trace!("tp370pgh01: busy waiting"); 183 + while self.busy.is_low()? {} 184 + Ok(()) 185 + } 186 + 187 + fn trigger_refresh(&mut self) -> Result<(), Tp370pgh01Error<Error>> { 188 + debug!("tp370pgh01: turning on DC/DC"); 189 + // turn on DC/DC 190 + self.spi.write_register(&[0x04])?; 191 + self.busy_wait()?; 192 + // refresh 193 + debug!("tp370pgh01: refreshing"); 194 + self.spi.write_register(&[0x12])?; 195 + self.busy_wait()?; 196 + // turn off DC/DC 197 + debug!("tp370pgh01: turning off DC/DC"); 198 + self.spi.write_register(&[0x02])?; 199 + self.busy_wait()?; 200 + 201 + Ok(()) 202 + } 203 + 204 + /// Refresh the display using a normal (non-fast) refresh. 205 + /// 206 + /// This type of refresh takes a couple of seconds, and the screen will flash between black and 207 + /// white multiple times before the image appears. 208 + /// 209 + /// * `image` - The image data to write to the display. 210 + /// * `temperature` - The ambient temperature in degrees Celsius. According to the display 211 + /// datasheet, this should be clamped between 0 and 60 degrees. 212 + pub fn refresh( 213 + &mut self, 214 + image: &[u8; IMAGE_BYTES], 215 + temperature: u8, 216 + ) -> Result<(), Tp370pgh01Error<Error>> { 217 + debug!("tp370pgh01: begin normal refresh"); 218 + 219 + let psr = self.get_psr()?; 220 + 221 + // set temperature 222 + self.spi.write_register(&[0xe5, temperature])?; 223 + self.spi.write_register(&[0xe0, 0x02])?; 224 + 225 + // set PSR 226 + self.spi.write_register(&[0x00])?; 227 + self.spi.write_data(&psr)?; 228 + 229 + // write image data 230 + debug!("tp370pgh01: writing image"); 231 + self.spi.write_register(&[0x10])?; 232 + self.spi.write_data_reversed(image)?; 233 + // write dummy data 234 + self.spi.write_register(&[0x13])?; 235 + for _ in 0..IMAGE_BYTES { 236 + self.spi.write_data(&[0x00])?; 237 + } 238 + debug!("tp370pgh01: finished writing image"); 239 + 240 + self.trigger_refresh()?; 241 + 242 + debug!("tp370pgh01: end normal refresh"); 243 + Ok(()) 244 + } 245 + 246 + /// Refresh the display using a fast refresh. 247 + /// 248 + /// This type of refresh takes around half a second and does not flash the screen, but is 249 + /// susceptible to ghosting. 250 + /// 251 + /// * `image` - The image data to write to the display. 252 + /// * `prev_image` The image data currently being displayed. 253 + /// * `temperature` - The ambient temperature in degrees Celsius. According to the display 254 + /// datasheet, this should be clamped between 0 and 60 degrees. 255 + pub fn refresh_fast( 256 + &mut self, 257 + image: &[u8; IMAGE_BYTES], 258 + prev_image: &[u8; IMAGE_BYTES], 259 + temperature: u8, 260 + ) -> Result<(), Tp370pgh01Error<Error>> { 261 + debug!("tp370pgh01: begin fast refresh"); 262 + 263 + let psr = self.get_psr()?; 264 + 265 + // set temperature 266 + self.spi.write_register(&[0xe5, temperature | 0x40])?; 267 + self.spi.write_register(&[0xe0, 0x02])?; 268 + // set PSR 269 + self.spi 270 + .write_register(&[0x00, psr[0] | 0x10, psr[1] | 0x02])?; 271 + // set "Vcom and data interval setting" 272 + self.spi.write_register(&[0x50, 0x07])?; 273 + 274 + // set "border setting" 275 + self.spi.write_register(&[0x50, 0x27])?; 276 + // send previous image 277 + debug!("tp370pgh01: writing image"); 278 + self.spi.write_register(&[0x10])?; 279 + self.spi.write_data_reversed(prev_image)?; 280 + // send new image 281 + self.spi.write_register(&[0x13])?; 282 + self.spi.write_data_reversed(image)?; 283 + debug!("tp370pgh01: finished writing image"); 284 + // set "border setting" 285 + self.spi.write_register(&[0x50, 0x07])?; 286 + 287 + self.trigger_refresh()?; 288 + 289 + debug!("tp370pgh01: end fast refresh"); 290 + Ok(()) 291 + } 292 + }
+40
tp370pgh01/src/rp2040.rs
··· 1 + use pervasive_spi::PervasiveSpiDelays; 2 + 3 + pub struct Rp2040PervasiveSpiDelays; 4 + 5 + impl PervasiveSpiDelays for Rp2040PervasiveSpiDelays { 6 + #[inline(always)] 7 + fn delay_read_after_sck_high(&self) { 8 + cortex_m::asm::delay(100); 9 + } 10 + 11 + #[inline(always)] 12 + fn delay_read_after_sck_low(&self) { 13 + cortex_m::asm::delay(100); 14 + } 15 + 16 + #[inline(always)] 17 + fn delay_read_after_byte(&self) { 18 + cortex_m::asm::delay(100); 19 + } 20 + 21 + #[inline(always)] 22 + fn delay_write_after_sda_set(&self) { 23 + // no delay 24 + } 25 + 26 + #[inline(always)] 27 + fn delay_write_after_sck_high(&self) { 28 + cortex_m::asm::nop(); 29 + } 30 + 31 + #[inline(always)] 32 + fn delay_write_after_sck_low(&self) { 33 + // no delay 34 + } 35 + 36 + #[inline(always)] 37 + fn delay_write_after_byte(&self) { 38 + // no delay 39 + } 40 + }