hcs_12ss59t/
lib.rs

1#![doc = include_str!("../README.md")]
2#![no_std]
3#![cfg_attr(docsrs, feature(doc_cfg))]
4
5pub mod font;
6use core::fmt::Display;
7
8pub use font::FontTable;
9
10pub mod animation;
11
12use embedded_hal::digital::OutputPin;
13use embedded_hal::spi::{self};
14
15const NUM_DIGITS: usize = 12;
16
17#[repr(u8)]
18#[allow(dead_code)]
19enum Command {
20    DCRamWrite = 0x10,
21    CGRamWrite = 0x20,
22    ADRamWrite = 0x30,
23    DisplayDutySet = 0x50,
24    NumDigitsSet = 0x60,
25    Lights = 0x70,
26}
27#[repr(u8)]
28#[allow(dead_code)]
29enum Lights {
30    Normal = 0x00,
31    Off = 0x01,
32    On = 0x02,
33}
34
35#[derive(Clone, Copy, Debug)]
36pub enum Error<E: spi::Error> {
37    Spi(E),
38    Gpio,
39    InvalidInput,
40}
41impl<E: spi::Error> From<E> for Error<E> {
42    fn from(value: E) -> Self {
43        Error::Spi(value)
44    }
45}
46impl<E: spi::Error> Display for Error<E> {
47    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
48        match self {
49            Error::Spi(e) => write!(f, "SPI error: {e:#?}"),
50            Error::Gpio => write!(f, "GPIO error"),
51            Error::InvalidInput => write!(f, "invalid input parameter"),
52        }
53    }
54}
55impl<E: spi::Error> core::error::Error for Error<E> {}
56
57/// A HCS-12SS59T instance
58///
59/// A stateless driver to configure a HCS-12SS59T.
60/// Usage is straight forward and requires little setup.
61///
62/// ## Example
63/// ```no_run
64/// # fn run() -> Result<(), hcs_12ss59t::Error<embedded_hal::spi::ErrorKind>> {
65/// use hcs_12ss59t::HCS12SS59T;
66/// # use embedded_hal_mock::eh1::digital::{
67/// #     Mock as PinMock, State as PinState, Transaction as PinTransaction,
68/// # };
69/// # use embedded_hal_mock::eh1::spi::{Mock as SpiMock, Transaction as SpiTransaction};
70/// # let spi_expectations = [
71/// #     SpiTransaction::transaction_start(),
72/// # ];
73/// # let spi = SpiMock::new(&spi_expectations);
74/// # let n_reset_expectations = [
75/// #     PinTransaction::set(PinState::Low),
76/// # ];
77/// # let n_reset = PinMock::new(&n_reset_expectations);
78/// # let cs_expectations = [
79/// #     PinTransaction::set(PinState::Low),
80/// # ];
81/// # let cs = PinMock::new(&cs_expectations);
82/// # let vdon_expectations = [
83/// #     PinTransaction::set(PinState::Low),
84/// # ];
85/// # let n_vdon = PinMock::new(&vdon_expectations);
86/// # let delay = embedded_hal_mock::eh1::delay::NoopDelay::new();
87///
88/// let mut my_vfd = HCS12SS59T::new(spi, n_reset, delay, Some(n_vdon), cs);
89///
90/// my_vfd.init()?;
91/// my_vfd.brightness(5)?;
92/// my_vfd.display_str("Hello world!")?;
93///
94/// let (spi, rst, delay, vdon, cs) = my_vfd.destroy();
95/// # Ok(())
96/// # }
97/// ```
98pub struct HCS12SS59T<SPI, RstPin, VdonPin, Delay, CsPin> {
99    spi: SPI,
100    n_reset: RstPin,
101    n_vdon: Option<VdonPin>,
102    delay: Delay,
103    cs: CsPin,
104}
105
106impl<SPI, RstPin, VdonPin, Delay, CsPin> HCS12SS59T<SPI, RstPin, VdonPin, Delay, CsPin>
107where
108    RstPin: OutputPin,
109    VdonPin: OutputPin,
110{
111    /// Constructs a new HCS12SS59T
112    ///
113    /// Initialization has to be done seperately by calling [init()](Self::init()).
114    ///
115    /// It is necessary to have a dedicated CS-Pin and a `Delay` due to timing restrictions of the HCS-12SS59T.
116    ///
117    /// Due to this, exclusive access to the SPI bus is necessary.
118    /// This is guaranteed by requiring a `SpiBus` rather than a `SpiDevice`.
119    ///
120    /// _Note_: If a shared bus is needed, this has to be implemented by the user
121    /// (by wrapping the HCS-112SS59T and a `SpiBus` and implementing a custom lock mechanism).
122    pub fn new(
123        spi: SPI,
124        n_reset: RstPin,
125        delay: Delay,
126        n_vdon: Option<VdonPin>,
127        cs: CsPin,
128    ) -> Self {
129        Self {
130            spi,
131            n_reset,
132            n_vdon,
133            delay,
134            cs,
135        }
136    }
137
138    pub fn destroy(self) -> (SPI, RstPin, Delay, Option<VdonPin>, CsPin) {
139        (self.spi, self.n_reset, self.delay, self.n_vdon, self.cs)
140    }
141
142    /// Turns the supply voltage off (if supply pin is configured)
143    pub fn vd_off(&mut self) -> Result<(), VdonPin::Error> {
144        if let Some(pin) = &mut self.n_vdon {
145            pin.set_high()?; // Display voltage OFF
146        }
147        Ok(())
148    }
149
150    /// Turns the supply voltage on (if supply pin is configured)
151    pub fn vd_on(&mut self) -> Result<(), VdonPin::Error> {
152        if let Some(pin) = &mut self.n_vdon {
153            pin.set_low()?; // Display voltage ON
154        }
155        Ok(())
156    }
157}
158
159#[cfg(any(not(feature = "async"), docsrs))]
160#[cfg_attr(docsrs, doc(cfg(not(feature = "async"))))]
161impl<SPI, RstPin, VdonPin, Delay, CsPin> HCS12SS59T<SPI, RstPin, VdonPin, Delay, CsPin>
162where
163    SPI: embedded_hal::spi::SpiBus,
164    RstPin: OutputPin,
165    VdonPin: OutputPin,
166    CsPin: OutputPin,
167    Delay: embedded_hal::delay::DelayNs,
168{
169    /// Initialize the VFD display
170    ///
171    /// Resets the display, turns on the supply voltage and sets brightness to 7.
172    pub fn init(&mut self) -> Result<(), Error<SPI::Error>> {
173        self.n_reset.set_low().map_err(|_| Error::Gpio)?;
174        self.delay.delay_us(25);
175        self.n_reset.set_high().map_err(|_| Error::Gpio)?;
176        self.delay.delay_us(5);
177
178        self.vd_on().map_err(|_| Error::Gpio)?;
179
180        self.send_cmd(Command::NumDigitsSet, NUM_DIGITS as u8)?;
181        self.send_cmd(Command::DisplayDutySet, 7)?;
182        self.send_cmd(Command::Lights, Lights::Normal as u8)?;
183
184        Ok(())
185    }
186
187    /// Set the brightness (duty cycle) of the Display
188    ///
189    /// Turns the display off when brightness is `0` and on when brightness is `1..15`.
190    pub fn brightness(&mut self, brightness: u8) -> Result<(), Error<SPI::Error>> {
191        match brightness {
192            0 => self.vd_off().map_err(|_| Error::Gpio),
193            1..=15 => {
194                self.vd_on().map_err(|_| Error::Gpio)?;
195                self.send_cmd(Command::DisplayDutySet, brightness)
196            }
197            _ => Err(Error::InvalidInput),
198        }
199    }
200
201    /// Send one command byte with with four bits argument payload
202    ///
203    /// (The higher four bit specify the command, the lower four bit are the argument)
204    fn send_cmd(&mut self, cmd: Command, arg: u8) -> Result<(), Error<SPI::Error>> {
205        let arg = arg & 0x0F;
206        let command = [cmd as u8 | arg];
207        self.cs.set_low().map_err(|_| Error::Gpio)?;
208        self.delay.delay_us(5);
209        self.spi.write(&command)?;
210        self.delay.delay_us(20);
211        self.cs.set_high().map_err(|_| Error::Gpio)?;
212        Ok(())
213    }
214
215    /// Write abritrary bytes to the display controller
216    pub fn write_buf(&mut self, buf: &[u8]) -> Result<(), Error<SPI::Error>> {
217        self.cs.set_low().map_err(|_| Error::Gpio)?;
218        self.delay.delay_us(1);
219        for byte in buf {
220            self.spi.write(&[*byte])?;
221            self.delay.delay_us(8);
222        }
223        self.delay.delay_us(12);
224        self.cs.set_high().map_err(|_| Error::Gpio)?;
225        Ok(())
226    }
227
228    /// Display a string
229    ///
230    /// Convenience method to avoid converting the string to a iterator first.
231    /// Characters are mapped using the internal [FontTable].
232    /// Strings are truncated to fit the display.
233    pub fn display_str(&mut self, text: &str) -> Result<(), Error<SPI::Error>> {
234        self.display(text.chars())
235    }
236
237    /// Write to the display RAM
238    ///
239    /// Displays the data, discarding unneded items.
240    ///
241    /// `From<char>` is implemented for [FontTable] so this method can
242    /// be called with strings by calling [chars()](::core::primitive::str::chars) on the string.
243    /// Alternatively [display_str](HCS12SS59T::display_str) does this for you.
244    pub fn display<T>(&mut self, data: T) -> Result<(), Error<SPI::Error>>
245    where
246        T: IntoIterator,
247        T::Item: Into<FontTable>,
248    {
249        let mut buf = [48_u8; NUM_DIGITS + 1];
250        buf[0] = Command::DCRamWrite as u8;
251
252        for (buf, c) in buf.iter_mut().skip(1).rev().zip(data.into_iter()) {
253            *buf = c.into() as u8;
254        }
255        self.cs.set_low().map_err(|_| Error::Gpio)?;
256        self.delay.delay_us(1);
257        for byte in buf {
258            self.spi.write(&[byte])?;
259            self.delay.delay_us(8);
260        }
261        self.delay.delay_us(12);
262        self.cs.set_high().map_err(|_| Error::Gpio)?;
263        Ok(())
264    }
265
266    /// Write a single character to display RAM
267    ///
268    /// The HCS-12SS59T has 16 byte DCRAM, from which 0..12 are usable for the 12 connected digits.
269    pub fn set_char<C: Into<FontTable>>(
270        &mut self,
271        addr: u8,
272        char: C,
273    ) -> Result<(), Error<SPI::Error>> {
274        let addr = addr & 0x0F;
275        let command = [Command::DCRamWrite as u8 | addr, char.into() as u8];
276
277        self.cs.set_low().map_err(|_| Error::Gpio)?;
278        self.delay.delay_us(1);
279        for byte in command {
280            self.spi.write(&[byte])?;
281            self.delay.delay_us(8);
282        }
283        self.delay.delay_us(12);
284        self.cs.set_high().map_err(|_| Error::Gpio)?;
285        Ok(())
286    }
287
288    /// Set character generator RAM
289    ///
290    /// Write a two byte character pattern to one of 16 CGRAM adresses.
291    ///
292    /// Valid address values are [FontTable::Ram0] to [FontTable::RamF]
293    ///
294    /// The pattern is specified with two bytes for 16 segments,
295    /// for a 14 segment display, segment 2 and 5 are don't care.
296    ///
297    /// |    Bit | 7     | 6     | 5     | 4     | 3     | 2     | 1     | 0    |
298    /// |-------:|-------|-------|-------|-------|-------|-------|-------|------|
299    /// | Byte 0 | SEG8  | SEG7  | SEG6  | SEG5  | SEG4  | SEG3  | SEG2  | SEG1 |
300    /// | Byte 1 | SEG16 | SEG15 | SEG14 | SEG13 | SEG12 | SEG11 | SEG10 | SEG9 |
301    ///
302    /// ``` text
303    ///   SEG1     SEG2
304    /// S S     S     0 3
305    /// E  E    E    1  G
306    /// G   G   G   G   E
307    /// 8    1  9  E    S
308    ///       6   S
309    ///   SEG15   SEG11
310    /// S     4 S S     4
311    /// E    1  E  E    G
312    /// G   G   G   G   E
313    /// 7  E    1    1  S
314    ///   S     3     2
315    ///   SEG6     SEG5
316    /// ```
317    pub fn set_cgram_pattern(
318        &mut self,
319        addr: FontTable,
320        pattern: [u8; 2],
321    ) -> Result<(), Error<SPI::Error>> {
322        use FontTable::*;
323        if !matches!(
324            addr,
325            Ram0 | Ram1
326                | Ram2
327                | Ram3
328                | Ram4
329                | Ram5
330                | Ram6
331                | Ram7
332                | Ram8
333                | Ram9
334                | RamA
335                | RamB
336                | RamC
337                | RamD
338                | RamE
339                | RamF
340        ) {
341            return Err(Error::InvalidInput);
342        }
343        let command = [
344            Command::CGRamWrite as u8 | addr as u8,
345            pattern[0],
346            pattern[1],
347        ];
348
349        self.cs.set_low().map_err(|_| Error::Gpio)?;
350        self.delay.delay_us(1);
351        for byte in command {
352            self.spi.write(&[byte])?;
353            self.delay.delay_us(8);
354        }
355        self.delay.delay_us(12);
356        self.cs.set_high().map_err(|_| Error::Gpio)?;
357        Ok(())
358    }
359}
360
361#[cfg(feature = "async")]
362#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
363impl<SPI, RstPin, VdonPin, Delay, CsPin> HCS12SS59T<SPI, RstPin, VdonPin, Delay, CsPin>
364where
365    SPI: embedded_hal_async::spi::SpiBus,
366    RstPin: OutputPin,
367    VdonPin: OutputPin,
368    CsPin: OutputPin,
369    Delay: embedded_hal_async::delay::DelayNs,
370{
371    /// Initialize the VFD display
372    ///
373    /// Resets the display, turns on the supply voltage and sets brightness to 7.
374    pub async fn init(&mut self) -> Result<(), Error<SPI::Error>> {
375        self.n_reset.set_low().map_err(|_| Error::Gpio)?;
376        self.delay.delay_us(25).await;
377        self.n_reset.set_high().map_err(|_| Error::Gpio)?;
378        self.delay.delay_us(5).await;
379
380        self.vd_on().map_err(|_| Error::Gpio)?;
381
382        self.send_cmd(Command::NumDigitsSet, NUM_DIGITS as u8)
383            .await?;
384        self.send_cmd(Command::DisplayDutySet, 7).await?;
385        self.send_cmd(Command::Lights, Lights::Normal as u8).await?;
386
387        Ok(())
388    }
389
390    /// Send one command byte with with four bits argument payload
391    ///
392    /// (The higher four bit specify the command, the lower four bit are the argument)
393    async fn send_cmd(&mut self, cmd: Command, arg: u8) -> Result<(), Error<SPI::Error>> {
394        let arg = arg & 0x0F;
395        let command = [cmd as u8 | arg];
396        self.cs.set_low().map_err(|_| Error::Gpio)?;
397        self.delay.delay_us(5).await;
398        self.spi.write(&command).await?;
399        self.delay.delay_us(20).await;
400        self.cs.set_high().map_err(|_| Error::Gpio)?;
401        Ok(())
402    }
403
404    /// Set the brightness (duty cycle) of the Display
405    ///
406    /// Turns the display off when brightness is `0` and on when brightness is `1..15`.
407    pub async fn brightness(&mut self, brightness: u8) -> Result<(), Error<SPI::Error>> {
408        match brightness {
409            0 => self.vd_off().map_err(|_| Error::Gpio),
410            1..=15 => {
411                self.vd_on().map_err(|_| Error::Gpio)?;
412                self.send_cmd(Command::DisplayDutySet, brightness).await
413            }
414            _ => Err(Error::InvalidInput),
415        }
416    }
417
418    /// Write abritrary bytes to the display controller
419    pub async fn write_buf(&mut self, buf: &[u8]) -> Result<(), Error<SPI::Error>> {
420        self.cs.set_low().map_err(|_| Error::Gpio)?;
421        self.delay.delay_us(1).await;
422        for byte in buf {
423            self.spi.write(&[*byte]).await?;
424            self.delay.delay_us(8).await;
425        }
426        self.delay.delay_us(12).await;
427        self.cs.set_high().map_err(|_| Error::Gpio)?;
428        Ok(())
429    }
430
431    /// Display a string
432    ///
433    /// Convenience method to avoid converting the string to a iterator first.
434    /// Characters are mapped using the internal [FontTable].
435    /// Strings are truncated to fit the display.
436    pub async fn display_str(&mut self, text: &str) -> Result<(), Error<SPI::Error>> {
437        self.display(text.chars()).await
438    }
439
440    /// Write to the display RAM
441    ///
442    /// Displays the data, discarding unneded items.
443    ///
444    /// `From<char>` is implemented for [FontTable] so this method can
445    /// be called with strings by calling [chars()](::core::primitive::str::chars) on the string.
446    /// Alternatively [display_str](HCS12SS59T::display_str) does this for you.
447    pub async fn display<T>(&mut self, data: T) -> Result<(), Error<SPI::Error>>
448    where
449        T: IntoIterator,
450        T::Item: Into<FontTable>,
451    {
452        let mut buf = [48_u8; NUM_DIGITS + 1];
453        buf[0] = Command::DCRamWrite as u8;
454
455        for (buf, c) in buf.iter_mut().skip(1).rev().zip(data.into_iter()) {
456            *buf = c.into() as u8;
457        }
458        self.cs.set_low().map_err(|_| Error::Gpio)?;
459        self.delay.delay_us(1).await;
460        for byte in buf {
461            self.spi.write(&[byte]).await?;
462            self.delay.delay_us(8).await;
463        }
464        self.delay.delay_us(12).await;
465        self.cs.set_high().map_err(|_| Error::Gpio)?;
466        Ok(())
467    }
468
469    /// Write a single character to display RAM
470    ///
471    /// The HCS-12SS59T has 16 byte DCRAM, from which 0..12 are usable for the 12 connected digits.
472    pub async fn set_char<C: Into<FontTable>>(
473        &mut self,
474        addr: u8,
475        char: C,
476    ) -> Result<(), Error<SPI::Error>> {
477        let addr = addr & 0x0F;
478        let command = [Command::DCRamWrite as u8 | addr, char.into() as u8];
479
480        self.cs.set_low().map_err(|_| Error::Gpio)?;
481        self.delay.delay_us(1).await;
482        for byte in command {
483            self.spi.write(&[byte]).await?;
484            self.delay.delay_us(8).await;
485        }
486        self.delay.delay_us(12).await;
487        self.cs.set_high().map_err(|_| Error::Gpio)?;
488        Ok(())
489    }
490
491    /// Set character generator RAM
492    ///
493    /// Write a two byte character pattern to one of 16 CGRAM adresses.
494    ///
495    /// Valid address values are [FontTable::Ram0] to [FontTable::RamF]
496    ///
497    /// The pattern is specified with two bytes for 16 segments,
498    /// for a 14 segment display, segment 2 and 5 are don't care.
499    ///
500    /// |    Bit | 7     | 6     | 5     | 4     | 3     | 2     | 1     | 0    |
501    /// |-------:|-------|-------|-------|-------|-------|-------|-------|------|
502    /// | Byte 0 | SEG8  | SEG7  | SEG6  | SEG5  | SEG4  | SEG3  | SEG2  | SEG1 |
503    /// | Byte 1 | SEG16 | SEG15 | SEG14 | SEG13 | SEG12 | SEG11 | SEG10 | SEG9 |
504    ///
505    /// ``` text
506    ///   SEG1     SEG2
507    /// S S     S     0 3
508    /// E  E    E    1  G
509    /// G   G   G   G   E
510    /// 8    1  9  E    S
511    ///       6   S
512    ///   SEG15   SEG11
513    /// S     4 S S     4
514    /// E    1  E  E    G
515    /// G   G   G   G   E
516    /// 7  E    1    1  S
517    ///   S     3     2
518    ///   SEG6     SEG5
519    /// ```
520    pub async fn set_cgram_pattern(
521        &mut self,
522        addr: FontTable,
523        pattern: [u8; 2],
524    ) -> Result<(), Error<SPI::Error>> {
525        use FontTable::*;
526        if !matches!(
527            addr,
528            Ram0 | Ram1
529                | Ram2
530                | Ram3
531                | Ram4
532                | Ram5
533                | Ram6
534                | Ram7
535                | Ram8
536                | Ram9
537                | RamA
538                | RamB
539                | RamC
540                | RamD
541                | RamE
542                | RamF
543        ) {
544            return Err(Error::InvalidInput);
545        }
546        let command = [
547            Command::CGRamWrite as u8 | addr as u8,
548            pattern[0],
549            pattern[1],
550        ];
551
552        self.cs.set_low().map_err(|_| Error::Gpio)?;
553        self.delay.delay_us(1).await;
554        for byte in command {
555            self.spi.write(&[byte]).await?;
556            self.delay.delay_us(8).await;
557        }
558        self.delay.delay_us(12).await;
559        self.cs.set_high().map_err(|_| Error::Gpio)?;
560        Ok(())
561    }
562}
563
564#[cfg(test)]
565#[macro_use]
566extern crate std;
567
568#[cfg(all(test, not(feature = "async")))]
569mod tests {
570
571    #[test]
572    fn test_init() {
573        use crate::HCS12SS59T;
574        use embedded_hal_mock::eh1::digital::{
575            Mock as PinMock, State as PinState, Transaction as PinTransaction,
576        };
577        use embedded_hal_mock::eh1::spi::{Mock as SpiMock, Transaction as SpiTransaction};
578
579        let spi_expectations = [
580            SpiTransaction::write(0x6C), // num digit set 12
581            SpiTransaction::write(0x57), // display duty set 7
582            SpiTransaction::write(0x70), // light set normal
583            SpiTransaction::write(0x55), // display duty set 5
584        ];
585        let spi = SpiMock::new(&spi_expectations);
586        let n_reset_expectations = [
587            PinTransaction::set(PinState::Low),
588            PinTransaction::set(PinState::High),
589        ];
590        let n_reset = PinMock::new(&n_reset_expectations);
591
592        let cs_expectations = [
593            PinTransaction::set(PinState::Low), // Command one
594            PinTransaction::set(PinState::High),
595            PinTransaction::set(PinState::Low), // Command two
596            PinTransaction::set(PinState::High),
597            PinTransaction::set(PinState::Low), // Command three
598            PinTransaction::set(PinState::High),
599            PinTransaction::set(PinState::Low), // Command four
600            PinTransaction::set(PinState::High),
601        ];
602        let cs = PinMock::new(&cs_expectations);
603
604        let vdon_expectations = [
605            PinTransaction::set(PinState::Low),
606            PinTransaction::set(PinState::Low),
607        ];
608        let n_vdon = PinMock::new(&vdon_expectations);
609
610        let delay = embedded_hal_mock::eh1::delay::NoopDelay::new();
611
612        let mut my_vfd = HCS12SS59T::new(spi, n_reset, delay, Some(n_vdon), cs);
613
614        my_vfd.init().unwrap();
615        my_vfd.brightness(5).unwrap();
616        // my_vfd.display_str("Hello world!").unwrap(); // testing a string write requires a spi transaction for every byte which also get mapped to the font table...
617
618        let (mut spi, mut rst, _delay, vdon, mut cs) = my_vfd.destroy();
619
620        spi.done();
621        rst.done();
622        if let Some(mut vdon) = vdon {
623            vdon.done();
624        }
625        cs.done();
626    }
627}
628
629#[cfg(all(test, feature = "async"))]
630mod tests {
631    #[tokio::test]
632    async fn test_init() {
633        use crate::HCS12SS59T;
634        use embedded_hal_mock::eh1::digital::{
635            Mock as PinMock, State as PinState, Transaction as PinTransaction,
636        };
637        use embedded_hal_mock::eh1::spi::{Mock as SpiMock, Transaction as SpiTransaction};
638
639        let spi_expectations = [
640            SpiTransaction::write(0x6C), // num digit set 12
641            SpiTransaction::write(0x57), // display duty set 7
642            SpiTransaction::write(0x70), // light set normal
643            SpiTransaction::write(0x55), // display duty set 5
644        ];
645        let spi = SpiMock::new(&spi_expectations);
646        let n_reset_expectations = [
647            PinTransaction::set(PinState::Low),
648            PinTransaction::set(PinState::High),
649        ];
650        let n_reset = PinMock::new(&n_reset_expectations);
651
652        let cs_expectations = [
653            PinTransaction::set(PinState::Low), // Command one
654            PinTransaction::set(PinState::High),
655            PinTransaction::set(PinState::Low), // Command two
656            PinTransaction::set(PinState::High),
657            PinTransaction::set(PinState::Low), // Command three
658            PinTransaction::set(PinState::High),
659            PinTransaction::set(PinState::Low), // Command four
660            PinTransaction::set(PinState::High),
661        ];
662        let cs = PinMock::new(&cs_expectations);
663
664        let vdon_expectations = [
665            PinTransaction::set(PinState::Low),
666            PinTransaction::set(PinState::Low),
667        ];
668        let n_vdon = PinMock::new(&vdon_expectations);
669
670        let delay = embedded_hal_mock::eh1::delay::NoopDelay::new();
671
672        let mut my_vfd = HCS12SS59T::new(spi, n_reset, delay, Some(n_vdon), cs);
673
674        my_vfd.init().await.unwrap();
675        my_vfd.brightness(5).await.unwrap();
676        // my_vfd.display_str("Hello world!").unwrap(); // testing a string write requires a spi transaction for every byte which also get mapped to the font table...
677
678        let (mut spi, mut rst, _delay, vdon, mut cs) = my_vfd.destroy();
679
680        spi.done();
681        rst.done();
682        if let Some(mut vdon) = vdon {
683            vdon.done();
684        }
685        cs.done();
686    }
687}