Skip to main content

mcp2221_hal/settings/
common.rs

1//! Utility types for various settings.
2
3use bit_field::BitField;
4
5use crate::Error;
6
7/// String limited to 30 UTF-16 code units.
8///
9/// The strings stored in the MCP2221 flash memory (used during USB enumeration)
10/// are limited to at most 60 bytes of UTF-16-encoded text.
11///
12/// Create a `DeviceString` by calling [`str::parse`] on a string slice, or
13/// [`DeviceString::try_from`] with an owned `String`.
14///
15/// ```rust
16/// # use mcp2221_hal::settings::DeviceString;
17/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
18/// let manufacturer: DeviceString = "Acme Widgets Company (UK) Ltd".parse()?;
19///
20/// let product = String::from("Internet of Widgets Hub v3.0");
21/// let product: DeviceString = product.try_into()?;
22/// # Ok(())
23/// # }
24/// ```
25///
26/// Note that some characters require two UTF-16 code units to express (4 bytes).
27///
28/// ```rust
29/// # use mcp2221_hal::settings::DeviceString;
30/// let serial = "4 bytes each: 🫐🫑🫒🫓🫔🫕🫖🫗🫘🫙";
31/// let result: Result<DeviceString, _> = serial.parse();
32/// assert!(result.is_err(), "More than 60 bytes when UTF-16 encoded.");
33/// ```
34///
35/// ## Datasheet
36///
37/// See table 3-7 and table 3-14 for details of how the device strings are read from
38/// and written to the MCP2221, including the length limitation. (Those tables are
39/// for the manufacturer string, but the following tables are identical except for
40/// the subcommand code.)
41#[derive(Debug, Clone)]
42pub struct DeviceString(String);
43
44impl TryFrom<String> for DeviceString {
45    type Error = &'static str;
46
47    fn try_from(value: String) -> Result<Self, Self::Error> {
48        // Check the number of u16s (two bytes) is within the limit.
49        if value.encode_utf16().count() <= 30 {
50            Ok(Self(value))
51        } else {
52            Err("String must be 60 bytes or fewer when UTF-16-encoded.")
53        }
54    }
55}
56
57impl std::str::FromStr for DeviceString {
58    type Err = &'static str;
59
60    fn from_str(s: &str) -> Result<Self, Self::Err> {
61        Self::try_from(s.to_owned())
62    }
63}
64
65impl DeviceString {
66    pub(crate) fn try_from_buffer(buf: &[u8; 64]) -> Result<Self, Error> {
67        assert_eq!(buf[3], 0x03, "String response sanity check.");
68
69        let n_bytes = buf[2] as usize - 2;
70        // Sanity-check the string length.
71        assert!(n_bytes <= 60, "String longer than specified.");
72        assert_eq!(n_bytes % 2, 0, "Odd number of utf-16 bytes received.");
73
74        // (buf[2] - 2) UTF-16 characters laid out in little-endian order
75        // from buf[4] onwards. These strings are at most 30 characters
76        // (60 bytes) long. See table 3-7 in the datasheet.
77        let n_utf16_chars = n_bytes / 2;
78        let mut str_utf16 = Vec::with_capacity(n_utf16_chars);
79        for char_number in 0..n_utf16_chars {
80            let low_idx = 4 + 2 * char_number;
81            let high_idx = 4 + 2 * char_number + 1;
82            let utf16 = u16::from_le_bytes([buf[low_idx], buf[high_idx]]);
83            str_utf16.push(utf16);
84        }
85
86        String::from_utf16(str_utf16.as_slice())
87            .map(Self)
88            .map_err(Error::InvalidStringFromDevice)
89    }
90
91    /// Write the utf-16 string to the buffer to be written to the MCP2221.
92    ///
93    /// See table 3-14 in the datasheet. This function writes the appropriate
94    /// count to byte 2, and the 0x03 constant to byte 3.
95    pub(crate) fn apply_to_flash_buffer(&self, buf: &mut [u8; 64]) {
96        let mut byte_count = 0;
97        let utf16_pairs = self.0.encode_utf16().map(u16::to_le_bytes);
98        for (unicode_char_number, [low, high]) in utf16_pairs.enumerate() {
99            let pos = 4 + (2 * unicode_char_number);
100            buf[pos] = low;
101            buf[pos + 1] = high;
102            byte_count += 2;
103        }
104        buf[2] = byte_count + 2;
105        buf[3] = 0x03; // Required constant. Perhaps marks the data as an LE UTF16 string.
106    }
107}
108
109impl std::fmt::Display for DeviceString {
110    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111        write!(f, "{}", self.0)
112    }
113}
114
115/// Clock output duty cycle.
116///
117/// Each case is the percentage of one clock period that is a high logic level.
118///
119/// ## Datasheet
120///
121/// See register 1-2 (ChipSettings1) in the datasheet for the details of the duty cycle
122/// options and bit pattern.
123#[derive(Debug, Default, Clone, Copy)]
124pub enum ClockDutyCycle {
125    /// 75% duty cycle.
126    P75,
127    /// 50% duty cycle (factory default).
128    #[default]
129    P50,
130    /// 25% duty cycle.
131    P25,
132    /// 0% duty cycle.
133    P0,
134}
135
136#[doc(hidden)]
137impl From<u8> for ClockDutyCycle {
138    fn from(value: u8) -> Self {
139        assert!(value <= 0b11, "Invalid bit pattern for duty cycle");
140        match value {
141            0b11 => Self::P75,
142            0b10 => Self::P50,
143            0b01 => Self::P25,
144            0b00 => Self::P0,
145            _ => unreachable!("Precondition assert covers > 3."),
146        }
147    }
148}
149
150#[doc(hidden)]
151impl From<ClockDutyCycle> for u8 {
152    fn from(value: ClockDutyCycle) -> u8 {
153        match value {
154            ClockDutyCycle::P75 => 0b11,
155            ClockDutyCycle::P50 => 0b10,
156            ClockDutyCycle::P25 => 0b01,
157            ClockDutyCycle::P0 => 0b00,
158        }
159    }
160}
161
162/// Clock output frequency.
163///
164/// The frequency options and their 3-bit representation suggests this is a shift value
165/// for an internal 48 MHz clock, but note that there is no 48 MHz clock output option
166/// and the bit pattern that for that (`0b000`) is marked "reserved".
167///
168/// ## Datasheet
169///
170/// See register 1-2 (ChipSettings1) in the datasheet for the details of the frequency
171/// options and bit pattern.
172#[allow(non_camel_case_types)]
173#[derive(Debug, Default, Clone, Copy)]
174pub enum ClockFrequency {
175    /// 375 kHz clock output.
176    _375_kHz,
177    /// 750 kHz clock output.
178    _750_kHz,
179    /// 1.5 MHz clock output.
180    _1_5_MHz,
181    /// 3 MHz clock output.
182    _3_MHz,
183    /// 6 MHz clock output.
184    _6_MHz,
185    /// 12 MHz clock output (factory default).
186    #[default]
187    _12_MHz,
188    /// 24 MHz clock output.
189    _24_MHz,
190}
191
192#[doc(hidden)]
193impl From<u8> for ClockFrequency {
194    /// Create a `ClockFrequency` from the 3 low bits of the raw "divider".
195    ///
196    /// # Panics
197    ///
198    /// Frequency pattern `0b000` is marked "Reserved" in the datasheet and attempting
199    /// to create a `ClockFrequency` with this value will fail an assertion.
200    ///
201    /// Any value greater than `0b111` will fail an assertion.
202    fn from(value: u8) -> Self {
203        assert!(value <= 0b111, "Invalid bit pattern for clock speed.");
204        assert_ne!(value, 0, "Use of Reserved clock speed bit pattern.");
205        match value {
206            0b111 => Self::_375_kHz,
207            0b110 => Self::_750_kHz,
208            0b101 => Self::_1_5_MHz,
209            0b100 => Self::_3_MHz,
210            0b011 => Self::_6_MHz,
211            0b010 => Self::_12_MHz,
212            0b001 => Self::_24_MHz,
213            _ => unreachable!("Precondition asserts cover 0 and > 7."),
214        }
215    }
216}
217
218#[doc(hidden)]
219impl From<ClockFrequency> for u8 {
220    fn from(value: ClockFrequency) -> Self {
221        match value {
222            ClockFrequency::_375_kHz => 0b111,
223            ClockFrequency::_750_kHz => 0b110,
224            ClockFrequency::_1_5_MHz => 0b101,
225            ClockFrequency::_3_MHz => 0b100,
226            ClockFrequency::_6_MHz => 0b011,
227            ClockFrequency::_12_MHz => 0b010,
228            ClockFrequency::_24_MHz => 0b001,
229        }
230    }
231}
232
233/// Clock output duty cycle and frequency.
234///
235/// If GP1 is configured for clock output (see [`Gp1Mode::ClockOutput`]), this
236/// setting determines the characteristics of the clock signal.
237///
238/// [`Gp1Mode::ClockOutput`]: crate::settings::Gp1Mode::ClockOutput
239///
240/// ## Datasheet
241///
242/// See register 1-2 for details. In the USB command section the datasheet is worded as
243/// if this is just a 5-bit divider, but really it is a 2-bit duty cycle selection, and
244/// a 3-bit frequency selection.
245#[derive(Debug, Default, Clone, Copy)]
246pub struct ClockOutputSetting(
247    /// The duty cycle (period high) of the clock output signal.
248    pub ClockDutyCycle,
249    /// The frequency of the clock output signal.
250    pub ClockFrequency
251);
252
253#[doc(hidden)]
254impl From<u8> for ClockOutputSetting {
255    /// Create a `ClockSetting` from the 5-bit "divider" read from the MCP2221.
256    ///
257    /// # Panics
258    ///
259    /// Frequency pattern `0b000` is marked "Reserved" in the datasheet and attempting
260    /// to create a [`ClockFrequency`] with this value will fail an assertion.
261    fn from(value: u8) -> Self {
262        assert!(
263            value <= 0b11111,
264            "Raw clock 'divider' must be in the low 5 bits."
265        );
266        Self(
267            ClockDutyCycle::from(value.get_bits(3..=4)),
268            ClockFrequency::from(value.get_bits(0..=2)),
269        )
270    }
271}
272
273#[doc(hidden)]
274impl From<ClockOutputSetting> for u8 {
275    fn from(value: ClockOutputSetting) -> Self {
276        let ClockOutputSetting(duty_cycle, frequency) = value;
277        let mut byte = 0u8;
278        // Set duty cycle.
279        byte.set_bits(3..=4, duty_cycle.into());
280        // Set frequency.
281        byte.set_bits(0..=2, frequency.into());
282        byte
283    }
284}