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
57pub 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 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 pub fn vd_off(&mut self) -> Result<(), VdonPin::Error> {
144 if let Some(pin) = &mut self.n_vdon {
145 pin.set_high()?; }
147 Ok(())
148 }
149
150 pub fn vd_on(&mut self) -> Result<(), VdonPin::Error> {
152 if let Some(pin) = &mut self.n_vdon {
153 pin.set_low()?; }
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 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 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 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 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 pub fn display_str(&mut self, text: &str) -> Result<(), Error<SPI::Error>> {
234 self.display(text.chars())
235 }
236
237 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 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 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 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 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 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 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 pub async fn display_str(&mut self, text: &str) -> Result<(), Error<SPI::Error>> {
437 self.display(text.chars()).await
438 }
439
440 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 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 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), SpiTransaction::write(0x57), SpiTransaction::write(0x70), SpiTransaction::write(0x55), ];
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), PinTransaction::set(PinState::High),
595 PinTransaction::set(PinState::Low), PinTransaction::set(PinState::High),
597 PinTransaction::set(PinState::Low), PinTransaction::set(PinState::High),
599 PinTransaction::set(PinState::Low), 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 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), SpiTransaction::write(0x57), SpiTransaction::write(0x70), SpiTransaction::write(0x55), ];
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), PinTransaction::set(PinState::High),
655 PinTransaction::set(PinState::Low), PinTransaction::set(PinState::High),
657 PinTransaction::set(PinState::Low), PinTransaction::set(PinState::High),
659 PinTransaction::set(PinState::Low), 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 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}