inky_frame/
frame.rs

1// Permission is hereby granted, free of charge, to any person obtaining a copy
2// of this software and associated documentation files (the "Software"), to deal
3// in the Software without restriction, including without limitation the rights
4// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
5// copies of the Software, and to permit persons to whom the Software is
6// furnished to do so, subject to the following conditions:
7//
8// The above copyright notice and this permission notice shall be included in
9// all copies or substantial portions of the Software.
10//
11// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
12// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
13// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
15// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
16// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
17// SOFTWARE.
18//
19
20#![no_implicit_prelude]
21
22extern crate core;
23extern crate rpsp;
24
25use core::convert::{From, Into};
26use core::fmt::{self, Debug, Formatter};
27use core::iter::IntoIterator;
28use core::marker::{Send, Sized};
29use core::ops::{Deref, DerefMut, FnOnce};
30use core::option::Option::{self, None, Some};
31use core::result::Result::{self, Err, Ok};
32
33use rpsp::Board;
34use rpsp::pin::{Pin, PinID};
35use rpsp::spi::{Spi, SpiBus, SpiError};
36
37use self::tga::Pixel;
38
39mod color;
40mod display;
41mod shift;
42pub mod tga;
43
44#[cfg_attr(rustfmt, rustfmt_skip)]
45pub use self::color::*;
46pub use self::display::*;
47pub use self::shift::*;
48
49const DEFAULT_CLEAR: u8 = 0x11u8;
50
51pub enum InkyError {
52    Spi(SpiError),
53    NoMemory,
54    InvalidBusyPins,
55}
56#[repr(u8)]
57pub enum InkyRotation {
58    /// Buttons on Top
59    Rotate0   = 0u8,
60    /// Buttons on Left
61    Rotate90  = 1u8,
62    /// Buttons on Bottom
63    Rotate180 = 2u8,
64    /// Buttons on Right
65    Rotate270 = 3u8,
66}
67
68pub struct InkyPins {
69    pub tx:       PinID,
70    pub sck:      PinID,
71    pub cs:       PinID,
72    pub rst:      PinID,
73    pub data:     PinID,
74    pub busy_pin: Option<PinID>,
75    pub sr_data:  Option<PinID>,
76    pub sr_clock: Option<PinID>,
77    pub sr_latch: Option<PinID>,
78}
79#[repr(transparent)]
80pub struct Bytes<const N: usize>([u8; N]);
81pub struct Inky<'a, const B: usize, const W: u16, const H: u16, M: InkyMemory<B> = Bytes<B>> {
82    dis: Display<'a, W, H>,
83    buf: M,
84    rot: InkyRotation,
85}
86
87pub trait InkyMemory<const N: usize>: Sized + Deref<Target = [u8]> + DerefMut {
88    fn new() -> Option<Self>;
89}
90
91pub type Inky4<'a> = Inky<'a, 128_000, 640u16, 400u16>;
92pub type Inky5<'a> = Inky<'a, 134_400, 600u16, 448u16>;
93// NOTE(sf): ^This one might not not be correct, don't have the hardware to
94//           test!
95
96#[cfg(any(feature = "static", feature = "static_large"))]
97/// The static version uses the Static 'heaped' allocator, which removes the
98/// large stack allocation of this struct.
99///
100/// This can only be used when the "static" or "static_large" feature is
101/// enabled. It is recommended to only use "static" when using this struct as
102/// "static_large" wastes ~6k bytes for the larger buffer size, which is unused.
103pub type Inky4Static<'a> = Inky<'a, 128_000, 640u16, 400u16, heaped::Static<128_000>>;
104#[cfg(feature = "static_large")]
105/// The static version uses the Static 'heaped' allocator, which removes the
106/// large stack allocation of this struct.
107///
108/// This can only be used when the "static_large" feature is enabled.
109pub type Inky5Static<'a> = Inky<'a, 134_400, 600u16, 448u16, heaped::Static<134_400>>;
110
111impl InkyPins {
112    #[inline(always)]
113    pub const fn inky_frame4() -> InkyPins {
114        InkyPins {
115            tx:       PinID::Pin19,
116            sck:      PinID::Pin18,
117            cs:       PinID::Pin17,
118            rst:      PinID::Pin27,
119            data:     PinID::Pin28,
120            busy_pin: None,
121            sr_data:  Some(PinID::Pin10),
122            sr_clock: Some(PinID::Pin8),
123            sr_latch: Some(PinID::Pin9),
124        }
125    }
126
127    #[inline]
128    pub const fn reset(mut self, p: PinID) -> InkyPins {
129        self.rst = p;
130        self
131    }
132    #[inline]
133    pub const fn spi_tx(mut self, p: PinID) -> InkyPins {
134        self.tx = p;
135        self
136    }
137    #[inline]
138    pub const fn spi_cs(mut self, p: PinID) -> InkyPins {
139        self.cs = p;
140        self
141    }
142    #[inline]
143    pub const fn spi_sck(mut self, p: PinID) -> InkyPins {
144        self.sck = p;
145        self
146    }
147    #[inline]
148    pub const fn busy_pin(mut self, p: PinID) -> InkyPins {
149        self.busy_pin = Some(p);
150        self
151    }
152    #[inline]
153    pub const fn shift_register(mut self, clock: PinID, latch: PinID, data: PinID) -> InkyPins {
154        self.sr_data = Some(data);
155        self.sr_clock = Some(clock);
156        self.sr_latch = Some(latch);
157        self
158    }
159
160    #[inline]
161    fn signal(self, p: &Board) -> Result<BusySignal, InkyError> {
162        if let Some(v) = self.busy_pin {
163            return Ok(BusySignal::Pin(Pin::get(p, v).into_input()));
164        }
165        match (self.sr_clock, self.sr_latch, self.sr_data) {
166            (Some(c), Some(l), Some(d)) => Ok(BusySignal::SR(ShiftRegister::new(p, c, l, d))),
167            _ => Err(InkyError::InvalidBusyPins),
168        }
169    }
170}
171impl<const B: usize, const W: u16, const H: u16, M: InkyMemory<B>> Inky<'_, B, W, H, M> {
172    #[inline]
173    pub fn create(p: &Board, cfg: InkyPins) -> Result<Inky<B, W, H, M>, InkyError> {
174        Ok(Inky {
175            dis: Display::create(
176                p,
177                cfg.tx,
178                cfg.sck,
179                cfg.cs,
180                cfg.rst,
181                cfg.data,
182                cfg.signal(p)?,
183            )
184            .map_err(InkyError::Spi)?,
185            buf: M::new().ok_or(InkyError::NoMemory)?,
186            rot: InkyRotation::Rotate0,
187        })
188    }
189    #[inline]
190    pub fn new<'a>(p: &Board, spi: impl Into<SpiBus<'a>>, cfg: InkyPins) -> Result<Inky<'a, B, W, H, M>, InkyError> {
191        Ok(Inky {
192            dis: Display::new(p, spi.into(), cfg.cs, cfg.rst, cfg.data, cfg.signal(p)?),
193            buf: M::new().ok_or(InkyError::NoMemory)?,
194            rot: InkyRotation::Rotate0,
195        })
196    }
197
198    #[inline(always)]
199    pub fn off(&mut self) {
200        self.dis.off();
201    }
202    #[inline]
203    pub fn clear(&mut self) {
204        self.buf.fill(DEFAULT_CLEAR);
205        self.dis.update(&self.buf);
206    }
207    #[inline(always)]
208    pub fn update(&mut self) {
209        self.dis.update(&self.buf)
210    }
211    #[inline(always)]
212    pub fn width(&self) -> u16 {
213        self.dis.width()
214    }
215    #[inline(always)]
216    pub fn height(&self) -> u16 {
217        self.dis.height()
218    }
219    #[inline(always)]
220    pub fn is_busy(&self) -> bool {
221        self.dis.is_busy()
222    }
223    #[inline(always)]
224    pub fn is_ready(&self) -> bool {
225        self.dis.is_ready()
226    }
227    #[inline(always)]
228    pub fn set_fill(&mut self, c: Color) {
229        self.buf.fill(c as u8);
230    }
231    #[inline(always)]
232    pub fn spi_bus(&mut self) -> &mut Spi {
233        self.dis.spi_bus()
234    }
235    #[inline(always)]
236    pub fn set_rotation(&mut self, r: InkyRotation) {
237        self.rot = r;
238    }
239    pub fn set_pixel(&mut self, x: u16, y: u16, c: Color) {
240        if !self.in_bounds(x, y) {
241            return;
242        }
243        let (i, v) = self.index(x, y);
244        if let Some(p) = self.buf.get_mut(i) {
245            *p = (*p & if v { 0xF } else { 0xF0 }) | if v { (c as u8) << 4 } else { c as u8 };
246        }
247    }
248    #[inline(always)]
249    pub fn shift_register(&self) -> Option<&ShiftRegister> {
250        self.dis.shift_register()
251    }
252    pub fn set_pixel_raw(&mut self, x: u16, y: u16, c: u32) {
253        if !self.in_bounds(x, y) {
254            return;
255        }
256        let d = dither(x, y, c);
257        let (i, v) = self.index(x, y);
258        if let Some(p) = self.buf.get_mut(i) {
259            *p = (*p & if v { 0xF } else { 0xF0 }) | if v { d << 4 } else { d };
260        }
261    }
262    #[inline(always)]
263    pub fn set_pixel_color(&mut self, x: u16, y: u16, c: RGB) {
264        self.set_pixel_raw(x, y, c.uint());
265    }
266    #[inline(always)]
267    pub fn set_with<E>(&mut self, func: impl FnOnce(&mut Inky<'_, B, W, H, M>) -> Result<(), E>) -> Result<(), E> {
268        func(self)
269    }
270    pub fn set_image<E>(&mut self, x: i32, y: i32, image: impl IntoIterator<Item = Result<Pixel, E>>) -> Result<(), E> {
271        // NOTE(sf): We don't bounds check here as we could offset images into
272        //            non-visible space to only show a part of them. It won't get
273        //            rendered anyway.
274        for e in image {
275            let r = e?;
276            if r.is_transparent() {
277                continue;
278            }
279            let (j, k) = (x + r.x, y + r.y);
280            if (j < 0) || (k < 0) {
281                continue;
282            }
283            let (f, g) = (j as u16, k as u16);
284            // NOTE(sf): Bounds check is down here.
285            if !self.in_bounds(f, g) {
286                continue;
287            }
288            let d = dither(f, g, r.color);
289            let (i, v) = self.index(f, g);
290            if let Some(p) = self.buf.get_mut(i) {
291                *p = (*p & if v { 0xF } else { 0xF0 }) | if v { d << 4 } else { d };
292            }
293        }
294        Ok(())
295    }
296
297    /// Returns immediately, the user must issue a
298    /// POF command using the 'off' function once
299    /// the display refresh is complete.
300    #[inline(always)]
301    pub unsafe fn update_async(&mut self) {
302        unsafe { self.dis.update_async(&self.buf) }
303    }
304
305    #[inline(always)]
306    fn in_bounds(&self, x: u16, y: u16) -> bool {
307        match self.rot {
308            InkyRotation::Rotate0 | InkyRotation::Rotate180 if x >= W || y >= H => false,
309            InkyRotation::Rotate90 | InkyRotation::Rotate270 if y >= W || x >= H => false,
310            _ => true,
311        }
312    }
313    #[inline]
314    fn index(&self, x: u16, y: u16) -> (usize, bool) {
315        let (q, w) = match self.rot {
316            InkyRotation::Rotate0 => (x, y),
317            InkyRotation::Rotate90 => (W - 1 - y, x),
318            InkyRotation::Rotate180 => (W - 1 - x, H - 1 - y),
319            InkyRotation::Rotate270 => (y, H - 1 - x),
320        };
321        (q as usize / 2 + (W as usize / 2) * w as usize, q & 0x1 != 0)
322    }
323}
324
325impl<const N: usize> Deref for Bytes<N> {
326    type Target = [u8];
327
328    #[inline(always)]
329    fn deref(&self) -> &[u8] {
330        &self.0
331    }
332}
333impl<const N: usize> DerefMut for Bytes<N> {
334    #[inline(always)]
335    fn deref_mut(&mut self) -> &mut [u8] {
336        &mut self.0
337    }
338}
339impl<const N: usize> InkyMemory<N> for Bytes<N> {
340    #[inline(always)]
341    fn new() -> Option<Bytes<N>> {
342        Some(Bytes([0u8; N]))
343    }
344}
345
346impl From<u8> for InkyRotation {
347    #[inline(always)]
348    fn from(v: u8) -> InkyRotation {
349        match v {
350            1 => InkyRotation::Rotate90,
351            2 => InkyRotation::Rotate180,
352            3 => InkyRotation::Rotate270,
353            _ => InkyRotation::Rotate0,
354        }
355    }
356}
357
358unsafe impl<const B: usize, const W: u16, const H: u16, M: InkyMemory<B>> Send for Inky<'_, B, W, H, M> {}
359
360#[cfg(feature = "debug")]
361impl Debug for InkyError {
362    #[inline]
363    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
364        match self {
365            InkyError::Spi(v) => f.debug_tuple("Spi").field(v).finish(),
366            InkyError::NoMemory => f.write_str("NoMemory"),
367            InkyError::InvalidBusyPins => f.write_str("InvalidBusyPins"),
368        }
369    }
370}
371#[cfg(not(feature = "debug"))]
372impl Debug for InkyError {
373    #[inline(always)]
374    fn fmt(&self, _f: &mut Formatter<'_>) -> fmt::Result {
375        Ok(())
376    }
377}
378
379#[cfg(any(feature = "static", feature = "static_large"))]
380pub mod heaped {
381    extern crate core;
382    extern crate rpsp;
383
384    use core::cell::UnsafeCell;
385    use core::marker::Sync;
386    use core::mem::forget;
387    use core::ops::{Deref, DerefMut, Drop};
388    use core::option::Option;
389    use core::ptr::NonNull;
390    use core::slice::{from_raw_parts, from_raw_parts_mut};
391
392    use rpsp::locks::Spinlock27;
393
394    use crate::frame::InkyMemory;
395
396    static INSTANCE: Inner = Inner::new();
397
398    /// Push the allocation for the memory backend to a static memory instead of
399    /// the stack, which saves room for other things.
400    pub struct Static<const N: usize>(NonNull<u8>);
401
402    struct Inner(UnsafeCell<[u8; Inner::SIZE]>);
403
404    impl Inner {
405        const SIZE: usize = if cfg!(feature = "static_large") { 0x20D00usize } else { 0x1F400usize };
406
407        #[inline(always)]
408        const fn new() -> Inner {
409            Inner(UnsafeCell::new([0u8; Inner::SIZE]))
410        }
411    }
412
413    impl<const N: usize> Drop for Static<N> {
414        #[inline(always)]
415        fn drop(&mut self) {
416            unsafe { Spinlock27::free() }
417        }
418    }
419    impl<const N: usize> Deref for Static<N> {
420        type Target = [u8];
421
422        #[inline(always)]
423        fn deref(&self) -> &[u8] {
424            unsafe { from_raw_parts(self.0.as_ptr(), N) }
425        }
426    }
427    impl<const N: usize> DerefMut for Static<N> {
428        #[inline(always)]
429        fn deref_mut(&mut self) -> &mut [u8] {
430            unsafe { from_raw_parts_mut(self.0.as_ptr(), N) }
431        }
432    }
433    impl<const N: usize> InkyMemory<N> for Static<N> {
434        #[inline(always)]
435        fn new() -> Option<Static<N>> {
436            Spinlock27::try_claim().map(|v| {
437                let r = Static(unsafe { NonNull::new_unchecked(INSTANCE.0.get() as *mut u8) });
438                forget(v);
439                r
440            })
441        }
442    }
443
444    unsafe impl Sync for Inner {}
445}