rpi_led_panel/
hardware_mapping.rs

1use std::{error::Error, ops::BitOr, str::FromStr};
2
3use crate::gpio_bits;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
6pub(crate) struct ColorBits {
7    pub(crate) r1: u32,
8    pub(crate) g1: u32,
9    pub(crate) b1: u32,
10    pub(crate) r2: u32,
11    pub(crate) g2: u32,
12    pub(crate) b2: u32,
13}
14
15impl ColorBits {
16    pub const fn unused() -> Self {
17        Self {
18            r1: 0,
19            g1: 0,
20            b1: 0,
21            r2: 0,
22            g2: 0,
23            b2: 0,
24        }
25    }
26
27    pub(crate) fn used_bits(&self) -> u32 {
28        self.r1 | self.r2 | self.g1 | self.g2 | self.b1 | self.b2
29    }
30
31    fn red_bits(&self) -> u32 {
32        self.r1 | self.r2
33    }
34
35    fn green_bits(&self) -> u32 {
36        self.g1 | self.g2
37    }
38
39    fn blue_bits(&self) -> u32 {
40        self.b1 | self.b2
41    }
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
45pub(crate) struct Panels {
46    pub(crate) color_bits: [ColorBits; 6],
47}
48
49impl Panels {
50    pub(crate) fn used_bits(&self) -> u32 {
51        self.red_bits() | self.green_bits() | self.blue_bits()
52    }
53
54    pub(crate) fn red_bits(&self) -> u32 {
55        self.color_bits
56            .iter()
57            .map(ColorBits::red_bits)
58            .fold(0, BitOr::bitor)
59    }
60
61    pub(crate) fn green_bits(&self) -> u32 {
62        self.color_bits
63            .iter()
64            .map(ColorBits::green_bits)
65            .fold(0, BitOr::bitor)
66    }
67
68    pub(crate) fn blue_bits(&self) -> u32 {
69        self.color_bits
70            .iter()
71            .map(ColorBits::blue_bits)
72            .fold(0, BitOr::bitor)
73    }
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
77pub struct HardwareMapping {
78    pub(crate) output_enable: u32,
79    pub(crate) clock: u32,
80    pub(crate) strobe: u32,
81
82    pub(crate) a: u32,
83    pub(crate) b: u32,
84    pub(crate) c: u32,
85    pub(crate) d: u32,
86    pub(crate) e: u32,
87
88    pub(crate) panels: Panels,
89}
90
91impl FromStr for HardwareMapping {
92    type Err = Box<dyn Error>;
93
94    fn from_str(s: &str) -> Result<Self, Self::Err> {
95        match s {
96            "AdafruitHat" => Ok(Self::adafruit_hat()),
97            "AdafruitHatPwm" => Ok(Self::adafruit_hat_pwm()),
98            "Regular" => Ok(Self::regular()),
99            "RegularPi1" => Ok(Self::regular_pi1()),
100            "Classic" => Ok(Self::classic()),
101            "ClassicPi1" => Ok(Self::classic_pi1()),
102            _ => Err(format!("'{s}' is not a valid GPIO mapping.").into()),
103        }
104    }
105}
106
107impl HardwareMapping {
108    pub(crate) fn used_bits(&self) -> u32 {
109        self.output_enable | self.clock | self.strobe | self.panels.used_bits()
110    }
111
112    /// Mask of bits while clocking in.
113    pub(crate) fn get_color_clock_mask(&self, parallel: usize) -> u32 {
114        let mut color_clk_mask: u32 = 0;
115        (0..6).for_each(|panel| {
116            if parallel > panel {
117                color_clk_mask |= &self.panels.color_bits[panel].used_bits();
118            }
119        });
120        color_clk_mask |= self.clock;
121        color_clk_mask
122    }
123
124    pub(crate) fn max_parallel_chains(&self) -> usize {
125        self.panels
126            .color_bits
127            .iter()
128            .filter(|p| p.used_bits() > 0)
129            .count()
130    }
131}
132
133impl HardwareMapping {
134    /// The regular hardware mapping used by the adapter PCBs.
135    #[must_use]
136    pub const fn regular() -> Self {
137        Self {
138            output_enable: gpio_bits!(18),
139            clock: gpio_bits!(17),
140            strobe: gpio_bits!(4),
141
142            a: gpio_bits!(22),
143            b: gpio_bits!(23),
144            c: gpio_bits!(24),
145            d: gpio_bits!(25),
146            e: gpio_bits!(15), // RxD kept free unless 1:64
147
148            panels: Panels {
149                color_bits: [
150                    /* Parallel chain 0, RGB for both sub-panels */
151                    ColorBits {
152                        r1: gpio_bits!(11), // masks: SPI0_SCKL
153                        g1: gpio_bits!(27), // Not on RPi1, Rev1; use "regular-pi1" instead
154                        b1: gpio_bits!(7),  // masks: SPI0_CE1
155                        r2: gpio_bits!(8),  // masks: SPI0_CE0
156                        g2: gpio_bits!(9),  // masks: SPI0_MISO
157                        b2: gpio_bits!(10), // masks: SPI0_MOSI
158                    },
159                    /* All the following are only available with 40 GPIP pins, on A+/B+/Pi2,3 */
160                    /* Chain 1 */
161                    ColorBits {
162                        r1: gpio_bits!(12),
163                        g1: gpio_bits!(5),
164                        b1: gpio_bits!(6),
165                        r2: gpio_bits!(19),
166                        g2: gpio_bits!(13),
167                        b2: gpio_bits!(20),
168                    },
169                    /* Chain 2 */
170                    ColorBits {
171                        r1: gpio_bits!(14), // masks TxD when parallel=3
172                        g1: gpio_bits!(2),  // masks SCL when parallel=3
173                        b1: gpio_bits!(3),  // masks SDA when parallel=3
174                        r2: gpio_bits!(26),
175                        g2: gpio_bits!(16),
176                        b2: gpio_bits!(21),
177                    },
178                    ColorBits::unused(),
179                    ColorBits::unused(),
180                    ColorBits::unused(),
181                ],
182            },
183        }
184    }
185
186    // An unmodified Adafruit HAT
187    #[must_use]
188    pub const fn adafruit_hat() -> Self {
189        Self {
190            output_enable: gpio_bits!(4),
191            clock: gpio_bits!(17),
192            strobe: gpio_bits!(21),
193
194            a: gpio_bits!(22),
195            b: gpio_bits!(26),
196            c: gpio_bits!(27),
197            d: gpio_bits!(20),
198            e: gpio_bits!(24), // Needs manual wiring
199
200            panels: Panels {
201                color_bits: [
202                    ColorBits {
203                        r1: gpio_bits!(5),
204                        g1: gpio_bits!(13),
205                        b1: gpio_bits!(6),
206                        r2: gpio_bits!(12),
207                        g2: gpio_bits!(16),
208                        b2: gpio_bits!(23),
209                    },
210                    ColorBits::unused(),
211                    ColorBits::unused(),
212                    ColorBits::unused(),
213                    ColorBits::unused(),
214                    ColorBits::unused(),
215                ],
216            },
217        }
218    }
219
220    // An Adafruit HAT with the PWM modification
221    #[must_use]
222    pub const fn adafruit_hat_pwm() -> Self {
223        Self {
224            output_enable: gpio_bits!(18),
225            ..Self::adafruit_hat()
226        }
227    }
228
229    /// The regular pin-out, but for Raspberry Pi1. The very first Pi1 Rev1 uses the same pin for GPIO-21 as
230    /// later Pis use GPIO-27. Make it work for both.
231    #[must_use]
232    pub const fn regular_pi1() -> Self {
233        Self {
234            output_enable: gpio_bits!(18),
235            clock: gpio_bits!(17),
236            strobe: gpio_bits!(4),
237
238            a: gpio_bits!(22),
239            b: gpio_bits!(23),
240            c: gpio_bits!(24),
241            d: gpio_bits!(25),
242            e: gpio_bits!(15), // RxD kept free unless 1:64
243
244            /* Parallel chain 0, RGB for both sub-panels */
245            panels: Panels {
246                color_bits: [
247                    ColorBits {
248                        // On Pi1 Rev1, the pin other Pis have GPIO27, these have GPIO21. So make this work for
249                        // both Rev1 and Rev2.
250                        r1: gpio_bits!(15, 27),
251                        g1: gpio_bits!(21),
252                        b1: gpio_bits!(7),  // masks: SPI0_CE1
253                        r2: gpio_bits!(8),  // masks: SPI0_CE0
254                        g2: gpio_bits!(9),  // masks: SPI0_MISO
255                        b2: gpio_bits!(10), // masks: SPI0_MOSI
256                    },
257                    // No more chains - there are not enough GPIO
258                    ColorBits::unused(),
259                    ColorBits::unused(),
260                    ColorBits::unused(),
261                    ColorBits::unused(),
262                    ColorBits::unused(),
263                ],
264            },
265        }
266    }
267
268    /// Early forms of this library had this as default mapping, mostly derived from the 26 GPIO-header
269    /// version so that it also can work on 40 Pin GPIO headers with more parallel chains. Not used anymore.
270    #[must_use]
271    pub const fn classic() -> Self {
272        Self {
273            output_enable: gpio_bits!(27), // Not available on RPi1, Rev 1
274            clock: gpio_bits!(11),
275            strobe: gpio_bits!(4),
276
277            a: gpio_bits!(7),
278            b: gpio_bits!(8),
279            c: gpio_bits!(9),
280            d: gpio_bits!(10),
281            e: 0,
282
283            panels: Panels {
284                color_bits: [
285                    ColorBits {
286                        r1: gpio_bits!(17),
287                        g1: gpio_bits!(18),
288                        b1: gpio_bits!(22),
289                        r2: gpio_bits!(23),
290                        g2: gpio_bits!(24),
291                        b2: gpio_bits!(25),
292                    },
293                    ColorBits {
294                        r1: gpio_bits!(12),
295                        g1: gpio_bits!(5),
296                        b1: gpio_bits!(6),
297                        r2: gpio_bits!(19),
298                        g2: gpio_bits!(13),
299                        b2: gpio_bits!(20),
300                    },
301                    ColorBits {
302                        r1: gpio_bits!(14), // masks TxD if parallel = 3
303                        g1: gpio_bits!(2),  // masks SDA if parallel = 3
304                        b1: gpio_bits!(3),  // masks SCL if parallel = 3
305                        r2: gpio_bits!(15),
306                        g2: gpio_bits!(26),
307                        b2: gpio_bits!(21),
308                    },
309                    ColorBits::unused(),
310                    ColorBits::unused(),
311                    ColorBits::unused(),
312                ],
313            },
314        }
315    }
316
317    /// Classic pin-out for Rev-A Raspberry Pi.
318    #[must_use]
319    pub const fn classic_pi1() -> Self {
320        Self {
321            // The Revision-1 and Revision-2 boards have different GPIO mapping on the P1-3 and P1-5. So we
322            // use both interpretations. To keep the I2C pins free, we avoid these in later mappings.
323            output_enable: gpio_bits!(0, 2),
324            clock: gpio_bits!(1, 3),
325            strobe: gpio_bits!(4),
326
327            a: gpio_bits!(7),
328            b: gpio_bits!(8),
329            c: gpio_bits!(9),
330            d: gpio_bits!(10),
331            e: 0,
332
333            panels: Panels {
334                color_bits: [
335                    ColorBits {
336                        r1: gpio_bits!(17),
337                        g1: gpio_bits!(18),
338                        b1: gpio_bits!(22),
339                        r2: gpio_bits!(23),
340                        g2: gpio_bits!(24),
341                        b2: gpio_bits!(25),
342                    },
343                    ColorBits::unused(),
344                    ColorBits::unused(),
345                    ColorBits::unused(),
346                    ColorBits::unused(),
347                    ColorBits::unused(),
348                ],
349            },
350        }
351    }
352}