thermal_print/
lib.rs

1/*  This program is free software: you can redistribute it and/or modify
2    it under the terms of the GNU General Public License as published by
3    the Free Software Foundation, either version 3 of the License, or
4    (at your option) any later version.
5
6    This program is distributed in the hope that it will be useful,
7    but WITHOUT ANY WARRANTY; without even the implied warranty of
8    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
9    GNU General Public License for more details.
10
11    You should have received a copy of the GNU General Public License
12    along with this program.  If not, see <http://www.gnu.org/licenses/>.
13*/
14
15//! Support for the CSN-A2 thermal printer via [`embedded_hal`]. This crate also supports bitmap printing
16//! via [`tinybmp`].
17//!
18//! # Usage
19//! Create a new [`Printer`] on a serial port on your platform and write text via the implemented [`core::fmt::Write`] trait. You can use the [`write!`] and [`writeln!`] macros to accomplish this.
20//!
21//! See the [`Printer`] struct documentation for advanced capabilities, such as printing barcodes and
22//! bitmaps.
23
24#![cfg_attr(not(feature = "std"), no_std)]
25
26#[cfg(not(feature = "std"))]
27extern crate alloc;
28
29#[cfg(not(feature = "std"))]
30use alloc::format;
31use bitvec::prelude::*;
32use core::fmt::{Arguments, Error, Write};
33use core::iter::zip;
34
35use derive_builder::Builder;
36use embedded_hal::delay;
37use num_enum::IntoPrimitive;
38use tinybmp::RawBmp;
39
40const ESC: u8 = 0x1B; // Escape
41const HT: u8 = 0x09; // Horizontal tab
42const MARK: u8 = 0x21; // !
43const AT: u8 = 0x40; // @
44const GS: u8 = 0x1D;
45
46const INIT_SEQUENCE: [u8; 2] = [ESC, AT];
47const TAB_STOP_SEQUENCE: [u8; 2] = [ESC, b'D'];
48const MODE_SEQUENCE: [u8; 2] = [ESC, MARK];
49/// Modes: Inverse, Upside-Down, Underline
50const MODE_ORDER: [[u8; 2]; 3] = [[GS, 0x42], [ESC, 0x7B], [ESC, 0x45]];
51
52const TAB_WIDTH: u8 = 4;
53/// Determines a cutoff value each pixel in a [`RawBmp`] is compared against. Pixels below this
54/// value get printed as a dot, pixels above not.
55pub const PIXEL_COLOR_CUTOFF: u32 = 0x0000FFFF;
56const BAUDRATE: u64 = 19_200;
57/// Maximum number of horizontal dots the printer can handle
58const DOT_WIDTH: u32 = 384;
59/// Time estimate for the printer to process one byte of data
60const BYTE_TIME_MICROS: u64 = ((11 * 1000000) + (BAUDRATE / 2)) / BAUDRATE;
61
62/// Specifies the used internal printer font. User-defined fonts are currently not supported by
63/// this driver.
64#[derive(Clone, Copy)]
65pub enum Font {
66    FontA,
67    FontB,
68}
69
70impl Default for Font {
71    fn default() -> Self {
72        Self::FontA
73    }
74}
75
76/// Determines whether text is aligned left, center, or right.
77pub enum Justification {
78    Left,
79    Center,
80    Right,
81}
82
83impl Default for Justification {
84    fn default() -> Self {
85        Self::Left
86    }
87}
88
89/// Sets no, normal, or thick underlining.
90pub enum Underline {
91    None,
92    Normal,
93    Double,
94}
95
96impl Default for Underline {
97    fn default() -> Self {
98        Self::None
99    }
100}
101
102/// Defines the raster bit-image mode.
103///
104/// | Mode           | Vertical Density  | Horizontal Density    |
105/// |----------------|-------------------|-----------------------|
106/// | `Normal`       | 203.2dpi          | 203.2dpi              |
107/// | `DoubleWidth`  | 203.2dpi          | 101.6dpi              |
108/// | `DoubleHeight` | 101.6dpi          | 203.2dpi              |
109/// | `Quadruple`    | 101.6dpi          | 101.6dpi              |
110///
111#[derive(IntoPrimitive)]
112#[repr(u8)]
113pub enum RasterBitImageMode {
114    Normal = 0,
115    DoubleWidth,
116    DoubleHeight,
117    Quadruple,
118}
119
120impl Default for RasterBitImageMode {
121    fn default() -> Self {
122        Self::Normal
123    }
124}
125
126/// Determines the used international character set. Default: `USA`.
127#[derive(IntoPrimitive)]
128#[repr(u8)]
129pub enum CharacterSet {
130    USA = 0,
131    France,
132    Germany,
133    UK,
134    DenmarkI,
135    Sweden,
136    Italy,
137    SpainI,
138    Japan,
139    Norway,
140    DenmarkII,
141    SpainII,
142    LatinAmerica,
143    Korea,
144    SloveniaCroatia,
145    China,
146}
147
148impl Default for CharacterSet {
149    fn default() -> Self {
150        Self::USA
151    }
152}
153
154/// Determines the used code page. Default: `CP437`.
155#[derive(IntoPrimitive)]
156#[repr(u8)]
157pub enum CodeTable {
158    CP437 = 0,
159    Katakana = 1,
160    CP850 = 2,
161    CP860 = 3,
162    CP863 = 4,
163    CP865 = 5,
164    WCP1251 = 6,
165    CP866 = 7,
166    MIK = 8,
167    CP755 = 9,
168    Iran = 10,
169    CP862 = 15,
170    WCP1252 = 16,
171    WCP1253 = 17,
172    CP852 = 18,
173    CP858 = 19,
174    IranII = 20,
175    Latvian = 21,
176    CP864 = 22,
177    Iso8859_1 = 23,
178    CP737 = 24,
179    WCP1257 = 25,
180    Thai = 26,
181    CP720 = 27,
182    CP855 = 28,
183    CP857 = 29,
184    WCP1250 = 30,
185    CP775 = 31,
186    WCP1254 = 32,
187    WCP1255 = 33,
188    WCP1256 = 34,
189    WCP1258 = 35,
190    Iso8859_2 = 36,
191    Iso8859_3 = 37,
192    Iso8859_4 = 38,
193    Iso8859_5 = 39,
194    Iso8859_6 = 40,
195    Iso8859_7 = 41,
196    Iso8859_8 = 42,
197    Iso8859_9 = 43,
198    Iso8859_15 = 44,
199    Thai2 = 45,
200    CP856 = 46,
201    CP874 = 47,
202}
203
204impl Default for CodeTable {
205    fn default() -> Self {
206        Self::CP437
207    }
208}
209
210/// Defines the barcode system to be used.
211///
212/// Some systems are considered binary-level, and some are multi-level systems, which is important for setting the barcode width. See [`BarcodeWidth`] for more information.
213#[derive(IntoPrimitive)]
214#[repr(u8)]
215pub enum BarCodeSystem {
216    UpcA = 65,
217    UpcE = 66,
218    Ean13 = 67,
219    Ean8 = 68,
220    Code39 = 69,
221    Itf = 70,
222    Codabar = 71,
223    Code93 = 72,
224    Code128 = 73,
225}
226
227impl Default for BarCodeSystem {
228    fn default() -> Self {
229        Self::UpcA
230    }
231}
232
233/// These are special characters described in the printer documentation.
234///
235/// TODO: Find out what exactly this is useful for.
236#[derive(IntoPrimitive)]
237#[repr(u8)]
238pub enum BarCodeSpecialCharacter {
239    Shift = 0x53,
240    CodeA = 0x41,
241    CodeB = 0x42,
242    CodeC = 0x43,
243    Fnc1 = 0x31,
244    Fnc2 = 0x32,
245    Fnc3 = 0x33,
246    Fnc4 = 0x34,
247    /// "{"
248    CurlyOpen = 0x7B,
249}
250
251/// See the table below for barcode width options depending on the barcode type.
252/// The default is `Width3`.
253///
254/// | Width     | Module Width (mm) for multi-level barcode | Thin Element (mm) for binary-level | Thick Element (mm) for binary-level |
255/// |-----------|-------------------------------------------|------------------------------------|-------------------------------------|
256/// | `Width2`  | 0.250                                     | 0.250                              | 0.625                               |
257/// | `Width3`  | 0.375                                     | 0.375                              | 1.000                               |
258/// | `Width4`  | 0.560                                     | 0.560                              | 1.250                               |
259/// | `Width5`  | 0.625                                     | 0.625                              | 1.625                               |
260/// | `Width6`  | 0.750                                     | 0.750                              | 2.000                               |
261///
262/// `UpcA`, `UpcE`, `Ean8`, `Ean13` `Code93` and `Code128` are considered multi-level barcodes.
263/// `Code39`, `Itf` and `Codabar` are binary-level codes.
264#[derive(IntoPrimitive)]
265#[repr(u8)]
266pub enum BarcodeWidth {
267    Width2 = 2,
268    Width3 = 3,
269    Width4 = 4,
270    Width5 = 5,
271    Width6 = 6,
272}
273
274/// Configures print options such as the font used, white-on-black, upside-down or bold printing,
275/// as well as double-width, double-height and strikethrough modes via individual `bool` flags.
276///
277/// Prefer to use [`PrintModeBuilder`] to construct.
278#[derive(Default, Builder, Clone, Copy)]
279#[builder(default, setter(into), no_std)]
280pub struct PrintMode {
281    /// the [`Font`] to be used
282    pub font: Font,
283    /// enables white-on-black printing
284    pub inverse: bool,
285    /// enables upside-down printing
286    pub upside_down: bool,
287    /// enables bold printing
288    pub emph: bool,
289    /// enables double-height printing mode
290    pub double_height: bool,
291    /// enables double-width printing mode
292    pub double_width: bool,
293    /// enables strikethrough mode
294    pub delete_line: bool,
295}
296
297/// Defines the printer's heat settings.
298///
299/// Prefer to use [`PrintSettingsBuilder`] to construct.
300#[derive(Builder, Clone, Copy)]
301#[builder(default, setter(into), no_std)]
302pub struct PrintSettings {
303    /// The maximum number of print head elements that will fire simultaneously. More dots require
304    /// a higher peak current, but speed up the printing process. Unit: 8 dots.
305    /// Default: 11 (96 dots)
306    dots: u8,
307    /// The duration that the heat elements are fired. A longer heating time results in a darker
308    /// print, but a slower printing speed. Unit: 10 microseconds. Default: 1.2
309    /// milliseconds.
310    time: u8,
311    /// The recovery time between firing of the heating dots. A longer recovery interval results in
312    /// clearer prints, but a slower printing speed and possibly static friction between the paper
313    /// and the print roll. Unit: 10 microseconds. Default: 200
314    /// microseconds.
315    interval: u8,
316}
317
318impl Default for PrintSettings {
319    fn default() -> Self {
320        PrintSettings {
321            dots: 11,
322            time: 120,
323            interval: 20,
324        }
325    }
326}
327
328impl Into<u8> for PrintMode {
329    fn into(self) -> u8 {
330        let mut mode = 0;
331
332        match self.font {
333            Font::FontA => mode &= 1 << 0,
334            Font::FontB => mode |= 1 << 0,
335        }
336
337        // Somehow this seems to be broken (some bit-flags are ignored); use the custom (mode-specific) commands defined in the datasheet instead
338        if self.inverse {
339            mode |= 1 << 1;
340        }
341
342        if self.upside_down {
343            mode |= 1 << 2;
344        }
345
346        if self.emph {
347            mode |= 1 << 3;
348        }
349
350        if self.double_height {
351            mode |= 1 << 4;
352        }
353
354        if self.double_width {
355            mode |= 1 << 5;
356        }
357
358        if self.delete_line {
359            mode |= 1 << 6;
360        }
361        mode
362    }
363}
364
365impl Into<[u8; 3]> for PrintSettings {
366    fn into(self) -> [u8; 3] {
367        [self.dots, self.time, self.interval]
368    }
369}
370
371/// A representation of the thermal printer. Implements the [`core::fmt::Write`] trait for printing
372/// normal text.
373pub struct Printer<
374    #[cfg(feature = "embedded-io")] Port: embedded_io::Write,
375    #[cfg(feature = "embedded-hal-nb")] Port: embedded_hal_nb::serial::Write,
376    Delay: delay::DelayNs> {
377    pub serial: Port,
378    pub delay: Delay,
379    prev_byte: char,
380    max_column: u8,
381    char_height: u8,
382    char_width: u8,
383    line_spacing: u8,
384    barcode_height: u8,
385    dot_print_time: u32,
386    dot_feed_time: u32,
387    current_column: u8,
388    print_mode: u8,
389}
390
391impl<
392    #[cfg(feature = "embedded-io")] Port: embedded_io::Write,
393    #[cfg(feature = "embedded-hal-nb")] Port: embedded_hal_nb::serial::Write,
394    Delay: delay::DelayNs> Printer<Port, Delay> {
395    /// Create a new `Printer` with default settings.
396    ///
397    /// You must specify the serial port to be used, as well as a delay implementation of your HAL
398    /// to allow the driver to block while the printer is outputting text.
399    pub fn new(serial: Port, delay: Delay) -> Printer<Port, Delay> {
400        Printer {
401            serial,
402            delay,
403            prev_byte: '\n',
404            max_column: 32,
405            char_height: 24,
406            char_width: 12,
407            line_spacing: 6,
408            barcode_height: 162,
409            dot_print_time: 0,
410            dot_feed_time: 0,
411            current_column: 0,
412            print_mode: 0,
413        }
414    }
415
416    /// Lower-level function to directly write an array of bytes to the output sink. Wraps around
417    /// [`write_byte`].
418    ///
419    /// Functions producing physical output on the printer should use [`write`] instead.
420    fn write_bytes(&mut self, bytes: &[u8]) {
421        for b in bytes.iter() {
422            self.write_byte(*b).unwrap();
423        }
424    }
425
426    /// Write a single byte to the underlying serial output.
427    ///
428    /// Functions producing physical output on the printer should use [`write_one`] instead.
429    fn write_byte(&mut self, byte: u8) -> Result<(), ()> {
430        #[cfg(feature = "embedded-io")]
431        let result = self.serial.write(&[byte]);
432        #[cfg(feature = "embedded-hal-nb")]
433        let result = self.serial.write(byte);
434        self.sleep(BYTE_TIME_MICROS);
435        match result {
436            Ok(_) => Ok(()),
437            Err(_) => Err(()),
438        }
439    }
440
441    /// Writes multiple bytes to the printer. Wraps around [`write_one`].
442    pub fn write(&mut self, bytes: &[u8]) {
443        for b in bytes.iter() {
444            self.write_one(*b).unwrap();
445        }
446    }
447
448    /// Write a single byte to the printer, keeping track of the physical position of the print
449    /// head and blocking accordingly. Control commands should be issued via [`write_byte`]
450    /// instead.
451    fn write_one(&mut self, byte: u8) -> Result<(), ()> {
452        #[cfg(feature = "embedded-io")]
453        let result = self.serial.write(&[byte]);
454        #[cfg(feature = "embedded-hal-nb")]
455        let result = self.serial.write(byte);
456
457        // To keep up with the physical hardware, we try to estimate the time it takes for the
458        // printer to output what we're sending it
459        let mut wait_duration: u64 = BYTE_TIME_MICROS;
460
461        // Check if we're encountering a line break
462        if byte == b'\n' || self.current_column == self.max_column {
463            let char_height: u64 = self.char_height.into();
464            let line_spacing: u64 = self.line_spacing.into();
465            let dot_feed_time: u64 = self.dot_feed_time.into();
466            let dot_print_time: u64 = self.dot_print_time.into();
467            if self.prev_byte == '\n' {
468                // Just a feed line
469                wait_duration += (char_height + line_spacing) * dot_feed_time;
470            } else {
471                // We still have characters to print
472                wait_duration += (char_height * dot_print_time) + (line_spacing * dot_feed_time);
473            }
474            self.current_column = 0;
475            self.prev_byte = '\n';
476        } else {
477            // Check if this is a tab
478            if byte == HT {
479                let next_column: u8 = (self.current_column / TAB_WIDTH) + 1;
480                self.current_column += next_column;
481            }
482            self.current_column += 1;
483            self.prev_byte = byte as char;
484        }
485        self.sleep(wait_duration);
486        match result {
487            Ok(_) => Ok(()),
488            Err(_) => Err(()),
489        }
490    }
491
492    /// Halt the program for the specified number of microseconds. We don't want to overrun the
493    /// printer's buffer, so this function is used to wait for the print head to physically produce
494    /// the desired output.
495    fn sleep(&mut self, duration: u64) {
496        self.delay.delay_us(duration as u32);
497    }
498
499    /// Configure the tab stop widths on the printer.
500    fn update_tabs(&mut self) {
501        self.write_bytes(&TAB_STOP_SEQUENCE);
502        for i in 1..(self.max_column / TAB_WIDTH) {
503            self.write_byte(i * TAB_WIDTH).unwrap();
504        }
505        self.write_byte(0x00).unwrap();
506    }
507
508    /// Send the initialization sequence and configure tab stops.
509    pub fn reset(&mut self) {
510        // Init
511        self.write_bytes(&INIT_SEQUENCE);
512
513        // Configure tab stops
514        self.update_tabs();
515        self.set_print_settings(PrintSettings::default());
516        self.set_character_set(CharacterSet::default());
517        self.set_code_table(CodeTable::default());
518        self.set_barcode_height(self.barcode_height);
519    }
520
521    /// Wake the device from sleep. Also block for 75ms, as according to the datasheet the
522    /// printer needs at least 50ms in order to be ready to receive commands.
523    pub fn wake(&mut self) {
524        self.write_bytes(&[ESC, 0x38, 0x00, 0x00]);
525        self.sleep(75_000);
526    }
527
528    /// Block for 500ms to allow the printer to boot, then wake it up, disable sleep, and call reset.
529    pub fn init(&mut self) {
530        // Allow time for the printer to initialize
531        self.sleep(500_000);
532        self.wake();
533        // Disable sleep
534        self.write_bytes(&[ESC, 0x38, 0x00, 0x00]);
535        self.reset();
536        self.feed();
537    }
538
539    /// Update internal representations of char height and width depending on the configured font
540    /// and print modes.
541    fn adjust_char_values(&mut self, print_mode: PrintMode) {
542        // Check font
543        self.char_height = match print_mode.font {
544            Font::FontA => 24,
545            Font::FontB => 17,
546        };
547        self.char_width = match print_mode.font {
548            Font::FontA => 12,
549            Font::FontB => 9,
550        };
551
552        // Check for double-width mode
553        if print_mode.double_width {
554            self.char_width *= 2;
555        }
556
557        // Check for double-height mode
558        if print_mode.double_height {
559            self.char_height *= 2;
560        }
561
562        self.max_column = (DOT_WIDTH / self.char_width as u32) as u8;
563    }
564
565    /// Select print mode(s), such as inverse printing or double-height mode.
566    pub fn set_print_mode(&mut self, print_mode: PrintMode) {
567        let mode_byte: u8 = print_mode.into();
568        self.print_mode = mode_byte;
569
570        self.write_bytes(&MODE_SEQUENCE);
571        self.write_byte(mode_byte).unwrap();
572
573        // For some modes a custom command seems to be necessary
574        let modes = [
575            print_mode.inverse as u8,
576            print_mode.upside_down as u8,
577            print_mode.emph as u8,
578        ];
579        for pair in zip(MODE_ORDER, modes) {
580            let (cmd, n) = pair;
581            self.write_bytes(&cmd);
582            self.write_byte(n).unwrap();
583        }
584
585        self.adjust_char_values(print_mode);
586    }
587
588    /// Configure print settings. See [`PrintSettings`] for more information.
589    pub fn set_print_settings(&mut self, print_settings: PrintSettings) {
590        let settings_bytes: [u8; 3] = print_settings.into();
591        self.write_bytes(&[ESC, 0x37]);
592        self.write_bytes(&settings_bytes);
593    }
594
595    pub fn set_justification(&mut self, justification: Justification) {
596        let justification_byte = match justification {
597            Justification::Left => 0x00,
598            Justification::Center => 0x01,
599            Justification::Right => 0x02,
600        };
601        self.write_bytes(&[ESC, 0x61, justification_byte]);
602    }
603
604    pub fn set_underline(&mut self, mode: Underline) {
605        let underline_byte = match mode {
606            Underline::None => 0x00,
607            Underline::Normal => 0x01,
608            Underline::Double => 0x02,
609        };
610        self.write_bytes(&[ESC, 0x2D, underline_byte]);
611    }
612
613    pub fn set_character_set(&mut self, character_set: CharacterSet) {
614        self.write_bytes(&[ESC, 0x52, character_set.into()]);
615    }
616
617    pub fn set_code_table(&mut self, code_table: CodeTable) {
618        self.write_bytes(&[ESC, 0x74, code_table.into()]);
619    }
620
621    /// Print a bitmap image. This command is not affected by print modes, but justification is
622    /// respected.
623    ///
624    /// Since only monochrome images can be printed, the raw color value of each pixel is matched
625    /// against [`PIXEL_COLOR_CUTOFF`]. If a pixel color is below this value, it produces a dot in
626    /// the output (reasoning that darker pixels should be printed, while lighter ones should not
627    /// be), otherwise not.
628    ///
629    /// # Example
630    /// ```
631    /// printer.init();
632    /// printer.print_bitmap(
633    ///     RawBmp::from_slice(
634    ///         include_bytes!("../resources/ferris.bmp")
635    ///     ).unwrap(),
636    ///     RasterBitImageMode::Normal
637    /// );
638    /// ```
639    pub fn print_bitmap(&mut self, bmp: RawBmp, mode: RasterBitImageMode) {
640        let x_bits = bmp.header().image_size.width;
641        let x_bytes = (x_bits / 8) as u8 + u8::from(x_bits % 8 != 0);
642        let y_bits = bmp.header().image_size.height;
643
644        // I don't understand what xH and yH are, but setting them to 0 seems to work.
645        self.write_bytes(&[GS, 0x76, 0, mode.into(), x_bytes, 0, y_bits as u8, 0]);
646
647        let mut image_bits = bitvec![u8, Msb0;];
648        for pixel in bmp.pixels() {
649            let column = pixel.position.x as u32;
650
651            image_bits.push(pixel.color < PIXEL_COLOR_CUTOFF);
652
653            if column == ((x_bits - 1) as u32) && x_bits % 8 > 0 {
654                let fill_bits = 8 - (x_bits % 8);
655
656                for _ in 0..fill_bits {
657                    image_bits.push(false);
658                }
659            }
660        }
661        for (i, byte) in image_bits.as_raw_slice().iter().enumerate() {
662            self.write_byte(*byte).unwrap();
663            if i as u8 % x_bytes == 0 {
664                self.sleep((self.dot_print_time + self.dot_feed_time) as u64);
665            }
666        }
667    }
668
669    /// Print a barcode with the specified `BarCodeSystem`. Note that each system requires a
670    /// specific range of characters.
671    pub fn print_barcode(&mut self, system: BarCodeSystem, text: &str) {
672        self.write_bytes(&[GS, 0x6B, system.into(), text.len() as u8]);
673        for b in text.chars() {
674            self.write_byte(b as u8).unwrap();
675        }
676        self.sleep(self.barcode_height as u64 * (self.dot_print_time + self.dot_feed_time) as u64)
677    }
678
679    /// Set the barcode height to the specified number of dots.
680    pub fn set_barcode_height(&mut self, height: u8) {
681        self.write_bytes(&[GS, 0x68, height]);
682    }
683
684    /// Set the space to the left of the barcode to the specified number of dots.
685    pub fn set_barcode_left_space(&mut self, space: u8) {
686        self.write_bytes(&[GS, 0x78, space]);
687    }
688
689    /// Set the barcode width to the specified value. See [`BarcodeWidth`] for more information.
690    pub fn set_barcode_width(&mut self, width: BarcodeWidth) {
691        self.write_bytes(&[GS, 0x77, width.into()]);
692    }
693
694    /// Enable or disable the 90° clockwise rotation mode.
695    pub fn set_rotation_mode(&mut self, rotate: bool) {
696        self.write_bytes(&[ESC, 0x56, rotate.into()]);
697    }
698
699    /// Feed the paper by exactly one line.
700    pub fn feed(&mut self) {
701        self.feed_n(1);
702    }
703
704    /// Feed the paper by the specified number of lines.
705    pub fn feed_n(&mut self, lines: u8) {
706        self.write_bytes(&[ESC, 0x4A, lines]);
707
708        let dot_feed_time: u64 = self.dot_feed_time.into();
709        let char_height: u64 = self.char_height.into();
710        self.sleep(dot_feed_time * char_height);
711        self.prev_byte = '\n';
712        self.current_column = 0;
713    }
714}
715
716impl<
717    #[cfg(feature = "embedded-io")] Port: embedded_io::Write,
718    #[cfg(feature = "embedded-hal-nb")] Port: embedded_hal_nb::serial::Write,
719    Delay: delay::DelayNs> Write for Printer<Port, Delay> {
720    fn write_str(&mut self, s: &str) -> Result<(), Error> {
721        self.write(s.as_bytes());
722        Ok(())
723    }
724
725    fn write_char(&mut self, c: char) -> Result<(), Error> {
726        self.write_one(c as u8).unwrap();
727        Ok(())
728    }
729
730    fn write_fmt(&mut self, args: Arguments<'_>) -> Result<(), Error> {
731        self.write_str(format!("{}", args).as_str())
732    }
733}