Skip to main content

pimoroni_tufty2040/
lib.rs

1#![no_std]
2
3pub extern crate rp2040_hal as hal;
4
5pub use hal::pac;
6
7use display_interface::{DataFormat, DisplayError, WriteOnlyDataCommand};
8use embedded_hal_0_2::digital::v2::OutputPin;
9use fugit::HertzU32;
10use hal::dma::{single_buffer, Channel, ChannelIndex, WriteTarget};
11use hal::gpio::PinId;
12use hal::pio::{
13    Buffers, PIOBuilder, PIOExt, PinDir, PinState, StateMachineIndex, Tx, UninitStateMachine,
14};
15use pio_proc::pio_file;
16
17#[cfg(feature = "rt")]
18pub use rp2040_hal::entry;
19
20/// The linker will place this boot block at the start of our program image. We
21/// need this to help the ROM bootloader get our code up and running.
22#[cfg(feature = "boot2")]
23#[link_section = ".boot2"]
24#[no_mangle]
25#[used]
26pub static BOOT2_FIRMWARE: [u8; 256] = rp2040_boot2::BOOT_LOADER_W25Q080;
27
28hal::bsp_pins!(
29    Gpio0 {
30        name: gpio0,
31        aliases: {
32            /// UART Function alias for pin [Pins::gpio0].
33            FunctionUart, PullNone: UartTx
34        }
35    },
36    Gpio1 {
37        name: gpio1,
38        aliases: {
39            /// UART Function alias for pin [Pins::gpio1].
40            FunctionUart, PullNone: UartRx
41        }
42    },
43    Gpio2 { name: lcd_backlight },
44    Gpio3 { name: i2c_int },
45    Gpio4 {
46        name: gpio4,
47        aliases: {
48            /// I2C Function alias for pin [Pins::gpio4].
49            FunctionI2C, PullUp: I2cSda
50        }
51    },
52    Gpio5 {
53        name: gpio5,
54        aliases: {
55            /// I2C Function alias for pin [Pins::gpio5].
56            FunctionI2C, PullUp: I2cScl
57        }
58    },
59    Gpio6 { name: sw_down },
60    Gpio7 { name: sw_a },
61    Gpio8 { name: sw_b },
62    Gpio9 { name: sw_c },
63    Gpio10 { name: lcd_cs },
64    Gpio11 { name: lcd_dc },
65    Gpio12 { name: lcd_wr },
66    Gpio13 { name: lcd_rd },
67    Gpio14 { name: lcd_db0 },
68    Gpio15 { name: lcd_db1 },
69    Gpio16 { name: lcd_db2 },
70    Gpio17 { name: lcd_db3 },
71    Gpio18 { name: lcd_db4 },
72    Gpio19 { name: lcd_db5 },
73    Gpio20 { name: lcd_db6 },
74    Gpio21 { name: lcd_db7 },
75    Gpio22 { name: sw_up },
76    Gpio23 { name: user_sw },
77    Gpio24 { name: vbus_detect },
78    Gpio25 { name: led },
79    Gpio26 { name: light_sense },
80    Gpio27 { name: sensor_power },
81    Gpio28 { name: vref_1v24 },
82    Gpio29 { name: vbat_sense },
83);
84
85pub const XOSC_CRYSTAL_FREQ: u32 = 12_000_000;
86
87#[inline]
88fn set_pin_bit<P: OutputPin>(pin: &mut P, bit: u8, value: u8) -> Result<(), DisplayError> {
89    pin.set_state(((bit & value) != 0).into())
90        .map_err(|_| DisplayError::BusWriteError)
91}
92
93struct WriteBytes<T>(T);
94
95// Allow DMA to do byte-size writes to an existing target,
96// SAFETY: This is only used with the PIO as a target, which is valid to write
97// byte-width.
98unsafe impl<T: WriteTarget> WriteTarget for WriteBytes<T> {
99    type TransmittedWord = u8;
100
101    #[inline]
102    fn tx_treq() -> Option<u8> {
103        T::tx_treq()
104    }
105
106    #[inline]
107    fn tx_address_count(&mut self) -> (u32, u32) {
108        self.0.tx_address_count()
109    }
110
111    #[inline]
112    fn tx_increment(&self) -> bool {
113        self.0.tx_increment()
114    }
115}
116
117pub trait DisplayDataLines {
118    fn flush(&mut self) {}
119
120    fn write_u8(&mut self, value: u8) -> Result<(), DisplayError>;
121
122    fn write_slice(&mut self, data: &[u8]) -> Result<(), DisplayError> {
123        for b in data.iter().copied() {
124            self.write_u8(b)?;
125        }
126
127        Ok(())
128    }
129
130    fn write_format(&mut self, data: DataFormat<'_>) -> Result<(), DisplayError> {
131        match data {
132            DataFormat::U8(bytes) => self.write_slice(bytes)?,
133            DataFormat::U16(items) => {
134                for value in items.iter().copied() {
135                    self.write_u8(value as u8)?;
136                    self.write_u8((value >> 8) as u8)?;
137                }
138            }
139            DataFormat::U16BE(items) => {
140                for value in items.iter().copied() {
141                    self.write_u8((value >> 8) as u8)?;
142                    self.write_u8(value as u8)?;
143                }
144            }
145            DataFormat::U16LE(items) => {
146                for value in items.iter().copied() {
147                    self.write_u8(value as u8)?;
148                    self.write_u8((value >> 8) as u8)?;
149                }
150            }
151            DataFormat::U8Iter(iter) => {
152                for value in iter {
153                    self.write_u8(value)?;
154                }
155            }
156            DataFormat::U16BEIter(iter) => {
157                for value in iter {
158                    self.write_u8((value >> 8) as u8)?;
159                    self.write_u8(value as u8)?;
160                }
161            }
162            DataFormat::U16LEIter(iter) => {
163                for value in iter {
164                    self.write_u8(value as u8)?;
165                    self.write_u8((value >> 8) as u8)?;
166                }
167            }
168            _ => unimplemented!(),
169        }
170
171        Ok(())
172    }
173}
174
175pub struct GpioDataLines<WR, D0, D1, D2, D3, D4, D5, D6, D7> {
176    pub wr: WR,
177    pub d0: D0,
178    pub d1: D1,
179    pub d2: D2,
180    pub d3: D3,
181    pub d4: D4,
182    pub d5: D5,
183    pub d6: D6,
184    pub d7: D7,
185}
186
187impl<
188        WR: OutputPin,
189        D0: OutputPin,
190        D1: OutputPin,
191        D2: OutputPin,
192        D3: OutputPin,
193        D4: OutputPin,
194        D5: OutputPin,
195        D6: OutputPin,
196        D7: OutputPin,
197    > GpioDataLines<WR, D0, D1, D2, D3, D4, D5, D6, D7>
198{
199    #[inline]
200    fn write_u8_inner(&mut self, value: u8) -> Result<(), DisplayError> {
201        set_pin_bit(&mut self.d0, value, 1 << 0)?;
202        set_pin_bit(&mut self.d1, value, 1 << 1)?;
203        set_pin_bit(&mut self.d2, value, 1 << 2)?;
204        set_pin_bit(&mut self.d3, value, 1 << 3)?;
205        set_pin_bit(&mut self.d4, value, 1 << 4)?;
206        set_pin_bit(&mut self.d5, value, 1 << 5)?;
207        set_pin_bit(&mut self.d6, value, 1 << 6)?;
208        set_pin_bit(&mut self.d7, value, 1 << 7)?;
209        Ok(())
210    }
211}
212
213impl<
214        WR: OutputPin,
215        D0: OutputPin,
216        D1: OutputPin,
217        D2: OutputPin,
218        D3: OutputPin,
219        D4: OutputPin,
220        D5: OutputPin,
221        D6: OutputPin,
222        D7: OutputPin,
223    > DisplayDataLines for GpioDataLines<WR, D0, D1, D2, D3, D4, D5, D6, D7>
224{
225    fn write_u8(&mut self, value: u8) -> Result<(), DisplayError> {
226        self.wr.set_low().map_err(|_| DisplayError::BusWriteError)?;
227        let err = self.write_u8_inner(value);
228        self.wr.set_high().ok();
229        err
230    }
231}
232
233type PioTx<P, SM, CH> = (Tx<(P, SM)>, Channel<CH>);
234
235pub struct PioDataLines<P: PIOExt, SM: StateMachineIndex, CH: ChannelIndex> {
236    tx: Option<PioTx<P, SM, CH>>,
237}
238
239impl<P: PIOExt, SM: StateMachineIndex, CH: ChannelIndex> PioDataLines<P, SM, CH> {
240    pub fn new(
241        pio: &mut hal::pio::PIO<P>,
242        sys_freq: HertzU32,
243        wr: impl PinId,
244        d0: impl PinId,
245        sm: UninitStateMachine<(P, SM)>,
246        ch: Channel<CH>,
247    ) -> PioDataLines<P, SM, CH> {
248        let d0 = d0.as_dyn().num;
249        let wr = wr.as_dyn().num;
250
251        let max_pio_clk = HertzU32::MHz(32);
252        let divider = (sys_freq + max_pio_clk - HertzU32::Hz(1)) / max_pio_clk;
253
254        let program = pio_file!("./src/st7789_parallel.pio");
255        let program = pio.install(&program.program).unwrap();
256        let (mut sm, _rx, tx) = PIOBuilder::from_installed_program(program)
257            .out_pins(d0, 8)
258            .side_set_pin_base(wr)
259            .buffers(Buffers::OnlyTx)
260            .pull_threshold(8)
261            .autopull(true)
262            .clock_divisor_fixed_point(divider as u16, 0)
263            .build(sm);
264        sm.set_pindirs([
265            (d0, PinDir::Output),
266            (d0 + 1, PinDir::Output),
267            (d0 + 2, PinDir::Output),
268            (d0 + 3, PinDir::Output),
269            (d0 + 4, PinDir::Output),
270            (d0 + 5, PinDir::Output),
271            (d0 + 6, PinDir::Output),
272            (d0 + 7, PinDir::Output),
273            (wr, PinDir::Output),
274        ]);
275        sm.set_pins([(wr, PinState::High)]);
276        sm.start();
277
278        PioDataLines { tx: Some((tx, ch)) }
279    }
280}
281
282impl<P: PIOExt, SM: StateMachineIndex, CH: ChannelIndex> DisplayDataLines
283    for PioDataLines<P, SM, CH>
284{
285    fn flush(&mut self) {
286        if let Some((tx, _)) = self.tx.as_mut() {
287            while !tx.is_empty() {}
288        }
289    }
290
291    fn write_u8(&mut self, value: u8) -> Result<(), DisplayError> {
292        if let Some((tx, _)) = self.tx.as_mut() {
293            while !tx.write(value as u32) {}
294            Ok(())
295        } else {
296            Err(DisplayError::BusWriteError)
297        }
298    }
299
300    fn write_slice(&mut self, data: &[u8]) -> Result<(), DisplayError> {
301        // SAFETY: transmute away lifetime, since we will always wait for DMA completion here.
302        let data: &'static [u8] = unsafe { core::mem::transmute(data) };
303
304        let (tx, ch) = self.tx.take().expect("DMA already in use");
305        let xfer = single_buffer::Config::new(ch, data, WriteBytes(tx)).start();
306        let (ch, _, WriteBytes(tx)) = xfer.wait();
307        self.tx = Some((tx, ch));
308        Ok(())
309    }
310}
311
312pub struct ParallelDisplayInterface<CS, DC, D> {
313    cs: CS,
314    dc: DC,
315    data_lines: D,
316}
317
318impl<CS: OutputPin, DC: OutputPin, D: DisplayDataLines> ParallelDisplayInterface<CS, DC, D> {
319    pub fn new(cs: CS, dc: DC, data_lines: D) -> ParallelDisplayInterface<CS, DC, D> {
320        ParallelDisplayInterface { cs, dc, data_lines }
321    }
322}
323
324impl<CS: OutputPin, DC: OutputPin, D: DisplayDataLines> WriteOnlyDataCommand
325    for ParallelDisplayInterface<CS, DC, D>
326{
327    fn send_commands(&mut self, cmds: DataFormat<'_>) -> Result<(), DisplayError> {
328        self.cs.set_low().map_err(|_| DisplayError::CSError)?;
329        self.dc.set_low().map_err(|_| DisplayError::DCError)?;
330
331        let err = self.data_lines.write_format(cmds);
332        self.data_lines.flush();
333
334        err
335    }
336
337    fn send_data(&mut self, buf: DataFormat<'_>) -> Result<(), DisplayError> {
338        self.dc.set_high().map_err(|_| DisplayError::DCError)?;
339
340        let err = self.data_lines.write_format(buf);
341        self.data_lines.flush();
342
343        err
344    }
345}
346
347pub struct DummyPin;
348
349impl OutputPin for DummyPin {
350    type Error = ();
351
352    fn set_high(&mut self) -> Result<(), Self::Error> {
353        Ok(())
354    }
355
356    fn set_low(&mut self) -> Result<(), Self::Error> {
357        Ok(())
358    }
359}