Skip to main content

embassy_usb/class/
cdc_acm.rs

1//! CDC-ACM class implementation, aka Serial over USB.
2
3use core::cell::{Cell, RefCell};
4use core::future::{Future, poll_fn};
5use core::mem::{self, MaybeUninit};
6use core::sync::atomic::{AtomicBool, Ordering};
7use core::task::Poll;
8
9use embassy_sync::blocking_mutex::CriticalSectionMutex;
10use embassy_sync::waitqueue::WakerRegistration;
11
12use crate::control::{self, InResponse, OutResponse, Recipient, Request, RequestType};
13use crate::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut};
14use crate::types::InterfaceNumber;
15use crate::{Builder, Handler};
16
17/// This should be used as `device_class` when building the `UsbDevice`.
18pub const USB_CLASS_CDC: u8 = 0x02;
19
20const USB_CLASS_CDC_DATA: u8 = 0x0a;
21const CDC_SUBCLASS_ACM: u8 = 0x02;
22const CDC_PROTOCOL_NONE: u8 = 0x00;
23
24const CS_INTERFACE: u8 = 0x24;
25const CDC_TYPE_HEADER: u8 = 0x00;
26const CDC_TYPE_ACM: u8 = 0x02;
27const CDC_TYPE_UNION: u8 = 0x06;
28
29const REQ_SEND_ENCAPSULATED_COMMAND: u8 = 0x00;
30#[allow(unused)]
31const REQ_GET_ENCAPSULATED_COMMAND: u8 = 0x01;
32const REQ_SET_LINE_CODING: u8 = 0x20;
33const REQ_GET_LINE_CODING: u8 = 0x21;
34const REQ_SET_CONTROL_LINE_STATE: u8 = 0x22;
35
36/// CDC ACM error.
37#[derive(Clone, Debug)]
38pub enum CdcAcmError {
39    /// USB is not connected.
40    NotConnected,
41}
42
43impl core::fmt::Display for CdcAcmError {
44    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
45        match *self {
46            Self::NotConnected => f.write_str("NotConnected"),
47        }
48    }
49}
50
51impl core::error::Error for CdcAcmError {}
52impl embedded_io_async::Error for CdcAcmError {
53    fn kind(&self) -> embedded_io_async::ErrorKind {
54        match *self {
55            Self::NotConnected => embedded_io_async::ErrorKind::NotConnected,
56        }
57    }
58}
59
60/// Internal state for CDC-ACM
61pub struct State<'a> {
62    control: MaybeUninit<Control<'a>>,
63    shared: ControlShared,
64}
65
66impl<'a> Default for State<'a> {
67    fn default() -> Self {
68        Self::new()
69    }
70}
71
72impl<'a> State<'a> {
73    /// Create a new `State`.
74    pub const fn new() -> Self {
75        Self {
76            control: MaybeUninit::uninit(),
77            shared: ControlShared::new(),
78        }
79    }
80}
81
82/// Packet level implementation of a CDC-ACM serial port.
83///
84/// This class can be used directly and it has the least overhead due to directly reading and
85/// writing USB packets with no intermediate buffers, but it will not act like a stream-like serial
86/// port. The following constraints must be followed if you use this class directly:
87///
88/// - `read_packet` must be called with a buffer large enough to hold `max_packet_size` bytes.
89/// - `write_packet` must not be called with a buffer larger than `max_packet_size` bytes.
90/// - If you write a packet that is exactly `max_packet_size` bytes long, it won't be processed by the
91///   host operating system until a subsequent shorter packet is sent. A zero-length packet (ZLP)
92///   can be sent if there is no other data to send. This is because USB bulk transactions must be
93///   terminated with a short packet, even if the bulk endpoint is used for stream-like data.
94pub struct CdcAcmClass<'d, D: Driver<'d>> {
95    _comm_ep: D::EndpointIn,
96    _data_if: InterfaceNumber,
97    read_ep: D::EndpointOut,
98    write_ep: D::EndpointIn,
99    control: &'d ControlShared,
100}
101
102struct Control<'a> {
103    comm_if: InterfaceNumber,
104    shared: &'a ControlShared,
105}
106
107/// Shared data between Control and CdcAcmClass
108struct ControlShared {
109    line_coding: CriticalSectionMutex<Cell<LineCoding>>,
110    dtr: AtomicBool,
111    rts: AtomicBool,
112
113    waker: RefCell<WakerRegistration>,
114    changed: AtomicBool,
115}
116
117impl Default for ControlShared {
118    fn default() -> Self {
119        Self::new()
120    }
121}
122
123impl ControlShared {
124    const fn new() -> Self {
125        ControlShared {
126            dtr: AtomicBool::new(false),
127            rts: AtomicBool::new(false),
128            line_coding: CriticalSectionMutex::new(Cell::new(LineCoding {
129                stop_bits: StopBits::One,
130                data_bits: 8,
131                parity_type: ParityType::None,
132                data_rate: 8_000,
133            })),
134            waker: RefCell::new(WakerRegistration::new()),
135            changed: AtomicBool::new(false),
136        }
137    }
138
139    fn changed(&self) -> impl Future<Output = ()> + '_ {
140        poll_fn(|cx| {
141            if self.changed.load(Ordering::Relaxed) {
142                self.changed.store(false, Ordering::Relaxed);
143                Poll::Ready(())
144            } else {
145                self.waker.borrow_mut().register(cx.waker());
146                Poll::Pending
147            }
148        })
149    }
150}
151
152impl<'a> Control<'a> {
153    fn shared(&mut self) -> &'a ControlShared {
154        self.shared
155    }
156}
157
158impl<'d> Handler for Control<'d> {
159    fn reset(&mut self) {
160        let shared = self.shared();
161        shared.line_coding.lock(|x| x.set(LineCoding::default()));
162        shared.dtr.store(false, Ordering::Relaxed);
163        shared.rts.store(false, Ordering::Relaxed);
164
165        shared.changed.store(true, Ordering::Relaxed);
166        shared.waker.borrow_mut().wake();
167    }
168
169    fn control_out(&mut self, req: control::Request, data: &[u8]) -> Option<OutResponse> {
170        if (req.request_type, req.recipient, req.index)
171            != (RequestType::Class, Recipient::Interface, self.comm_if.0 as u16)
172        {
173            return None;
174        }
175
176        match req.request {
177            REQ_SEND_ENCAPSULATED_COMMAND => {
178                // We don't actually support encapsulated commands but pretend we do for standards
179                // compatibility.
180                Some(OutResponse::Accepted)
181            }
182            REQ_SET_LINE_CODING if data.len() >= 7 => {
183                let coding = LineCoding {
184                    data_rate: u32::from_le_bytes(data[0..4].try_into().unwrap()),
185                    stop_bits: data[4].into(),
186                    parity_type: data[5].into(),
187                    data_bits: data[6],
188                };
189                let shared = self.shared();
190                shared.line_coding.lock(|x| x.set(coding));
191                debug!("Set line coding to: {:?}", coding);
192
193                shared.changed.store(true, Ordering::Relaxed);
194                shared.waker.borrow_mut().wake();
195
196                Some(OutResponse::Accepted)
197            }
198            REQ_SET_CONTROL_LINE_STATE => {
199                let dtr = (req.value & 0x0001) != 0;
200                let rts = (req.value & 0x0002) != 0;
201
202                let shared = self.shared();
203                shared.dtr.store(dtr, Ordering::Relaxed);
204                shared.rts.store(rts, Ordering::Relaxed);
205                debug!("Set dtr {}, rts {}", dtr, rts);
206
207                shared.changed.store(true, Ordering::Relaxed);
208                shared.waker.borrow_mut().wake();
209
210                Some(OutResponse::Accepted)
211            }
212            _ => Some(OutResponse::Rejected),
213        }
214    }
215
216    fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> Option<InResponse<'a>> {
217        if (req.request_type, req.recipient, req.index)
218            != (RequestType::Class, Recipient::Interface, self.comm_if.0 as u16)
219        {
220            return None;
221        }
222
223        match req.request {
224            // REQ_GET_ENCAPSULATED_COMMAND is not really supported - it will be rejected below.
225            REQ_GET_LINE_CODING if req.length == 7 => {
226                debug!("Sending line coding");
227                let coding = self.shared().line_coding.lock(Cell::get);
228                assert!(buf.len() >= 7);
229                buf[0..4].copy_from_slice(&coding.data_rate.to_le_bytes());
230                buf[4] = coding.stop_bits as u8;
231                buf[5] = coding.parity_type as u8;
232                buf[6] = coding.data_bits;
233                Some(InResponse::Accepted(&buf[0..7]))
234            }
235            _ => Some(InResponse::Rejected),
236        }
237    }
238}
239
240impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> {
241    /// Creates a new CdcAcmClass with the provided UsbBus and `max_packet_size` in bytes. For
242    /// full-speed devices, `max_packet_size` has to be one of 8, 16, 32 or 64.
243    pub fn new(builder: &mut Builder<'d, D>, state: &'d mut State<'d>, max_packet_size: u16) -> Self {
244        assert!(builder.control_buf_len() >= 7);
245
246        let mut func = builder.function(USB_CLASS_CDC, CDC_SUBCLASS_ACM, CDC_PROTOCOL_NONE);
247
248        // Control interface
249        let mut iface = func.interface();
250        let comm_if = iface.interface_number();
251        let data_if = u8::from(comm_if) + 1;
252        let mut alt = iface.alt_setting(USB_CLASS_CDC, CDC_SUBCLASS_ACM, CDC_PROTOCOL_NONE, None);
253
254        alt.descriptor(
255            CS_INTERFACE,
256            &[
257                CDC_TYPE_HEADER, // bDescriptorSubtype
258                0x10,
259                0x01, // bcdCDC (1.10)
260            ],
261        );
262        alt.descriptor(
263            CS_INTERFACE,
264            &[
265                CDC_TYPE_ACM, // bDescriptorSubtype
266                0x02,         // bmCapabilities:
267                              // D1: Device supports the request combination of
268                              // Set_Line_Coding, Set_Control_Line_State, Get_Line_Coding,
269                              // and the Notification Serial_State.
270            ],
271        );
272        alt.descriptor(
273            CS_INTERFACE,
274            &[
275                CDC_TYPE_UNION, // bDescriptorSubtype
276                comm_if.into(), // bControlInterface
277                data_if,        // bSubordinateInterface
278            ],
279        );
280
281        let comm_ep = alt.endpoint_interrupt_in(None, 8, 255);
282
283        // Data interface
284        let mut iface = func.interface();
285        let data_if = iface.interface_number();
286        let mut alt = iface.alt_setting(USB_CLASS_CDC_DATA, 0x00, CDC_PROTOCOL_NONE, None);
287        let read_ep = alt.endpoint_bulk_out(None, max_packet_size);
288        let write_ep = alt.endpoint_bulk_in(None, max_packet_size);
289
290        drop(func);
291
292        let control = state.control.write(Control {
293            shared: &state.shared,
294            comm_if,
295        });
296        builder.handler(control);
297
298        let control_shared = &state.shared;
299
300        CdcAcmClass {
301            _comm_ep: comm_ep,
302            _data_if: data_if,
303            read_ep,
304            write_ep,
305            control: control_shared,
306        }
307    }
308
309    /// Gets the maximum packet size in bytes.
310    pub fn max_packet_size(&self) -> u16 {
311        // The size is the same for both endpoints.
312        self.read_ep.info().max_packet_size
313    }
314
315    /// Gets the current line coding. The line coding contains information that's mainly relevant
316    /// for USB to UART serial port emulators, and can be ignored if not relevant.
317    pub fn line_coding(&self) -> LineCoding {
318        self.control.line_coding.lock(Cell::get)
319    }
320
321    /// Gets the DTR (data terminal ready) state
322    pub fn dtr(&self) -> bool {
323        self.control.dtr.load(Ordering::Relaxed)
324    }
325
326    /// Gets the RTS (request to send) state
327    pub fn rts(&self) -> bool {
328        self.control.rts.load(Ordering::Relaxed)
329    }
330
331    /// Writes a single packet into the IN endpoint.
332    pub async fn write_packet(&mut self, data: &[u8]) -> Result<(), EndpointError> {
333        self.write_ep.write(data).await
334    }
335
336    /// Reads a single packet from the OUT endpoint.
337    pub async fn read_packet(&mut self, data: &mut [u8]) -> Result<usize, EndpointError> {
338        self.read_ep.read(data).await
339    }
340
341    /// Waits for the USB host to enable this interface
342    pub async fn wait_connection(&mut self) {
343        self.read_ep.wait_enabled().await;
344    }
345
346    /// Split the class into a sender and receiver.
347    ///
348    /// This allows concurrently sending and receiving packets from separate tasks.
349    pub fn split(self) -> (Sender<'d, D>, Receiver<'d, D>) {
350        (
351            Sender {
352                write_ep: self.write_ep,
353                control: self.control,
354            },
355            Receiver {
356                read_ep: self.read_ep,
357                control: self.control,
358            },
359        )
360    }
361
362    /// Split the class into sender, receiver and control
363    ///
364    /// Allows concurrently sending and receiving packets whilst monitoring for
365    /// control changes (dtr, rts)
366    pub fn split_with_control(self) -> (Sender<'d, D>, Receiver<'d, D>, ControlChanged<'d>) {
367        (
368            Sender {
369                write_ep: self.write_ep,
370                control: self.control,
371            },
372            Receiver {
373                read_ep: self.read_ep,
374                control: self.control,
375            },
376            ControlChanged { control: self.control },
377        )
378    }
379}
380
381/// CDC ACM Control status change monitor
382///
383/// You can obtain a `ControlChanged` with [`CdcAcmClass::split_with_control`]
384pub struct ControlChanged<'d> {
385    control: &'d ControlShared,
386}
387
388impl<'d> ControlChanged<'d> {
389    /// Return a future for when the control settings change
390    pub async fn control_changed(&self) {
391        self.control.changed().await;
392    }
393
394    /// Gets the DTR (data terminal ready) state
395    pub fn dtr(&self) -> bool {
396        self.control.dtr.load(Ordering::Relaxed)
397    }
398
399    /// Gets the RTS (request to send) state
400    pub fn rts(&self) -> bool {
401        self.control.rts.load(Ordering::Relaxed)
402    }
403}
404
405/// CDC ACM class packet sender.
406///
407/// You can obtain a `Sender` with [`CdcAcmClass::split`]
408pub struct Sender<'d, D: Driver<'d>> {
409    write_ep: D::EndpointIn,
410    control: &'d ControlShared,
411}
412
413impl<'d, D: Driver<'d>> Sender<'d, D> {
414    /// Gets the maximum packet size in bytes.
415    pub fn max_packet_size(&self) -> u16 {
416        // The size is the same for both endpoints.
417        self.write_ep.info().max_packet_size
418    }
419
420    /// Gets the current line coding. The line coding contains information that's mainly relevant
421    /// for USB to UART serial port emulators, and can be ignored if not relevant.
422    pub fn line_coding(&self) -> LineCoding {
423        self.control.line_coding.lock(Cell::get)
424    }
425
426    /// Gets the DTR (data terminal ready) state
427    pub fn dtr(&self) -> bool {
428        self.control.dtr.load(Ordering::Relaxed)
429    }
430
431    /// Gets the RTS (request to send) state
432    pub fn rts(&self) -> bool {
433        self.control.rts.load(Ordering::Relaxed)
434    }
435
436    /// Writes a single packet into the IN endpoint.
437    pub async fn write_packet(&mut self, data: &[u8]) -> Result<(), EndpointError> {
438        self.write_ep.write(data).await
439    }
440
441    /// Waits for the USB host to enable this interface
442    pub async fn wait_connection(&mut self) {
443        self.write_ep.wait_enabled().await;
444    }
445}
446
447impl<'d, D: Driver<'d>> embedded_io_async::ErrorType for Sender<'d, D> {
448    type Error = CdcAcmError;
449}
450
451impl<'d, D: Driver<'d>> embedded_io_async::Write for Sender<'d, D> {
452    async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
453        let len = core::cmp::min(buf.len(), self.max_packet_size() as usize);
454        match self.write_packet(&buf[..len]).await {
455            Ok(()) => Ok(len),
456            Err(EndpointError::BufferOverflow) => unreachable!(),
457            Err(EndpointError::Disabled) => Err(CdcAcmError::NotConnected),
458        }
459    }
460
461    async fn flush(&mut self) -> Result<(), Self::Error> {
462        Ok(())
463    }
464}
465
466/// CDC ACM class packet receiver.
467///
468/// You can obtain a `Receiver` with [`CdcAcmClass::split`]
469pub struct Receiver<'d, D: Driver<'d>> {
470    read_ep: D::EndpointOut,
471    control: &'d ControlShared,
472}
473
474impl<'d, D: Driver<'d>> Receiver<'d, D> {
475    /// Gets the maximum packet size in bytes.
476    pub fn max_packet_size(&self) -> u16 {
477        // The size is the same for both endpoints.
478        self.read_ep.info().max_packet_size
479    }
480
481    /// Gets the current line coding. The line coding contains information that's mainly relevant
482    /// for USB to UART serial port emulators, and can be ignored if not relevant.
483    pub fn line_coding(&self) -> LineCoding {
484        self.control.line_coding.lock(Cell::get)
485    }
486
487    /// Gets the DTR (data terminal ready) state
488    pub fn dtr(&self) -> bool {
489        self.control.dtr.load(Ordering::Relaxed)
490    }
491
492    /// Gets the RTS (request to send) state
493    pub fn rts(&self) -> bool {
494        self.control.rts.load(Ordering::Relaxed)
495    }
496
497    /// Reads a single packet from the OUT endpoint.
498    /// Must be called with a buffer large enough to hold max_packet_size bytes.
499    pub async fn read_packet(&mut self, data: &mut [u8]) -> Result<usize, EndpointError> {
500        self.read_ep.read(data).await
501    }
502
503    /// Waits for the USB host to enable this interface
504    pub async fn wait_connection(&mut self) {
505        self.read_ep.wait_enabled().await;
506    }
507
508    /// Turn the `Receiver` into a [`BufferedReceiver`].
509    ///
510    /// The supplied buffer must be large enough to hold max_packet_size bytes.
511    pub fn into_buffered(self, buf: &'d mut [u8]) -> BufferedReceiver<'d, D> {
512        BufferedReceiver {
513            receiver: self,
514            buffer: buf,
515            start: 0,
516            end: 0,
517        }
518    }
519}
520
521/// CDC ACM class buffered receiver.
522///
523/// It is a requirement of the [`embedded_io_async::Read`] trait that arbitrarily small lengths of
524/// data can be read from the stream. The [`Receiver`] can only read full packets at a time. The
525/// `BufferedReceiver` instead buffers a single packet if the caller does not read all of the data,
526/// so that the remaining data can be returned in subsequent calls.
527///
528/// If you have no requirement to use the [`embedded_io_async::Read`] trait or to read a data length
529/// less than the packet length, then it is more efficient to use the [`Receiver`] directly.
530///
531/// You can obtain a `BufferedReceiver` with [`Receiver::into_buffered`].
532///
533/// [`embedded_io_async::Read`]: https://docs.rs/embedded-io-async/latest/embedded_io_async/trait.Read.html
534pub struct BufferedReceiver<'d, D: Driver<'d>> {
535    receiver: Receiver<'d, D>,
536    buffer: &'d mut [u8],
537    start: usize,
538    end: usize,
539}
540
541impl<'d, D: Driver<'d>> BufferedReceiver<'d, D> {
542    fn read_from_buffer(&mut self, buf: &mut [u8]) -> usize {
543        let available = &self.buffer[self.start..self.end];
544        let len = core::cmp::min(available.len(), buf.len());
545        buf[..len].copy_from_slice(&available[..len]);
546        self.start += len;
547        len
548    }
549
550    /// Gets the current line coding. The line coding contains information that's mainly relevant
551    /// for USB to UART serial port emulators, and can be ignored if not relevant.
552    pub fn line_coding(&self) -> LineCoding {
553        self.receiver.line_coding()
554    }
555
556    /// Gets the DTR (data terminal ready) state
557    pub fn dtr(&self) -> bool {
558        self.receiver.dtr()
559    }
560
561    /// Gets the RTS (request to send) state
562    pub fn rts(&self) -> bool {
563        self.receiver.rts()
564    }
565
566    /// Waits for the USB host to enable this interface
567    pub async fn wait_connection(&mut self) {
568        self.receiver.wait_connection().await;
569    }
570}
571
572impl<'d, D: Driver<'d>> embedded_io_async::ErrorType for BufferedReceiver<'d, D> {
573    type Error = CdcAcmError;
574}
575
576impl<'d, D: Driver<'d>> embedded_io_async::Read for BufferedReceiver<'d, D> {
577    async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
578        // If there is a buffered packet, return data from that first
579        if self.start != self.end {
580            return Ok(self.read_from_buffer(buf));
581        }
582
583        // If the caller's buffer is large enough to contain an entire packet, read directly into
584        // that instead of buffering the packet internally.
585        if buf.len() > self.receiver.max_packet_size() as usize {
586            return match self.receiver.read_packet(buf).await {
587                Ok(n) => Ok(n),
588                Err(EndpointError::BufferOverflow) => unreachable!(),
589                Err(EndpointError::Disabled) => Err(CdcAcmError::NotConnected),
590            };
591        }
592
593        // Otherwise read a packet into the internal buffer, and return some of it to the caller.
594        //
595        // It's important that `start` and `end` be updated in this order so they're left in a
596        // consistent state if the `read` future is dropped mid-execution, e.g. from a timeout.
597        match self.receiver.read_packet(&mut self.buffer).await {
598            Ok(n) => self.end = n,
599            Err(EndpointError::BufferOverflow) => unreachable!(),
600            Err(EndpointError::Disabled) => return Err(CdcAcmError::NotConnected),
601        }
602        self.start = 0;
603        return Ok(self.read_from_buffer(buf));
604    }
605}
606
607/// Number of stop bits for LineCoding
608#[derive(Copy, Clone, Debug, PartialEq, Eq)]
609#[cfg_attr(feature = "defmt", derive(defmt::Format))]
610pub enum StopBits {
611    /// 1 stop bit
612    One = 0,
613
614    /// 1.5 stop bits
615    OnePointFive = 1,
616
617    /// 2 stop bits
618    Two = 2,
619}
620
621impl From<u8> for StopBits {
622    fn from(value: u8) -> Self {
623        if value <= 2 {
624            unsafe { mem::transmute(value) }
625        } else {
626            StopBits::One
627        }
628    }
629}
630
631/// Parity for LineCoding
632#[derive(Copy, Clone, Debug, PartialEq, Eq)]
633#[cfg_attr(feature = "defmt", derive(defmt::Format))]
634pub enum ParityType {
635    /// No parity bit.
636    None = 0,
637    /// Parity bit is 1 if the amount of `1` bits in the data byte is odd.
638    Odd = 1,
639    /// Parity bit is 1 if the amount of `1` bits in the data byte is even.
640    Even = 2,
641    /// Parity bit is always 1
642    Mark = 3,
643    /// Parity bit is always 0
644    Space = 4,
645}
646
647impl From<u8> for ParityType {
648    fn from(value: u8) -> Self {
649        if value <= 4 {
650            unsafe { mem::transmute(value) }
651        } else {
652            ParityType::None
653        }
654    }
655}
656
657/// Line coding parameters
658///
659/// This is provided by the host for specifying the standard UART parameters such as baud rate. Can
660/// be ignored if you don't plan to interface with a physical UART.
661#[derive(Clone, Copy, Debug)]
662#[cfg_attr(feature = "defmt", derive(defmt::Format))]
663pub struct LineCoding {
664    stop_bits: StopBits,
665    data_bits: u8,
666    parity_type: ParityType,
667    data_rate: u32,
668}
669
670impl LineCoding {
671    /// Gets the number of stop bits for UART communication.
672    pub fn stop_bits(&self) -> StopBits {
673        self.stop_bits
674    }
675
676    /// Gets the number of data bits for UART communication.
677    pub const fn data_bits(&self) -> u8 {
678        self.data_bits
679    }
680
681    /// Gets the parity type for UART communication.
682    pub const fn parity_type(&self) -> ParityType {
683        self.parity_type
684    }
685
686    /// Gets the data rate in bits per second for UART communication.
687    pub const fn data_rate(&self) -> u32 {
688        self.data_rate
689    }
690}
691
692impl Default for LineCoding {
693    fn default() -> Self {
694        LineCoding {
695            stop_bits: StopBits::One,
696            data_bits: 8,
697            parity_type: ParityType::None,
698            data_rate: 8_000,
699        }
700    }
701}