epd_gde021a1/
lib.rs

1//! A simple Driver for the DGE021A1 ePaper Controller (172x72 B/W via SPI)
2//! - Built using [`embedded-hal`] traits.
3//! - Graphics support is added through [`embedded-graphics`] crate
4//!
5//! [`embedded-graphics`]: https://docs.rs/embedded-graphics/
6//! [`embedded-hal`]: https://docs.rs/embedded-hal
7//!
8//! # Examples
9//!
10//! One full example can be found in
11//! [the examples/ folder](https://github.com/almedso/epd-gde021a21/blob/master/examples)
12//!
13//! Firstly initialize the display ...
14//!
15//! ```rust
16//!     use stm32l0xx_hal::{pac, gpio::*, prelude::*, spi::*, rcc::{Config,RccExt}};
17//!     use epd_gde021A1::GDE021A1;
18//!
19//!     let dp = pac::Peripherals::take().unwrap();
20//!     let cp = cortex_m::Peripherals::take().unwrap();
21//!
22//!     // Configure the clock
23//!     let mut rcc = dp.RCC.freeze(Config::hsi16());
24//!
25//!     // Acquire the GPIOx peripheral.
26//!     // This also enables the clock for GPIOx in the RCC register.
27//!     let gpioa = dp.GPIOA.split(&mut rcc);
28//!     let gpiob = dp.GPIOB.split(&mut rcc);
29//!
30//!     // Configure the pins
31//!     let chip_sel = gpioa.pa15.into_push_pull_output();
32//!     let data_cmd = gpiob.pb11.into_push_pull_output();
33//!     let reset = gpiob.pb2.into_push_pull_output();
34//!     let busy = gpiob.pb8.into_pull_up_input();
35//!
36//!     // Configure the SPI
37//!     let mosi = gpiob.pb5;
38//!     let clk = gpiob.pb3;
39//!     let spi = dp.SPI1.spi((clk, NoMiso, mosi), MODE_0, 1_000_000.hz(), &mut rcc);
40//!
41//!     // Get the time delay
42//!     let mut delay = cp.SYST.delay(rcc.clocks);
43//!
44//!     // Finally initialize the display structure
45//!     let mut disp =  GDE021A1::new(spi, reset, Some(chip_sel), data_cmd, busy);
46//!     disp.init(&mut delay).expect("could not init display");
47//!
48//! ```
49//!
50//! Secondly use the created display by writing to the RAM buffer and finally refreshing the chip.
51//!
52//! ```rust
53//!     extern crate embedded_graphics;
54//!     use embedded_graphics::{
55//!         pixelcolor::BinaryColor,
56//!         style::{PrimitiveStyle, TextStyle},
57//!         primitives::Circle,
58//!         fonts::{Font6x8, Text},
59//!         prelude::*,
60//!     };
61//!
62//!     disp.clear();  // All pixels turn white - RAM buffer only
63//!
64//!     // Draw a circle on the RAM buffer
65//!     let elem =  Circle::new(Point::new(140, 36), 25)
66//!          .into_styled(PrimitiveStyle::with_fill(BinaryColor::On));
67//!     elem.draw(&mut disp);  // Draw inside the RAM buffer
68//!
69//!     // Draw some text
70//!     let elem = Text::new("Hello Rust!", Point::new(1, 8))
71//!         .into_styled(TextStyle::new(Font6x8, BinaryColor::On));
72//!     elem.draw(&mut disp);  // Draw inside the RAM buffer
73//!
74//!     // ePaper display needs to be refreshed  - write the RAM buffer to the chip
75//!     disp.refresh(&mut delay).expect("could not flush display");
76//!
77//! ```
78//! # Features
79//!
80//! ## `graphics` (enabled by default)
81//!
82//! Enable the `graphics` feature in `Cargo.toml` to get access to features in the
83//! [`embedded-graphics`] crate. This adds the `.draw()` method to the [`GDE021A1`] struct which
84//! accepts any `embedded-graphics` compatible item.
85//!
86//! [embedded-hal]: https://docs.rs/embedded-hal
87//! [`embedded-graphics`]: https://docs.rs/embedded-graphics
88
89#![crate_type = "lib"]
90#![cfg_attr(test, allow(unused_imports))]
91#![cfg_attr(not(test), no_std)]
92
93#![deny(missing_docs)]
94
95use core::convert::TryInto;
96use embedded_hal::{
97    blocking::delay::DelayUs, blocking::spi, digital::v2::InputPin, digital::v2::OutputPin,
98};
99use num_derive::ToPrimitive;
100
101#[derive(Debug)]
102/// Errors that can occur while using the display
103pub enum Error<CommError, PinError> {
104    /// SPI communication error
105    Comm(CommError),
106    /// GPIO Output error
107    Pin(PinError),
108}
109
110/// GDE021A1 instructions.
111#[derive(ToPrimitive)]
112enum Instruction {
113    GateDrivingVoltageControl = 0x03,
114    DeepSleepModeDisable = 0x10,
115    DateEntryModeSetting = 17,
116    MasterActivation = 0x20,
117    DisplayUpdateDisableRamBypass = 0x21,
118    DisplayUpdateControl2 = 0x22,
119    WriteRam = 0x24, // 36,
120    WriteVCOMRegister = 44,
121    WriteLUTRegister = 50,
122    BorderWaveform = 0x3C,
123    SetRamXStartEndAddress = 0x44, //68
124    SetRamYStartEndAddress = 69,
125    SetRamXAddressCounter = 0x4E,      // 78
126    SetRamYAddressCounter = 0x4F,      // 79
127    BoosterInternalFeedbackSel = 0xF0, // 240
128}
129
130#[derive(ToPrimitive, Clone, Copy)]
131/// The ePaper supports four grey levels per pixel as color
132pub enum Color {
133    #[doc(hidden)]
134    BLACK = 0b00,
135    #[doc(hidden)]
136    DARKGRAY = 0b01,
137    #[doc(hidden)]
138    LIGHTGRAY = 0b10,
139    #[doc(hidden)]
140    WHITE = 0b11,
141}
142
143/// Look-up table for the ePaper (90 bytes)
144const WF_LUT: [u8; 90] = [
145    0x82, 0x00, 0x00, 0x00, 0xAA, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0x00,
146    0x55, 0xAA, 0xAA, 0x00, 0x55, 0x55, 0x55, 0x55, 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
147    0xAA, 0xAA, 0xAA, 0xAA, 0x15, 0x15, 0x15, 0x15, 0x05, 0x05, 0x05, 0x05, 0x01, 0x01, 0x01, 0x01,
148    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
149    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
150    0x41, 0x45, 0xF1, 0xFF, 0x5F, 0x55, 0x01, 0x00, 0x00, 0x00,
151];
152
153// x dimension
154const WIDTH: u16 = 172;
155const GEOM_WIDTH: i32 = WIDTH as i32;
156// y dimension 4 Pixel are encoded by one byte
157// geometrical hight is 72 (i.e. 18 * 4 Pixel)
158const HEIGHT: u16 = 18;
159const GEOM_HEIGHT: i32 = (4 * HEIGHT) as i32;
160
161/// Shift pixels
162fn pixel_to_byte(position: usize, color: u8) -> u8 {
163    let p = position << 1;
164    color << p
165}
166
167/// Generate and-able mask for position
168fn position_mask(position: usize) -> u8 {
169    let val: u8 = 0b000_0011;
170    let p = position << 1;
171    !(val << p)
172}
173
174/// Compute corresponding byte
175fn overwrite_pixel_in_byte(byte: u8, position: usize, color: u8) -> u8 {
176    let val: u8 = byte & position_mask(position);
177    val | pixel_to_byte(position, color)
178}
179
180/// The ePaper GDE021A1 Driver data structure
181///
182/// It is composed of
183/// * GPIO pins to set/read
184/// * SPI interfacae to write data to
185/// * a RAM buffer of 3096 byte (172x72 pixel, two bit per pixel)
186///
187/// Draw something to the display as well as clearing the display
188/// happens in MCU RAM only. The physical display will only updated by the refresh
189/// method, since writing to the chip is resource consuming (communication, energy, etc.)
190///
191/// Drawing must be on the RAM cache of the MCU and not on the RAM cache of the chip
192/// because the manipulation of single pixels without affecting neighbor pixels must be supported
193/// by the embedded graphics trait and communicating with the chip only allows updates of a set of
194/// four pixels.
195///
196/// This driver supports four gray level pixel color
197/// * *embedded_graphics::pixelcolor::Gray2::new(0)* maps to *black*
198/// * *embedded_graphics::pixelcolor::Gray2::new(1)* maps to *dark gray*
199/// * *embedded_graphics::pixelcolor::Gray2::new(2)* maps to *light gray*
200/// * *embedded_graphics::pixelcolor::Gray2::new(3)* maps to *white*
201///
202/// This driver supports binary pixel color:
203/// * *embedded_graphics::pixelcolor::BinaryColor::On* maps to *black*
204/// * *embedded_graphics::pixelcolor::BinaryColor::Off* maps to *white*.
205///
206pub struct GDE021A1<SPI, RST, CS, DC, BSY>
207where
208    SPI: spi::Write<u8>,
209    RST: OutputPin,
210    CS: OutputPin,
211    DC: OutputPin,
212    BSY: InputPin,
213{
214    /// SPI interface
215    spi: SPI,
216    /// Reset pin.
217    rst: RST,
218    /// CS pin
219    cs: Option<CS>,
220    /// Busy pin
221    bsy: BSY,
222    /// Data Command selector pin
223    dc: DC,
224    // Buffer covering the RAM to drive single pixel
225    buffer: [u8; (WIDTH * HEIGHT) as usize],
226}
227
228impl<SPI, RST, CS, DC, BSY, PinError, SPIError> GDE021A1<SPI, RST, CS, DC, BSY>
229where
230    SPI: spi::Write<u8, Error = SPIError>,
231    RST: OutputPin<Error = PinError>,
232    CS: OutputPin<Error = PinError>,
233    DC: OutputPin<Error = PinError>,
234    BSY: InputPin<Error = PinError>,
235{
236    /// Create a new driver instance that uses SPI connection.
237    pub fn new(spi: SPI, rst: RST, cs: Option<CS>, dc: DC, bsy: BSY) -> Self {
238        GDE021A1 {
239            spi,
240            rst,
241            cs,
242            dc,
243            bsy,
244            buffer: [0xFF; (WIDTH * HEIGHT) as usize],
245        }
246    }
247
248    /// Initialize the display controller according to STM demo app
249    pub fn init(&mut self, delay: &mut dyn DelayUs<u32>) -> Result<(), Error<SPIError, PinError>> {
250        self.enable_cs(delay)?;
251        self.hard_reset(delay)?;
252        delay.delay_us(10_000); // 10 ms initial delay
253
254        self.write_command(Instruction::DeepSleepModeDisable)?;
255        self.write_data(0x00)?; // 0 means disable 1 would mean enable
256        self.write_command(Instruction::DateEntryModeSetting)?;
257        self.write_data(0x03)?;
258        self.write_command(Instruction::SetRamXStartEndAddress)?;
259        self.write_data(0x00)?; // RAM X address start = 00h
260                                // RAM X address end = 11h (17 * 4pixels by address = 72 pixels)
261        self.write_data(0x11)?;
262        self.write_command(Instruction::SetRamYStartEndAddress)?;
263        self.write_data(0x00)?; // RAM Y address start = 0
264        self.write_data(0xAB)?; // RAM Y address end = 171
265        self.write_command(Instruction::SetRamXAddressCounter)?;
266        self.write_data(0x00)?;
267        self.write_command(Instruction::SetRamYAddressCounter)?;
268        self.write_data(0x00)?;
269        self.write_command(Instruction::BoosterInternalFeedbackSel)?;
270        self.write_data(0x1F)?; // internal feedback is used
271        self.write_command(Instruction::DisplayUpdateDisableRamBypass)?;
272        // Disable RAM bypass and set GS transition to GSA = GS0 and GSB = GS3
273        self.write_data(0x03)?;
274        self.write_command(Instruction::WriteVCOMRegister)?;
275        self.write_data(0xA0)?;
276        self.write_command(Instruction::BorderWaveform)?;
277        self.write_data(0x64)?;
278        self.write_command(Instruction::WriteLUTRegister)?;
279        for elem in WF_LUT.iter() {
280            self.write_data(*elem)?;
281        }
282
283        self.disable_cs(delay)?;
284        Ok(())
285    }
286
287    /// Clear just resets the buffer to white
288    pub fn clear(&mut self) {
289        self.clear_with_color(Color::WHITE);
290    }
291
292    /// Flush buffer to update entire display
293    ///
294    /// The RAM buffer of the display on the MCU is transferred to the ePaper chip and
295    /// the update command sequence instructs the chip to physically present the new content.
296    /// The call is blocked until the busy pin signals completion of the operation of the physical
297    /// display
298    pub fn refresh(
299        &mut self,
300        delay: &mut dyn DelayUs<u32>,
301    ) -> Result<(), Error<SPIError, PinError>> {
302        self.write_buffer(delay)?;
303
304        self.enable_cs(delay)?;
305
306        self.write_command(Instruction::DisplayUpdateDisableRamBypass)?;
307        self.write_data(0x03)?; // data sequence option
308        self.write_command(Instruction::DisplayUpdateControl2)?;
309        self.write_data(0xC4)?; // data sequence option
310        self.write_command(Instruction::MasterActivation)?;
311
312        self.disable_cs(delay)?;
313        self.busy_wait();
314
315        self.close_charge_pump(delay)?;
316
317        Ok(())
318    }
319
320    /// Enable the chip and plan for some delay
321    fn enable_cs(&mut self, delay: &mut dyn DelayUs<u32>) -> Result<(), Error<SPIError, PinError>> {
322        if let Some(cs) = self.cs.as_mut() {
323            cs.set_low().map_err(Error::Pin)?;
324            delay.delay_us(100);
325        }
326        Ok(())
327    }
328
329    /// Disable the chip after some delay
330    fn disable_cs(
331        &mut self,
332        delay: &mut dyn DelayUs<u32>,
333    ) -> Result<(), Error<SPIError, PinError>> {
334        if let Some(cs) = self.cs.as_mut() {
335            delay.delay_us(100);
336            cs.set_high().map_err(Error::Pin)?;
337        }
338        Ok(())
339    }
340
341    /// Reset the chip
342    fn hard_reset(
343        &mut self,
344        delay: &mut dyn DelayUs<u32>,
345    ) -> Result<(), Error<SPIError, PinError>> {
346        self.rst.set_low().map_err(Error::Pin)?;
347        delay.delay_us(10_000);
348        self.rst.set_high().map_err(Error::Pin)?;
349        delay.delay_us(10_000);
350        Ok(())
351    }
352
353    fn write_command(&mut self, command: Instruction) -> Result<(), Error<SPIError, PinError>> {
354        self.dc.set_low().map_err(Error::Pin)?;
355        self.spi.write(&[command as u8]).map_err(Error::Comm)?;
356        Ok(())
357    }
358
359    fn write_data(&mut self, data: u8) -> Result<(), Error<SPIError, PinError>> {
360        self.dc.set_high().map_err(Error::Pin)?;
361        self.spi.write(&[data]).map_err(Error::Comm)?;
362        Ok(())
363    }
364
365    /// Clear just resets the buffer to a defined color
366    fn clear_with_color(&mut self, color: Color) {
367        let color: u8 = color as u8;
368        let val: u8 = pixel_to_byte(0, color);
369        let val: u8 = pixel_to_byte(1, color) | val;
370        let val: u8 = pixel_to_byte(2, color) | val;
371        let val: u8 = pixel_to_byte(3, color) | val;
372        for byte in self.buffer.iter_mut() {
373            *byte = val;
374        }
375    }
376
377    // Set the Display section to update
378    fn set_display_window(
379        &mut self,
380        x_pos: u8,
381        y_pos: u8,
382        width: u8,
383        height: u8,
384    ) -> Result<(), Error<SPIError, PinError>> {
385        /* Set Y position and the height */
386        self.write_command(Instruction::SetRamXStartEndAddress)?;
387        self.write_data(y_pos)?;
388        self.write_data(height)?;
389        /* Set X position and the width */
390        self.write_command(Instruction::SetRamYStartEndAddress)?;
391        self.write_data(x_pos)?;
392        self.write_data(width)?;
393        /* Set the height counter */
394        self.write_command(Instruction::SetRamXAddressCounter)?;
395        self.write_data(y_pos)?;
396        /* Set the width counter */
397        self.write_command(Instruction::SetRamYAddressCounter)?;
398        self.write_data(x_pos)?;
399        Ok(())
400    }
401
402    /// Write buffer
403    fn write_buffer(
404        &mut self,
405        delay: &mut dyn DelayUs<u32>,
406    ) -> Result<(), Error<SPIError, PinError>> {
407        self.enable_cs(delay)?;
408
409        // select the complete window and complete display buffer
410        self.set_display_window(0, 0, WIDTH as u8 - 1, HEIGHT as u8 - 1)?;
411        // Write the complete RAM
412        self.write_command(Instruction::WriteRam)?;
413        for elem in 0..self.buffer.len() {
414            self.write_data(self.buffer[elem])?;
415        }
416
417        self.disable_cs(delay)?;
418
419        Ok(())
420    }
421
422    /// Wait until busy flag is cleared by the chip
423    pub fn busy_wait(&self) {
424        while match self.bsy.is_low() {
425            Ok(x) => x,
426            _ => false,
427        } {}
428    }
429
430    /// Flush buffer to update entire display according to manual suggestion
431    fn close_charge_pump(
432        &mut self,
433        delay: &mut dyn DelayUs<u32>,
434    ) -> Result<(), Error<SPIError, PinError>> {
435        self.enable_cs(delay)?;
436
437        self.write_command(Instruction::DisplayUpdateControl2)?;
438        self.write_data(0x03)?; // Disable CP and disable clock signal
439        self.write_command(Instruction::MasterActivation)?;
440
441        self.disable_cs(delay)?;
442        delay.delay_us(400_000);
443
444        Ok(())
445    }
446
447    /// Initialize the display controller according to manual suggestion
448    pub fn alt_init(
449        &mut self,
450        delay: &mut dyn DelayUs<u32>,
451    ) -> Result<(), Error<SPIError, PinError>> {
452        self.enable_cs(delay)?;
453        self.hard_reset(delay)?;
454        delay.delay_us(1_000); // 10 ms initial delay
455
456        self.write_command(Instruction::DisplayUpdateDisableRamBypass)?;
457        self.write_data(0x8F)?;
458        self.write_command(Instruction::GateDrivingVoltageControl)?;
459        self.write_data(0x00)?;
460        self.write_command(Instruction::WriteRam)?;
461        for _i in 0..5760 {
462            self.write_data(0xFF)?;
463        }
464        self.write_data(0xF8)?;
465        self.write_command(Instruction::MasterActivation)?;
466
467        self.disable_cs(delay)?;
468        Ok(())
469    }
470}
471
472#[cfg(feature = "graphics")]
473extern crate embedded_graphics;
474#[cfg(feature = "graphics")]
475use self::embedded_graphics::{
476    prelude::RawData,
477    drawable,
478    geometry::Size,
479    pixelcolor::{BinaryColor, Gray2, raw::RawU2 },
480    DrawTarget
481};
482
483#[cfg(feature = "graphics")]
484impl<SPI, CS, RST, DC, BSY, PinError, SPIError> DrawTarget<Gray2>
485    for GDE021A1<SPI, CS, RST, DC, BSY>
486where
487    SPI: spi::Write<u8, Error = SPIError>,
488    RST: OutputPin<Error = PinError>,
489    CS: OutputPin<Error = PinError>,
490    DC: OutputPin<Error = PinError>,
491    BSY: InputPin<Error = PinError>,
492{
493    type Error = Error<SPIError, PinError>;
494
495    fn size(&self) -> Size {
496        Size::new(WIDTH.try_into().unwrap(), (4 * HEIGHT).try_into().unwrap())
497    }
498
499    fn draw_pixel(
500        &mut self,
501        pixel: drawable::Pixel<Gray2>,
502    ) -> Result<(), Error<SPIError, PinError>> {
503        let drawable::Pixel(coord, color) = pixel;
504        if let Ok((x @ 0..=GEOM_WIDTH, y @ 0..=GEOM_HEIGHT)) = coord.try_into() {
505            let r : RawU2 = color.into();
506            let c : u8 = r.into_inner();
507            // Convert to remainder for pixel position and base for y address
508            let p: usize = (y % 4).try_into().unwrap();
509            let p: usize = 3 - p;
510            let y: usize = (y / 4).try_into().unwrap();
511            // Mirror x to make text appear correctly
512            let x: usize = (GEOM_WIDTH - x).try_into().unwrap();
513            let byte: u8 = self.buffer[y + x * HEIGHT as usize];
514            self.buffer[y + x * HEIGHT as usize] = overwrite_pixel_in_byte(byte, p, c);
515        }
516        Ok(())
517    }
518}
519
520
521impl<SPI, CS, RST, DC, BSY, PinError, SPIError> DrawTarget<BinaryColor>
522    for GDE021A1<SPI, CS, RST, DC, BSY>
523where
524    SPI: spi::Write<u8, Error = SPIError>,
525    RST: OutputPin<Error = PinError>,
526    CS: OutputPin<Error = PinError>,
527    DC: OutputPin<Error = PinError>,
528    BSY: InputPin<Error = PinError>,
529{
530    type Error = Error<SPIError, PinError>;
531
532    fn size(&self) -> Size {
533        Size::new(WIDTH.try_into().unwrap(), (4 * HEIGHT).try_into().unwrap())
534    }
535
536    fn draw_pixel(
537        &mut self,
538        pixel: drawable::Pixel<BinaryColor>,
539    ) -> Result<(), Error<SPIError, PinError>> {
540        let drawable::Pixel(coord, color) = pixel;
541        if let Ok((x @ 0..=GEOM_WIDTH, y @ 0..=GEOM_HEIGHT)) = coord.try_into() {
542            let c: Color = match color {
543                BinaryColor::On => Color::BLACK,
544                BinaryColor::Off => Color::WHITE,
545            };
546            let c: u8 = c as u8;
547            // Convert to remainder for pixel position and base for y address
548            let p: usize = (y % 4).try_into().unwrap();
549            let p: usize = 3 - p;
550            let y: usize = (y / 4).try_into().unwrap();
551            // Mirror x to make text appear correctly
552            let x: usize = (GEOM_WIDTH - x).try_into().unwrap();
553            let byte: u8 = self.buffer[y + x * HEIGHT as usize];
554            self.buffer[y + x * HEIGHT as usize] = overwrite_pixel_in_byte(byte, p, c);
555        }
556        Ok(())
557    }
558}
559
560#[cfg(test)]
561mod tests {
562
563    use crate::{pixel_to_byte, position_mask, Color};
564
565    #[test]
566    fn it_should_convert_pixel_to_byte_at_zero_pos() {
567        assert_eq!(0b0000_0000, pixel_to_byte(0, Color::BLACK as u8));
568        assert_eq!(0b0000_0001, pixel_to_byte(0, Color::DARKGRAY as u8));
569        assert_eq!(0b0000_0010, pixel_to_byte(0, Color::LIGHTGRAY as u8));
570        assert_eq!(0b0000_0011, pixel_to_byte(0, Color::WHITE as u8));
571    }
572
573    #[test]
574    fn it_should_convert_pixel_to_byte_at_one_pos() {
575        assert_eq!(0b0000_0000, pixel_to_byte(1, Color::BLACK as u8));
576        assert_eq!(0b0000_0100, pixel_to_byte(1, Color::DARKGRAY as u8));
577        assert_eq!(0b0000_1000, pixel_to_byte(1, Color::LIGHTGRAY as u8));
578        assert_eq!(0b0000_1100, pixel_to_byte(1, Color::WHITE as u8));
579    }
580
581    #[test]
582    fn it_should_convert_pixel_to_byte_at_two_pos() {
583        assert_eq!(0b0000_0000, pixel_to_byte(2, Color::BLACK as u8));
584        assert_eq!(0b0001_0000, pixel_to_byte(2, Color::DARKGRAY as u8));
585        assert_eq!(0b0010_0000, pixel_to_byte(2, Color::LIGHTGRAY as u8));
586        assert_eq!(0b0011_0000, pixel_to_byte(2, Color::WHITE as u8));
587    }
588
589    #[test]
590    fn it_should_convert_pixel_to_byte_at_three_pos() {
591        assert_eq!(0b0000_0000, pixel_to_byte(3, Color::BLACK as u8));
592        assert_eq!(0b0100_0000, pixel_to_byte(3, Color::DARKGRAY as u8));
593        assert_eq!(0b1000_0000, pixel_to_byte(3, Color::LIGHTGRAY as u8));
594        assert_eq!(0b1100_0000, pixel_to_byte(3, Color::WHITE as u8));
595    }
596
597    #[test]
598    fn it_should_compute_a_position_mask() {
599        assert_eq!(0b1111_1100, position_mask(0));
600        assert_eq!(0b1111_0011, position_mask(1));
601        assert_eq!(0b1100_1111, position_mask(2));
602        assert_eq!(0b0011_1111, position_mask(3));
603    }
604}