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}