kenwood_thd75/types/band.rs
1//! Band selection for the TH-D75 transceiver.
2
3use std::fmt;
4
5use crate::error::ValidationError;
6
7/// Radio band index (0-13).
8///
9/// The TH-D75 uses a numeric band index in the `FO` and `ME` commands.
10/// Variants `A` and `B` correspond to the two main VFO bands; the
11/// remaining `Band2`..`Band13` map to additional sub-band selections.
12///
13/// # Band architecture (per Kenwood Operating Tips §1.1, §5.9; User Manual Chapter 5)
14///
15/// - **Band A** (upper display): Amateur-only TX/RX at 144 MHz, 220 MHz
16/// (TH-D75A only), and 430 MHz. Supports FM and DV modes.
17/// Pressing and holding `[Left]/[Right]` cycles: 144 <-> 220 <-> 430 MHz.
18/// Band A uses a double super heterodyne receiver (1st IF 57.15 MHz,
19/// 2nd IF 450 kHz) with VCO/PLL IC800 and IF IC IC900 (AK2365AU).
20/// - **Band B** (lower display): Wideband RX from 0.1-524 MHz. Supports
21/// FM, DV, AM, LSB, USB, CW, NFM, WFM (FM Radio mode only), and DR.
22/// Band B has an independent receiver chain with its own VCO/PLL IC700,
23/// IF IC IC1002 (AK2365AU), and a third IF stage at 10.8 kHz via 3rd
24/// mixer IC1001 for AM/SSB/CW demodulation. 1st IF is 58.05 MHz, 2nd
25/// IF is 450 kHz. This independent hardware allows both bands to
26/// receive simultaneously.
27/// Pressing and holding `[Left]/[Right]` cycles: 430 <-> UHF(470-524) <->
28/// LF/MF(0.1-1.71) <-> HF(1.71-29.7) <-> 50(29.7-76) <-> FMBC(76-108) <->
29/// 118(108-136) <-> 144(136-174) <-> VHF(174-216/230) <-> 200/300(216/230-410) <-> 430 MHz.
30///
31/// Both bands share the MAIN MPU (IC2005, OMAP-L138), CODEC (IC2011),
32/// and SUB MPU (IC1103) which controls the VCO/PLLs and IF ICs via SPI.
33/// The VCO/PLL reference clocks are TCXO1 57.6 MHz (X600) and TCXO2
34/// 55.95 MHz (X601), selected by analog switches IC604/IC605.
35///
36/// Per service manual §2.1.5, the Band B VCO/PLL (IC700) is also used
37/// for transmission on all bands. Band A's VCO/PLL (IC800) handles
38/// Band A 1st local oscillation only.
39///
40/// # Hardware signal path (per service manual §2.1.3, §2.1.4)
41///
42/// ```text
43/// Band A RX: ANT → LNA Q404/Q406 → BPF → 1st MIX Q400 → IF 57.15MHz
44/// → MCF XF1 → IF AMP Q900 → IC900 → 2nd IF 450kHz → CODEC IC2011
45///
46/// Band B RX: ANT → LNA Q404/Q406 → BPF → 1st MIX Q500 → IF 58.05MHz
47/// → MCF XF2 → IF AMP Q1000 → IC1002 → 2nd IF 450kHz → CODEC IC2011
48/// (AM/SSB/CW: → 3rd MIX IC1001 → 3rd IF 10.8kHz → CODEC)
49///
50/// TX (all): CODEC IC2011 → MOD AMP IC2027 → SUB MPU IC1103 → Band B
51/// VCO/PLL IC700 → RF AMP Q201 → PRE DRV IC201 → DRV AMP Q212
52/// → FINAL AMP Q217/Q218 → ANT
53/// ```
54///
55/// Band A is the CTRL/PTT band by default. Band B supports all
56/// demodulation modes including SSB/CW with DSP and an IF receive filter.
57///
58/// # Dual/Single band display (per User Manual Chapter 5)
59///
60/// Press `[F]`, `[A/B]` to toggle between dual-band (both A and B visible)
61/// and single-band (only the selected band visible) display modes.
62///
63/// # Two-wave simultaneous reception (per User Manual Chapter 2)
64///
65/// Supported band combinations: `VxU`, `UxV`, `UxU` (both models), plus
66/// `Vx220M`, `220MxV`, `Ux220M` (TH-D75A only). D-STAR 2-wave simultaneous
67/// reception is also supported.
68#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
69pub enum Band {
70 /// Band A — amateur TX/RX (144/220/430 MHz). Index 0.
71 A = 0,
72 /// Band B — wideband RX (0.1–524 MHz, all modes). Index 1.
73 B = 1,
74 /// Band 2 (index 2). Extended band index used internally by the firmware
75 /// for multi-band selection. Most CAT commands (e.g., `FQ`, `MD`, `SQ`)
76 /// only accept Band A (0) or Band B (1); sending an extended index
77 /// typically results in a `?` error response.
78 Band2 = 2,
79 /// Band 3 (index 3). Extended firmware band index — see [`Band::Band2`]
80 /// for details on CAT command restrictions.
81 Band3 = 3,
82 /// Band 4 (index 4). Extended firmware band index — see [`Band::Band2`]
83 /// for details on CAT command restrictions.
84 Band4 = 4,
85 /// Band 5 (index 5). Extended firmware band index — see [`Band::Band2`]
86 /// for details on CAT command restrictions.
87 Band5 = 5,
88 /// Band 6 (index 6). Extended firmware band index — see [`Band::Band2`]
89 /// for details on CAT command restrictions.
90 Band6 = 6,
91 /// Band 7 (index 7). Extended firmware band index — see [`Band::Band2`]
92 /// for details on CAT command restrictions.
93 Band7 = 7,
94 /// Band 8 (index 8). Extended firmware band index — see [`Band::Band2`]
95 /// for details on CAT command restrictions.
96 Band8 = 8,
97 /// Band 9 (index 9). Extended firmware band index — see [`Band::Band2`]
98 /// for details on CAT command restrictions.
99 Band9 = 9,
100 /// Band 10 (index 10). Extended firmware band index — see [`Band::Band2`]
101 /// for details on CAT command restrictions.
102 Band10 = 10,
103 /// Band 11 (index 11). Extended firmware band index — see [`Band::Band2`]
104 /// for details on CAT command restrictions.
105 Band11 = 11,
106 /// Band 12 (index 12). Extended firmware band index — see [`Band::Band2`]
107 /// for details on CAT command restrictions.
108 Band12 = 12,
109 /// Band 13 (index 13). Extended firmware band index — see [`Band::Band2`]
110 /// for details on CAT command restrictions.
111 Band13 = 13,
112}
113
114impl Band {
115 /// Number of valid band values (0-13).
116 pub const COUNT: u8 = 14;
117}
118
119impl fmt::Display for Band {
120 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121 match self {
122 Self::A => f.write_str("A"),
123 Self::B => f.write_str("B"),
124 other => write!(f, "Band {}", u8::from(*other)),
125 }
126 }
127}
128
129impl TryFrom<u8> for Band {
130 type Error = ValidationError;
131
132 fn try_from(value: u8) -> Result<Self, Self::Error> {
133 match value {
134 0 => Ok(Self::A),
135 1 => Ok(Self::B),
136 2 => Ok(Self::Band2),
137 3 => Ok(Self::Band3),
138 4 => Ok(Self::Band4),
139 5 => Ok(Self::Band5),
140 6 => Ok(Self::Band6),
141 7 => Ok(Self::Band7),
142 8 => Ok(Self::Band8),
143 9 => Ok(Self::Band9),
144 10 => Ok(Self::Band10),
145 11 => Ok(Self::Band11),
146 12 => Ok(Self::Band12),
147 13 => Ok(Self::Band13),
148 _ => Err(ValidationError::BandOutOfRange(value)),
149 }
150 }
151}
152
153impl From<Band> for u8 {
154 fn from(band: Band) -> Self {
155 band as Self
156 }
157}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162 use crate::error::ValidationError;
163
164 #[test]
165 fn band_valid_range() {
166 for i in 0u8..Band::COUNT {
167 assert!(Band::try_from(i).is_ok(), "Band({i}) should be valid");
168 }
169 }
170
171 #[test]
172 fn band_invalid() {
173 assert!(Band::try_from(Band::COUNT).is_err());
174 assert!(Band::try_from(255).is_err());
175 }
176
177 #[test]
178 fn band_round_trip() {
179 for i in 0u8..Band::COUNT {
180 let val = Band::try_from(i).unwrap();
181 assert_eq!(u8::from(val), i);
182 }
183 }
184
185 #[test]
186 fn band_error_variant() {
187 let err = Band::try_from(Band::COUNT).unwrap_err();
188 assert!(matches!(err, ValidationError::BandOutOfRange(14)));
189 }
190
191 #[test]
192 fn band_display() {
193 assert_eq!(Band::A.to_string(), "A");
194 assert_eq!(Band::B.to_string(), "B");
195 assert_eq!(Band::Band5.to_string(), "Band 5");
196 assert_eq!(Band::Band13.to_string(), "Band 13");
197 }
198}