diff options
| author | Bogdan Petru Chircu Mare <[email protected]> | 2025-10-31 21:44:48 -0700 |
|---|---|---|
| committer | Bogdan Petru Chircu Mare <[email protected]> | 2025-10-31 21:44:48 -0700 |
| commit | 0443134bc47918d2f8f0ede1b292b372629f8894 (patch) | |
| tree | 823f569464a6e001b4eb4246c9ad8083c8c20847 /src | |
| parent | 47e383545f4aac3bfaec0563429cc721540e665a (diff) | |
feat(mcxa276): initial HAL import
Diffstat (limited to 'src')
| -rw-r--r-- | src/adc.rs | 376 | ||||
| -rw-r--r-- | src/baremetal/mod.rs | 6 | ||||
| -rw-r--r-- | src/board.rs | 14 | ||||
| -rw-r--r-- | src/clocks.rs | 136 | ||||
| -rw-r--r-- | src/config.rs | 20 | ||||
| -rw-r--r-- | src/gpio.rs | 246 | ||||
| -rw-r--r-- | src/interrupt.rs | 349 | ||||
| -rw-r--r-- | src/lib.rs | 165 | ||||
| -rw-r--r-- | src/lpuart/buffered.rs | 686 | ||||
| -rw-r--r-- | src/lpuart/mod.rs | 1208 | ||||
| -rw-r--r-- | src/main.rs | 18 | ||||
| -rw-r--r-- | src/ostimer.rs | 704 | ||||
| -rw-r--r-- | src/pins.rs | 127 | ||||
| -rw-r--r-- | src/reset.rs | 112 | ||||
| -rw-r--r-- | src/rtc.rs | 281 | ||||
| -rw-r--r-- | src/uart.rs | 308 |
16 files changed, 4732 insertions, 24 deletions
diff --git a/src/adc.rs b/src/adc.rs new file mode 100644 index 000000000..dd6530605 --- /dev/null +++ b/src/adc.rs | |||
| @@ -0,0 +1,376 @@ | |||
| 1 | //! ADC driver | ||
| 2 | use crate::pac; | ||
| 3 | use core::sync::atomic::{AtomicBool, Ordering}; | ||
| 4 | |||
| 5 | use crate::pac::adc1::ctrl::{CalAvgs}; | ||
| 6 | use crate::pac::adc1::cfg::{Refsel, Pwrsel, Tprictrl, Tres, Tcmdres, HptExdi}; | ||
| 7 | use crate::pac::adc1::tctrl::{Tcmd, Tpri}; | ||
| 8 | use crate::pac::adc1::cmdl1::{Adch, Ctype, Mode}; | ||
| 9 | use crate::pac::adc1::cmdh1::{Next, Avgs, Sts, Cmpen}; | ||
| 10 | |||
| 11 | type Regs = pac::adc1::RegisterBlock; | ||
| 12 | |||
| 13 | static INTERRUPT_TRIGGERED: AtomicBool = AtomicBool::new(false); | ||
| 14 | // Token-based instance pattern like embassy-imxrt | ||
| 15 | pub trait Instance { | ||
| 16 | fn ptr() -> *const Regs; | ||
| 17 | } | ||
| 18 | |||
| 19 | /// Token for ADC1 | ||
| 20 | #[cfg(feature = "adc1")] | ||
| 21 | pub type Adc1 = crate::peripherals::ADC1; | ||
| 22 | #[cfg(feature = "adc1")] | ||
| 23 | impl Instance for crate::peripherals::ADC1 { | ||
| 24 | #[inline(always)] | ||
| 25 | fn ptr() -> *const Regs { | ||
| 26 | pac::Adc1::ptr() | ||
| 27 | } | ||
| 28 | } | ||
| 29 | |||
| 30 | // Also implement Instance for the Peri wrapper type | ||
| 31 | #[cfg(feature = "adc1")] | ||
| 32 | impl Instance for embassy_hal_internal::Peri<'_, crate::peripherals::ADC1> { | ||
| 33 | #[inline(always)] | ||
| 34 | fn ptr() -> *const Regs { | ||
| 35 | pac::Adc1::ptr() | ||
| 36 | } | ||
| 37 | } | ||
| 38 | |||
| 39 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
| 40 | #[repr(u8)] | ||
| 41 | pub enum TriggerPriorityPolicy { | ||
| 42 | ConvPreemptImmediatelyNotAutoResumed = 0, | ||
| 43 | ConvPreemptSoftlyNotAutoResumed = 1, | ||
| 44 | ConvPreemptImmediatelyAutoRestarted = 4, | ||
| 45 | ConvPreemptSoftlyAutoRestarted = 5, | ||
| 46 | ConvPreemptImmediatelyAutoResumed = 12, | ||
| 47 | ConvPreemptSoftlyAutoResumed = 13, | ||
| 48 | ConvPreemptSubsequentlyNotAutoResumed = 2, | ||
| 49 | ConvPreemptSubsequentlyAutoRestarted = 6, | ||
| 50 | ConvPreemptSubsequentlyAutoResumed = 14, | ||
| 51 | TriggerPriorityExceptionDisabled = 16, | ||
| 52 | } | ||
| 53 | |||
| 54 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
| 55 | pub struct LpadcConfig { | ||
| 56 | pub enable_in_doze_mode: bool, | ||
| 57 | pub conversion_average_mode: CalAvgs, | ||
| 58 | pub enable_analog_preliminary: bool, | ||
| 59 | pub power_up_delay: u8, | ||
| 60 | pub reference_voltage_source: Refsel, | ||
| 61 | pub power_level_mode: Pwrsel, | ||
| 62 | pub trigger_priority_policy: TriggerPriorityPolicy, | ||
| 63 | pub enable_conv_pause: bool, | ||
| 64 | pub conv_pause_delay: u16, | ||
| 65 | pub fifo_watermark: u8, | ||
| 66 | } | ||
| 67 | |||
| 68 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
| 69 | pub struct ConvCommandConfig { | ||
| 70 | pub sample_channel_mode: Ctype, | ||
| 71 | pub channel_number: Adch, | ||
| 72 | pub chained_next_command_number: Next, | ||
| 73 | pub enable_auto_channel_increment: bool, | ||
| 74 | pub loop_count: u8, | ||
| 75 | pub hardware_average_mode: Avgs, | ||
| 76 | pub sample_time_mode: Sts, | ||
| 77 | pub hardware_compare_mode: Cmpen, | ||
| 78 | pub hardware_compare_value_high: u32, | ||
| 79 | pub hardware_compare_value_low: u32, | ||
| 80 | pub conversion_resolution_mode: Mode, | ||
| 81 | pub enable_wait_trigger: bool, | ||
| 82 | } | ||
| 83 | |||
| 84 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
| 85 | pub struct ConvTriggerConfig { | ||
| 86 | pub target_command_id: Tcmd, | ||
| 87 | pub delay_power: u8, | ||
| 88 | pub priority: Tpri, | ||
| 89 | pub enable_hardware_trigger: bool, | ||
| 90 | } | ||
| 91 | |||
| 92 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
| 93 | pub struct ConvResult { | ||
| 94 | pub command_id_source: u32, | ||
| 95 | pub loop_count_index: u32, | ||
| 96 | pub trigger_id_source: u32, | ||
| 97 | pub conv_value: u16, | ||
| 98 | } | ||
| 99 | |||
| 100 | |||
| 101 | pub struct Adc<I: Instance> { | ||
| 102 | _inst: core::marker::PhantomData<I>, | ||
| 103 | } | ||
| 104 | |||
| 105 | impl<I: Instance> Adc<I> { | ||
| 106 | /// initialize ADC | ||
| 107 | pub fn new(_inst: impl Instance, config : LpadcConfig) -> Self { | ||
| 108 | let adc = unsafe { &*I::ptr() }; | ||
| 109 | |||
| 110 | /* Reset the module. */ | ||
| 111 | adc.ctrl().modify(|_, w| w.rst().held_in_reset()); | ||
| 112 | adc.ctrl().modify(|_, w| w.rst().released_from_reset()); | ||
| 113 | |||
| 114 | adc.ctrl().modify(|_, w| w.rstfifo0().trigger_reset()); | ||
| 115 | |||
| 116 | /* Disable the module before setting configuration. */ | ||
| 117 | adc.ctrl().modify(|_, w| w.adcen().disabled()); | ||
| 118 | |||
| 119 | /* Configure the module generally. */ | ||
| 120 | if config.enable_in_doze_mode { | ||
| 121 | adc.ctrl().modify(|_, w| w.dozen().enabled()); | ||
| 122 | } else { | ||
| 123 | adc.ctrl().modify(|_, w| w.dozen().disabled()); | ||
| 124 | } | ||
| 125 | |||
| 126 | /* Set calibration average mode. */ | ||
| 127 | adc.ctrl().modify(|_, w| w.cal_avgs().variant(config.conversion_average_mode)); | ||
| 128 | |||
| 129 | adc.cfg().write(|w| unsafe { | ||
| 130 | let w = if config.enable_analog_preliminary { | ||
| 131 | w.pwren().pre_enabled() | ||
| 132 | } else { | ||
| 133 | w | ||
| 134 | }; | ||
| 135 | |||
| 136 | w.pudly().bits(config.power_up_delay) | ||
| 137 | .refsel().variant(config.reference_voltage_source) | ||
| 138 | .pwrsel().variant(config.power_level_mode) | ||
| 139 | .tprictrl().variant(match config.trigger_priority_policy { | ||
| 140 | TriggerPriorityPolicy::ConvPreemptSoftlyNotAutoResumed | | ||
| 141 | TriggerPriorityPolicy::ConvPreemptSoftlyAutoRestarted | | ||
| 142 | TriggerPriorityPolicy::ConvPreemptSoftlyAutoResumed => Tprictrl::FinishCurrentOnPriority, | ||
| 143 | TriggerPriorityPolicy::ConvPreemptSubsequentlyNotAutoResumed | | ||
| 144 | TriggerPriorityPolicy::ConvPreemptSubsequentlyAutoRestarted | | ||
| 145 | TriggerPriorityPolicy::ConvPreemptSubsequentlyAutoResumed => Tprictrl::FinishSequenceOnPriority, | ||
| 146 | _ => Tprictrl::AbortCurrentOnPriority, | ||
| 147 | }) | ||
| 148 | .tres().variant(match config.trigger_priority_policy { | ||
| 149 | TriggerPriorityPolicy::ConvPreemptImmediatelyAutoRestarted | | ||
| 150 | TriggerPriorityPolicy::ConvPreemptSoftlyAutoRestarted | | ||
| 151 | TriggerPriorityPolicy::ConvPreemptImmediatelyAutoResumed | | ||
| 152 | TriggerPriorityPolicy::ConvPreemptSoftlyAutoResumed | | ||
| 153 | TriggerPriorityPolicy::ConvPreemptSubsequentlyAutoRestarted | | ||
| 154 | TriggerPriorityPolicy::ConvPreemptSubsequentlyAutoResumed => Tres::Enabled, | ||
| 155 | _ => Tres::Disabled, | ||
| 156 | }) | ||
| 157 | .tcmdres().variant(match config.trigger_priority_policy { | ||
| 158 | TriggerPriorityPolicy::ConvPreemptImmediatelyAutoResumed | | ||
| 159 | TriggerPriorityPolicy::ConvPreemptSoftlyAutoResumed | | ||
| 160 | TriggerPriorityPolicy::ConvPreemptSubsequentlyAutoResumed | | ||
| 161 | TriggerPriorityPolicy::TriggerPriorityExceptionDisabled => Tcmdres::Enabled, | ||
| 162 | _ => Tcmdres::Disabled, | ||
| 163 | }) | ||
| 164 | .hpt_exdi().variant(match config.trigger_priority_policy { | ||
| 165 | TriggerPriorityPolicy::TriggerPriorityExceptionDisabled => HptExdi::Disabled, | ||
| 166 | _ => HptExdi::Enabled, | ||
| 167 | }) | ||
| 168 | }); | ||
| 169 | |||
| 170 | if config.enable_conv_pause { | ||
| 171 | adc.pause().modify(|_, w| unsafe { | ||
| 172 | w.pauseen().enabled() | ||
| 173 | .pausedly().bits(config.conv_pause_delay) | ||
| 174 | }); | ||
| 175 | } else { | ||
| 176 | adc.pause().write(|w| unsafe { w.bits(0) }); | ||
| 177 | } | ||
| 178 | |||
| 179 | adc.fctrl0().write(|w| unsafe { w.fwmark().bits(config.fifo_watermark) }); | ||
| 180 | |||
| 181 | // Enable ADC | ||
| 182 | adc.ctrl().modify(|_, w| w.adcen().enabled()); | ||
| 183 | |||
| 184 | Self { _inst: core::marker::PhantomData } | ||
| 185 | } | ||
| 186 | |||
| 187 | pub fn deinit(&self) { | ||
| 188 | let adc = unsafe { &*I::ptr() }; | ||
| 189 | adc.ctrl().modify(|_, w| w.adcen().disabled()); | ||
| 190 | } | ||
| 191 | |||
| 192 | |||
| 193 | pub fn get_default_config() -> LpadcConfig { | ||
| 194 | LpadcConfig { | ||
| 195 | enable_in_doze_mode: true, | ||
| 196 | conversion_average_mode: CalAvgs::NoAverage, | ||
| 197 | enable_analog_preliminary: false, | ||
| 198 | power_up_delay: 0x80, | ||
| 199 | reference_voltage_source: Refsel::Option1, | ||
| 200 | power_level_mode: Pwrsel::Lowest, | ||
| 201 | trigger_priority_policy: TriggerPriorityPolicy::ConvPreemptImmediatelyNotAutoResumed, | ||
| 202 | enable_conv_pause: false, | ||
| 203 | conv_pause_delay: 0, | ||
| 204 | fifo_watermark: 0, | ||
| 205 | } | ||
| 206 | } | ||
| 207 | |||
| 208 | pub fn do_offset_calibration(&self) { | ||
| 209 | let adc = unsafe { &*I::ptr() }; | ||
| 210 | // Enable calibration mode | ||
| 211 | adc.ctrl().modify(|_, w| w.calofs().offset_calibration_request_pending()); | ||
| 212 | |||
| 213 | // Wait for calibration to complete (polling status register) | ||
| 214 | while adc.stat().read().cal_rdy().is_not_set() {} | ||
| 215 | } | ||
| 216 | |||
| 217 | pub fn get_gain_conv_result(&self, mut gain_adjustment: f32) -> u32{ | ||
| 218 | let mut gcra_array = [0u32; 17]; | ||
| 219 | let mut gcalr: u32 = 0; | ||
| 220 | |||
| 221 | for i in (1..=17).rev() { | ||
| 222 | let shift = 16 - (i - 1); | ||
| 223 | let step = 1.0 / (1u32 << shift) as f32; | ||
| 224 | let tmp = (gain_adjustment / step) as u32; | ||
| 225 | gcra_array[i - 1] = tmp; | ||
| 226 | gain_adjustment = gain_adjustment - tmp as f32 * step; | ||
| 227 | } | ||
| 228 | |||
| 229 | for i in (1..=17).rev() { | ||
| 230 | gcalr += gcra_array[i - 1] << (i - 1); | ||
| 231 | } | ||
| 232 | gcalr | ||
| 233 | } | ||
| 234 | |||
| 235 | pub fn do_auto_calibration(&self) { | ||
| 236 | let adc = unsafe { &*I::ptr() }; | ||
| 237 | adc.ctrl().modify(|_, w| w.cal_req().calibration_request_pending()); | ||
| 238 | |||
| 239 | while adc.gcc0().read().rdy().is_gain_cal_not_valid() {} | ||
| 240 | |||
| 241 | |||
| 242 | let mut gcca = adc.gcc0().read().gain_cal().bits() as u32; | ||
| 243 | if gcca & (((0xFFFF >> 0) + 1) >> 1) != 0 { | ||
| 244 | gcca |= !0xFFFF; | ||
| 245 | } | ||
| 246 | |||
| 247 | let gcra = 131072.0 / (131072.0 - gcca as f32); | ||
| 248 | |||
| 249 | // Write to GCR0 | ||
| 250 | adc.gcr0().write(|w| unsafe { w.bits(self.get_gain_conv_result(gcra)) }); | ||
| 251 | |||
| 252 | adc.gcr0().modify(|_, w| w.rdy().set_bit()); | ||
| 253 | |||
| 254 | // Wait for calibration to complete (polling status register) | ||
| 255 | while adc.stat().read().cal_rdy().is_not_set() {} | ||
| 256 | } | ||
| 257 | |||
| 258 | pub fn do_software_trigger(&self, trigger_id_mask: u32) { | ||
| 259 | let adc = unsafe { &*I::ptr() }; | ||
| 260 | adc.swtrig().write(|w| unsafe { w.bits(trigger_id_mask) }); | ||
| 261 | } | ||
| 262 | |||
| 263 | pub fn get_default_conv_command_config(&self) -> ConvCommandConfig { | ||
| 264 | ConvCommandConfig { | ||
| 265 | sample_channel_mode: Ctype::SingleEndedASideChannel, | ||
| 266 | channel_number: Adch::SelectCh0, | ||
| 267 | chained_next_command_number: Next::NoNextCmdTerminateOnFinish, | ||
| 268 | enable_auto_channel_increment: false, | ||
| 269 | loop_count: 0, | ||
| 270 | hardware_average_mode: Avgs::NoAverage, | ||
| 271 | sample_time_mode: Sts::Sample3p5, | ||
| 272 | hardware_compare_mode: Cmpen::DisabledAlwaysStoreResult, | ||
| 273 | hardware_compare_value_high: 0, | ||
| 274 | hardware_compare_value_low: 0, | ||
| 275 | conversion_resolution_mode: Mode::Data12Bits, | ||
| 276 | enable_wait_trigger: false, | ||
| 277 | } | ||
| 278 | } | ||
| 279 | |||
| 280 | //TBD Need to add cmdlx and cmdhx with x {2..7} | ||
| 281 | pub fn set_conv_command_config(&self, index: u32, config: &ConvCommandConfig) { | ||
| 282 | let adc = unsafe { &*I::ptr() }; | ||
| 283 | |||
| 284 | match index { | ||
| 285 | 1 => { | ||
| 286 | adc.cmdl1().write(|w| { | ||
| 287 | w.adch().variant(config.channel_number) | ||
| 288 | .ctype().variant(config.sample_channel_mode) | ||
| 289 | .mode().variant(config.conversion_resolution_mode) | ||
| 290 | }); | ||
| 291 | adc.cmdh1().write(|w| unsafe { | ||
| 292 | w.next().variant(config.chained_next_command_number) | ||
| 293 | .loop_().bits(config.loop_count) | ||
| 294 | .avgs().variant(config.hardware_average_mode) | ||
| 295 | .sts().variant(config.sample_time_mode) | ||
| 296 | .cmpen().variant(config.hardware_compare_mode); | ||
| 297 | if config.enable_wait_trigger { | ||
| 298 | w.wait_trig().enabled(); | ||
| 299 | } | ||
| 300 | if config.enable_auto_channel_increment { | ||
| 301 | w.lwi().enabled(); | ||
| 302 | } | ||
| 303 | w | ||
| 304 | }); | ||
| 305 | } | ||
| 306 | _ => panic!("Invalid command index: must be between 1 and 7"), | ||
| 307 | } | ||
| 308 | } | ||
| 309 | |||
| 310 | pub fn get_default_conv_trigger_config(&self) -> ConvTriggerConfig { | ||
| 311 | ConvTriggerConfig { | ||
| 312 | target_command_id: Tcmd::NotValid, | ||
| 313 | delay_power: 0, | ||
| 314 | priority: Tpri::HighestPriority, | ||
| 315 | enable_hardware_trigger: false, | ||
| 316 | } | ||
| 317 | } | ||
| 318 | |||
| 319 | pub fn set_conv_trigger_config(&self, trigger_id: usize, config: &ConvTriggerConfig) { | ||
| 320 | let adc = unsafe { &*I::ptr() }; | ||
| 321 | let tctrl = &adc.tctrl(trigger_id); | ||
| 322 | |||
| 323 | tctrl.write(|w| unsafe { | ||
| 324 | let w = w.tcmd().variant(config.target_command_id); | ||
| 325 | let w = w.tdly().bits(config.delay_power); | ||
| 326 | w.tpri().variant(config.priority); | ||
| 327 | if config.enable_hardware_trigger { | ||
| 328 | w.hten().enabled() | ||
| 329 | } else { | ||
| 330 | w | ||
| 331 | } | ||
| 332 | }); | ||
| 333 | } | ||
| 334 | |||
| 335 | pub fn do_reset_fifo(&self) { | ||
| 336 | let adc = unsafe { &*I::ptr() }; | ||
| 337 | adc.ctrl().modify(|_, w| w.rstfifo0().trigger_reset()); | ||
| 338 | } | ||
| 339 | |||
| 340 | pub fn enable_interrupt(&self, mask: u32) { | ||
| 341 | let adc = unsafe { &*I::ptr() }; | ||
| 342 | adc.ie().modify(|r, w| unsafe { w.bits(r.bits() | mask) }); | ||
| 343 | INTERRUPT_TRIGGERED.store(false, Ordering::SeqCst); | ||
| 344 | } | ||
| 345 | |||
| 346 | pub fn is_interrupt_triggered(&self) -> bool { | ||
| 347 | INTERRUPT_TRIGGERED.load(Ordering::Relaxed) | ||
| 348 | } | ||
| 349 | } | ||
| 350 | |||
| 351 | pub fn get_conv_result() -> Option<ConvResult> { | ||
| 352 | let adc = unsafe { &*pac::Adc1::ptr() }; | ||
| 353 | let fifo = adc.resfifo0().read().bits(); | ||
| 354 | const VALID_MASK: u32 = 1 << 31; | ||
| 355 | if fifo & VALID_MASK == 0 { | ||
| 356 | return None; | ||
| 357 | } | ||
| 358 | |||
| 359 | Some(ConvResult { | ||
| 360 | command_id_source: (fifo >> 24) & 0x0F, | ||
| 361 | loop_count_index: (fifo >> 20) & 0x0F, | ||
| 362 | trigger_id_source: (fifo >> 16) & 0x0F, | ||
| 363 | conv_value: (fifo & 0xFFFF) as u16, | ||
| 364 | }) | ||
| 365 | } | ||
| 366 | |||
| 367 | pub fn on_interrupt() { | ||
| 368 | if get_conv_result().is_some() { | ||
| 369 | INTERRUPT_TRIGGERED.store(true, Ordering::SeqCst); | ||
| 370 | } | ||
| 371 | } | ||
| 372 | |||
| 373 | pub struct AdcHandler; | ||
| 374 | impl crate::interrupt::typelevel::Handler<crate::interrupt::typelevel::ADC1> for AdcHandler { | ||
| 375 | unsafe fn on_interrupt() { on_interrupt(); } | ||
| 376 | } \ No newline at end of file | ||
diff --git a/src/baremetal/mod.rs b/src/baremetal/mod.rs deleted file mode 100644 index c03b9538b..000000000 --- a/src/baremetal/mod.rs +++ /dev/null | |||
| @@ -1,6 +0,0 @@ | |||
| 1 | use core::panic::PanicInfo; | ||
| 2 | |||
| 3 | #[panic_handler] | ||
| 4 | fn panic(_info: &PanicInfo) -> ! { | ||
| 5 | loop {} | ||
| 6 | } | ||
diff --git a/src/board.rs b/src/board.rs new file mode 100644 index 000000000..fa679e82c --- /dev/null +++ b/src/board.rs | |||
| @@ -0,0 +1,14 @@ | |||
| 1 | use crate::{clocks, pac, pins}; | ||
| 2 | |||
| 3 | /// Initialize clocks and pin muxing for UART2 debug console. | ||
| 4 | pub unsafe fn init_uart2(p: &pac::Peripherals) { | ||
| 5 | clocks::ensure_frolf_running(p); | ||
| 6 | clocks::enable_uart2_port2(p); | ||
| 7 | pins::configure_uart2_pins_port2(); | ||
| 8 | clocks::select_uart2_clock(p); | ||
| 9 | } | ||
| 10 | |||
| 11 | /// Initialize clocks for the LED GPIO/PORT used by the blink example. | ||
| 12 | pub unsafe fn init_led(p: &pac::Peripherals) { | ||
| 13 | clocks::enable_led_port(p); | ||
| 14 | } | ||
diff --git a/src/clocks.rs b/src/clocks.rs new file mode 100644 index 000000000..5336c3efe --- /dev/null +++ b/src/clocks.rs | |||
| @@ -0,0 +1,136 @@ | |||
| 1 | //! Clock control helpers (no magic numbers, PAC field access only). | ||
| 2 | //! Provides reusable gate abstractions for peripherals used by the examples. | ||
| 3 | use crate::pac; | ||
| 4 | |||
| 5 | /// Trait describing an AHB clock gate that can be toggled through MRCC. | ||
| 6 | pub trait Gate { | ||
| 7 | /// Enable the clock gate. | ||
| 8 | unsafe fn enable(mrcc: &pac::mrcc0::RegisterBlock); | ||
| 9 | |||
| 10 | /// Return whether the clock gate is currently enabled. | ||
| 11 | fn is_enabled(mrcc: &pac::mrcc0::RegisterBlock) -> bool; | ||
| 12 | } | ||
| 13 | |||
| 14 | /// Enable a clock gate for the given peripheral set. | ||
| 15 | #[inline] | ||
| 16 | pub unsafe fn enable<G: Gate>(peripherals: &pac::Peripherals) { | ||
| 17 | let mrcc = &peripherals.mrcc0; | ||
| 18 | G::enable(mrcc); | ||
| 19 | while !G::is_enabled(mrcc) {} | ||
| 20 | core::arch::asm!("dsb sy; isb sy", options(nomem, nostack, preserves_flags)); | ||
| 21 | } | ||
| 22 | |||
| 23 | /// Check whether a gate is currently enabled. | ||
| 24 | #[inline] | ||
| 25 | pub fn is_enabled<G: Gate>(peripherals: &pac::Peripherals) -> bool { | ||
| 26 | G::is_enabled(&peripherals.mrcc0) | ||
| 27 | } | ||
| 28 | |||
| 29 | macro_rules! impl_cc_gate { | ||
| 30 | ($name:ident, $reg:ident, $field:ident) => { | ||
| 31 | pub struct $name; | ||
| 32 | |||
| 33 | impl Gate for $name { | ||
| 34 | #[inline] | ||
| 35 | unsafe fn enable(mrcc: &pac::mrcc0::RegisterBlock) { | ||
| 36 | mrcc.$reg().modify(|_, w| w.$field().enabled()); | ||
| 37 | } | ||
| 38 | |||
| 39 | #[inline] | ||
| 40 | fn is_enabled(mrcc: &pac::mrcc0::RegisterBlock) -> bool { | ||
| 41 | mrcc.$reg().read().$field().is_enabled() | ||
| 42 | } | ||
| 43 | } | ||
| 44 | }; | ||
| 45 | } | ||
| 46 | |||
| 47 | pub mod gate { | ||
| 48 | use super::*; | ||
| 49 | |||
| 50 | impl_cc_gate!(Port2, mrcc_glb_cc1, port2); | ||
| 51 | impl_cc_gate!(Port3, mrcc_glb_cc1, port3); | ||
| 52 | impl_cc_gate!(Ostimer0, mrcc_glb_cc1, ostimer0); | ||
| 53 | impl_cc_gate!(Lpuart2, mrcc_glb_cc0, lpuart2); | ||
| 54 | impl_cc_gate!(Gpio3, mrcc_glb_cc2, gpio3); | ||
| 55 | impl_cc_gate!(Port1, mrcc_glb_cc1, port1); | ||
| 56 | impl_cc_gate!(Adc1, mrcc_glb_cc1, adc1); | ||
| 57 | } | ||
| 58 | |||
| 59 | /// Convenience helper enabling the PORT2 and LPUART2 gates required for the debug UART. | ||
| 60 | pub unsafe fn enable_uart2_port2(peripherals: &pac::Peripherals) { | ||
| 61 | enable::<gate::Port2>(peripherals); | ||
| 62 | enable::<gate::Lpuart2>(peripherals); | ||
| 63 | } | ||
| 64 | |||
| 65 | /// Convenience helper enabling the PORT3 and GPIO3 gates used by the LED in the examples. | ||
| 66 | pub unsafe fn enable_led_port(peripherals: &pac::Peripherals) { | ||
| 67 | enable::<gate::Port3>(peripherals); | ||
| 68 | enable::<gate::Gpio3>(peripherals); | ||
| 69 | } | ||
| 70 | |||
| 71 | /// Convenience helper enabling the OSTIMER0 clock gate. | ||
| 72 | pub unsafe fn enable_ostimer0(peripherals: &pac::Peripherals) { | ||
| 73 | enable::<gate::Ostimer0>(peripherals); | ||
| 74 | } | ||
| 75 | |||
| 76 | pub unsafe fn select_uart2_clock(peripherals: &pac::Peripherals) { | ||
| 77 | // Use FRO_LF_DIV (already running) MUX=0 DIV=0 | ||
| 78 | let mrcc = &peripherals.mrcc0; | ||
| 79 | mrcc.mrcc_lpuart2_clksel() | ||
| 80 | .write(|w| w.mux().clkroot_func_0()); | ||
| 81 | mrcc.mrcc_lpuart2_clkdiv().write(|w| unsafe { w.bits(0) }); | ||
| 82 | } | ||
| 83 | |||
| 84 | pub unsafe fn ensure_frolf_running(peripherals: &pac::Peripherals) { | ||
| 85 | // Ensure FRO_LF divider clock is running (reset default HALT=1 stops it) | ||
| 86 | let sys = &peripherals.syscon; | ||
| 87 | sys.frolfdiv().modify(|_, w| { | ||
| 88 | // DIV defaults to 0; keep it explicit and clear HALT | ||
| 89 | unsafe { w.div().bits(0) }.halt().run() | ||
| 90 | }); | ||
| 91 | } | ||
| 92 | |||
| 93 | /// Compute the FRO_LF_DIV output frequency currently selected for LPUART2. | ||
| 94 | /// Assumes select_uart2_clock() has chosen MUX=0 (FRO_LF_DIV) and DIV is set in SYSCON.FRO_LF_DIV. | ||
| 95 | pub unsafe fn uart2_src_hz(peripherals: &pac::Peripherals) -> u32 { | ||
| 96 | // SYSCON.FRO_LF_DIV: DIV field is simple divider: freq_out = 12_000_000 / (DIV+1) for many NXP parts. | ||
| 97 | // On MCXA276 FRO_LF base is 12 MHz; our init keeps DIV=0, so result=12_000_000. | ||
| 98 | // Read it anyway for future generality. | ||
| 99 | let div = peripherals.syscon.frolfdiv().read().div().bits() as u32; | ||
| 100 | let base = 12_000_000u32; | ||
| 101 | base / (div + 1) | ||
| 102 | } | ||
| 103 | |||
| 104 | /// Enable clock gate and release reset for OSTIMER0. | ||
| 105 | /// Select OSTIMER0 clock source = 1 MHz root (working bring-up configuration). | ||
| 106 | pub unsafe fn select_ostimer0_clock_1m(peripherals: &pac::Peripherals) { | ||
| 107 | let mrcc = &peripherals.mrcc0; | ||
| 108 | mrcc.mrcc_ostimer0_clksel().write(|w| w.mux().clkroot_1m()); | ||
| 109 | } | ||
| 110 | |||
| 111 | pub unsafe fn init_fro16k(peripherals: &pac::Peripherals) { | ||
| 112 | let vbat = &peripherals.vbat0; | ||
| 113 | // Enable FRO16K oscillator | ||
| 114 | vbat.froctla().modify(|_, w| w.fro_en().set_bit()); | ||
| 115 | |||
| 116 | // Lock the control register | ||
| 117 | vbat.frolcka().modify(|_, w| w.lock().set_bit()); | ||
| 118 | |||
| 119 | // Enable clock outputs to both VSYS and VDD_CORE domains | ||
| 120 | // Bit 0: clk_16k0 to VSYS domain | ||
| 121 | // Bit 1: clk_16k1 to VDD_CORE domain | ||
| 122 | vbat.froclke().modify(|_, w| unsafe { w.clke().bits(0x3) }); | ||
| 123 | } | ||
| 124 | |||
| 125 | pub unsafe fn enable_adc(peripherals: &pac::Peripherals) { | ||
| 126 | enable::<gate::Port1>(peripherals); | ||
| 127 | enable::<gate::Adc1>(peripherals); | ||
| 128 | } | ||
| 129 | |||
| 130 | pub unsafe fn select_adc_clock(peripherals: &pac::Peripherals) { | ||
| 131 | // Use FRO_LF_DIV (already running) MUX=0 DIV=0 | ||
| 132 | let mrcc = &peripherals.mrcc0; | ||
| 133 | mrcc.mrcc_adc_clksel() | ||
| 134 | .write(|w| w.mux().clkroot_func_0()); | ||
| 135 | mrcc.mrcc_adc_clkdiv().write(|w| unsafe { w.bits(0) }); | ||
| 136 | } \ No newline at end of file | ||
diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 000000000..93aed5a99 --- /dev/null +++ b/src/config.rs | |||
| @@ -0,0 +1,20 @@ | |||
| 1 | // HAL configuration (minimal), mirroring embassy-imxrt style | ||
| 2 | |||
| 3 | use crate::interrupt::Priority; | ||
| 4 | |||
| 5 | #[non_exhaustive] | ||
| 6 | pub struct Config { | ||
| 7 | pub time_interrupt_priority: Priority, | ||
| 8 | pub rtc_interrupt_priority: Priority, | ||
| 9 | pub adc_interrupt_priority: Priority, | ||
| 10 | } | ||
| 11 | |||
| 12 | impl Default for Config { | ||
| 13 | fn default() -> Self { | ||
| 14 | Self { | ||
| 15 | time_interrupt_priority: Priority::from(0), | ||
| 16 | rtc_interrupt_priority: Priority::from(0), | ||
| 17 | adc_interrupt_priority: Priority::from(0), | ||
| 18 | } | ||
| 19 | } | ||
| 20 | } | ||
diff --git a/src/gpio.rs b/src/gpio.rs new file mode 100644 index 000000000..08f375cba --- /dev/null +++ b/src/gpio.rs | |||
| @@ -0,0 +1,246 @@ | |||
| 1 | //! GPIO driver built around a type-erased `Flex` pin, similar to other Embassy HALs. | ||
| 2 | //! The exported `Output`/`Input` drivers own a `Flex` so they no longer depend on the | ||
| 3 | //! concrete pin type. | ||
| 4 | |||
| 5 | use core::marker::PhantomData; | ||
| 6 | |||
| 7 | use crate::{pac, pins as pin_config}; | ||
| 8 | |||
| 9 | /// Logical level for GPIO pins. | ||
| 10 | #[derive(Copy, Clone, Eq, PartialEq, Debug)] | ||
| 11 | pub enum Level { | ||
| 12 | Low, | ||
| 13 | High, | ||
| 14 | } | ||
| 15 | |||
| 16 | pub type Gpio = crate::peripherals::GPIO; | ||
| 17 | |||
| 18 | /// Type-erased representation of a GPIO pin. | ||
| 19 | #[derive(Copy, Clone)] | ||
| 20 | pub struct AnyPin { | ||
| 21 | port: u8, | ||
| 22 | pin: u8, | ||
| 23 | gpio: *const pac::gpio0::RegisterBlock, | ||
| 24 | } | ||
| 25 | |||
| 26 | impl AnyPin { | ||
| 27 | /// Create an `AnyPin` from raw components. | ||
| 28 | pub fn new(port: u8, pin: u8, gpio: *const pac::gpio0::RegisterBlock) -> Self { | ||
| 29 | Self { port, pin, gpio } | ||
| 30 | } | ||
| 31 | |||
| 32 | #[inline(always)] | ||
| 33 | fn mask(&self) -> u32 { | ||
| 34 | 1u32 << self.pin | ||
| 35 | } | ||
| 36 | |||
| 37 | #[inline(always)] | ||
| 38 | fn gpio(&self) -> &'static pac::gpio0::RegisterBlock { | ||
| 39 | unsafe { &*self.gpio } | ||
| 40 | } | ||
| 41 | |||
| 42 | #[inline(always)] | ||
| 43 | pub fn port_index(&self) -> u8 { | ||
| 44 | self.port | ||
| 45 | } | ||
| 46 | |||
| 47 | #[inline(always)] | ||
| 48 | pub fn pin_index(&self) -> u8 { | ||
| 49 | self.pin | ||
| 50 | } | ||
| 51 | } | ||
| 52 | |||
| 53 | /// Type-level trait implemented by concrete pin ZSTs. | ||
| 54 | pub trait PinId { | ||
| 55 | fn port_index() -> u8; | ||
| 56 | fn pin_index() -> u8; | ||
| 57 | fn gpio_ptr() -> *const pac::gpio0::RegisterBlock; | ||
| 58 | |||
| 59 | fn set_mux_gpio() { | ||
| 60 | unsafe { pin_config::set_pin_mux_gpio(Self::port_index(), Self::pin_index()) } | ||
| 61 | } | ||
| 62 | |||
| 63 | fn degrade() -> AnyPin { | ||
| 64 | AnyPin::new(Self::port_index(), Self::pin_index(), Self::gpio_ptr()) | ||
| 65 | } | ||
| 66 | } | ||
| 67 | |||
| 68 | pub mod pins { | ||
| 69 | use super::{pac, AnyPin, PinId}; | ||
| 70 | |||
| 71 | macro_rules! define_pin { | ||
| 72 | ($Name:ident, $port:literal, $pin:literal, $GpioBlk:ident) => { | ||
| 73 | pub struct $Name; | ||
| 74 | impl super::PinId for $Name { | ||
| 75 | #[inline(always)] | ||
| 76 | fn port_index() -> u8 { | ||
| 77 | $port | ||
| 78 | } | ||
| 79 | #[inline(always)] | ||
| 80 | fn pin_index() -> u8 { | ||
| 81 | $pin | ||
| 82 | } | ||
| 83 | #[inline(always)] | ||
| 84 | fn gpio_ptr() -> *const pac::gpio0::RegisterBlock { | ||
| 85 | pac::$GpioBlk::ptr() | ||
| 86 | } | ||
| 87 | } | ||
| 88 | |||
| 89 | impl $Name { | ||
| 90 | /// Convenience helper to obtain a type-erased handle to this pin. | ||
| 91 | pub fn degrade() -> AnyPin { | ||
| 92 | <Self as PinId>::degrade() | ||
| 93 | } | ||
| 94 | |||
| 95 | pub fn set_mux_gpio() { | ||
| 96 | <Self as PinId>::set_mux_gpio() | ||
| 97 | } | ||
| 98 | } | ||
| 99 | }; | ||
| 100 | } | ||
| 101 | |||
| 102 | // Extend this list as more pins are needed. | ||
| 103 | define_pin!(PIO3_18, 3, 18, Gpio3); | ||
| 104 | } | ||
| 105 | |||
| 106 | /// A flexible pin that can be configured as input or output. | ||
| 107 | pub struct Flex<'d> { | ||
| 108 | pin: AnyPin, | ||
| 109 | _marker: PhantomData<&'d mut ()>, | ||
| 110 | } | ||
| 111 | |||
| 112 | impl<'d> Flex<'d> { | ||
| 113 | pub fn new(pin: AnyPin) -> Self { | ||
| 114 | Self { | ||
| 115 | pin, | ||
| 116 | _marker: PhantomData, | ||
| 117 | } | ||
| 118 | } | ||
| 119 | |||
| 120 | #[inline(always)] | ||
| 121 | fn gpio(&self) -> &'static pac::gpio0::RegisterBlock { | ||
| 122 | self.pin.gpio() | ||
| 123 | } | ||
| 124 | |||
| 125 | #[inline(always)] | ||
| 126 | fn mask(&self) -> u32 { | ||
| 127 | self.pin.mask() | ||
| 128 | } | ||
| 129 | |||
| 130 | pub fn set_as_input(&mut self) { | ||
| 131 | let mask = self.mask(); | ||
| 132 | let gpio = self.gpio(); | ||
| 133 | gpio.pddr() | ||
| 134 | .modify(|r, w| unsafe { w.bits(r.bits() & !mask) }); | ||
| 135 | } | ||
| 136 | |||
| 137 | pub fn set_as_output(&mut self) { | ||
| 138 | let mask = self.mask(); | ||
| 139 | let gpio = self.gpio(); | ||
| 140 | gpio.pddr() | ||
| 141 | .modify(|r, w| unsafe { w.bits(r.bits() | mask) }); | ||
| 142 | } | ||
| 143 | |||
| 144 | pub fn set_high(&mut self) { | ||
| 145 | self.gpio().psor().write(|w| unsafe { w.bits(self.mask()) }); | ||
| 146 | } | ||
| 147 | |||
| 148 | pub fn set_low(&mut self) { | ||
| 149 | self.gpio().pcor().write(|w| unsafe { w.bits(self.mask()) }); | ||
| 150 | } | ||
| 151 | |||
| 152 | pub fn set_level(&mut self, level: Level) { | ||
| 153 | match level { | ||
| 154 | Level::High => self.set_high(), | ||
| 155 | Level::Low => self.set_low(), | ||
| 156 | } | ||
| 157 | } | ||
| 158 | |||
| 159 | pub fn toggle(&mut self) { | ||
| 160 | self.gpio().ptor().write(|w| unsafe { w.bits(self.mask()) }); | ||
| 161 | } | ||
| 162 | |||
| 163 | pub fn is_high(&self) -> bool { | ||
| 164 | (self.gpio().pdir().read().bits() & self.mask()) != 0 | ||
| 165 | } | ||
| 166 | |||
| 167 | pub fn is_low(&self) -> bool { | ||
| 168 | !self.is_high() | ||
| 169 | } | ||
| 170 | } | ||
| 171 | |||
| 172 | /// GPIO output driver that owns a `Flex` pin. | ||
| 173 | pub struct Output<'d> { | ||
| 174 | flex: Flex<'d>, | ||
| 175 | } | ||
| 176 | |||
| 177 | impl<'d> Output<'d> { | ||
| 178 | pub fn new(pin: AnyPin, initial: Level) -> Self { | ||
| 179 | let mut flex = Flex::new(pin); | ||
| 180 | flex.set_level(initial); | ||
| 181 | flex.set_as_output(); | ||
| 182 | Self { flex } | ||
| 183 | } | ||
| 184 | |||
| 185 | #[inline] | ||
| 186 | pub fn set_high(&mut self) { | ||
| 187 | self.flex.set_high(); | ||
| 188 | } | ||
| 189 | |||
| 190 | #[inline] | ||
| 191 | pub fn set_low(&mut self) { | ||
| 192 | self.flex.set_low(); | ||
| 193 | } | ||
| 194 | |||
| 195 | #[inline] | ||
| 196 | pub fn set_level(&mut self, level: Level) { | ||
| 197 | self.flex.set_level(level); | ||
| 198 | } | ||
| 199 | |||
| 200 | #[inline] | ||
| 201 | pub fn toggle(&mut self) { | ||
| 202 | self.flex.toggle(); | ||
| 203 | } | ||
| 204 | |||
| 205 | #[inline] | ||
| 206 | pub fn is_set_high(&self) -> bool { | ||
| 207 | self.flex.is_high() | ||
| 208 | } | ||
| 209 | |||
| 210 | #[inline] | ||
| 211 | pub fn is_set_low(&self) -> bool { | ||
| 212 | !self.is_set_high() | ||
| 213 | } | ||
| 214 | |||
| 215 | /// Expose the inner `Flex` if callers need to reconfigure the pin. | ||
| 216 | pub fn into_flex(self) -> Flex<'d> { | ||
| 217 | self.flex | ||
| 218 | } | ||
| 219 | } | ||
| 220 | |||
| 221 | /// GPIO input driver that owns a `Flex` pin. | ||
| 222 | pub struct Input<'d> { | ||
| 223 | flex: Flex<'d>, | ||
| 224 | } | ||
| 225 | |||
| 226 | impl<'d> Input<'d> { | ||
| 227 | pub fn new(pin: AnyPin) -> Self { | ||
| 228 | let mut flex = Flex::new(pin); | ||
| 229 | flex.set_as_input(); | ||
| 230 | Self { flex } | ||
| 231 | } | ||
| 232 | |||
| 233 | #[inline] | ||
| 234 | pub fn is_high(&self) -> bool { | ||
| 235 | self.flex.is_high() | ||
| 236 | } | ||
| 237 | |||
| 238 | #[inline] | ||
| 239 | pub fn is_low(&self) -> bool { | ||
| 240 | self.flex.is_low() | ||
| 241 | } | ||
| 242 | |||
| 243 | pub fn into_flex(self) -> Flex<'d> { | ||
| 244 | self.flex | ||
| 245 | } | ||
| 246 | } | ||
diff --git a/src/interrupt.rs b/src/interrupt.rs new file mode 100644 index 000000000..8226f01ab --- /dev/null +++ b/src/interrupt.rs | |||
| @@ -0,0 +1,349 @@ | |||
| 1 | //! Minimal interrupt helpers mirroring embassy-imxrt style for OS_EVENT and LPUART2. | ||
| 2 | //! Type-level interrupt traits and bindings are provided by the | ||
| 3 | //! `embassy_hal_internal::interrupt_mod!` macro via the generated module below. | ||
| 4 | |||
| 5 | mod generated { | ||
| 6 | embassy_hal_internal::interrupt_mod!(OS_EVENT, LPUART2, RTC, ADC1); | ||
| 7 | } | ||
| 8 | |||
| 9 | pub use generated::interrupt::typelevel; | ||
| 10 | pub use generated::interrupt::Priority; | ||
| 11 | |||
| 12 | use crate::pac::Interrupt; | ||
| 13 | use core::sync::atomic::{AtomicU16, AtomicU32, Ordering}; | ||
| 14 | |||
| 15 | /// Trait for configuring and controlling interrupts. | ||
| 16 | /// | ||
| 17 | /// This trait provides a consistent interface for interrupt management across | ||
| 18 | /// different interrupt sources, similar to embassy-imxrt's InterruptExt. | ||
| 19 | pub trait InterruptExt { | ||
| 20 | /// Clear any pending interrupt in NVIC. | ||
| 21 | fn unpend(&self); | ||
| 22 | |||
| 23 | /// Set NVIC priority for this interrupt. | ||
| 24 | fn set_priority(&self, priority: Priority); | ||
| 25 | |||
| 26 | /// Enable this interrupt in NVIC. | ||
| 27 | /// | ||
| 28 | /// # Safety | ||
| 29 | /// This function is unsafe because it can enable interrupts that may not be | ||
| 30 | /// properly configured, potentially leading to undefined behavior. | ||
| 31 | unsafe fn enable(&self); | ||
| 32 | |||
| 33 | /// Disable this interrupt in NVIC. | ||
| 34 | /// | ||
| 35 | /// # Safety | ||
| 36 | /// This function is unsafe because disabling interrupts may leave the system | ||
| 37 | /// in an inconsistent state if the interrupt was expected to fire. | ||
| 38 | unsafe fn disable(&self); | ||
| 39 | |||
| 40 | /// Check if the interrupt is pending in NVIC. | ||
| 41 | fn is_pending(&self) -> bool; | ||
| 42 | } | ||
| 43 | |||
| 44 | #[derive(Clone, Copy, Debug, Default)] | ||
| 45 | pub struct DefaultHandlerSnapshot { | ||
| 46 | pub vector: u16, | ||
| 47 | pub count: u32, | ||
| 48 | pub cfsr: u32, | ||
| 49 | pub hfsr: u32, | ||
| 50 | pub stacked_pc: u32, | ||
| 51 | pub stacked_lr: u32, | ||
| 52 | pub stacked_sp: u32, | ||
| 53 | } | ||
| 54 | |||
| 55 | static LAST_DEFAULT_VECTOR: AtomicU16 = AtomicU16::new(0); | ||
| 56 | static LAST_DEFAULT_COUNT: AtomicU32 = AtomicU32::new(0); | ||
| 57 | static LAST_DEFAULT_CFSR: AtomicU32 = AtomicU32::new(0); | ||
| 58 | static LAST_DEFAULT_HFSR: AtomicU32 = AtomicU32::new(0); | ||
| 59 | static LAST_DEFAULT_PC: AtomicU32 = AtomicU32::new(0); | ||
| 60 | static LAST_DEFAULT_LR: AtomicU32 = AtomicU32::new(0); | ||
| 61 | static LAST_DEFAULT_SP: AtomicU32 = AtomicU32::new(0); | ||
| 62 | |||
| 63 | #[inline] | ||
| 64 | pub fn default_handler_snapshot() -> DefaultHandlerSnapshot { | ||
| 65 | DefaultHandlerSnapshot { | ||
| 66 | vector: LAST_DEFAULT_VECTOR.load(Ordering::Relaxed), | ||
| 67 | count: LAST_DEFAULT_COUNT.load(Ordering::Relaxed), | ||
| 68 | cfsr: LAST_DEFAULT_CFSR.load(Ordering::Relaxed), | ||
| 69 | hfsr: LAST_DEFAULT_HFSR.load(Ordering::Relaxed), | ||
| 70 | stacked_pc: LAST_DEFAULT_PC.load(Ordering::Relaxed), | ||
| 71 | stacked_lr: LAST_DEFAULT_LR.load(Ordering::Relaxed), | ||
| 72 | stacked_sp: LAST_DEFAULT_SP.load(Ordering::Relaxed), | ||
| 73 | } | ||
| 74 | } | ||
| 75 | |||
| 76 | #[inline] | ||
| 77 | pub fn clear_default_handler_snapshot() { | ||
| 78 | LAST_DEFAULT_VECTOR.store(0, Ordering::Relaxed); | ||
| 79 | LAST_DEFAULT_COUNT.store(0, Ordering::Relaxed); | ||
| 80 | LAST_DEFAULT_CFSR.store(0, Ordering::Relaxed); | ||
| 81 | LAST_DEFAULT_HFSR.store(0, Ordering::Relaxed); | ||
| 82 | LAST_DEFAULT_PC.store(0, Ordering::Relaxed); | ||
| 83 | LAST_DEFAULT_LR.store(0, Ordering::Relaxed); | ||
| 84 | LAST_DEFAULT_SP.store(0, Ordering::Relaxed); | ||
| 85 | } | ||
| 86 | |||
| 87 | /// OS_EVENT interrupt helper with methods similar to embassy-imxrt's InterruptExt. | ||
| 88 | pub struct OsEvent; | ||
| 89 | pub const OS_EVENT: OsEvent = OsEvent; | ||
| 90 | |||
| 91 | impl InterruptExt for OsEvent { | ||
| 92 | /// Clear any pending OS_EVENT in NVIC. | ||
| 93 | #[inline] | ||
| 94 | fn unpend(&self) { | ||
| 95 | cortex_m::peripheral::NVIC::unpend(Interrupt::OS_EVENT); | ||
| 96 | } | ||
| 97 | |||
| 98 | /// Set NVIC priority for OS_EVENT. | ||
| 99 | #[inline] | ||
| 100 | fn set_priority(&self, priority: Priority) { | ||
| 101 | unsafe { | ||
| 102 | let mut nvic = cortex_m::peripheral::Peripherals::steal().NVIC; | ||
| 103 | nvic.set_priority(Interrupt::OS_EVENT, u8::from(priority)); | ||
| 104 | } | ||
| 105 | } | ||
| 106 | |||
| 107 | /// Enable OS_EVENT in NVIC. | ||
| 108 | #[inline] | ||
| 109 | unsafe fn enable(&self) { | ||
| 110 | cortex_m::peripheral::NVIC::unmask(Interrupt::OS_EVENT); | ||
| 111 | } | ||
| 112 | |||
| 113 | /// Disable OS_EVENT in NVIC. | ||
| 114 | #[inline] | ||
| 115 | unsafe fn disable(&self) { | ||
| 116 | cortex_m::peripheral::NVIC::mask(Interrupt::OS_EVENT); | ||
| 117 | } | ||
| 118 | |||
| 119 | /// Check if OS_EVENT is pending in NVIC. | ||
| 120 | #[inline] | ||
| 121 | fn is_pending(&self) -> bool { | ||
| 122 | cortex_m::peripheral::NVIC::is_pending(Interrupt::OS_EVENT) | ||
| 123 | } | ||
| 124 | } | ||
| 125 | |||
| 126 | impl OsEvent { | ||
| 127 | /// Configure OS_EVENT interrupt for timer operation. | ||
| 128 | /// This sets up the NVIC priority, enables the interrupt, and ensures global interrupts are enabled. | ||
| 129 | /// Also performs a software event to wake any pending WFE. | ||
| 130 | pub fn configure_for_timer(&self, priority: Priority) { | ||
| 131 | // Configure NVIC | ||
| 132 | self.unpend(); | ||
| 133 | self.set_priority(priority); | ||
| 134 | unsafe { | ||
| 135 | self.enable(); | ||
| 136 | } | ||
| 137 | |||
| 138 | // Ensure global interrupts are enabled in no-reset scenarios (e.g., cargo run) | ||
| 139 | // Debuggers typically perform a reset which leaves PRIMASK=0; cargo run may not. | ||
| 140 | unsafe { | ||
| 141 | cortex_m::interrupt::enable(); | ||
| 142 | } | ||
| 143 | |||
| 144 | // Wake any executor WFE that might be sleeping when we armed the first deadline | ||
| 145 | cortex_m::asm::sev(); | ||
| 146 | } | ||
| 147 | } | ||
| 148 | |||
| 149 | /// LPUART2 interrupt helper with methods similar to embassy-imxrt's InterruptExt. | ||
| 150 | pub struct Lpuart2; | ||
| 151 | pub const LPUART2: Lpuart2 = Lpuart2; | ||
| 152 | |||
| 153 | impl InterruptExt for Lpuart2 { | ||
| 154 | /// Clear any pending LPUART2 in NVIC. | ||
| 155 | #[inline] | ||
| 156 | fn unpend(&self) { | ||
| 157 | cortex_m::peripheral::NVIC::unpend(Interrupt::LPUART2); | ||
| 158 | } | ||
| 159 | |||
| 160 | /// Set NVIC priority for LPUART2. | ||
| 161 | #[inline] | ||
| 162 | fn set_priority(&self, priority: Priority) { | ||
| 163 | unsafe { | ||
| 164 | let mut nvic = cortex_m::peripheral::Peripherals::steal().NVIC; | ||
| 165 | nvic.set_priority(Interrupt::LPUART2, u8::from(priority)); | ||
| 166 | } | ||
| 167 | } | ||
| 168 | |||
| 169 | /// Enable LPUART2 in NVIC. | ||
| 170 | #[inline] | ||
| 171 | unsafe fn enable(&self) { | ||
| 172 | cortex_m::peripheral::NVIC::unmask(Interrupt::LPUART2); | ||
| 173 | } | ||
| 174 | |||
| 175 | /// Disable LPUART2 in NVIC. | ||
| 176 | #[inline] | ||
| 177 | unsafe fn disable(&self) { | ||
| 178 | cortex_m::peripheral::NVIC::mask(Interrupt::LPUART2); | ||
| 179 | } | ||
| 180 | |||
| 181 | /// Check if LPUART2 is pending in NVIC. | ||
| 182 | #[inline] | ||
| 183 | fn is_pending(&self) -> bool { | ||
| 184 | cortex_m::peripheral::NVIC::is_pending(Interrupt::LPUART2) | ||
| 185 | } | ||
| 186 | } | ||
| 187 | |||
| 188 | impl Lpuart2 { | ||
| 189 | /// Configure LPUART2 interrupt for UART operation. | ||
| 190 | /// This sets up the NVIC priority, enables the interrupt, and ensures global interrupts are enabled. | ||
| 191 | pub fn configure_for_uart(&self, priority: Priority) { | ||
| 192 | // Configure NVIC | ||
| 193 | self.unpend(); | ||
| 194 | self.set_priority(priority); | ||
| 195 | unsafe { | ||
| 196 | self.enable(); | ||
| 197 | } | ||
| 198 | |||
| 199 | // Ensure global interrupts are enabled in no-reset scenarios (e.g., cargo run) | ||
| 200 | // Debuggers typically perform a reset which leaves PRIMASK=0; cargo run may not. | ||
| 201 | unsafe { | ||
| 202 | cortex_m::interrupt::enable(); | ||
| 203 | } | ||
| 204 | } | ||
| 205 | |||
| 206 | /// Install LPUART2 handler into the RAM vector table. | ||
| 207 | /// Safety: See `install_irq_handler`. | ||
| 208 | pub unsafe fn install_handler(&self, handler: unsafe extern "C" fn()) { | ||
| 209 | install_irq_handler(Interrupt::LPUART2, handler); | ||
| 210 | } | ||
| 211 | } | ||
| 212 | |||
| 213 | pub struct Rtc; | ||
| 214 | pub const RTC: Rtc = Rtc; | ||
| 215 | |||
| 216 | impl InterruptExt for Rtc { | ||
| 217 | /// Clear any pending RTC in NVIC. | ||
| 218 | #[inline] | ||
| 219 | fn unpend(&self) { | ||
| 220 | cortex_m::peripheral::NVIC::unpend(Interrupt::RTC); | ||
| 221 | } | ||
| 222 | |||
| 223 | /// Set NVIC priority for RTC. | ||
| 224 | #[inline] | ||
| 225 | fn set_priority(&self, priority: Priority) { | ||
| 226 | unsafe { | ||
| 227 | let mut nvic = cortex_m::peripheral::Peripherals::steal().NVIC; | ||
| 228 | nvic.set_priority(Interrupt::RTC, u8::from(priority)); | ||
| 229 | } | ||
| 230 | } | ||
| 231 | |||
| 232 | /// Enable RTC in NVIC. | ||
| 233 | #[inline] | ||
| 234 | unsafe fn enable(&self) { | ||
| 235 | cortex_m::peripheral::NVIC::unmask(Interrupt::RTC); | ||
| 236 | } | ||
| 237 | |||
| 238 | /// Disable RTC in NVIC. | ||
| 239 | #[inline] | ||
| 240 | unsafe fn disable(&self) { | ||
| 241 | cortex_m::peripheral::NVIC::mask(Interrupt::RTC); | ||
| 242 | } | ||
| 243 | |||
| 244 | /// Check if RTC is pending in NVIC. | ||
| 245 | #[inline] | ||
| 246 | fn is_pending(&self) -> bool { | ||
| 247 | cortex_m::peripheral::NVIC::is_pending(Interrupt::RTC) | ||
| 248 | } | ||
| 249 | } | ||
| 250 | |||
| 251 | pub struct Adc; | ||
| 252 | pub const ADC1: Adc = Adc; | ||
| 253 | |||
| 254 | impl InterruptExt for Adc { | ||
| 255 | /// Clear any pending ADC1 in NVIC. | ||
| 256 | #[inline] | ||
| 257 | fn unpend(&self) { | ||
| 258 | cortex_m::peripheral::NVIC::unpend(Interrupt::ADC1); | ||
| 259 | } | ||
| 260 | |||
| 261 | /// Set NVIC priority for ADC1. | ||
| 262 | #[inline] | ||
| 263 | fn set_priority(&self, priority: Priority) { | ||
| 264 | unsafe { | ||
| 265 | let mut nvic = cortex_m::peripheral::Peripherals::steal().NVIC; | ||
| 266 | nvic.set_priority(Interrupt::ADC1, u8::from(priority)); | ||
| 267 | } | ||
| 268 | } | ||
| 269 | |||
| 270 | /// Enable ADC1 in NVIC. | ||
| 271 | #[inline] | ||
| 272 | unsafe fn enable(&self) { | ||
| 273 | cortex_m::peripheral::NVIC::unmask(Interrupt::ADC1); | ||
| 274 | } | ||
| 275 | |||
| 276 | /// Disable ADC1 in NVIC. | ||
| 277 | #[inline] | ||
| 278 | unsafe fn disable(&self) { | ||
| 279 | cortex_m::peripheral::NVIC::mask(Interrupt::ADC1); | ||
| 280 | } | ||
| 281 | |||
| 282 | /// Check if ADC1 is pending in NVIC. | ||
| 283 | #[inline] | ||
| 284 | fn is_pending(&self) -> bool { | ||
| 285 | cortex_m::peripheral::NVIC::is_pending(Interrupt::ADC1) | ||
| 286 | } | ||
| 287 | } | ||
| 288 | |||
| 289 | /// Set VTOR (Vector Table Offset) to a RAM-based vector table. | ||
| 290 | /// Pass a pointer to the first word in the RAM table (stack pointer slot 0). | ||
| 291 | /// Safety: Caller must ensure the RAM table is valid and aligned as required by the core. | ||
| 292 | pub unsafe fn vtor_set_ram_vector_base(base: *const u32) { | ||
| 293 | core::ptr::write_volatile(0xE000_ED08 as *mut u32, base as u32); | ||
| 294 | } | ||
| 295 | |||
| 296 | /// Install an interrupt handler into the current VTOR-based vector table. | ||
| 297 | /// This writes the function pointer at index 16 + irq number. | ||
| 298 | /// Safety: Caller must ensure VTOR points at a writable RAM table and that `handler` | ||
| 299 | /// has the correct ABI and lifetime. | ||
| 300 | pub unsafe fn install_irq_handler(irq: Interrupt, handler: unsafe extern "C" fn()) { | ||
| 301 | let vtor_base = core::ptr::read_volatile(0xE000_ED08 as *const u32) as *mut u32; | ||
| 302 | let idx = 16 + (irq as isize as usize); | ||
| 303 | core::ptr::write_volatile(vtor_base.add(idx), handler as usize as u32); | ||
| 304 | } | ||
| 305 | |||
| 306 | impl OsEvent { | ||
| 307 | /// Convenience to install the OS_EVENT handler into the RAM vector table. | ||
| 308 | /// Safety: See `install_irq_handler`. | ||
| 309 | pub unsafe fn install_handler(&self, handler: extern "C" fn()) { | ||
| 310 | install_irq_handler(Interrupt::OS_EVENT, handler); | ||
| 311 | } | ||
| 312 | } | ||
| 313 | |||
| 314 | /// Install OS_EVENT handler by raw address. Useful to avoid fn pointer type mismatches. | ||
| 315 | /// Safety: Caller must ensure the address is a valid `extern "C" fn()` handler. | ||
| 316 | pub unsafe fn os_event_install_handler_raw(handler_addr: usize) { | ||
| 317 | let vtor_base = core::ptr::read_volatile(0xE000_ED08 as *const u32) as *mut u32; | ||
| 318 | let idx = 16 + (Interrupt::OS_EVENT as isize as usize); | ||
| 319 | core::ptr::write_volatile(vtor_base.add(idx), handler_addr as u32); | ||
| 320 | } | ||
| 321 | |||
| 322 | /// Provide a conservative default IRQ handler that avoids wedging the system. | ||
| 323 | /// It clears all NVIC pending bits and returns, so spurious or reserved IRQs | ||
| 324 | /// don’t trap the core in an infinite WFI loop during bring-up. | ||
| 325 | #[no_mangle] | ||
| 326 | pub unsafe extern "C" fn DefaultHandler() { | ||
| 327 | let active = core::ptr::read_volatile(0xE000_ED04 as *const u32) & 0x1FF; | ||
| 328 | let cfsr = core::ptr::read_volatile(0xE000_ED28 as *const u32); | ||
| 329 | let hfsr = core::ptr::read_volatile(0xE000_ED2C as *const u32); | ||
| 330 | |||
| 331 | let sp = cortex_m::register::msp::read(); | ||
| 332 | let stacked = sp as *const u32; | ||
| 333 | // Stacked registers follow ARMv8-M procedure call standard order | ||
| 334 | let stacked_pc = unsafe { stacked.add(6).read() }; | ||
| 335 | let stacked_lr = unsafe { stacked.add(5).read() }; | ||
| 336 | |||
| 337 | LAST_DEFAULT_VECTOR.store(active as u16, Ordering::Relaxed); | ||
| 338 | LAST_DEFAULT_CFSR.store(cfsr, Ordering::Relaxed); | ||
| 339 | LAST_DEFAULT_HFSR.store(hfsr, Ordering::Relaxed); | ||
| 340 | LAST_DEFAULT_COUNT.fetch_add(1, Ordering::Relaxed); | ||
| 341 | LAST_DEFAULT_PC.store(stacked_pc, Ordering::Relaxed); | ||
| 342 | LAST_DEFAULT_LR.store(stacked_lr, Ordering::Relaxed); | ||
| 343 | LAST_DEFAULT_SP.store(sp, Ordering::Relaxed); | ||
| 344 | |||
| 345 | // Do nothing here: on some MCUs/TrustZone setups, writing NVIC from a spurious | ||
| 346 | // handler can fault if targeting the Secure bank. Just return. | ||
| 347 | cortex_m::asm::dsb(); | ||
| 348 | cortex_m::asm::isb(); | ||
| 349 | } | ||
diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 000000000..eb4727106 --- /dev/null +++ b/src/lib.rs | |||
| @@ -0,0 +1,165 @@ | |||
| 1 | #![no_std] | ||
| 2 | |||
| 3 | pub mod clocks; // still provide clock helpers | ||
| 4 | #[cfg(feature = "gpio")] | ||
| 5 | pub mod gpio; | ||
| 6 | pub mod pins; // pin mux helpers | ||
| 7 | pub mod reset; // reset control helpers | ||
| 8 | |||
| 9 | pub mod config; | ||
| 10 | pub mod interrupt; | ||
| 11 | pub mod ostimer; | ||
| 12 | pub mod uart; | ||
| 13 | pub mod lpuart; | ||
| 14 | pub mod rtc; | ||
| 15 | pub mod adc; | ||
| 16 | |||
| 17 | embassy_hal_internal::peripherals!( | ||
| 18 | #[cfg(feature = "lpuart2")] | ||
| 19 | LPUART2, | ||
| 20 | #[cfg(feature = "ostimer0")] | ||
| 21 | OSTIMER0, | ||
| 22 | #[cfg(feature = "gpio")] | ||
| 23 | GPIO, | ||
| 24 | #[cfg(feature = "rtc0")] | ||
| 25 | RTC0, | ||
| 26 | #[cfg(feature = "adc1")] | ||
| 27 | ADC1, | ||
| 28 | ); | ||
| 29 | |||
| 30 | /// Get access to the PAC Peripherals for low-level register access. | ||
| 31 | /// This is a lazy-initialized singleton that can be called after init(). | ||
| 32 | #[allow(static_mut_refs)] | ||
| 33 | pub fn pac() -> &'static pac::Peripherals { | ||
| 34 | // SAFETY: We only call this after init(), and the PAC is a singleton. | ||
| 35 | // The embassy peripheral tokens ensure we don't have multiple mutable accesses. | ||
| 36 | unsafe { | ||
| 37 | static mut PAC_INSTANCE: Option<pac::Peripherals> = None; | ||
| 38 | if PAC_INSTANCE.is_none() { | ||
| 39 | PAC_INSTANCE = Some(pac::Peripherals::steal()); | ||
| 40 | } | ||
| 41 | PAC_INSTANCE.as_ref().unwrap() | ||
| 42 | } | ||
| 43 | } | ||
| 44 | |||
| 45 | pub use cortex_m_rt; | ||
| 46 | pub use mcxa276_pac as pac; | ||
| 47 | // Use cortex-m-rt's #[interrupt] attribute directly; PAC does not re-export it. | ||
| 48 | |||
| 49 | // Re-export interrupt traits and types | ||
| 50 | pub use interrupt::InterruptExt; | ||
| 51 | #[cfg(feature = "ostimer0")] | ||
| 52 | pub use ostimer::Ostimer0 as Ostimer0Token; | ||
| 53 | #[cfg(feature = "lpuart2")] | ||
| 54 | pub use uart::Lpuart2 as Uart2Token; | ||
| 55 | #[cfg(feature = "rtc0")] | ||
| 56 | pub use rtc::Rtc0 as Rtc0Token; | ||
| 57 | #[cfg(feature = "adc1")] | ||
| 58 | pub use adc::Adc1 as Adc1Token; | ||
| 59 | #[cfg(feature = "gpio")] | ||
| 60 | pub use gpio::{pins::*, AnyPin, Flex, Gpio as GpioToken, Input, Level, Output}; | ||
| 61 | |||
| 62 | /// Initialize HAL with configuration (mirrors embassy-imxrt style). Minimal: just take peripherals. | ||
| 63 | /// Also applies configurable NVIC priority for the OSTIMER OS_EVENT interrupt (no enabling). | ||
| 64 | #[allow(unused_variables)] | ||
| 65 | pub fn init(cfg: crate::config::Config) -> Peripherals { | ||
| 66 | let peripherals = Peripherals::take(); | ||
| 67 | #[cfg(feature = "ostimer0")] | ||
| 68 | { | ||
| 69 | // Apply user-configured priority early; enabling is left to examples/apps | ||
| 70 | crate::interrupt::OS_EVENT.set_priority(cfg.time_interrupt_priority); | ||
| 71 | } | ||
| 72 | #[cfg(feature = "rtc0")] | ||
| 73 | { | ||
| 74 | // Apply user-configured priority early; enabling is left to examples/apps | ||
| 75 | crate::interrupt::RTC.set_priority(cfg.rtc_interrupt_priority); | ||
| 76 | } | ||
| 77 | #[cfg(feature = "adc1")] | ||
| 78 | { | ||
| 79 | // Apply user-configured priority early; enabling is left to examples/apps | ||
| 80 | crate::interrupt::ADC1.set_priority(cfg.adc_interrupt_priority); | ||
| 81 | } | ||
| 82 | peripherals | ||
| 83 | } | ||
| 84 | |||
| 85 | /// Optional hook called by cortex-m-rt before RAM init. | ||
| 86 | /// We proactively mask and clear all NVIC IRQs to avoid wedges from stale state | ||
| 87 | /// left by soft resets/debug sessions. | ||
| 88 | /// | ||
| 89 | /// NOTE: Manual VTOR setup is required for RAM execution. The cortex-m-rt 'set-vtor' | ||
| 90 | /// feature is incompatible with our setup because it expects __vector_table to be | ||
| 91 | /// defined differently than how our RAM-based linker script arranges it. | ||
| 92 | #[no_mangle] | ||
| 93 | pub unsafe extern "C" fn __pre_init() { | ||
| 94 | // Set the VTOR to point to the interrupt vector table in RAM | ||
| 95 | // This is required since code runs from RAM on this MCU | ||
| 96 | crate::interrupt::vtor_set_ram_vector_base(0x2000_0000 as *const u32); | ||
| 97 | |||
| 98 | // Mask and clear pending for all NVIC lines (0..127) to avoid stale state across runs. | ||
| 99 | let nvic = &*cortex_m::peripheral::NVIC::PTR; | ||
| 100 | for i in 0..4 { | ||
| 101 | // 4 words x 32 = 128 IRQs | ||
| 102 | nvic.icer[i].write(0xFFFF_FFFF); | ||
| 103 | nvic.icpr[i].write(0xFFFF_FFFF); | ||
| 104 | } | ||
| 105 | // Do NOT touch peripheral registers here: clocks may be off and accesses can fault. | ||
| 106 | crate::interrupt::clear_default_handler_snapshot(); | ||
| 107 | } | ||
| 108 | |||
| 109 | /// Internal helper to dispatch a type-level interrupt handler. | ||
| 110 | #[inline(always)] | ||
| 111 | #[doc(hidden)] | ||
| 112 | pub unsafe fn __handle_interrupt<T, H>() | ||
| 113 | where | ||
| 114 | T: crate::interrupt::typelevel::Interrupt, | ||
| 115 | H: crate::interrupt::typelevel::Handler<T>, | ||
| 116 | { | ||
| 117 | H::on_interrupt(); | ||
| 118 | } | ||
| 119 | |||
| 120 | /// Macro to bind interrupts to handlers, similar to embassy-imxrt. | ||
| 121 | /// | ||
| 122 | /// Example: | ||
| 123 | /// - Bind OS_EVENT to the OSTIMER time-driver handler | ||
| 124 | /// bind_interrupts!(struct Irqs { OS_EVENT => crate::ostimer::time_driver::OsEventHandler; }); | ||
| 125 | #[macro_export] | ||
| 126 | macro_rules! bind_interrupts { | ||
| 127 | ($(#[$attr:meta])* $vis:vis struct $name:ident { | ||
| 128 | $( | ||
| 129 | $(#[cfg($cond_irq:meta)])? | ||
| 130 | $irq:ident => $( | ||
| 131 | $(#[cfg($cond_handler:meta)])? | ||
| 132 | $handler:ty | ||
| 133 | ),*; | ||
| 134 | )* | ||
| 135 | }) => { | ||
| 136 | #[derive(Copy, Clone)] | ||
| 137 | $(#[$attr])* | ||
| 138 | $vis struct $name; | ||
| 139 | |||
| 140 | $( | ||
| 141 | #[allow(non_snake_case)] | ||
| 142 | #[no_mangle] | ||
| 143 | $(#[cfg($cond_irq)])? | ||
| 144 | unsafe extern "C" fn $irq() { | ||
| 145 | unsafe { | ||
| 146 | $( | ||
| 147 | $(#[cfg($cond_handler)])? | ||
| 148 | <$handler as $crate::interrupt::typelevel::Handler<$crate::interrupt::typelevel::$irq>>::on_interrupt(); | ||
| 149 | )* | ||
| 150 | } | ||
| 151 | } | ||
| 152 | |||
| 153 | $(#[cfg($cond_irq)])? | ||
| 154 | $crate::bind_interrupts!(@inner | ||
| 155 | $( | ||
| 156 | $(#[cfg($cond_handler)])? | ||
| 157 | unsafe impl $crate::interrupt::typelevel::Binding<$crate::interrupt::typelevel::$irq, $handler> for $name {} | ||
| 158 | )* | ||
| 159 | ); | ||
| 160 | )* | ||
| 161 | }; | ||
| 162 | (@inner $($t:tt)*) => { | ||
| 163 | $($t)* | ||
| 164 | } | ||
| 165 | } | ||
diff --git a/src/lpuart/buffered.rs b/src/lpuart/buffered.rs new file mode 100644 index 000000000..03673d975 --- /dev/null +++ b/src/lpuart/buffered.rs | |||
| @@ -0,0 +1,686 @@ | |||
| 1 | use core::future::poll_fn; | ||
| 2 | use core::marker::PhantomData; | ||
| 3 | use core::sync::atomic::{AtomicBool, Ordering}; | ||
| 4 | use core::task::Poll; | ||
| 5 | |||
| 6 | use embassy_hal_internal::atomic_ring_buffer::RingBuffer; | ||
| 7 | use embassy_hal_internal::Peri; | ||
| 8 | use embassy_sync::waitqueue::AtomicWaker; | ||
| 9 | |||
| 10 | use super::*; | ||
| 11 | use crate::interrupt; | ||
| 12 | |||
| 13 | // ============================================================================ | ||
| 14 | // STATIC STATE MANAGEMENT | ||
| 15 | // ============================================================================ | ||
| 16 | |||
| 17 | /// State for buffered LPUART operations | ||
| 18 | pub struct State { | ||
| 19 | rx_waker: AtomicWaker, | ||
| 20 | rx_buf: RingBuffer, | ||
| 21 | tx_waker: AtomicWaker, | ||
| 22 | tx_buf: RingBuffer, | ||
| 23 | tx_done: AtomicBool, | ||
| 24 | initialized: AtomicBool, | ||
| 25 | } | ||
| 26 | |||
| 27 | impl State { | ||
| 28 | /// Create a new state instance | ||
| 29 | pub const fn new() -> Self { | ||
| 30 | Self { | ||
| 31 | rx_waker: AtomicWaker::new(), | ||
| 32 | rx_buf: RingBuffer::new(), | ||
| 33 | tx_waker: AtomicWaker::new(), | ||
| 34 | tx_buf: RingBuffer::new(), | ||
| 35 | tx_done: AtomicBool::new(true), | ||
| 36 | initialized: AtomicBool::new(false), | ||
| 37 | } | ||
| 38 | } | ||
| 39 | } | ||
| 40 | // ============================================================================ | ||
| 41 | // BUFFERED DRIVER STRUCTURES | ||
| 42 | // ============================================================================ | ||
| 43 | |||
| 44 | /// Buffered LPUART driver | ||
| 45 | pub struct BufferedLpuart<'a> { | ||
| 46 | tx: BufferedLpuartTx<'a>, | ||
| 47 | rx: BufferedLpuartRx<'a>, | ||
| 48 | } | ||
| 49 | |||
| 50 | /// Buffered LPUART TX driver | ||
| 51 | pub struct BufferedLpuartTx<'a> { | ||
| 52 | info: Info, | ||
| 53 | state: &'static State, | ||
| 54 | _tx_pin: Peri<'a, AnyPin>, | ||
| 55 | } | ||
| 56 | |||
| 57 | /// Buffered LPUART RX driver | ||
| 58 | pub struct BufferedLpuartRx<'a> { | ||
| 59 | info: Info, | ||
| 60 | state: &'static State, | ||
| 61 | _rx_pin: Peri<'a, AnyPin>, | ||
| 62 | } | ||
| 63 | |||
| 64 | // ============================================================================ | ||
| 65 | // BUFFERED LPUART IMPLEMENTATION | ||
| 66 | // ============================================================================ | ||
| 67 | |||
| 68 | impl<'a> BufferedLpuart<'a> { | ||
| 69 | /// Create a new buffered LPUART instance | ||
| 70 | pub fn new<T: Instance>( | ||
| 71 | _inner: Peri<'a, T>, | ||
| 72 | tx_pin: Peri<'a, impl TxPin<T>>, | ||
| 73 | rx_pin: Peri<'a, impl RxPin<T>>, | ||
| 74 | _irq: impl interrupt::typelevel::Binding<T::Interrupt, BufferedInterruptHandler<T>> + 'a, | ||
| 75 | tx_buffer: &'a mut [u8], | ||
| 76 | rx_buffer: &'a mut [u8], | ||
| 77 | config: Config, | ||
| 78 | ) -> Result<Self> { | ||
| 79 | // Configure pins | ||
| 80 | tx_pin.as_tx(); | ||
| 81 | rx_pin.as_rx(); | ||
| 82 | |||
| 83 | // Convert pins to AnyPin | ||
| 84 | let tx_pin: Peri<'a, AnyPin> = tx_pin.into(); | ||
| 85 | let rx_pin: Peri<'a, AnyPin> = rx_pin.into(); | ||
| 86 | |||
| 87 | let state = T::buffered_state(); | ||
| 88 | |||
| 89 | // Initialize the peripheral | ||
| 90 | Self::init::<T>( | ||
| 91 | Some(&tx_pin), | ||
| 92 | Some(&rx_pin), | ||
| 93 | None, | ||
| 94 | None, | ||
| 95 | tx_buffer, | ||
| 96 | rx_buffer, | ||
| 97 | config, | ||
| 98 | )?; | ||
| 99 | |||
| 100 | Ok(Self { | ||
| 101 | tx: BufferedLpuartTx { | ||
| 102 | info: T::info(), | ||
| 103 | state, | ||
| 104 | _tx_pin: tx_pin, | ||
| 105 | }, | ||
| 106 | rx: BufferedLpuartRx { | ||
| 107 | info: T::info(), | ||
| 108 | state, | ||
| 109 | _rx_pin: rx_pin, | ||
| 110 | }, | ||
| 111 | }) | ||
| 112 | } | ||
| 113 | |||
| 114 | /// Create a new buffered LPUART with flexible pin configuration | ||
| 115 | pub fn new_with_pins<T: Instance>( | ||
| 116 | _inner: Peri<'a, T>, | ||
| 117 | tx_pin: Option<Peri<'a, impl TxPin<T>>>, | ||
| 118 | rx_pin: Option<Peri<'a, impl RxPin<T>>>, | ||
| 119 | rts_pin: Option<Peri<'a, impl RtsPin<T>>>, | ||
| 120 | cts_pin: Option<Peri<'a, impl CtsPin<T>>>, | ||
| 121 | _irq: impl interrupt::typelevel::Binding<T::Interrupt, BufferedInterruptHandler<T>> + 'a, | ||
| 122 | tx_buffer: &'a mut [u8], | ||
| 123 | rx_buffer: &'a mut [u8], | ||
| 124 | config: Config, | ||
| 125 | ) -> Result<Self> { | ||
| 126 | // Configure pins if provided | ||
| 127 | let tx_pin = tx_pin.map(|pin| { | ||
| 128 | pin.as_tx(); | ||
| 129 | let converted: Peri<'a, AnyPin> = pin.into(); | ||
| 130 | converted | ||
| 131 | }); | ||
| 132 | |||
| 133 | let rx_pin = rx_pin.map(|pin| { | ||
| 134 | pin.as_rx(); | ||
| 135 | let converted: Peri<'a, AnyPin> = pin.into(); | ||
| 136 | converted | ||
| 137 | }); | ||
| 138 | |||
| 139 | let rts_pin = rts_pin.map(|pin| { | ||
| 140 | pin.as_rts(); | ||
| 141 | let converted: Peri<'a, AnyPin> = pin.into(); | ||
| 142 | converted | ||
| 143 | }); | ||
| 144 | |||
| 145 | let cts_pin = cts_pin.map(|pin| { | ||
| 146 | pin.as_cts(); | ||
| 147 | let converted: Peri<'a, AnyPin> = pin.into(); | ||
| 148 | converted | ||
| 149 | }); | ||
| 150 | |||
| 151 | let state = T::buffered_state(); | ||
| 152 | |||
| 153 | // Initialize the peripheral | ||
| 154 | Self::init::<T>( | ||
| 155 | tx_pin.as_ref(), | ||
| 156 | rx_pin.as_ref(), | ||
| 157 | rts_pin.as_ref(), | ||
| 158 | cts_pin.as_ref(), | ||
| 159 | tx_buffer, | ||
| 160 | rx_buffer, | ||
| 161 | config, | ||
| 162 | )?; | ||
| 163 | |||
| 164 | // Create TX and RX instances | ||
| 165 | let (tx, rx) = if let (Some(tx_pin), Some(rx_pin)) = (tx_pin, rx_pin) { | ||
| 166 | ( | ||
| 167 | BufferedLpuartTx { | ||
| 168 | info: T::info(), | ||
| 169 | state, | ||
| 170 | _tx_pin: tx_pin, | ||
| 171 | }, | ||
| 172 | BufferedLpuartRx { | ||
| 173 | info: T::info(), | ||
| 174 | state, | ||
| 175 | _rx_pin: rx_pin, | ||
| 176 | }, | ||
| 177 | ) | ||
| 178 | } else { | ||
| 179 | return Err(Error::InvalidArgument); | ||
| 180 | }; | ||
| 181 | |||
| 182 | Ok(Self { tx, rx }) | ||
| 183 | } | ||
| 184 | |||
| 185 | fn init<T: Instance>( | ||
| 186 | _tx: Option<&Peri<'a, AnyPin>>, | ||
| 187 | _rx: Option<&Peri<'a, AnyPin>>, | ||
| 188 | _rts: Option<&Peri<'a, AnyPin>>, | ||
| 189 | _cts: Option<&Peri<'a, AnyPin>>, | ||
| 190 | tx_buffer: &'a mut [u8], | ||
| 191 | rx_buffer: &'a mut [u8], | ||
| 192 | mut config: Config, | ||
| 193 | ) -> Result<()> { | ||
| 194 | let regs = T::info().regs; | ||
| 195 | let state = T::buffered_state(); | ||
| 196 | |||
| 197 | // Check if already initialized | ||
| 198 | if state.initialized.load(Ordering::Relaxed) { | ||
| 199 | return Err(Error::InvalidArgument); | ||
| 200 | } | ||
| 201 | |||
| 202 | // Initialize ring buffers | ||
| 203 | assert!(!tx_buffer.is_empty()); | ||
| 204 | unsafe { state.tx_buf.init(tx_buffer.as_mut_ptr(), tx_buffer.len()) } | ||
| 205 | |||
| 206 | assert!(!rx_buffer.is_empty()); | ||
| 207 | unsafe { state.rx_buf.init(rx_buffer.as_mut_ptr(), rx_buffer.len()) } | ||
| 208 | |||
| 209 | // Mark as initialized | ||
| 210 | state.initialized.store(true, Ordering::Relaxed); | ||
| 211 | |||
| 212 | // Enable TX and RX for buffered operation | ||
| 213 | config.enable_tx = true; | ||
| 214 | config.enable_rx = true; | ||
| 215 | |||
| 216 | // Perform standard initialization | ||
| 217 | perform_software_reset(regs); | ||
| 218 | disable_transceiver(regs); | ||
| 219 | configure_baudrate(regs, config.baudrate_bps, config.clock)?; | ||
| 220 | configure_frame_format(regs, &config); | ||
| 221 | configure_control_settings(regs, &config); | ||
| 222 | configure_fifo(regs, &config); | ||
| 223 | clear_all_status_flags(regs); | ||
| 224 | configure_flow_control(regs, &config); | ||
| 225 | configure_bit_order(regs, config.msb_firs); | ||
| 226 | |||
| 227 | // Enable interrupts for buffered operation | ||
| 228 | cortex_m::interrupt::free(|_| { | ||
| 229 | regs.ctrl().modify(|_, w| { | ||
| 230 | w.rie() | ||
| 231 | .enabled() // RX interrupt | ||
| 232 | .orie() | ||
| 233 | .enabled() // Overrun interrupt | ||
| 234 | .peie() | ||
| 235 | .enabled() // Parity error interrupt | ||
| 236 | .feie() | ||
| 237 | .enabled() // Framing error interrupt | ||
| 238 | .neie() | ||
| 239 | .enabled() // Noise error interrupt | ||
| 240 | }); | ||
| 241 | }); | ||
| 242 | |||
| 243 | // Enable the transceiver | ||
| 244 | enable_transceiver(regs, config.enable_tx, config.enable_rx); | ||
| 245 | |||
| 246 | // Enable the interrupt | ||
| 247 | // unsafe { | ||
| 248 | // // TODO: Used the propper interrupt enable method for the specific LPUART instance | ||
| 249 | // // T::Interrupt::enable(); | ||
| 250 | // } | ||
| 251 | |||
| 252 | Ok(()) | ||
| 253 | } | ||
| 254 | |||
| 255 | /// Split the buffered LPUART into separate TX and RX parts | ||
| 256 | pub fn split(self) -> (BufferedLpuartTx<'a>, BufferedLpuartRx<'a>) { | ||
| 257 | (self.tx, self.rx) | ||
| 258 | } | ||
| 259 | |||
| 260 | /// Get mutable references to TX and RX parts | ||
| 261 | pub fn split_ref(&mut self) -> (&mut BufferedLpuartTx<'a>, &mut BufferedLpuartRx<'a>) { | ||
| 262 | (&mut self.tx, &mut self.rx) | ||
| 263 | } | ||
| 264 | } | ||
| 265 | |||
| 266 | // ============================================================================ | ||
| 267 | // BUFFERED TX IMPLEMENTATION | ||
| 268 | // ============================================================================ | ||
| 269 | |||
| 270 | impl<'a> BufferedLpuartTx<'a> { | ||
| 271 | /// Create a new TX-only buffered LPUART | ||
| 272 | pub fn new<T: Instance>( | ||
| 273 | _inner: Peri<'a, T>, | ||
| 274 | tx_pin: Peri<'a, impl TxPin<T>>, | ||
| 275 | _irq: impl interrupt::typelevel::Binding<T::Interrupt, BufferedInterruptHandler<T>> + 'a, | ||
| 276 | tx_buffer: &'a mut [u8], | ||
| 277 | config: Config, | ||
| 278 | ) -> Result<Self> { | ||
| 279 | tx_pin.as_tx(); | ||
| 280 | let tx_pin: Peri<'a, AnyPin> = tx_pin.into(); | ||
| 281 | |||
| 282 | let info = T::info(); | ||
| 283 | let state = T::buffered_state(); | ||
| 284 | |||
| 285 | // Check if already initialized | ||
| 286 | if state.initialized.load(Ordering::Relaxed) { | ||
| 287 | return Err(Error::InvalidArgument); | ||
| 288 | } | ||
| 289 | |||
| 290 | // Initialize TX ring buffer only | ||
| 291 | unsafe { | ||
| 292 | let tx_buf = &state.tx_buf as *const _ as *mut RingBuffer; | ||
| 293 | (*tx_buf).init(tx_buffer.as_mut_ptr(), tx_buffer.len()); | ||
| 294 | } | ||
| 295 | |||
| 296 | state.initialized.store(true, Ordering::Relaxed); | ||
| 297 | |||
| 298 | // Initialize with TX only | ||
| 299 | BufferedLpuart::init::<T>( | ||
| 300 | Some(&tx_pin), | ||
| 301 | None, | ||
| 302 | None, | ||
| 303 | None, | ||
| 304 | tx_buffer, | ||
| 305 | &mut [], // Empty RX buffer | ||
| 306 | config, | ||
| 307 | )?; | ||
| 308 | |||
| 309 | Ok(Self { | ||
| 310 | info, | ||
| 311 | state, | ||
| 312 | _tx_pin: tx_pin, | ||
| 313 | }) | ||
| 314 | } | ||
| 315 | |||
| 316 | /// Write data asynchronously | ||
| 317 | pub async fn write(&mut self, buf: &[u8]) -> Result<usize> { | ||
| 318 | let mut written = 0; | ||
| 319 | |||
| 320 | for &byte in buf { | ||
| 321 | // Wait for space in the buffer | ||
| 322 | poll_fn(|cx| { | ||
| 323 | self.state.tx_waker.register(cx.waker()); | ||
| 324 | |||
| 325 | let mut writer = unsafe { self.state.tx_buf.writer() }; | ||
| 326 | if writer.push_one(byte) { | ||
| 327 | // Enable TX interrupt to start transmission | ||
| 328 | cortex_m::interrupt::free(|_| { | ||
| 329 | self.info.regs.ctrl().modify(|_, w| w.tie().enabled()); | ||
| 330 | }); | ||
| 331 | Poll::Ready(Ok(())) | ||
| 332 | } else { | ||
| 333 | Poll::Pending | ||
| 334 | } | ||
| 335 | }) | ||
| 336 | .await?; | ||
| 337 | |||
| 338 | written += 1; | ||
| 339 | } | ||
| 340 | |||
| 341 | Ok(written) | ||
| 342 | } | ||
| 343 | |||
| 344 | /// Flush the TX buffer and wait for transmission to complete | ||
| 345 | pub async fn flush(&mut self) -> Result<()> { | ||
| 346 | // Wait for TX buffer to empty and transmission to complete | ||
| 347 | poll_fn(|cx| { | ||
| 348 | self.state.tx_waker.register(cx.waker()); | ||
| 349 | |||
| 350 | let tx_empty = self.state.tx_buf.is_empty(); | ||
| 351 | let fifo_empty = self.info.regs.water().read().txcount().bits() == 0; | ||
| 352 | let tc_complete = self.info.regs.stat().read().tc().is_complete(); | ||
| 353 | |||
| 354 | if tx_empty && fifo_empty && tc_complete { | ||
| 355 | Poll::Ready(Ok(())) | ||
| 356 | } else { | ||
| 357 | // Enable appropriate interrupt | ||
| 358 | cortex_m::interrupt::free(|_| { | ||
| 359 | if !tx_empty { | ||
| 360 | self.info.regs.ctrl().modify(|_, w| w.tie().enabled()); | ||
| 361 | } else { | ||
| 362 | self.info.regs.ctrl().modify(|_, w| w.tcie().enabled()); | ||
| 363 | } | ||
| 364 | }); | ||
| 365 | Poll::Pending | ||
| 366 | } | ||
| 367 | }) | ||
| 368 | .await | ||
| 369 | } | ||
| 370 | |||
| 371 | /// Try to write without blocking | ||
| 372 | pub fn try_write(&mut self, buf: &[u8]) -> Result<usize> { | ||
| 373 | let mut writer = unsafe { self.state.tx_buf.writer() }; | ||
| 374 | let mut written = 0; | ||
| 375 | |||
| 376 | for &byte in buf { | ||
| 377 | if writer.push_one(byte) { | ||
| 378 | written += 1; | ||
| 379 | } else { | ||
| 380 | break; | ||
| 381 | } | ||
| 382 | } | ||
| 383 | |||
| 384 | if written > 0 { | ||
| 385 | // Enable TX interrupt to start transmission | ||
| 386 | cortex_m::interrupt::free(|_| { | ||
| 387 | self.info.regs.ctrl().modify(|_, w| w.tie().enabled()); | ||
| 388 | }); | ||
| 389 | } | ||
| 390 | |||
| 391 | Ok(written) | ||
| 392 | } | ||
| 393 | } | ||
| 394 | |||
| 395 | // ============================================================================ | ||
| 396 | // BUFFERED RX IMPLEMENTATION | ||
| 397 | // ============================================================================ | ||
| 398 | |||
| 399 | impl<'a> BufferedLpuartRx<'a> { | ||
| 400 | /// Create a new RX-only buffered LPUART | ||
| 401 | pub fn new<T: Instance>( | ||
| 402 | _inner: Peri<'a, T>, | ||
| 403 | rx_pin: Peri<'a, impl RxPin<T>>, | ||
| 404 | _irq: impl interrupt::typelevel::Binding<T::Interrupt, BufferedInterruptHandler<T>> + 'a, | ||
| 405 | rx_buffer: &'a mut [u8], | ||
| 406 | config: Config, | ||
| 407 | ) -> Result<Self> { | ||
| 408 | rx_pin.as_rx(); | ||
| 409 | let rx_pin: Peri<'a, AnyPin> = rx_pin.into(); | ||
| 410 | |||
| 411 | let info = T::info(); | ||
| 412 | let state = T::buffered_state(); | ||
| 413 | |||
| 414 | // Check if already initialized | ||
| 415 | if state.initialized.load(Ordering::Relaxed) { | ||
| 416 | return Err(Error::InvalidArgument); | ||
| 417 | } | ||
| 418 | |||
| 419 | // Initialize RX ring buffer only | ||
| 420 | unsafe { | ||
| 421 | let rx_buf = &state.rx_buf as *const _ as *mut RingBuffer; | ||
| 422 | (*rx_buf).init(rx_buffer.as_mut_ptr(), rx_buffer.len()); | ||
| 423 | } | ||
| 424 | |||
| 425 | state.initialized.store(true, Ordering::Relaxed); | ||
| 426 | |||
| 427 | // Initialize with RX only | ||
| 428 | BufferedLpuart::init::<T>( | ||
| 429 | None, | ||
| 430 | Some(&rx_pin), | ||
| 431 | None, | ||
| 432 | None, | ||
| 433 | &mut [], // Empty TX buffer | ||
| 434 | rx_buffer, | ||
| 435 | config, | ||
| 436 | )?; | ||
| 437 | |||
| 438 | Ok(Self { | ||
| 439 | info, | ||
| 440 | state, | ||
| 441 | _rx_pin: rx_pin, | ||
| 442 | }) | ||
| 443 | } | ||
| 444 | |||
| 445 | /// Read data asynchronously | ||
| 446 | pub async fn read(&mut self, buf: &mut [u8]) -> Result<usize> { | ||
| 447 | if buf.is_empty() { | ||
| 448 | return Ok(0); | ||
| 449 | } | ||
| 450 | |||
| 451 | let mut read = 0; | ||
| 452 | |||
| 453 | // Try to read available data | ||
| 454 | poll_fn(|cx| { | ||
| 455 | self.state.rx_waker.register(cx.waker()); | ||
| 456 | |||
| 457 | // Disable RX interrupt while reading from buffer | ||
| 458 | cortex_m::interrupt::free(|_| { | ||
| 459 | self.info.regs.ctrl().modify(|_, w| w.rie().disabled()); | ||
| 460 | }); | ||
| 461 | |||
| 462 | let mut reader = unsafe { self.state.rx_buf.reader() }; | ||
| 463 | let available = reader.pop(|data| { | ||
| 464 | let to_copy = core::cmp::min(data.len(), buf.len() - read); | ||
| 465 | if to_copy > 0 { | ||
| 466 | buf[read..read + to_copy].copy_from_slice(&data[..to_copy]); | ||
| 467 | read += to_copy; | ||
| 468 | } | ||
| 469 | to_copy | ||
| 470 | }); | ||
| 471 | |||
| 472 | // Re-enable RX interrupt | ||
| 473 | cortex_m::interrupt::free(|_| { | ||
| 474 | self.info.regs.ctrl().modify(|_, w| w.rie().enabled()); | ||
| 475 | }); | ||
| 476 | |||
| 477 | if read > 0 { | ||
| 478 | Poll::Ready(Ok(read)) | ||
| 479 | } else if available == 0 { | ||
| 480 | Poll::Pending | ||
| 481 | } else { | ||
| 482 | Poll::Ready(Ok(0)) | ||
| 483 | } | ||
| 484 | }) | ||
| 485 | .await | ||
| 486 | } | ||
| 487 | |||
| 488 | /// Try to read without blocking | ||
| 489 | pub fn try_read(&mut self, buf: &mut [u8]) -> Result<usize> { | ||
| 490 | if buf.is_empty() { | ||
| 491 | return Ok(0); | ||
| 492 | } | ||
| 493 | |||
| 494 | // Disable RX interrupt while reading from buffer | ||
| 495 | cortex_m::interrupt::free(|_| { | ||
| 496 | self.info.regs.ctrl().modify(|_, w| w.rie().disabled()); | ||
| 497 | }); | ||
| 498 | |||
| 499 | let mut reader = unsafe { self.state.rx_buf.reader() }; | ||
| 500 | let read = reader.pop(|data| { | ||
| 501 | let to_copy = core::cmp::min(data.len(), buf.len()); | ||
| 502 | if to_copy > 0 { | ||
| 503 | buf[..to_copy].copy_from_slice(&data[..to_copy]); | ||
| 504 | } | ||
| 505 | to_copy | ||
| 506 | }); | ||
| 507 | |||
| 508 | // Re-enable RX interrupt | ||
| 509 | cortex_m::interrupt::free(|_| { | ||
| 510 | self.info.regs.ctrl().modify(|_, w| w.rie().enabled()); | ||
| 511 | }); | ||
| 512 | |||
| 513 | Ok(read) | ||
| 514 | } | ||
| 515 | } | ||
| 516 | |||
| 517 | // ============================================================================ | ||
| 518 | // INTERRUPT HANDLER | ||
| 519 | // ============================================================================ | ||
| 520 | |||
| 521 | /// Buffered UART interrupt handler | ||
| 522 | pub struct BufferedInterruptHandler<T: Instance> { | ||
| 523 | _phantom: PhantomData<T>, | ||
| 524 | } | ||
| 525 | |||
| 526 | impl<T: Instance> crate::interrupt::typelevel::Handler<T::Interrupt> | ||
| 527 | for BufferedInterruptHandler<T> | ||
| 528 | { | ||
| 529 | unsafe fn on_interrupt() { | ||
| 530 | let regs = T::info().regs; | ||
| 531 | let state = T::buffered_state(); | ||
| 532 | |||
| 533 | // Check if this instance is initialized | ||
| 534 | if !state.initialized.load(Ordering::Relaxed) { | ||
| 535 | return; | ||
| 536 | } | ||
| 537 | |||
| 538 | let ctrl = regs.ctrl().read(); | ||
| 539 | let stat = regs.stat().read(); | ||
| 540 | let has_fifo = regs.param().read().rxfifo().bits() > 0; | ||
| 541 | |||
| 542 | // Handle overrun error | ||
| 543 | if stat.or().is_overrun() { | ||
| 544 | regs.stat().write(|w| w.or().clear_bit_by_one()); | ||
| 545 | state.rx_waker.wake(); | ||
| 546 | return; | ||
| 547 | } | ||
| 548 | |||
| 549 | // Clear other error flags | ||
| 550 | if stat.pf().is_parity() { | ||
| 551 | regs.stat().write(|w| w.pf().clear_bit_by_one()); | ||
| 552 | } | ||
| 553 | if stat.fe().is_error() { | ||
| 554 | regs.stat().write(|w| w.fe().clear_bit_by_one()); | ||
| 555 | } | ||
| 556 | if stat.nf().is_noise() { | ||
| 557 | regs.stat().write(|w| w.nf().clear_bit_by_one()); | ||
| 558 | } | ||
| 559 | |||
| 560 | // Handle RX data | ||
| 561 | if ctrl.rie().is_enabled() && (has_data(regs) || stat.idle().is_idle()) { | ||
| 562 | let mut pushed_any = false; | ||
| 563 | let mut writer = state.rx_buf.writer(); | ||
| 564 | |||
| 565 | if has_fifo { | ||
| 566 | // Read from FIFO | ||
| 567 | while regs.water().read().rxcount().bits() > 0 { | ||
| 568 | let byte = (regs.data().read().bits() & 0xFF) as u8; | ||
| 569 | if writer.push_one(byte) { | ||
| 570 | pushed_any = true; | ||
| 571 | } else { | ||
| 572 | // Buffer full, stop reading | ||
| 573 | break; | ||
| 574 | } | ||
| 575 | } | ||
| 576 | } else { | ||
| 577 | // Read single byte | ||
| 578 | if regs.stat().read().rdrf().is_rxdata() { | ||
| 579 | let byte = (regs.data().read().bits() & 0xFF) as u8; | ||
| 580 | if writer.push_one(byte) { | ||
| 581 | pushed_any = true; | ||
| 582 | } | ||
| 583 | } | ||
| 584 | } | ||
| 585 | |||
| 586 | if pushed_any { | ||
| 587 | state.rx_waker.wake(); | ||
| 588 | } | ||
| 589 | |||
| 590 | // Clear idle flag if set | ||
| 591 | if stat.idle().is_idle() { | ||
| 592 | regs.stat().write(|w| w.idle().clear_bit_by_one()); | ||
| 593 | } | ||
| 594 | } | ||
| 595 | |||
| 596 | // Handle TX data | ||
| 597 | if ctrl.tie().is_enabled() { | ||
| 598 | let mut sent_any = false; | ||
| 599 | let mut reader = state.tx_buf.reader(); | ||
| 600 | |||
| 601 | // Send data while TX buffer is ready and we have data | ||
| 602 | while regs.stat().read().tdre().is_no_txdata() { | ||
| 603 | if let Some(byte) = reader.pop_one() { | ||
| 604 | regs.data().write(|w| w.bits(u32::from(byte))); | ||
| 605 | sent_any = true; | ||
| 606 | } else { | ||
| 607 | // No more data to send | ||
| 608 | break; | ||
| 609 | } | ||
| 610 | } | ||
| 611 | |||
| 612 | if sent_any { | ||
| 613 | state.tx_waker.wake(); | ||
| 614 | } | ||
| 615 | |||
| 616 | // If buffer is empty, switch to TC interrupt or disable | ||
| 617 | if state.tx_buf.is_empty() { | ||
| 618 | cortex_m::interrupt::free(|_| { | ||
| 619 | regs.ctrl() | ||
| 620 | .modify(|_, w| w.tie().disabled().tcie().enabled()); | ||
| 621 | }); | ||
| 622 | } | ||
| 623 | } | ||
| 624 | |||
| 625 | // Handle transmission complete | ||
| 626 | if ctrl.tcie().is_enabled() { | ||
| 627 | if regs.stat().read().tc().is_complete() { | ||
| 628 | state.tx_done.store(true, Ordering::Release); | ||
| 629 | state.tx_waker.wake(); | ||
| 630 | |||
| 631 | // Disable TC interrupt | ||
| 632 | cortex_m::interrupt::free(|_| { | ||
| 633 | regs.ctrl().modify(|_, w| w.tcie().disabled()); | ||
| 634 | }); | ||
| 635 | } | ||
| 636 | } | ||
| 637 | } | ||
| 638 | } | ||
| 639 | |||
| 640 | // ============================================================================ | ||
| 641 | // EMBEDDED-IO ASYNC TRAIT IMPLEMENTATIONS | ||
| 642 | // ============================================================================ | ||
| 643 | |||
| 644 | impl embedded_io_async::ErrorType for BufferedLpuartTx<'_> { | ||
| 645 | type Error = Error; | ||
| 646 | } | ||
| 647 | |||
| 648 | impl embedded_io_async::ErrorType for BufferedLpuartRx<'_> { | ||
| 649 | type Error = Error; | ||
| 650 | } | ||
| 651 | |||
| 652 | impl embedded_io_async::ErrorType for BufferedLpuart<'_> { | ||
| 653 | type Error = Error; | ||
| 654 | } | ||
| 655 | |||
| 656 | impl embedded_io_async::Write for BufferedLpuartTx<'_> { | ||
| 657 | async fn write(&mut self, buf: &[u8]) -> core::result::Result<usize, Self::Error> { | ||
| 658 | self.write(buf).await | ||
| 659 | } | ||
| 660 | |||
| 661 | async fn flush(&mut self) -> core::result::Result<(), Self::Error> { | ||
| 662 | self.flush().await | ||
| 663 | } | ||
| 664 | } | ||
| 665 | |||
| 666 | impl embedded_io_async::Read for BufferedLpuartRx<'_> { | ||
| 667 | async fn read(&mut self, buf: &mut [u8]) -> core::result::Result<usize, Self::Error> { | ||
| 668 | self.read(buf).await | ||
| 669 | } | ||
| 670 | } | ||
| 671 | |||
| 672 | impl embedded_io_async::Write for BufferedLpuart<'_> { | ||
| 673 | async fn write(&mut self, buf: &[u8]) -> core::result::Result<usize, Self::Error> { | ||
| 674 | self.tx.write(buf).await | ||
| 675 | } | ||
| 676 | |||
| 677 | async fn flush(&mut self) -> core::result::Result<(), Self::Error> { | ||
| 678 | self.tx.flush().await | ||
| 679 | } | ||
| 680 | } | ||
| 681 | |||
| 682 | impl embedded_io_async::Read for BufferedLpuart<'_> { | ||
| 683 | async fn read(&mut self, buf: &mut [u8]) -> core::result::Result<usize, Self::Error> { | ||
| 684 | self.rx.read(buf).await | ||
| 685 | } | ||
| 686 | } | ||
diff --git a/src/lpuart/mod.rs b/src/lpuart/mod.rs new file mode 100644 index 000000000..431547f86 --- /dev/null +++ b/src/lpuart/mod.rs | |||
| @@ -0,0 +1,1208 @@ | |||
| 1 | use crate::interrupt; | ||
| 2 | use core::marker::PhantomData; | ||
| 3 | use embassy_hal_internal::{Peri, PeripheralType}; | ||
| 4 | use paste::paste; | ||
| 5 | |||
| 6 | use crate::pac; | ||
| 7 | use crate::pac::lpuart0::baud::Sbns as StopBits; | ||
| 8 | use crate::pac::lpuart0::ctrl::{ | ||
| 9 | Idlecfg as IdleConfig, Ilt as IdleType, Pt as Parity, M as DataBits, | ||
| 10 | }; | ||
| 11 | use crate::pac::lpuart0::modir::{Txctsc as TxCtsConfig, Txctssrc as TxCtsSource}; | ||
| 12 | use crate::pac::lpuart0::stat::Msbf as MsbFirst; | ||
| 13 | |||
| 14 | pub mod buffered; | ||
| 15 | |||
| 16 | // ============================================================================ | ||
| 17 | // STUB IMPLEMENTATION | ||
| 18 | // ============================================================================ | ||
| 19 | |||
| 20 | // Stub implementation for LIB (Peripherals), GPIO, DMA and CLOCK until stable API | ||
| 21 | // Pin and Clock initialization is currently done at the examples level. | ||
| 22 | |||
| 23 | // --- START LIB --- | ||
| 24 | |||
| 25 | // Use our own instance of Peripherals, until we align `lib.rs` with the EMBASSY-IMXRT approach | ||
| 26 | // Inlined peripherals_definition! to bypass the `Peripherals::take_with_cs()` check | ||
| 27 | // SHOULD NOT BE USED IN THE FINAL VERSION | ||
| 28 | pub mod lib { | ||
| 29 | // embassy_hal_internal::peripherals!(LPUART2, PIO2_2, PIO2_3) | ||
| 30 | |||
| 31 | embassy_hal_internal::peripherals_definition!(LPUART2, PIO2_2, PIO2_3,); | ||
| 32 | #[doc = r" Struct containing all the peripheral singletons."] | ||
| 33 | #[doc = r""] | ||
| 34 | #[doc = r" To obtain the peripherals, you must initialize the HAL, by calling [`crate::init`]."] | ||
| 35 | #[allow(non_snake_case)] | ||
| 36 | pub struct Peripherals { | ||
| 37 | #[doc = concat!(stringify!(LPUART2)," peripheral")] | ||
| 38 | pub LPUART2: embassy_hal_internal::Peri<'static, peripherals::LPUART2>, | ||
| 39 | #[doc = concat!(stringify!(PIO2_2)," peripheral")] | ||
| 40 | pub PIO2_2: embassy_hal_internal::Peri<'static, peripherals::PIO2_2>, | ||
| 41 | #[doc = concat!(stringify!(PIO2_3)," peripheral")] | ||
| 42 | pub PIO2_3: embassy_hal_internal::Peri<'static, peripherals::PIO2_3>, | ||
| 43 | } | ||
| 44 | impl Peripherals { | ||
| 45 | #[doc = r"Returns all the peripherals *once*"] | ||
| 46 | #[inline] | ||
| 47 | pub(crate) fn take() -> Self { | ||
| 48 | critical_section::with(Self::take_with_cs) | ||
| 49 | } | ||
| 50 | #[doc = r"Returns all the peripherals *once*"] | ||
| 51 | #[inline] | ||
| 52 | pub(crate) fn take_with_cs(_cs: critical_section::CriticalSection) -> Self { | ||
| 53 | #[no_mangle] | ||
| 54 | static mut _EMBASSY_DEVICE_PERIPHERALS2: bool = false; // ALIGN: Temporary fix to use stub Peripherals | ||
| 55 | unsafe { | ||
| 56 | if _EMBASSY_DEVICE_PERIPHERALS2 { | ||
| 57 | panic!("init called more than once!") | ||
| 58 | } | ||
| 59 | _EMBASSY_DEVICE_PERIPHERALS2 = true; | ||
| 60 | Self::steal() | ||
| 61 | } | ||
| 62 | } | ||
| 63 | } | ||
| 64 | impl Peripherals { | ||
| 65 | #[doc = r" Unsafely create an instance of this peripheral out of thin air."] | ||
| 66 | #[doc = r""] | ||
| 67 | #[doc = r" # Safety"] | ||
| 68 | #[doc = r""] | ||
| 69 | #[doc = r" You must ensure that you're only using one instance of this type at a time."] | ||
| 70 | #[inline] | ||
| 71 | pub unsafe fn steal() -> Self { | ||
| 72 | Self { | ||
| 73 | LPUART2: peripherals::LPUART2::steal(), | ||
| 74 | PIO2_2: peripherals::PIO2_2::steal(), | ||
| 75 | PIO2_3: peripherals::PIO2_3::steal(), | ||
| 76 | } | ||
| 77 | } | ||
| 78 | } | ||
| 79 | |||
| 80 | /// Initialize HAL | ||
| 81 | pub fn init() -> Peripherals { | ||
| 82 | Peripherals::take() | ||
| 83 | } | ||
| 84 | } | ||
| 85 | |||
| 86 | // --- END LIB --- | ||
| 87 | |||
| 88 | // --- START GPIO --- | ||
| 89 | |||
| 90 | mod gpio { | ||
| 91 | use embassy_hal_internal::PeripheralType; | ||
| 92 | trait SealedPin {} | ||
| 93 | |||
| 94 | #[allow(private_bounds)] | ||
| 95 | pub trait GpioPin: SealedPin + Sized + PeripheralType + Into<AnyPin> + 'static { | ||
| 96 | /// Type-erase the pin. | ||
| 97 | fn degrade(self) -> AnyPin { | ||
| 98 | todo!() | ||
| 99 | } | ||
| 100 | } | ||
| 101 | |||
| 102 | // Add this macro to implement GpioPin for all pins | ||
| 103 | macro_rules! impl_gpio_pin { | ||
| 104 | ($($pin:ident),*) => { | ||
| 105 | $( | ||
| 106 | impl SealedPin for super::lib::peripherals::$pin {} | ||
| 107 | |||
| 108 | impl GpioPin for super::lib::peripherals::$pin {} | ||
| 109 | |||
| 110 | impl Into<AnyPin> for super::lib::peripherals::$pin { | ||
| 111 | fn into(self) -> AnyPin { | ||
| 112 | AnyPin | ||
| 113 | } | ||
| 114 | } | ||
| 115 | )* | ||
| 116 | }; | ||
| 117 | } | ||
| 118 | |||
| 119 | // Implement GpioPin for all pins from lib.rs | ||
| 120 | impl_gpio_pin!(PIO2_2, PIO2_3); | ||
| 121 | |||
| 122 | #[derive(Debug, Clone, Copy)] | ||
| 123 | pub struct AnyPin; | ||
| 124 | |||
| 125 | impl PeripheralType for AnyPin {} | ||
| 126 | |||
| 127 | pub enum Alt { | ||
| 128 | ALT3, | ||
| 129 | } | ||
| 130 | } | ||
| 131 | |||
| 132 | use gpio::{AnyPin, GpioPin as Pin}; | ||
| 133 | |||
| 134 | // --- END GPIO --- | ||
| 135 | |||
| 136 | // --- START DMA --- | ||
| 137 | mod dma { | ||
| 138 | pub struct Channel<'d> { | ||
| 139 | pub(super) _lifetime: core::marker::PhantomData<&'d ()>, | ||
| 140 | } | ||
| 141 | } | ||
| 142 | |||
| 143 | use dma::Channel; | ||
| 144 | |||
| 145 | // --- END DMA --- | ||
| 146 | |||
| 147 | // --- START CLOCK --- | ||
| 148 | mod clock { | ||
| 149 | #[derive(Debug, Clone, Copy)] | ||
| 150 | pub enum Clock { | ||
| 151 | FroLf, // Low-Frequency Free-Running Oscillator | ||
| 152 | } | ||
| 153 | } | ||
| 154 | |||
| 155 | use clock::Clock; | ||
| 156 | |||
| 157 | // --- END CLOCK --- | ||
| 158 | |||
| 159 | // ============================================================================ | ||
| 160 | // MISC | ||
| 161 | // ============================================================================ | ||
| 162 | |||
| 163 | mod sealed { | ||
| 164 | /// Simply seal a trait to prevent external implementations | ||
| 165 | pub trait Sealed {} | ||
| 166 | } | ||
| 167 | |||
| 168 | // ============================================================================ | ||
| 169 | // INSTANCE TRAIT | ||
| 170 | // ============================================================================ | ||
| 171 | |||
| 172 | pub type Regs = &'static crate::pac::lpuart0::RegisterBlock; | ||
| 173 | |||
| 174 | pub trait SealedInstance { | ||
| 175 | fn info() -> Info; | ||
| 176 | fn index() -> usize; | ||
| 177 | fn buffered_state() -> &'static buffered::State; | ||
| 178 | } | ||
| 179 | |||
| 180 | pub struct Info { | ||
| 181 | pub regs: Regs, | ||
| 182 | } | ||
| 183 | |||
| 184 | /// Trait for LPUART peripheral instances | ||
| 185 | #[allow(private_bounds)] | ||
| 186 | pub trait Instance: SealedInstance + PeripheralType + 'static + Send { | ||
| 187 | type Interrupt: interrupt::typelevel::Interrupt; | ||
| 188 | } | ||
| 189 | |||
| 190 | macro_rules! impl_instance { | ||
| 191 | ($($n:expr),*) => { | ||
| 192 | $( | ||
| 193 | paste!{ | ||
| 194 | impl SealedInstance for lib::peripherals::[<LPUART $n>] { | ||
| 195 | fn info() -> Info { | ||
| 196 | Info { | ||
| 197 | regs: unsafe { &*pac::[<Lpuart $n>]::ptr() }, | ||
| 198 | } | ||
| 199 | } | ||
| 200 | |||
| 201 | #[inline] | ||
| 202 | fn index() -> usize { | ||
| 203 | $n | ||
| 204 | } | ||
| 205 | |||
| 206 | fn buffered_state() -> &'static buffered::State { | ||
| 207 | static BUFFERED_STATE: buffered::State = buffered::State::new(); | ||
| 208 | &BUFFERED_STATE | ||
| 209 | } | ||
| 210 | } | ||
| 211 | |||
| 212 | impl Instance for lib::peripherals::[<LPUART $n>] { | ||
| 213 | type Interrupt = crate::interrupt::typelevel::[<LPUART $n>]; | ||
| 214 | } | ||
| 215 | } | ||
| 216 | )* | ||
| 217 | }; | ||
| 218 | } | ||
| 219 | |||
| 220 | // impl_instance!(0, 1, 2, 3, 4); | ||
| 221 | impl_instance!(2); | ||
| 222 | |||
| 223 | // ============================================================================ | ||
| 224 | // INSTANCE HELPER FUNCTIONS | ||
| 225 | // ============================================================================ | ||
| 226 | |||
| 227 | /// Perform software reset on the LPUART peripheral | ||
| 228 | pub fn perform_software_reset(regs: Regs) { | ||
| 229 | // Software reset - set and clear RST bit (Global register) | ||
| 230 | regs.global().write(|w| w.rst().reset()); | ||
| 231 | regs.global().write(|w| w.rst().no_effect()); | ||
| 232 | } | ||
| 233 | |||
| 234 | /// Disable both transmitter and receiver | ||
| 235 | pub fn disable_transceiver(regs: Regs) { | ||
| 236 | regs.ctrl().modify(|_, w| w.te().disabled().re().disabled()); | ||
| 237 | } | ||
| 238 | |||
| 239 | /// Calculate and configure baudrate settings | ||
| 240 | pub fn configure_baudrate(regs: Regs, baudrate_bps: u32, clock: Clock) -> Result<()> { | ||
| 241 | let clock_freq = get_fc_freq(clock)?; | ||
| 242 | let (osr, sbr) = calculate_baudrate(baudrate_bps, clock_freq)?; | ||
| 243 | |||
| 244 | // Configure BAUD register | ||
| 245 | regs.baud().modify(|_, w| unsafe { | ||
| 246 | // Clear and set OSR | ||
| 247 | w.osr().bits((osr - 1) as u8); | ||
| 248 | // Clear and set SBR | ||
| 249 | w.sbr().bits(sbr); | ||
| 250 | // Set BOTHEDGE if OSR is between 4 and 7 | ||
| 251 | if osr > 3 && osr < 8 { | ||
| 252 | w.bothedge().enabled() | ||
| 253 | } else { | ||
| 254 | w.bothedge().disabled() | ||
| 255 | } | ||
| 256 | }); | ||
| 257 | |||
| 258 | Ok(()) | ||
| 259 | } | ||
| 260 | |||
| 261 | /// Configure frame format (stop bits, data bits) | ||
| 262 | pub fn configure_frame_format(regs: Regs, config: &Config) { | ||
| 263 | // Configure stop bits | ||
| 264 | regs.baud() | ||
| 265 | .modify(|_, w| w.sbns().variant(config.stop_bits_count)); | ||
| 266 | |||
| 267 | // Clear M10 for now (10-bit mode) | ||
| 268 | regs.baud().modify(|_, w| w.m10().disabled()); | ||
| 269 | } | ||
| 270 | |||
| 271 | /// Configure control settings (parity, data bits, idle config, pin swap) | ||
| 272 | pub fn configure_control_settings(regs: Regs, config: &Config) { | ||
| 273 | regs.ctrl().modify(|_, w| { | ||
| 274 | // Parity configuration | ||
| 275 | let mut w = if let Some(parity) = config.parity_mode { | ||
| 276 | w.pe().enabled().pt().variant(parity) | ||
| 277 | } else { | ||
| 278 | w.pe().disabled() | ||
| 279 | }; | ||
| 280 | |||
| 281 | // Data bits configuration | ||
| 282 | w = match config.data_bits_count { | ||
| 283 | DataBits::Data8 => { | ||
| 284 | if config.parity_mode.is_some() { | ||
| 285 | w.m().data9() // 8 data + 1 parity = 9 bits | ||
| 286 | } else { | ||
| 287 | w.m().data8() // 8 data bits only | ||
| 288 | } | ||
| 289 | } | ||
| 290 | DataBits::Data9 => w.m().data9(), | ||
| 291 | }; | ||
| 292 | |||
| 293 | // Idle configuration | ||
| 294 | w = w.idlecfg().variant(config.rx_idle_config); | ||
| 295 | w = w.ilt().variant(config.rx_idle_type); | ||
| 296 | |||
| 297 | // Swap TXD/RXD if configured | ||
| 298 | if config.swap_txd_rxd { | ||
| 299 | w.swap().swap() | ||
| 300 | } else { | ||
| 301 | w.swap().standard() | ||
| 302 | } | ||
| 303 | }); | ||
| 304 | } | ||
| 305 | |||
| 306 | /// Configure FIFO settings and watermarks | ||
| 307 | pub fn configure_fifo(regs: Regs, config: &Config) { | ||
| 308 | // Configure WATER register for FIFO watermarks | ||
| 309 | regs.water().write(|w| unsafe { | ||
| 310 | w.rxwater() | ||
| 311 | .bits(config.rx_fifo_watermark as u8) | ||
| 312 | .txwater() | ||
| 313 | .bits(config.tx_fifo_watermark as u8) | ||
| 314 | }); | ||
| 315 | |||
| 316 | // Enable TX/RX FIFOs | ||
| 317 | regs.fifo() | ||
| 318 | .modify(|_, w| w.txfe().enabled().rxfe().enabled()); | ||
| 319 | |||
| 320 | // Flush FIFOs | ||
| 321 | regs.fifo() | ||
| 322 | .modify(|_, w| w.txflush().txfifo_rst().rxflush().rxfifo_rst()); | ||
| 323 | } | ||
| 324 | |||
| 325 | /// Clear all status flags | ||
| 326 | pub fn clear_all_status_flags(regs: Regs) { | ||
| 327 | regs.stat().reset(); | ||
| 328 | } | ||
| 329 | |||
| 330 | /// Configure hardware flow control if enabled | ||
| 331 | pub fn configure_flow_control(regs: Regs, config: &Config) { | ||
| 332 | if config.enable_rx_rts || config.enable_tx_cts { | ||
| 333 | regs.modir().modify(|_, w| { | ||
| 334 | let mut w = w; | ||
| 335 | |||
| 336 | // Configure TX CTS | ||
| 337 | w = w.txctsc().variant(config.tx_cts_config); | ||
| 338 | w = w.txctssrc().variant(config.tx_cts_source); | ||
| 339 | |||
| 340 | if config.enable_rx_rts { | ||
| 341 | w = w.rxrtse().enabled(); | ||
| 342 | } else { | ||
| 343 | w = w.rxrtse().disabled(); | ||
| 344 | } | ||
| 345 | |||
| 346 | if config.enable_tx_cts { | ||
| 347 | w = w.txctse().enabled(); | ||
| 348 | } else { | ||
| 349 | w = w.txctse().disabled(); | ||
| 350 | } | ||
| 351 | |||
| 352 | w | ||
| 353 | }); | ||
| 354 | } | ||
| 355 | } | ||
| 356 | |||
| 357 | /// Configure bit order (MSB first or LSB first) | ||
| 358 | pub fn configure_bit_order(regs: Regs, msb_first: MsbFirst) { | ||
| 359 | regs.stat().modify(|_, w| w.msbf().variant(msb_first)); | ||
| 360 | } | ||
| 361 | |||
| 362 | /// Enable transmitter and/or receiver based on configuration | ||
| 363 | pub fn enable_transceiver(regs: Regs, enable_tx: bool, enable_rx: bool) { | ||
| 364 | regs.ctrl().modify(|_, w| { | ||
| 365 | let mut w = w; | ||
| 366 | if enable_tx { | ||
| 367 | w = w.te().enabled(); | ||
| 368 | } | ||
| 369 | if enable_rx { | ||
| 370 | w = w.re().enabled(); | ||
| 371 | } | ||
| 372 | w | ||
| 373 | }); | ||
| 374 | } | ||
| 375 | |||
| 376 | pub fn calculate_baudrate(baudrate: u32, src_clock_hz: u32) -> Result<(u8, u16)> { | ||
| 377 | let mut baud_diff = baudrate; | ||
| 378 | let mut osr = 0u8; | ||
| 379 | let mut sbr = 0u16; | ||
| 380 | |||
| 381 | // Try OSR values from 4 to 32 | ||
| 382 | for osr_temp in 4u8..=32u8 { | ||
| 383 | // Calculate SBR: (srcClock_Hz * 2 / (baudRate * osr) + 1) / 2 | ||
| 384 | let sbr_calc = ((src_clock_hz * 2) / (baudrate * osr_temp as u32) + 1) / 2; | ||
| 385 | |||
| 386 | let sbr_temp = if sbr_calc == 0 { | ||
| 387 | 1 | ||
| 388 | } else if sbr_calc > 0x1FFF { | ||
| 389 | 0x1FFF | ||
| 390 | } else { | ||
| 391 | sbr_calc as u16 | ||
| 392 | }; | ||
| 393 | |||
| 394 | // Calculate actual baud rate | ||
| 395 | let calculated_baud = src_clock_hz / (osr_temp as u32 * sbr_temp as u32); | ||
| 396 | |||
| 397 | let temp_diff = if calculated_baud > baudrate { | ||
| 398 | calculated_baud - baudrate | ||
| 399 | } else { | ||
| 400 | baudrate - calculated_baud | ||
| 401 | }; | ||
| 402 | |||
| 403 | if temp_diff <= baud_diff { | ||
| 404 | baud_diff = temp_diff; | ||
| 405 | osr = osr_temp; | ||
| 406 | sbr = sbr_temp; | ||
| 407 | } | ||
| 408 | } | ||
| 409 | |||
| 410 | // Check if baud rate difference is within 3% | ||
| 411 | if baud_diff > (baudrate / 100) * 3 { | ||
| 412 | return Err(Error::UnsupportedBaudrate); | ||
| 413 | } | ||
| 414 | |||
| 415 | Ok((osr, sbr)) | ||
| 416 | } | ||
| 417 | |||
| 418 | pub fn get_fc_freq(clock: Clock) -> Result<u32> { | ||
| 419 | // This is a placeholder - actual implementation would query the clock system | ||
| 420 | // In real implementation, this would get the LPUART clock frequency | ||
| 421 | match clock { | ||
| 422 | Clock::FroLf => Ok(12_000_000), // Low frequency oscillator | ||
| 423 | #[allow(unreachable_patterns)] | ||
| 424 | _ => Err(Error::InvalidArgument), | ||
| 425 | } | ||
| 426 | } | ||
| 427 | |||
| 428 | /// Wait for all transmit operations to complete | ||
| 429 | pub fn wait_for_tx_complete(regs: Regs) { | ||
| 430 | // Wait for TX FIFO to empty | ||
| 431 | while regs.water().read().txcount().bits() != 0 { | ||
| 432 | // Wait for TX FIFO to drain | ||
| 433 | } | ||
| 434 | |||
| 435 | // Wait for last character to shift out (TC = Transmission Complete) | ||
| 436 | while regs.stat().read().tc().is_active() { | ||
| 437 | // Wait for transmission to complete | ||
| 438 | } | ||
| 439 | } | ||
| 440 | |||
| 441 | pub fn check_and_clear_rx_errors(regs: Regs) -> Result<()> { | ||
| 442 | let stat = regs.stat().read(); | ||
| 443 | let mut status = Ok(()); | ||
| 444 | |||
| 445 | // Check for overrun first - other error flags are prevented when OR is set | ||
| 446 | if stat.or().is_overrun() { | ||
| 447 | regs.stat().write(|w| w.or().clear_bit_by_one()); | ||
| 448 | |||
| 449 | return Err(Error::Overrun); | ||
| 450 | } | ||
| 451 | |||
| 452 | if stat.pf().is_parity() { | ||
| 453 | regs.stat().write(|w| w.pf().clear_bit_by_one()); | ||
| 454 | status = Err(Error::Parity); | ||
| 455 | } | ||
| 456 | |||
| 457 | if stat.fe().is_error() { | ||
| 458 | regs.stat().write(|w| w.fe().clear_bit_by_one()); | ||
| 459 | status = Err(Error::Framing); | ||
| 460 | } | ||
| 461 | |||
| 462 | if stat.nf().is_noise() { | ||
| 463 | regs.stat().write(|w| w.nf().clear_bit_by_one()); | ||
| 464 | status = Err(Error::Noise); | ||
| 465 | } | ||
| 466 | |||
| 467 | status | ||
| 468 | } | ||
| 469 | |||
| 470 | pub fn has_data(regs: Regs) -> bool { | ||
| 471 | if regs.param().read().rxfifo().bits() > 0 { | ||
| 472 | // FIFO is available - check RXCOUNT in WATER register | ||
| 473 | regs.water().read().rxcount().bits() > 0 | ||
| 474 | } else { | ||
| 475 | // No FIFO - check RDRF flag in STAT register | ||
| 476 | regs.stat().read().rdrf().is_rxdata() | ||
| 477 | } | ||
| 478 | } | ||
| 479 | |||
| 480 | // ============================================================================ | ||
| 481 | // PIN TRAITS FOR LPUART FUNCTIONALITY | ||
| 482 | // ============================================================================ | ||
| 483 | |||
| 484 | impl<T: Pin> sealed::Sealed for T {} | ||
| 485 | |||
| 486 | /// io configuration trait for Lpuart Tx configuration | ||
| 487 | pub trait TxPin<T: Instance>: Pin + sealed::Sealed + PeripheralType { | ||
| 488 | /// convert the pin to appropriate function for Lpuart Tx usage | ||
| 489 | fn as_tx(&self); | ||
| 490 | } | ||
| 491 | |||
| 492 | /// io configuration trait for Lpuart Rx configuration | ||
| 493 | pub trait RxPin<T: Instance>: Pin + sealed::Sealed + PeripheralType { | ||
| 494 | /// convert the pin to appropriate function for Lpuart Rx usage | ||
| 495 | fn as_rx(&self); | ||
| 496 | } | ||
| 497 | |||
| 498 | /// io configuration trait for Lpuart Cts | ||
| 499 | pub trait CtsPin<T: Instance>: Pin + sealed::Sealed + PeripheralType { | ||
| 500 | /// convert the pin to appropriate function for Lpuart Cts usage | ||
| 501 | fn as_cts(&self); | ||
| 502 | } | ||
| 503 | |||
| 504 | /// io configuration trait for Lpuart Rts | ||
| 505 | pub trait RtsPin<T: Instance>: Pin + sealed::Sealed + PeripheralType { | ||
| 506 | /// convert the pin to appropriate function for Lpuart Rts usage | ||
| 507 | fn as_rts(&self); | ||
| 508 | } | ||
| 509 | |||
| 510 | macro_rules! impl_pin_trait { | ||
| 511 | ($fcn:ident, $mode:ident, $($pin:ident, $alt:ident),*) => { | ||
| 512 | paste! { | ||
| 513 | $( | ||
| 514 | impl [<$mode:camel Pin>]<lib::peripherals::$fcn> for lib::peripherals::$pin { | ||
| 515 | fn [<as_ $mode>](&self) { | ||
| 516 | let _alt = gpio::Alt::$alt; | ||
| 517 | // todo!("Configure pin for LPUART function") | ||
| 518 | } | ||
| 519 | } | ||
| 520 | )* | ||
| 521 | } | ||
| 522 | }; | ||
| 523 | } | ||
| 524 | |||
| 525 | // Document identifier: MCXA343/344 Rev. 1DraftB ReleaseCandidate, 2025-07-10 - 6.1 MCX A173, A174 Signal Multiplexing and Pin Assignments | ||
| 526 | // impl_pin_trait!(LPUART0, rx, PIO2_0, ALT2, PIO0_2, ALT2, PIO0_20, ALT3); | ||
| 527 | // impl_pin_trait!(LPUART0, tx, PIO2_1, ALT2, PIO0_3, ALT2, PIO0_21, ALT3); | ||
| 528 | // impl_pin_trait!(LPUART0, rts, PIO2_2, ALT2, PIO0_0, ALT2, PIO0_22, ALT3); | ||
| 529 | // impl_pin_trait!(LPUART0, cts, PIO2_3, ALT2, PIO0_1, ALT2, PIO0_23, ALT3); | ||
| 530 | impl_pin_trait!(LPUART2, rx, PIO2_3, ALT3); | ||
| 531 | impl_pin_trait!(LPUART2, tx, PIO2_2, ALT3); | ||
| 532 | |||
| 533 | // ============================================================================ | ||
| 534 | // ERROR TYPES AND RESULTS | ||
| 535 | // ============================================================================ | ||
| 536 | |||
| 537 | /// LPUART error types | ||
| 538 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] | ||
| 539 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 540 | pub enum Error { | ||
| 541 | /// Read error | ||
| 542 | Read, | ||
| 543 | /// Buffer overflow | ||
| 544 | Overrun, | ||
| 545 | /// Noise error | ||
| 546 | Noise, | ||
| 547 | /// Framing error | ||
| 548 | Framing, | ||
| 549 | /// Parity error | ||
| 550 | Parity, | ||
| 551 | /// Failure | ||
| 552 | Fail, | ||
| 553 | /// Invalid argument | ||
| 554 | InvalidArgument, | ||
| 555 | /// Lpuart baud rate cannot be supported with the given clock | ||
| 556 | UnsupportedBaudrate, | ||
| 557 | /// RX FIFO Empty | ||
| 558 | RxFifoEmpty, | ||
| 559 | /// TX FIFO Full | ||
| 560 | TxFifoFull, | ||
| 561 | /// TX Busy | ||
| 562 | TxBusy, | ||
| 563 | } | ||
| 564 | |||
| 565 | /// A specialized Result type for LPUART operations | ||
| 566 | pub type Result<T> = core::result::Result<T, Error>; | ||
| 567 | |||
| 568 | // ============================================================================ | ||
| 569 | // CONFIGURATION STRUCTURES | ||
| 570 | // ============================================================================ | ||
| 571 | |||
| 572 | /// Lpuart config | ||
| 573 | #[derive(Debug, Clone, Copy)] | ||
| 574 | pub struct Config { | ||
| 575 | /// Baud rate in bits per second | ||
| 576 | pub baudrate_bps: u32, | ||
| 577 | /// Clock | ||
| 578 | pub clock: Clock, | ||
| 579 | /// Parity configuration | ||
| 580 | pub parity_mode: Option<Parity>, | ||
| 581 | /// Number of data bits | ||
| 582 | pub data_bits_count: DataBits, | ||
| 583 | /// MSB First or LSB First configuration | ||
| 584 | pub msb_firs: MsbFirst, | ||
| 585 | /// Number of stop bits | ||
| 586 | pub stop_bits_count: StopBits, | ||
| 587 | /// TX FIFO watermark | ||
| 588 | pub tx_fifo_watermark: u8, | ||
| 589 | /// RX FIFO watermark | ||
| 590 | pub rx_fifo_watermark: u8, | ||
| 591 | /// RX RTS enable | ||
| 592 | pub enable_rx_rts: bool, | ||
| 593 | /// TX CTS enable | ||
| 594 | pub enable_tx_cts: bool, | ||
| 595 | /// TX CTS source | ||
| 596 | pub tx_cts_source: TxCtsSource, | ||
| 597 | /// TX CTS configure | ||
| 598 | pub tx_cts_config: TxCtsConfig, | ||
| 599 | /// RX IDLE type | ||
| 600 | pub rx_idle_type: IdleType, | ||
| 601 | /// RX IDLE configuration | ||
| 602 | pub rx_idle_config: IdleConfig, | ||
| 603 | /// Enable transmitter | ||
| 604 | pub enable_tx: bool, | ||
| 605 | /// Enable receiver | ||
| 606 | pub enable_rx: bool, | ||
| 607 | /// Swap TXD and RXD pins | ||
| 608 | pub swap_txd_rxd: bool, | ||
| 609 | } | ||
| 610 | |||
| 611 | impl Default for Config { | ||
| 612 | fn default() -> Self { | ||
| 613 | Self { | ||
| 614 | baudrate_bps: 115_200u32, | ||
| 615 | clock: Clock::FroLf, | ||
| 616 | parity_mode: None, | ||
| 617 | data_bits_count: DataBits::Data8, | ||
| 618 | msb_firs: MsbFirst::LsbFirst, | ||
| 619 | stop_bits_count: StopBits::One, | ||
| 620 | tx_fifo_watermark: 0, | ||
| 621 | rx_fifo_watermark: 1, | ||
| 622 | enable_rx_rts: false, | ||
| 623 | enable_tx_cts: false, | ||
| 624 | tx_cts_source: TxCtsSource::Cts, | ||
| 625 | tx_cts_config: TxCtsConfig::Start, | ||
| 626 | rx_idle_type: IdleType::FromStart, | ||
| 627 | rx_idle_config: IdleConfig::Idle1, | ||
| 628 | enable_tx: false, | ||
| 629 | enable_rx: false, | ||
| 630 | swap_txd_rxd: false, | ||
| 631 | } | ||
| 632 | } | ||
| 633 | } | ||
| 634 | |||
| 635 | /// LPUART status flags | ||
| 636 | #[derive(Debug, Clone, Copy)] | ||
| 637 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 638 | pub struct Status { | ||
| 639 | /// Transmit data register empty | ||
| 640 | pub tx_empty: bool, | ||
| 641 | /// Transmission complete | ||
| 642 | pub tx_complete: bool, | ||
| 643 | /// Receive data register full | ||
| 644 | pub rx_full: bool, | ||
| 645 | /// Idle line detected | ||
| 646 | pub idle: bool, | ||
| 647 | /// Receiver overrun | ||
| 648 | pub overrun: bool, | ||
| 649 | /// Noise error | ||
| 650 | pub noise: bool, | ||
| 651 | /// Framing error | ||
| 652 | pub framing: bool, | ||
| 653 | /// Parity error | ||
| 654 | pub parity: bool, | ||
| 655 | } | ||
| 656 | |||
| 657 | // ============================================================================ | ||
| 658 | // MODE TRAITS (BLOCKING/ASYNC) | ||
| 659 | // ============================================================================ | ||
| 660 | |||
| 661 | /// Driver move trait. | ||
| 662 | #[allow(private_bounds)] | ||
| 663 | pub trait Mode: sealed::Sealed {} | ||
| 664 | |||
| 665 | /// Blocking mode. | ||
| 666 | pub struct Blocking; | ||
| 667 | impl sealed::Sealed for Blocking {} | ||
| 668 | impl Mode for Blocking {} | ||
| 669 | |||
| 670 | /// Async mode. | ||
| 671 | pub struct Async; | ||
| 672 | impl sealed::Sealed for Async {} | ||
| 673 | impl Mode for Async {} | ||
| 674 | |||
| 675 | // ============================================================================ | ||
| 676 | // CORE DRIVER STRUCTURES | ||
| 677 | // ============================================================================ | ||
| 678 | |||
| 679 | /// Lpuart driver. | ||
| 680 | pub struct Lpuart<'a, M: Mode> { | ||
| 681 | info: Info, | ||
| 682 | tx: LpuartTx<'a, M>, | ||
| 683 | rx: LpuartRx<'a, M>, | ||
| 684 | } | ||
| 685 | |||
| 686 | /// Lpuart TX driver. | ||
| 687 | pub struct LpuartTx<'a, M: Mode> { | ||
| 688 | info: Info, | ||
| 689 | _tx_pin: Peri<'a, AnyPin>, | ||
| 690 | _tx_dma: Option<Channel<'a>>, | ||
| 691 | mode: PhantomData<(&'a (), M)>, | ||
| 692 | } | ||
| 693 | |||
| 694 | /// Lpuart Rx driver. | ||
| 695 | pub struct LpuartRx<'a, M: Mode> { | ||
| 696 | info: Info, | ||
| 697 | _rx_pin: Peri<'a, AnyPin>, | ||
| 698 | _rx_dma: Option<Channel<'a>>, | ||
| 699 | mode: PhantomData<(&'a (), M)>, | ||
| 700 | } | ||
| 701 | |||
| 702 | // ============================================================================ | ||
| 703 | // LPUART CORE IMPLEMENTATION | ||
| 704 | // ============================================================================ | ||
| 705 | |||
| 706 | impl<'a, M: Mode> Lpuart<'a, M> { | ||
| 707 | fn init<T: Instance>( | ||
| 708 | _tx: Option<&Peri<'a, AnyPin>>, | ||
| 709 | _rx: Option<&Peri<'a, AnyPin>>, | ||
| 710 | _rts: Option<&Peri<'a, AnyPin>>, | ||
| 711 | _cts: Option<&Peri<'a, AnyPin>>, | ||
| 712 | config: Config, | ||
| 713 | ) -> Result<()> { | ||
| 714 | let regs = T::info().regs; | ||
| 715 | |||
| 716 | // Perform initialization sequence | ||
| 717 | perform_software_reset(regs); | ||
| 718 | disable_transceiver(regs); | ||
| 719 | configure_baudrate(regs, config.baudrate_bps, config.clock)?; | ||
| 720 | configure_frame_format(regs, &config); | ||
| 721 | configure_control_settings(regs, &config); | ||
| 722 | configure_fifo(regs, &config); | ||
| 723 | clear_all_status_flags(regs); | ||
| 724 | configure_flow_control(regs, &config); | ||
| 725 | configure_bit_order(regs, config.msb_firs); | ||
| 726 | enable_transceiver(regs, config.enable_tx, config.enable_rx); | ||
| 727 | |||
| 728 | Ok(()) | ||
| 729 | } | ||
| 730 | |||
| 731 | /// Deinitialize the LPUART peripheral | ||
| 732 | pub fn deinit(&self) -> Result<()> { | ||
| 733 | let regs = self.info.regs; | ||
| 734 | |||
| 735 | // Wait for TX operations to complete | ||
| 736 | wait_for_tx_complete(regs); | ||
| 737 | |||
| 738 | // Clear all status flags | ||
| 739 | clear_all_status_flags(regs); | ||
| 740 | |||
| 741 | // Disable the module - clear all CTRL register bits | ||
| 742 | regs.ctrl().reset(); | ||
| 743 | |||
| 744 | Ok(()) | ||
| 745 | } | ||
| 746 | |||
| 747 | /// Split the Lpuart into a transmitter and receiver | ||
| 748 | pub fn split(self) -> (LpuartTx<'a, M>, LpuartRx<'a, M>) { | ||
| 749 | (self.tx, self.rx) | ||
| 750 | } | ||
| 751 | |||
| 752 | /// Split the Lpuart into a transmitter and receiver by mutable reference | ||
| 753 | pub fn split_ref(&mut self) -> (&mut LpuartTx<'a, M>, &mut LpuartRx<'a, M>) { | ||
| 754 | (&mut self.tx, &mut self.rx) | ||
| 755 | } | ||
| 756 | } | ||
| 757 | |||
| 758 | // ============================================================================ | ||
| 759 | // BLOCKING MODE IMPLEMENTATIONS | ||
| 760 | // ============================================================================ | ||
| 761 | |||
| 762 | impl<'a> Lpuart<'a, Blocking> { | ||
| 763 | /// Create a new blocking LPUART instance with TX and RX pins. | ||
| 764 | pub fn new_blocking<T: Instance>( | ||
| 765 | _inner: Peri<'a, T>, | ||
| 766 | tx_pin: Peri<'a, impl TxPin<T>>, | ||
| 767 | rx_pin: Peri<'a, impl RxPin<T>>, | ||
| 768 | config: Config, | ||
| 769 | ) -> Result<Self> { | ||
| 770 | // Configure the pins for LPUART usage | ||
| 771 | tx_pin.as_tx(); | ||
| 772 | rx_pin.as_rx(); | ||
| 773 | |||
| 774 | // Convert pins to AnyPin | ||
| 775 | let tx_pin: Peri<'a, AnyPin> = tx_pin.into(); | ||
| 776 | let rx_pin: Peri<'a, AnyPin> = rx_pin.into(); | ||
| 777 | |||
| 778 | // Initialize the peripheral | ||
| 779 | Self::init::<T>(Some(&tx_pin), Some(&rx_pin), None, None, config)?; | ||
| 780 | |||
| 781 | Ok(Self { | ||
| 782 | info: T::info(), | ||
| 783 | tx: LpuartTx::new_inner(T::info(), tx_pin, None), | ||
| 784 | rx: LpuartRx::new_inner(T::info(), rx_pin, None), | ||
| 785 | }) | ||
| 786 | } | ||
| 787 | } | ||
| 788 | |||
| 789 | // ---------------------------------------------------------------------------- | ||
| 790 | // Blocking TX Implementation | ||
| 791 | // ---------------------------------------------------------------------------- | ||
| 792 | |||
| 793 | impl<'a, M: Mode> LpuartTx<'a, M> { | ||
| 794 | fn new_inner(info: Info, tx_pin: Peri<'a, AnyPin>, tx_dma: Option<Channel<'a>>) -> Self { | ||
| 795 | Self { | ||
| 796 | info, | ||
| 797 | _tx_pin: tx_pin, | ||
| 798 | _tx_dma: tx_dma, | ||
| 799 | mode: PhantomData, | ||
| 800 | } | ||
| 801 | } | ||
| 802 | } | ||
| 803 | |||
| 804 | impl<'a> LpuartTx<'a, Blocking> { | ||
| 805 | /// Create a new blocking LPUART which can only send data. | ||
| 806 | pub fn new_blocking<T: Instance>( | ||
| 807 | _inner: Peri<'a, T>, | ||
| 808 | tx_pin: Peri<'a, impl TxPin<T>>, | ||
| 809 | config: Config, | ||
| 810 | ) -> Result<Self> { | ||
| 811 | tx_pin.as_tx(); | ||
| 812 | |||
| 813 | let tx_pin: Peri<'a, AnyPin> = tx_pin.into(); | ||
| 814 | |||
| 815 | Lpuart::<Blocking>::init::<T>(Some(&tx_pin), None, None, None, config)?; | ||
| 816 | |||
| 817 | Ok(Self::new_inner(T::info(), tx_pin, None)) | ||
| 818 | } | ||
| 819 | |||
| 820 | fn write_byte_internal(&mut self, byte: u8) -> Result<()> { | ||
| 821 | self.info | ||
| 822 | .regs | ||
| 823 | .data() | ||
| 824 | .modify(|_, w| unsafe { w.bits(u32::from(byte)) }); | ||
| 825 | |||
| 826 | Ok(()) | ||
| 827 | } | ||
| 828 | |||
| 829 | fn blocking_write_byte(&mut self, byte: u8) -> Result<()> { | ||
| 830 | while self.info.regs.stat().read().tdre().is_txdata() {} | ||
| 831 | self.write_byte_internal(byte) | ||
| 832 | } | ||
| 833 | |||
| 834 | fn write_byte(&mut self, byte: u8) -> Result<()> { | ||
| 835 | if self.info.regs.stat().read().tdre().is_txdata() { | ||
| 836 | Err(Error::TxFifoFull) | ||
| 837 | } else { | ||
| 838 | self.write_byte_internal(byte) | ||
| 839 | } | ||
| 840 | } | ||
| 841 | |||
| 842 | /// Write data to LPUART TX blocking execution until all data is sent. | ||
| 843 | pub fn blocking_write(&mut self, buf: &[u8]) -> Result<()> { | ||
| 844 | for x in buf { | ||
| 845 | self.blocking_write_byte(*x)?; | ||
| 846 | } | ||
| 847 | |||
| 848 | Ok(()) | ||
| 849 | } | ||
| 850 | |||
| 851 | /// Write data to LPUART TX without blocking. | ||
| 852 | pub fn write(&mut self, buf: &[u8]) -> Result<()> { | ||
| 853 | for x in buf { | ||
| 854 | self.write_byte(*x)?; | ||
| 855 | } | ||
| 856 | |||
| 857 | Ok(()) | ||
| 858 | } | ||
| 859 | |||
| 860 | /// Flush LPUART TX blocking execution until all data has been transmitted. | ||
| 861 | pub fn blocking_flush(&mut self) -> Result<()> { | ||
| 862 | while self.info.regs.water().read().txcount().bits() != 0 { | ||
| 863 | // Wait for TX FIFO to drain | ||
| 864 | } | ||
| 865 | |||
| 866 | // Wait for last character to shift out | ||
| 867 | while self.info.regs.stat().read().tc().is_active() { | ||
| 868 | // Wait for transmission to complete | ||
| 869 | } | ||
| 870 | |||
| 871 | Ok(()) | ||
| 872 | } | ||
| 873 | |||
| 874 | /// Flush LPUART TX. | ||
| 875 | pub fn flush(&mut self) -> Result<()> { | ||
| 876 | // Check if TX FIFO is empty | ||
| 877 | if self.info.regs.water().read().txcount().bits() != 0 { | ||
| 878 | return Err(Error::TxBusy); | ||
| 879 | } | ||
| 880 | |||
| 881 | // Check if transmission is complete | ||
| 882 | if self.info.regs.stat().read().tc().is_active() { | ||
| 883 | return Err(Error::TxBusy); | ||
| 884 | } | ||
| 885 | |||
| 886 | Ok(()) | ||
| 887 | } | ||
| 888 | } | ||
| 889 | |||
| 890 | // ---------------------------------------------------------------------------- | ||
| 891 | // Blocking RX Implementation | ||
| 892 | // ---------------------------------------------------------------------------- | ||
| 893 | |||
| 894 | impl<'a, M: Mode> LpuartRx<'a, M> { | ||
| 895 | fn new_inner(info: Info, rx_pin: Peri<'a, AnyPin>, rx_dma: Option<Channel<'a>>) -> Self { | ||
| 896 | Self { | ||
| 897 | info, | ||
| 898 | _rx_pin: rx_pin, | ||
| 899 | _rx_dma: rx_dma, | ||
| 900 | mode: PhantomData, | ||
| 901 | } | ||
| 902 | } | ||
| 903 | } | ||
| 904 | |||
| 905 | impl<'a> LpuartRx<'a, Blocking> { | ||
| 906 | /// Create a new blocking LPUART which can only receive data. | ||
| 907 | pub fn new_blocking<T: Instance>( | ||
| 908 | _inner: Peri<'a, T>, | ||
| 909 | rx_pin: Peri<'a, impl RxPin<T>>, | ||
| 910 | config: Config, | ||
| 911 | ) -> Result<Self> { | ||
| 912 | rx_pin.as_rx(); | ||
| 913 | |||
| 914 | let rx_pin: Peri<'a, AnyPin> = rx_pin.into(); | ||
| 915 | |||
| 916 | Lpuart::<Blocking>::init::<T>(None, Some(&rx_pin), None, None, config)?; | ||
| 917 | |||
| 918 | Ok(Self::new_inner(T::info(), rx_pin, None)) | ||
| 919 | } | ||
| 920 | |||
| 921 | fn read_byte_internal(&mut self) -> Result<u8> { | ||
| 922 | let data = self.info.regs.data().read(); | ||
| 923 | |||
| 924 | Ok((data.bits() & 0xFF) as u8) | ||
| 925 | } | ||
| 926 | |||
| 927 | fn read_byte(&mut self) -> Result<u8> { | ||
| 928 | check_and_clear_rx_errors(self.info.regs)?; | ||
| 929 | |||
| 930 | if !has_data(self.info.regs) { | ||
| 931 | return Err(Error::RxFifoEmpty); | ||
| 932 | } | ||
| 933 | |||
| 934 | self.read_byte_internal() | ||
| 935 | } | ||
| 936 | |||
| 937 | fn blocking_read_byte(&mut self) -> Result<u8> { | ||
| 938 | loop { | ||
| 939 | if has_data(self.info.regs) { | ||
| 940 | return self.read_byte_internal(); | ||
| 941 | } | ||
| 942 | |||
| 943 | check_and_clear_rx_errors(self.info.regs)?; | ||
| 944 | } | ||
| 945 | } | ||
| 946 | |||
| 947 | /// Read data from LPUART RX without blocking. | ||
| 948 | pub fn read(&mut self, buf: &mut [u8]) -> Result<()> { | ||
| 949 | for byte in buf.iter_mut() { | ||
| 950 | *byte = self.read_byte()?; | ||
| 951 | } | ||
| 952 | Ok(()) | ||
| 953 | } | ||
| 954 | |||
| 955 | /// Read data from LPUART RX blocking execution until the buffer is filled. | ||
| 956 | pub fn blocking_read(&mut self, buf: &mut [u8]) -> Result<()> { | ||
| 957 | for byte in buf.iter_mut() { | ||
| 958 | *byte = self.blocking_read_byte()?; | ||
| 959 | } | ||
| 960 | Ok(()) | ||
| 961 | } | ||
| 962 | } | ||
| 963 | |||
| 964 | impl<'a> Lpuart<'a, Blocking> { | ||
| 965 | /// Read data from LPUART RX blocking execution until the buffer is filled | ||
| 966 | pub fn blocking_read(&mut self, buf: &mut [u8]) -> Result<()> { | ||
| 967 | self.rx.blocking_read(buf) | ||
| 968 | } | ||
| 969 | |||
| 970 | /// Read data from LPUART RX without blocking | ||
| 971 | pub fn read(&mut self, buf: &mut [u8]) -> Result<()> { | ||
| 972 | self.rx.read(buf) | ||
| 973 | } | ||
| 974 | |||
| 975 | /// Write data to LPUART TX blocking execution until all data is sent | ||
| 976 | pub fn blocking_write(&mut self, buf: &[u8]) -> Result<()> { | ||
| 977 | self.tx.blocking_write(buf) | ||
| 978 | } | ||
| 979 | |||
| 980 | /// Write data to LPUART TX without blocking | ||
| 981 | pub fn write(&mut self, buf: &[u8]) -> Result<()> { | ||
| 982 | self.tx.write(buf) | ||
| 983 | } | ||
| 984 | |||
| 985 | /// Flush LPUART TX blocking execution until all data has been transmitted | ||
| 986 | pub fn blocking_flush(&mut self) -> Result<()> { | ||
| 987 | self.tx.blocking_flush() | ||
| 988 | } | ||
| 989 | |||
| 990 | /// Flush LPUART TX without blocking | ||
| 991 | pub fn flush(&mut self) -> Result<()> { | ||
| 992 | self.tx.flush() | ||
| 993 | } | ||
| 994 | } | ||
| 995 | |||
| 996 | // ============================================================================ | ||
| 997 | // ASYNC MODE IMPLEMENTATIONS | ||
| 998 | // ============================================================================ | ||
| 999 | |||
| 1000 | // TODO: Implement async mode for LPUART | ||
| 1001 | |||
| 1002 | // ============================================================================ | ||
| 1003 | // EMBEDDED-HAL 0.2 TRAIT IMPLEMENTATIONS | ||
| 1004 | // ============================================================================ | ||
| 1005 | |||
| 1006 | impl embedded_hal_02::serial::Read<u8> for LpuartRx<'_, Blocking> { | ||
| 1007 | type Error = Error; | ||
| 1008 | |||
| 1009 | fn read(&mut self) -> core::result::Result<u8, nb::Error<Self::Error>> { | ||
| 1010 | let mut buf = [0; 1]; | ||
| 1011 | match self.read(&mut buf) { | ||
| 1012 | Ok(_) => Ok(buf[0]), | ||
| 1013 | Err(Error::RxFifoEmpty) => Err(nb::Error::WouldBlock), | ||
| 1014 | Err(e) => Err(nb::Error::Other(e)), | ||
| 1015 | } | ||
| 1016 | } | ||
| 1017 | } | ||
| 1018 | |||
| 1019 | impl embedded_hal_02::serial::Write<u8> for LpuartTx<'_, Blocking> { | ||
| 1020 | type Error = Error; | ||
| 1021 | |||
| 1022 | fn write(&mut self, word: u8) -> core::result::Result<(), nb::Error<Self::Error>> { | ||
| 1023 | match self.write(&[word]) { | ||
| 1024 | Ok(_) => Ok(()), | ||
| 1025 | Err(Error::TxFifoFull) => Err(nb::Error::WouldBlock), | ||
| 1026 | Err(e) => Err(nb::Error::Other(e)), | ||
| 1027 | } | ||
| 1028 | } | ||
| 1029 | |||
| 1030 | fn flush(&mut self) -> core::result::Result<(), nb::Error<Self::Error>> { | ||
| 1031 | match self.flush() { | ||
| 1032 | Ok(_) => Ok(()), | ||
| 1033 | Err(Error::TxBusy) => Err(nb::Error::WouldBlock), | ||
| 1034 | Err(e) => Err(nb::Error::Other(e)), | ||
| 1035 | } | ||
| 1036 | } | ||
| 1037 | } | ||
| 1038 | |||
| 1039 | impl embedded_hal_02::blocking::serial::Write<u8> for LpuartTx<'_, Blocking> { | ||
| 1040 | type Error = Error; | ||
| 1041 | |||
| 1042 | fn bwrite_all(&mut self, buffer: &[u8]) -> core::result::Result<(), Self::Error> { | ||
| 1043 | self.blocking_write(buffer) | ||
| 1044 | } | ||
| 1045 | |||
| 1046 | fn bflush(&mut self) -> core::result::Result<(), Self::Error> { | ||
| 1047 | self.blocking_flush() | ||
| 1048 | } | ||
| 1049 | } | ||
| 1050 | |||
| 1051 | impl embedded_hal_02::serial::Read<u8> for Lpuart<'_, Blocking> { | ||
| 1052 | type Error = Error; | ||
| 1053 | |||
| 1054 | fn read(&mut self) -> core::result::Result<u8, nb::Error<Self::Error>> { | ||
| 1055 | embedded_hal_02::serial::Read::read(&mut self.rx) | ||
| 1056 | } | ||
| 1057 | } | ||
| 1058 | |||
| 1059 | impl embedded_hal_02::serial::Write<u8> for Lpuart<'_, Blocking> { | ||
| 1060 | type Error = Error; | ||
| 1061 | |||
| 1062 | fn write(&mut self, word: u8) -> core::result::Result<(), nb::Error<Self::Error>> { | ||
| 1063 | embedded_hal_02::serial::Write::write(&mut self.tx, word) | ||
| 1064 | } | ||
| 1065 | |||
| 1066 | fn flush(&mut self) -> core::result::Result<(), nb::Error<Self::Error>> { | ||
| 1067 | embedded_hal_02::serial::Write::flush(&mut self.tx) | ||
| 1068 | } | ||
| 1069 | } | ||
| 1070 | |||
| 1071 | impl embedded_hal_02::blocking::serial::Write<u8> for Lpuart<'_, Blocking> { | ||
| 1072 | type Error = Error; | ||
| 1073 | |||
| 1074 | fn bwrite_all(&mut self, buffer: &[u8]) -> core::result::Result<(), Self::Error> { | ||
| 1075 | self.blocking_write(buffer) | ||
| 1076 | } | ||
| 1077 | |||
| 1078 | fn bflush(&mut self) -> core::result::Result<(), Self::Error> { | ||
| 1079 | self.blocking_flush() | ||
| 1080 | } | ||
| 1081 | } | ||
| 1082 | |||
| 1083 | // ============================================================================ | ||
| 1084 | // EMBEDDED-HAL-NB TRAIT IMPLEMENTATIONS | ||
| 1085 | // ============================================================================ | ||
| 1086 | |||
| 1087 | impl embedded_hal_nb::serial::Error for Error { | ||
| 1088 | fn kind(&self) -> embedded_hal_nb::serial::ErrorKind { | ||
| 1089 | match *self { | ||
| 1090 | Self::Framing => embedded_hal_nb::serial::ErrorKind::FrameFormat, | ||
| 1091 | Self::Overrun => embedded_hal_nb::serial::ErrorKind::Overrun, | ||
| 1092 | Self::Parity => embedded_hal_nb::serial::ErrorKind::Parity, | ||
| 1093 | Self::Noise => embedded_hal_nb::serial::ErrorKind::Noise, | ||
| 1094 | _ => embedded_hal_nb::serial::ErrorKind::Other, | ||
| 1095 | } | ||
| 1096 | } | ||
| 1097 | } | ||
| 1098 | |||
| 1099 | impl embedded_hal_nb::serial::ErrorType for LpuartRx<'_, Blocking> { | ||
| 1100 | type Error = Error; | ||
| 1101 | } | ||
| 1102 | |||
| 1103 | impl embedded_hal_nb::serial::ErrorType for LpuartTx<'_, Blocking> { | ||
| 1104 | type Error = Error; | ||
| 1105 | } | ||
| 1106 | |||
| 1107 | impl embedded_hal_nb::serial::ErrorType for Lpuart<'_, Blocking> { | ||
| 1108 | type Error = Error; | ||
| 1109 | } | ||
| 1110 | |||
| 1111 | impl embedded_hal_nb::serial::Read for LpuartRx<'_, Blocking> { | ||
| 1112 | fn read(&mut self) -> nb::Result<u8, Self::Error> { | ||
| 1113 | let mut buf = [0; 1]; | ||
| 1114 | match self.read(&mut buf) { | ||
| 1115 | Ok(_) => Ok(buf[0]), | ||
| 1116 | Err(Error::RxFifoEmpty) => Err(nb::Error::WouldBlock), | ||
| 1117 | Err(e) => Err(nb::Error::Other(e)), | ||
| 1118 | } | ||
| 1119 | } | ||
| 1120 | } | ||
| 1121 | |||
| 1122 | impl embedded_hal_nb::serial::Write for LpuartTx<'_, Blocking> { | ||
| 1123 | fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> { | ||
| 1124 | match self.write(&[word]) { | ||
| 1125 | Ok(_) => Ok(()), | ||
| 1126 | Err(Error::TxFifoFull) => Err(nb::Error::WouldBlock), | ||
| 1127 | Err(e) => Err(nb::Error::Other(e)), | ||
| 1128 | } | ||
| 1129 | } | ||
| 1130 | |||
| 1131 | fn flush(&mut self) -> nb::Result<(), Self::Error> { | ||
| 1132 | match self.flush() { | ||
| 1133 | Ok(_) => Ok(()), | ||
| 1134 | Err(Error::TxBusy) => Err(nb::Error::WouldBlock), | ||
| 1135 | Err(e) => Err(nb::Error::Other(e)), | ||
| 1136 | } | ||
| 1137 | } | ||
| 1138 | } | ||
| 1139 | |||
| 1140 | impl embedded_hal_nb::serial::Read for Lpuart<'_, Blocking> { | ||
| 1141 | fn read(&mut self) -> nb::Result<u8, Self::Error> { | ||
| 1142 | embedded_hal_nb::serial::Read::read(&mut self.rx) | ||
| 1143 | } | ||
| 1144 | } | ||
| 1145 | |||
| 1146 | impl embedded_hal_nb::serial::Write for Lpuart<'_, Blocking> { | ||
| 1147 | fn write(&mut self, char: u8) -> nb::Result<(), Self::Error> { | ||
| 1148 | embedded_hal_nb::serial::Write::write(&mut self.tx, char) | ||
| 1149 | } | ||
| 1150 | |||
| 1151 | fn flush(&mut self) -> nb::Result<(), Self::Error> { | ||
| 1152 | embedded_hal_nb::serial::Write::flush(&mut self.tx) | ||
| 1153 | } | ||
| 1154 | } | ||
| 1155 | |||
| 1156 | // ============================================================================ | ||
| 1157 | // EMBEDDED-IO TRAIT IMPLEMENTATIONS | ||
| 1158 | // ============================================================================ | ||
| 1159 | |||
| 1160 | impl embedded_io::Error for Error { | ||
| 1161 | fn kind(&self) -> embedded_io::ErrorKind { | ||
| 1162 | embedded_io::ErrorKind::Other | ||
| 1163 | } | ||
| 1164 | } | ||
| 1165 | |||
| 1166 | impl embedded_io::ErrorType for LpuartRx<'_, Blocking> { | ||
| 1167 | type Error = Error; | ||
| 1168 | } | ||
| 1169 | |||
| 1170 | impl embedded_io::ErrorType for LpuartTx<'_, Blocking> { | ||
| 1171 | type Error = Error; | ||
| 1172 | } | ||
| 1173 | |||
| 1174 | impl embedded_io::ErrorType for Lpuart<'_, Blocking> { | ||
| 1175 | type Error = Error; | ||
| 1176 | } | ||
| 1177 | |||
| 1178 | impl embedded_io::Read for LpuartRx<'_, Blocking> { | ||
| 1179 | fn read(&mut self, buf: &mut [u8]) -> core::result::Result<usize, Self::Error> { | ||
| 1180 | self.blocking_read(buf).map(|_| buf.len()) | ||
| 1181 | } | ||
| 1182 | } | ||
| 1183 | |||
| 1184 | impl embedded_io::Write for LpuartTx<'_, Blocking> { | ||
| 1185 | fn write(&mut self, buf: &[u8]) -> core::result::Result<usize, Self::Error> { | ||
| 1186 | self.blocking_write(buf).map(|_| buf.len()) | ||
| 1187 | } | ||
| 1188 | |||
| 1189 | fn flush(&mut self) -> core::result::Result<(), Self::Error> { | ||
| 1190 | self.blocking_flush() | ||
| 1191 | } | ||
| 1192 | } | ||
| 1193 | |||
| 1194 | impl embedded_io::Read for Lpuart<'_, Blocking> { | ||
| 1195 | fn read(&mut self, buf: &mut [u8]) -> core::result::Result<usize, Self::Error> { | ||
| 1196 | embedded_io::Read::read(&mut self.rx, buf) | ||
| 1197 | } | ||
| 1198 | } | ||
| 1199 | |||
| 1200 | impl embedded_io::Write for Lpuart<'_, Blocking> { | ||
| 1201 | fn write(&mut self, buf: &[u8]) -> core::result::Result<usize, Self::Error> { | ||
| 1202 | embedded_io::Write::write(&mut self.tx, buf) | ||
| 1203 | } | ||
| 1204 | |||
| 1205 | fn flush(&mut self) -> core::result::Result<(), Self::Error> { | ||
| 1206 | embedded_io::Write::flush(&mut self.tx) | ||
| 1207 | } | ||
| 1208 | } | ||
diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 7111536ff..000000000 --- a/src/main.rs +++ /dev/null | |||
| @@ -1,18 +0,0 @@ | |||
| 1 | #![cfg_attr(target_os = "none", no_std)] | ||
| 2 | #![cfg_attr(target_os = "none", no_main)] | ||
| 3 | |||
| 4 | #[cfg(target_os = "none")] | ||
| 5 | mod baremetal; | ||
| 6 | |||
| 7 | #[cfg(not(target_os = "none"))] | ||
| 8 | fn main() { | ||
| 9 | println!("Hello, world!"); | ||
| 10 | } | ||
| 11 | |||
| 12 | #[cfg(test)] | ||
| 13 | mod tests { | ||
| 14 | #[test] | ||
| 15 | fn it_works() { | ||
| 16 | assert_eq!(2 + 2, 4); | ||
| 17 | } | ||
| 18 | } | ||
diff --git a/src/ostimer.rs b/src/ostimer.rs new file mode 100644 index 000000000..b9688313a --- /dev/null +++ b/src/ostimer.rs | |||
| @@ -0,0 +1,704 @@ | |||
| 1 | //! # OSTIMER Driver with Robustness Features | ||
| 2 | //! | ||
| 3 | //! This module provides an async timer driver for the NXP MCXA276 OSTIMER peripheral | ||
| 4 | //! with protection against race conditions and timer rollover issues. | ||
| 5 | //! | ||
| 6 | //! ## Features | ||
| 7 | //! | ||
| 8 | //! - Async timing with embassy-time integration | ||
| 9 | //! - Gray code counter handling (42-bit counter) | ||
| 10 | //! - Interrupt-driven wakeups | ||
| 11 | //! - Configurable interrupt priority | ||
| 12 | //! - **Race condition protection**: Critical sections and atomic operations | ||
| 13 | //! - **Timer rollover handling**: Bounds checking and rollover prevention | ||
| 14 | //! | ||
| 15 | //! ## Clock Frequency Configuration | ||
| 16 | //! | ||
| 17 | //! The OSTIMER frequency depends on your system's clock configuration. You must provide | ||
| 18 | //! the actual frequency when calling `time_driver::init()`. | ||
| 19 | //! | ||
| 20 | //! ## Race Condition Protection | ||
| 21 | //! - Critical sections in interrupt handlers prevent concurrent access | ||
| 22 | //! - Atomic register operations with memory barriers | ||
| 23 | //! - Proper interrupt flag clearing and validation | ||
| 24 | //! | ||
| 25 | //! ## Timer Rollover Handling | ||
| 26 | //! - Bounds checking prevents scheduling beyond timer capacity | ||
| 27 | //! - Immediate wake for timestamps that would cause rollover issues | ||
| 28 | #![allow(dead_code)] | ||
| 29 | |||
| 30 | use crate::interrupt::InterruptExt; | ||
| 31 | use crate::pac; | ||
| 32 | use core::sync::atomic::{AtomicBool, Ordering}; | ||
| 33 | |||
| 34 | // PAC defines the shared RegisterBlock under `ostimer0`. | ||
| 35 | type Regs = pac::ostimer0::RegisterBlock; | ||
| 36 | |||
| 37 | // OSTIMER EVTIMER register layout constants | ||
| 38 | /// Total width of the EVTIMER counter in bits (42 bits total) | ||
| 39 | const EVTIMER_TOTAL_BITS: u32 = 42; | ||
| 40 | /// Width of the low part of EVTIMER (bits 31:0) | ||
| 41 | const EVTIMER_LO_BITS: u32 = 32; | ||
| 42 | /// Width of the high part of EVTIMER (bits 41:32) | ||
| 43 | const EVTIMER_HI_BITS: u32 = 10; | ||
| 44 | /// Bit position where high part starts in the combined 64-bit value | ||
| 45 | const EVTIMER_HI_SHIFT: u32 = 32; | ||
| 46 | |||
| 47 | /// Bit mask for the high part of EVTIMER | ||
| 48 | const EVTIMER_HI_MASK: u16 = (1 << EVTIMER_HI_BITS) - 1; | ||
| 49 | |||
| 50 | /// Maximum value for MATCH_L register (32-bit) | ||
| 51 | const MATCH_L_MAX: u32 = u32::MAX; | ||
| 52 | /// Maximum value for MATCH_H register (10-bit) | ||
| 53 | const MATCH_H_MAX: u16 = EVTIMER_HI_MASK; | ||
| 54 | |||
| 55 | /// Bit mask for extracting the low 32 bits from a 64-bit value | ||
| 56 | const LOW_32_BIT_MASK: u64 = u32::MAX as u64; | ||
| 57 | |||
| 58 | /// Gray code conversion bit shifts (most significant to least) | ||
| 59 | const GRAY_CONVERSION_SHIFTS: [u32; 6] = [32, 16, 8, 4, 2, 1]; | ||
| 60 | |||
| 61 | /// Maximum timer value before rollover (2^42 - 1 ticks) | ||
| 62 | /// Actual rollover time depends on the configured clock frequency | ||
| 63 | const TIMER_MAX_VALUE: u64 = (1u64 << EVTIMER_TOTAL_BITS) - 1; | ||
| 64 | |||
| 65 | /// Threshold for detecting timer rollover in comparisons (1 second at 1MHz) | ||
| 66 | const TIMER_ROLLOVER_THRESHOLD: u64 = 1_000_000; | ||
| 67 | |||
| 68 | /// Common default interrupt priority for OSTIMER | ||
| 69 | const DEFAULT_INTERRUPT_PRIORITY: u8 = 3; | ||
| 70 | |||
| 71 | // Global alarm state for interrupt handling | ||
| 72 | static ALARM_ACTIVE: AtomicBool = AtomicBool::new(false); | ||
| 73 | static mut ALARM_CALLBACK: Option<fn()> = None; | ||
| 74 | static mut ALARM_FLAG: Option<*const AtomicBool> = None; | ||
| 75 | static mut ALARM_TARGET_TIME: u64 = 0; | ||
| 76 | |||
| 77 | /// Number of tight spin iterations between elapsed time checks while waiting for MATCH writes to return to the idle (0) state. | ||
| 78 | const MATCH_WRITE_READY_SPINS: usize = 512; | ||
| 79 | /// Maximum time (in OSTIMER ticks) to wait for MATCH registers to become writable (~5 ms at 1 MHz). | ||
| 80 | const MATCH_WRITE_READY_TIMEOUT_TICKS: u64 = 5_000; | ||
| 81 | /// Short stabilization delay executed after toggling the MRCC reset line to let the OSTIMER bus interface settle. | ||
| 82 | const RESET_STABILIZE_SPINS: usize = 512; | ||
| 83 | |||
| 84 | pub(super) fn wait_for_match_write_ready(r: &Regs) -> bool { | ||
| 85 | let start = now_ticks_read(); | ||
| 86 | let mut spin_budget = 0usize; | ||
| 87 | |||
| 88 | loop { | ||
| 89 | if r.osevent_ctrl().read().match_wr_rdy().bit_is_clear() { | ||
| 90 | return true; | ||
| 91 | } | ||
| 92 | |||
| 93 | cortex_m::asm::nop(); | ||
| 94 | spin_budget += 1; | ||
| 95 | |||
| 96 | if spin_budget >= MATCH_WRITE_READY_SPINS { | ||
| 97 | spin_budget = 0; | ||
| 98 | |||
| 99 | let elapsed = now_ticks_read().wrapping_sub(start); | ||
| 100 | if elapsed >= MATCH_WRITE_READY_TIMEOUT_TICKS { | ||
| 101 | return false; | ||
| 102 | } | ||
| 103 | } | ||
| 104 | } | ||
| 105 | } | ||
| 106 | |||
| 107 | pub(super) fn wait_for_match_write_complete(r: &Regs) -> bool { | ||
| 108 | let start = now_ticks_read(); | ||
| 109 | let mut spin_budget = 0usize; | ||
| 110 | |||
| 111 | loop { | ||
| 112 | if r.osevent_ctrl().read().match_wr_rdy().bit_is_clear() { | ||
| 113 | return true; | ||
| 114 | } | ||
| 115 | |||
| 116 | cortex_m::asm::nop(); | ||
| 117 | spin_budget += 1; | ||
| 118 | |||
| 119 | if spin_budget >= MATCH_WRITE_READY_SPINS { | ||
| 120 | spin_budget = 0; | ||
| 121 | |||
| 122 | let elapsed = now_ticks_read().wrapping_sub(start); | ||
| 123 | if elapsed >= MATCH_WRITE_READY_TIMEOUT_TICKS { | ||
| 124 | return false; | ||
| 125 | } | ||
| 126 | } | ||
| 127 | } | ||
| 128 | } | ||
| 129 | |||
| 130 | fn prime_match_registers(r: &Regs) { | ||
| 131 | // Disable the interrupt, clear any pending flag, then wait until the MATCH registers are writable. | ||
| 132 | r.osevent_ctrl().write(|w| { | ||
| 133 | w.ostimer_intrflag() | ||
| 134 | .clear_bit_by_one() | ||
| 135 | .ostimer_intena() | ||
| 136 | .clear_bit() | ||
| 137 | }); | ||
| 138 | |||
| 139 | if wait_for_match_write_ready(r) { | ||
| 140 | r.match_l() | ||
| 141 | .write(|w| unsafe { w.match_value().bits(MATCH_L_MAX) }); | ||
| 142 | r.match_h() | ||
| 143 | .write(|w| unsafe { w.match_value().bits(MATCH_H_MAX) }); | ||
| 144 | let _ = wait_for_match_write_complete(r); | ||
| 145 | } | ||
| 146 | } | ||
| 147 | |||
| 148 | /// Single-shot alarm functionality for OSTIMER | ||
| 149 | pub struct Alarm<'d> { | ||
| 150 | /// Whether the alarm is currently active | ||
| 151 | active: AtomicBool, | ||
| 152 | /// Callback to execute when alarm expires (optional) | ||
| 153 | callback: Option<fn()>, | ||
| 154 | /// Flag that gets set when alarm expires (optional) | ||
| 155 | flag: Option<&'d AtomicBool>, | ||
| 156 | _phantom: core::marker::PhantomData<&'d mut ()>, | ||
| 157 | } | ||
| 158 | |||
| 159 | impl<'d> Alarm<'d> { | ||
| 160 | /// Create a new alarm instance | ||
| 161 | pub fn new() -> Self { | ||
| 162 | Self { | ||
| 163 | active: AtomicBool::new(false), | ||
| 164 | callback: None, | ||
| 165 | flag: None, | ||
| 166 | _phantom: core::marker::PhantomData, | ||
| 167 | } | ||
| 168 | } | ||
| 169 | |||
| 170 | /// Set a callback that will be executed when the alarm expires | ||
| 171 | /// Note: Due to interrupt handler constraints, callbacks must be static function pointers | ||
| 172 | pub fn with_callback(mut self, callback: fn()) -> Self { | ||
| 173 | self.callback = Some(callback); | ||
| 174 | self | ||
| 175 | } | ||
| 176 | |||
| 177 | /// Set a flag that will be set to true when the alarm expires | ||
| 178 | pub fn with_flag(mut self, flag: &'d AtomicBool) -> Self { | ||
| 179 | self.flag = Some(flag); | ||
| 180 | self | ||
| 181 | } | ||
| 182 | |||
| 183 | /// Check if the alarm is currently active | ||
| 184 | pub fn is_active(&self) -> bool { | ||
| 185 | self.active.load(Ordering::Acquire) | ||
| 186 | } | ||
| 187 | |||
| 188 | /// Cancel the alarm if it's active | ||
| 189 | pub fn cancel(&self) { | ||
| 190 | self.active.store(false, Ordering::Release); | ||
| 191 | } | ||
| 192 | } | ||
| 193 | |||
| 194 | /// Configuration for Ostimer::new() | ||
| 195 | #[derive(Copy, Clone)] | ||
| 196 | pub struct Config { | ||
| 197 | /// Initialize MATCH registers to their max values and mask/clear the interrupt flag. | ||
| 198 | pub init_match_max: bool, | ||
| 199 | /// OSTIMER clock frequency in Hz (must match the actual hardware clock) | ||
| 200 | pub clock_frequency_hz: u64, | ||
| 201 | } | ||
| 202 | |||
| 203 | impl Default for Config { | ||
| 204 | fn default() -> Self { | ||
| 205 | Self { | ||
| 206 | init_match_max: true, | ||
| 207 | // Default to 1MHz - user should override this with actual frequency | ||
| 208 | clock_frequency_hz: 1_000_000, | ||
| 209 | } | ||
| 210 | } | ||
| 211 | } | ||
| 212 | |||
| 213 | /// OSTIMER peripheral instance | ||
| 214 | pub struct Ostimer<'d, I: Instance> { | ||
| 215 | _inst: core::marker::PhantomData<I>, | ||
| 216 | clock_frequency_hz: u64, | ||
| 217 | _phantom: core::marker::PhantomData<&'d mut ()>, | ||
| 218 | } | ||
| 219 | |||
| 220 | impl<'d, I: Instance> Ostimer<'d, I> { | ||
| 221 | /// Construct OSTIMER handle. | ||
| 222 | /// Requires clocks for the instance to be enabled by the board before calling. | ||
| 223 | /// Does not enable NVIC or INTENA; use time_driver::init() for async operation. | ||
| 224 | pub fn new(_inst: impl Instance, cfg: Config, _p: &'d crate::pac::Peripherals) -> Self { | ||
| 225 | assert!( | ||
| 226 | cfg.clock_frequency_hz > 0, | ||
| 227 | "OSTIMER frequency must be greater than 0" | ||
| 228 | ); | ||
| 229 | |||
| 230 | if cfg.init_match_max { | ||
| 231 | let r: &Regs = unsafe { &*I::ptr() }; | ||
| 232 | // Mask INTENA, clear pending flag, and set MATCH to max so no spurious IRQ fires. | ||
| 233 | prime_match_registers(r); | ||
| 234 | } | ||
| 235 | |||
| 236 | Self { | ||
| 237 | _inst: core::marker::PhantomData, | ||
| 238 | clock_frequency_hz: cfg.clock_frequency_hz, | ||
| 239 | _phantom: core::marker::PhantomData, | ||
| 240 | } | ||
| 241 | } | ||
| 242 | |||
| 243 | /// Get the configured clock frequency in Hz | ||
| 244 | pub fn clock_frequency_hz(&self) -> u64 { | ||
| 245 | self.clock_frequency_hz | ||
| 246 | } | ||
| 247 | |||
| 248 | /// Read the current timer counter value in timer ticks | ||
| 249 | /// | ||
| 250 | /// # Returns | ||
| 251 | /// Current timer counter value as a 64-bit unsigned integer | ||
| 252 | pub fn now(&self) -> u64 { | ||
| 253 | now_ticks_read() | ||
| 254 | } | ||
| 255 | |||
| 256 | /// Reset the timer counter to zero | ||
| 257 | /// | ||
| 258 | /// This performs a hardware reset of the OSTIMER peripheral, which will reset | ||
| 259 | /// the counter to zero and clear any pending interrupts. Note that this will | ||
| 260 | /// affect all timer operations including embassy-time. | ||
| 261 | /// | ||
| 262 | /// # Safety | ||
| 263 | /// This operation will reset the entire OSTIMER peripheral. Any active alarms | ||
| 264 | /// or time_driver operations will be disrupted. Use with caution. | ||
| 265 | pub fn reset(&self, peripherals: &crate::pac::Peripherals) { | ||
| 266 | critical_section::with(|_| { | ||
| 267 | let r: &Regs = unsafe { &*I::ptr() }; | ||
| 268 | |||
| 269 | // Mask the peripheral interrupt flag before we toggle the reset line so that | ||
| 270 | // no new NVIC activity races with the reset sequence. | ||
| 271 | r.osevent_ctrl().write(|w| { | ||
| 272 | w.ostimer_intrflag() | ||
| 273 | .clear_bit_by_one() | ||
| 274 | .ostimer_intena() | ||
| 275 | .clear_bit() | ||
| 276 | }); | ||
| 277 | |||
| 278 | unsafe { | ||
| 279 | crate::reset::assert::<crate::reset::line::Ostimer0>(peripherals); | ||
| 280 | } | ||
| 281 | |||
| 282 | for _ in 0..RESET_STABILIZE_SPINS { | ||
| 283 | cortex_m::asm::nop(); | ||
| 284 | } | ||
| 285 | |||
| 286 | unsafe { | ||
| 287 | crate::reset::release::<crate::reset::line::Ostimer0>(peripherals); | ||
| 288 | } | ||
| 289 | |||
| 290 | while !<crate::reset::line::Ostimer0 as crate::reset::ResetLine>::is_released( | ||
| 291 | &peripherals.mrcc0, | ||
| 292 | ) { | ||
| 293 | cortex_m::asm::nop(); | ||
| 294 | } | ||
| 295 | |||
| 296 | for _ in 0..RESET_STABILIZE_SPINS { | ||
| 297 | cortex_m::asm::nop(); | ||
| 298 | } | ||
| 299 | |||
| 300 | // Clear alarm bookkeeping before re-arming MATCH registers. | ||
| 301 | ALARM_ACTIVE.store(false, Ordering::Release); | ||
| 302 | unsafe { | ||
| 303 | ALARM_TARGET_TIME = 0; | ||
| 304 | ALARM_CALLBACK = None; | ||
| 305 | ALARM_FLAG = None; | ||
| 306 | } | ||
| 307 | |||
| 308 | prime_match_registers(r); | ||
| 309 | }); | ||
| 310 | |||
| 311 | // Ensure no stale OS_EVENT request remains pending after the reset sequence. | ||
| 312 | crate::interrupt::OS_EVENT.unpend(); | ||
| 313 | } | ||
| 314 | |||
| 315 | /// Schedule a single-shot alarm to expire after the specified delay in microseconds | ||
| 316 | /// | ||
| 317 | /// # Parameters | ||
| 318 | /// * `alarm` - The alarm instance to schedule | ||
| 319 | /// * `delay_us` - Delay in microseconds from now | ||
| 320 | /// | ||
| 321 | /// # Returns | ||
| 322 | /// `true` if the alarm was scheduled successfully, `false` if it would exceed timer capacity | ||
| 323 | pub fn schedule_alarm_delay(&self, alarm: &Alarm, delay_us: u64) -> bool { | ||
| 324 | let delay_ticks = (delay_us * self.clock_frequency_hz) / 1_000_000; | ||
| 325 | let target_time = now_ticks_read() + delay_ticks; | ||
| 326 | self.schedule_alarm_at(alarm, target_time) | ||
| 327 | } | ||
| 328 | |||
| 329 | /// Schedule a single-shot alarm to expire at the specified absolute time in timer ticks | ||
| 330 | /// | ||
| 331 | /// # Parameters | ||
| 332 | /// * `alarm` - The alarm instance to schedule | ||
| 333 | /// * `target_ticks` - Absolute time in timer ticks when the alarm should expire | ||
| 334 | /// | ||
| 335 | /// # Returns | ||
| 336 | /// `true` if the alarm was scheduled successfully, `false` if it would exceed timer capacity | ||
| 337 | pub fn schedule_alarm_at(&self, alarm: &Alarm, target_ticks: u64) -> bool { | ||
| 338 | let now = now_ticks_read(); | ||
| 339 | |||
| 340 | // Check if target time is in the past | ||
| 341 | if target_ticks <= now { | ||
| 342 | // Execute callback immediately if alarm was supposed to be active | ||
| 343 | if alarm.active.load(Ordering::Acquire) { | ||
| 344 | alarm.active.store(false, Ordering::Release); | ||
| 345 | if let Some(callback) = alarm.callback { | ||
| 346 | callback(); | ||
| 347 | } | ||
| 348 | if let Some(flag) = &alarm.flag { | ||
| 349 | flag.store(true, Ordering::Release); | ||
| 350 | } | ||
| 351 | } | ||
| 352 | return true; | ||
| 353 | } | ||
| 354 | |||
| 355 | // Check for timer rollover | ||
| 356 | let max_future = now + TIMER_MAX_VALUE; | ||
| 357 | if target_ticks > max_future { | ||
| 358 | return false; // Would exceed timer capacity | ||
| 359 | } | ||
| 360 | |||
| 361 | // Program the timer | ||
| 362 | let r: &Regs = unsafe { &*I::ptr() }; | ||
| 363 | |||
| 364 | critical_section::with(|_| { | ||
| 365 | // Disable interrupt and clear flag | ||
| 366 | r.osevent_ctrl().write(|w| { | ||
| 367 | w.ostimer_intrflag() | ||
| 368 | .clear_bit_by_one() | ||
| 369 | .ostimer_intena() | ||
| 370 | .clear_bit() | ||
| 371 | }); | ||
| 372 | |||
| 373 | if !wait_for_match_write_ready(r) { | ||
| 374 | prime_match_registers(r); | ||
| 375 | |||
| 376 | if !wait_for_match_write_ready(r) { | ||
| 377 | alarm.active.store(false, Ordering::Release); | ||
| 378 | ALARM_ACTIVE.store(false, Ordering::Release); | ||
| 379 | unsafe { | ||
| 380 | ALARM_TARGET_TIME = 0; | ||
| 381 | ALARM_CALLBACK = None; | ||
| 382 | ALARM_FLAG = None; | ||
| 383 | } | ||
| 384 | return false; | ||
| 385 | } | ||
| 386 | } | ||
| 387 | |||
| 388 | // Mark alarm as active now that we know the MATCH registers are writable | ||
| 389 | alarm.active.store(true, Ordering::Release); | ||
| 390 | |||
| 391 | // Set global alarm state for interrupt handler | ||
| 392 | ALARM_ACTIVE.store(true, Ordering::Release); | ||
| 393 | unsafe { | ||
| 394 | ALARM_TARGET_TIME = target_ticks; | ||
| 395 | ALARM_CALLBACK = alarm.callback; | ||
| 396 | ALARM_FLAG = alarm.flag.map(|f| f as *const AtomicBool); | ||
| 397 | } | ||
| 398 | |||
| 399 | // Program MATCH registers (Gray-coded) | ||
| 400 | let gray = bin_to_gray(target_ticks); | ||
| 401 | let l = (gray & LOW_32_BIT_MASK) as u32; | ||
| 402 | let h = (((gray >> EVTIMER_HI_SHIFT) as u16) & EVTIMER_HI_MASK) as u16; | ||
| 403 | |||
| 404 | r.match_l().write(|w| unsafe { w.match_value().bits(l) }); | ||
| 405 | r.match_h().write(|w| unsafe { w.match_value().bits(h) }); | ||
| 406 | |||
| 407 | if !wait_for_match_write_complete(r) { | ||
| 408 | alarm.active.store(false, Ordering::Release); | ||
| 409 | ALARM_ACTIVE.store(false, Ordering::Release); | ||
| 410 | unsafe { | ||
| 411 | ALARM_TARGET_TIME = 0; | ||
| 412 | ALARM_CALLBACK = None; | ||
| 413 | ALARM_FLAG = None; | ||
| 414 | } | ||
| 415 | return false; | ||
| 416 | } | ||
| 417 | |||
| 418 | let now_after_program = now_ticks_read(); | ||
| 419 | let intrflag_set = r.osevent_ctrl().read().ostimer_intrflag().bit_is_set(); | ||
| 420 | if now_after_program >= target_ticks && !intrflag_set { | ||
| 421 | alarm.active.store(false, Ordering::Release); | ||
| 422 | ALARM_ACTIVE.store(false, Ordering::Release); | ||
| 423 | unsafe { | ||
| 424 | ALARM_TARGET_TIME = 0; | ||
| 425 | ALARM_CALLBACK = None; | ||
| 426 | ALARM_FLAG = None; | ||
| 427 | } | ||
| 428 | return false; | ||
| 429 | } | ||
| 430 | |||
| 431 | // Enable interrupt | ||
| 432 | r.osevent_ctrl().write(|w| w.ostimer_intena().set_bit()); | ||
| 433 | |||
| 434 | true | ||
| 435 | }) | ||
| 436 | } | ||
| 437 | |||
| 438 | /// Cancel any active alarm | ||
| 439 | pub fn cancel_alarm(&self, alarm: &Alarm) { | ||
| 440 | critical_section::with(|_| { | ||
| 441 | alarm.cancel(); | ||
| 442 | |||
| 443 | // Clear global alarm state | ||
| 444 | ALARM_ACTIVE.store(false, Ordering::Release); | ||
| 445 | unsafe { ALARM_TARGET_TIME = 0 }; | ||
| 446 | |||
| 447 | // Reset MATCH registers to maximum values to prevent spurious interrupts | ||
| 448 | let r: &Regs = unsafe { &*I::ptr() }; | ||
| 449 | prime_match_registers(r); | ||
| 450 | }); | ||
| 451 | } | ||
| 452 | |||
| 453 | /// Check if an alarm has expired (call this from your interrupt handler) | ||
| 454 | /// Returns true if the alarm was active and has now expired | ||
| 455 | pub fn check_alarm_expired(&self, alarm: &Alarm) -> bool { | ||
| 456 | if alarm.active.load(Ordering::Acquire) { | ||
| 457 | alarm.active.store(false, Ordering::Release); | ||
| 458 | |||
| 459 | // Execute callback | ||
| 460 | if let Some(callback) = alarm.callback { | ||
| 461 | callback(); | ||
| 462 | } | ||
| 463 | |||
| 464 | // Set flag | ||
| 465 | if let Some(flag) = &alarm.flag { | ||
| 466 | flag.store(true, Ordering::Release); | ||
| 467 | } | ||
| 468 | |||
| 469 | true | ||
| 470 | } else { | ||
| 471 | false | ||
| 472 | } | ||
| 473 | } | ||
| 474 | } | ||
| 475 | |||
| 476 | /// Read current EVTIMER (Gray-coded) and convert to binary ticks. | ||
| 477 | #[inline(always)] | ||
| 478 | fn now_ticks_read() -> u64 { | ||
| 479 | let r: &Regs = unsafe { &*pac::Ostimer0::ptr() }; | ||
| 480 | |||
| 481 | // Read high then low to minimize incoherent snapshots | ||
| 482 | let hi = (r.evtimerh().read().evtimer_count_value().bits() as u64) & (EVTIMER_HI_MASK as u64); | ||
| 483 | let lo = r.evtimerl().read().evtimer_count_value().bits() as u64; | ||
| 484 | |||
| 485 | // Combine and convert from Gray code to binary | ||
| 486 | let gray = lo | (hi << EVTIMER_HI_SHIFT); | ||
| 487 | gray_to_bin(gray) | ||
| 488 | } | ||
| 489 | |||
| 490 | // Instance trait like other drivers, providing a PAC pointer for this OSTIMER instance | ||
| 491 | pub trait Instance { | ||
| 492 | fn ptr() -> *const Regs; | ||
| 493 | } | ||
| 494 | |||
| 495 | // Token for OSTIMER0 provided by embassy-hal-internal peripherals macro. | ||
| 496 | #[cfg(feature = "ostimer0")] | ||
| 497 | pub type Ostimer0 = crate::peripherals::OSTIMER0; | ||
| 498 | |||
| 499 | #[cfg(feature = "ostimer0")] | ||
| 500 | impl Instance for crate::peripherals::OSTIMER0 { | ||
| 501 | #[inline(always)] | ||
| 502 | fn ptr() -> *const Regs { | ||
| 503 | pac::Ostimer0::ptr() | ||
| 504 | } | ||
| 505 | } | ||
| 506 | |||
| 507 | // Also implement Instance for the Peri wrapper type | ||
| 508 | #[cfg(feature = "ostimer0")] | ||
| 509 | impl Instance for embassy_hal_internal::Peri<'_, crate::peripherals::OSTIMER0> { | ||
| 510 | #[inline(always)] | ||
| 511 | fn ptr() -> *const Regs { | ||
| 512 | pac::Ostimer0::ptr() | ||
| 513 | } | ||
| 514 | } | ||
| 515 | |||
| 516 | #[inline(always)] | ||
| 517 | fn bin_to_gray(x: u64) -> u64 { | ||
| 518 | x ^ (x >> 1) | ||
| 519 | } | ||
| 520 | |||
| 521 | #[inline(always)] | ||
| 522 | fn gray_to_bin(gray: u64) -> u64 { | ||
| 523 | // More efficient iterative conversion using predefined shifts | ||
| 524 | let mut bin = gray; | ||
| 525 | for &shift in &GRAY_CONVERSION_SHIFTS { | ||
| 526 | bin ^= bin >> shift; | ||
| 527 | } | ||
| 528 | bin | ||
| 529 | } | ||
| 530 | |||
| 531 | #[cfg(feature = "ostimer0")] | ||
| 532 | pub mod time_driver { | ||
| 533 | use super::{ | ||
| 534 | bin_to_gray, now_ticks_read, Regs, ALARM_ACTIVE, ALARM_CALLBACK, ALARM_FLAG, | ||
| 535 | ALARM_TARGET_TIME, EVTIMER_HI_MASK, EVTIMER_HI_SHIFT, LOW_32_BIT_MASK, | ||
| 536 | }; | ||
| 537 | use crate::pac; | ||
| 538 | use core::sync::atomic::Ordering; | ||
| 539 | use core::task::Waker; | ||
| 540 | use embassy_sync::waitqueue::AtomicWaker; | ||
| 541 | use embassy_time_driver as etd; | ||
| 542 | pub struct Driver; | ||
| 543 | static TIMER_WAKER: AtomicWaker = AtomicWaker::new(); | ||
| 544 | |||
| 545 | impl etd::Driver for Driver { | ||
| 546 | fn now(&self) -> u64 { | ||
| 547 | // Use the hardware counter (frequency configured in init) | ||
| 548 | super::now_ticks_read() | ||
| 549 | } | ||
| 550 | |||
| 551 | fn schedule_wake(&self, timestamp: u64, waker: &Waker) { | ||
| 552 | let now = self.now(); | ||
| 553 | |||
| 554 | // If timestamp is in the past or very close to now, wake immediately | ||
| 555 | if timestamp <= now { | ||
| 556 | waker.wake_by_ref(); | ||
| 557 | return; | ||
| 558 | } | ||
| 559 | |||
| 560 | // Prevent scheduling too far in the future (beyond timer rollover) | ||
| 561 | // This prevents wraparound issues | ||
| 562 | let max_future = now + super::TIMER_MAX_VALUE; | ||
| 563 | if timestamp > max_future { | ||
| 564 | // For very long timeouts, wake immediately to avoid rollover issues | ||
| 565 | waker.wake_by_ref(); | ||
| 566 | return; | ||
| 567 | } | ||
| 568 | |||
| 569 | // Register the waker first so any immediate wake below is observed by the executor. | ||
| 570 | TIMER_WAKER.register(waker); | ||
| 571 | |||
| 572 | let r: &Regs = unsafe { &*pac::Ostimer0::ptr() }; | ||
| 573 | |||
| 574 | critical_section::with(|_| { | ||
| 575 | // Mask INTENA and clear flag | ||
| 576 | r.osevent_ctrl().write(|w| { | ||
| 577 | w.ostimer_intrflag() | ||
| 578 | .clear_bit_by_one() | ||
| 579 | .ostimer_intena() | ||
| 580 | .clear_bit() | ||
| 581 | }); | ||
| 582 | |||
| 583 | // Read back to ensure W1C took effect on hardware | ||
| 584 | let _ = r.osevent_ctrl().read().ostimer_intrflag().bit(); | ||
| 585 | |||
| 586 | if !super::wait_for_match_write_ready(r) { | ||
| 587 | super::prime_match_registers(r); | ||
| 588 | |||
| 589 | if !super::wait_for_match_write_ready(r) { | ||
| 590 | // If we can't safely program MATCH, wake immediately and leave INTENA masked. | ||
| 591 | waker.wake_by_ref(); | ||
| 592 | return; | ||
| 593 | } | ||
| 594 | } | ||
| 595 | |||
| 596 | // Program MATCH (Gray-coded). Write low then high, then fence. | ||
| 597 | let gray = bin_to_gray(timestamp); | ||
| 598 | let l = (gray & LOW_32_BIT_MASK) as u32; | ||
| 599 | |||
| 600 | let h = (((gray >> EVTIMER_HI_SHIFT) as u16) & EVTIMER_HI_MASK) as u16; | ||
| 601 | |||
| 602 | r.match_l().write(|w| unsafe { w.match_value().bits(l) }); | ||
| 603 | r.match_h().write(|w| unsafe { w.match_value().bits(h) }); | ||
| 604 | |||
| 605 | if !super::wait_for_match_write_complete(r) { | ||
| 606 | waker.wake_by_ref(); | ||
| 607 | return; | ||
| 608 | } | ||
| 609 | |||
| 610 | let now_after_program = super::now_ticks_read(); | ||
| 611 | let intrflag_set = r.osevent_ctrl().read().ostimer_intrflag().bit_is_set(); | ||
| 612 | if now_after_program >= timestamp && !intrflag_set { | ||
| 613 | waker.wake_by_ref(); | ||
| 614 | return; | ||
| 615 | } | ||
| 616 | |||
| 617 | // Enable peripheral interrupt | ||
| 618 | r.osevent_ctrl().write(|w| w.ostimer_intena().set_bit()); | ||
| 619 | }); | ||
| 620 | } | ||
| 621 | } | ||
| 622 | |||
| 623 | /// Install the global embassy-time driver and configure NVIC priority for OS_EVENT. | ||
| 624 | /// | ||
| 625 | /// # Parameters | ||
| 626 | /// * `priority` - Interrupt priority for the OSTIMER interrupt | ||
| 627 | /// * `frequency_hz` - Actual OSTIMER clock frequency in Hz (stored for future use) | ||
| 628 | /// | ||
| 629 | /// Note: The frequency parameter is currently accepted for API compatibility. | ||
| 630 | /// The embassy_time_driver macro handles driver registration automatically. | ||
| 631 | pub fn init(priority: crate::interrupt::Priority, frequency_hz: u64) { | ||
| 632 | // Mask/clear at peripheral and set default MATCH | ||
| 633 | let r: &Regs = unsafe { &*pac::Ostimer0::ptr() }; | ||
| 634 | super::prime_match_registers(r); | ||
| 635 | |||
| 636 | // Configure NVIC for timer operation | ||
| 637 | crate::interrupt::OS_EVENT.configure_for_timer(priority); | ||
| 638 | |||
| 639 | // Note: The embassy_time_driver macro automatically registers the driver | ||
| 640 | // The frequency parameter is accepted for future compatibility | ||
| 641 | let _ = frequency_hz; // Suppress unused parameter warning | ||
| 642 | } | ||
| 643 | |||
| 644 | // Export the global time driver expected by embassy-time | ||
| 645 | embassy_time_driver::time_driver_impl!(static DRIVER: Driver = Driver); | ||
| 646 | |||
| 647 | /// To be called from the OS_EVENT IRQ. | ||
| 648 | pub fn on_interrupt() { | ||
| 649 | let r: &Regs = unsafe { &*pac::Ostimer0::ptr() }; | ||
| 650 | |||
| 651 | // Critical section to prevent races with schedule_wake | ||
| 652 | critical_section::with(|_| { | ||
| 653 | // Check if interrupt is actually pending and handle it atomically | ||
| 654 | if r.osevent_ctrl().read().ostimer_intrflag().bit_is_set() { | ||
| 655 | // Clear flag and disable interrupt atomically | ||
| 656 | r.osevent_ctrl().write(|w| { | ||
| 657 | w.ostimer_intrflag() | ||
| 658 | .clear_bit_by_one() // Write-1-to-clear using safe helper | ||
| 659 | .ostimer_intena() | ||
| 660 | .clear_bit() | ||
| 661 | }); | ||
| 662 | |||
| 663 | // Wake the waiting task | ||
| 664 | TIMER_WAKER.wake(); | ||
| 665 | |||
| 666 | // Handle alarm callback if active and this interrupt is for the alarm | ||
| 667 | if ALARM_ACTIVE.load(Ordering::SeqCst) { | ||
| 668 | let current_time = now_ticks_read(); | ||
| 669 | let target_time = unsafe { ALARM_TARGET_TIME }; | ||
| 670 | |||
| 671 | // Check if current time is close to alarm target time (within 1000 ticks for timing variations) | ||
| 672 | if current_time >= target_time && current_time <= target_time + 1000 { | ||
| 673 | ALARM_ACTIVE.store(false, Ordering::SeqCst); | ||
| 674 | unsafe { ALARM_TARGET_TIME = 0 }; | ||
| 675 | |||
| 676 | // Execute callback if set | ||
| 677 | unsafe { | ||
| 678 | if let Some(callback) = ALARM_CALLBACK { | ||
| 679 | callback(); | ||
| 680 | } | ||
| 681 | } | ||
| 682 | |||
| 683 | // Set flag if provided | ||
| 684 | unsafe { | ||
| 685 | if let Some(flag) = ALARM_FLAG { | ||
| 686 | (*flag).store(true, Ordering::SeqCst); | ||
| 687 | } | ||
| 688 | } | ||
| 689 | } | ||
| 690 | } | ||
| 691 | } | ||
| 692 | }); | ||
| 693 | } | ||
| 694 | |||
| 695 | /// Type-level handler to be used with bind_interrupts! for OS_EVENT. | ||
| 696 | pub struct OsEventHandler; | ||
| 697 | impl crate::interrupt::typelevel::Handler<crate::interrupt::typelevel::OS_EVENT> | ||
| 698 | for OsEventHandler | ||
| 699 | { | ||
| 700 | unsafe fn on_interrupt() { | ||
| 701 | on_interrupt(); | ||
| 702 | } | ||
| 703 | } | ||
| 704 | } | ||
diff --git a/src/pins.rs b/src/pins.rs new file mode 100644 index 000000000..d46a3e6b3 --- /dev/null +++ b/src/pins.rs | |||
| @@ -0,0 +1,127 @@ | |||
| 1 | //! Pin configuration helpers (separate from peripheral drivers). | ||
| 2 | use crate::pac; | ||
| 3 | |||
| 4 | pub unsafe fn configure_uart2_pins_port2() { | ||
| 5 | // P2_2 = LPUART2_TX ALT3, P2_3 = LPUART2_RX ALT3 with pull-up, input enable, high drive, slow slew. | ||
| 6 | let port2 = &*pac::Port2::ptr(); | ||
| 7 | port2.pcr2().write(|w| { | ||
| 8 | w.ps() | ||
| 9 | .ps1() | ||
| 10 | .pe() | ||
| 11 | .pe1() | ||
| 12 | .sre() | ||
| 13 | .sre1() | ||
| 14 | .dse() | ||
| 15 | .dse1() | ||
| 16 | .mux() | ||
| 17 | .mux11() | ||
| 18 | .ibe() | ||
| 19 | .ibe1() | ||
| 20 | }); | ||
| 21 | port2.pcr3().write(|w| { | ||
| 22 | w.ps() | ||
| 23 | .ps1() | ||
| 24 | .pe() | ||
| 25 | .pe1() | ||
| 26 | .sre() | ||
| 27 | .sre1() | ||
| 28 | .dse() | ||
| 29 | .dse1() | ||
| 30 | .mux() | ||
| 31 | .mux11() | ||
| 32 | .ibe() | ||
| 33 | .ibe1() | ||
| 34 | }); | ||
| 35 | core::arch::asm!("dsb sy; isb sy"); | ||
| 36 | } | ||
| 37 | |||
| 38 | pub unsafe fn configure_adc_pins() { | ||
| 39 | // P1_10 = ADC1_A8 | ||
| 40 | let port1 = &*pac::Port1::ptr(); | ||
| 41 | port1.pcr10().write(|w| { | ||
| 42 | w.ps() | ||
| 43 | .ps0() | ||
| 44 | .pe() | ||
| 45 | .pe0() | ||
| 46 | .sre() | ||
| 47 | .sre0() | ||
| 48 | .ode() | ||
| 49 | .ode0() | ||
| 50 | .dse() | ||
| 51 | .dse0() | ||
| 52 | .mux() | ||
| 53 | .mux00() | ||
| 54 | .ibe() | ||
| 55 | .ibe0() | ||
| 56 | .inv() | ||
| 57 | .inv0() | ||
| 58 | .lk() | ||
| 59 | .lk0() | ||
| 60 | |||
| 61 | }); | ||
| 62 | core::arch::asm!("dsb sy; isb sy"); | ||
| 63 | } | ||
| 64 | |||
| 65 | /// Configure a pin for a specific mux alternative. | ||
| 66 | /// | ||
| 67 | /// # Arguments | ||
| 68 | /// * `port` - Port number (0-4) | ||
| 69 | /// * `pin` - Pin number (varies by port: PORT0=0-7, PORT1=0-19, PORT2=0-26, PORT3=0-31, PORT4=0-7) | ||
| 70 | /// * `mux` - Mux alternative (0-15, where 0 = GPIO, 1-15 = other functions) | ||
| 71 | pub unsafe fn set_pin_mux(port: u8, pin: u8, mux: u8) { | ||
| 72 | // Validate mux value (0-15) | ||
| 73 | if mux > 15 { | ||
| 74 | panic!("Invalid mux value: {}, must be 0-15", mux); | ||
| 75 | } | ||
| 76 | |||
| 77 | // Validate pin number based on port | ||
| 78 | let max_pin = match port { | ||
| 79 | 0 => 7, // PORT0: pins 0-7 | ||
| 80 | 1 => 19, // PORT1: pins 0-19 | ||
| 81 | 2 => 26, // PORT2: pins 0-26 | ||
| 82 | 3 => 31, // PORT3: pins 0-31 | ||
| 83 | 4 => 7, // PORT4: pins 0-7 | ||
| 84 | _ => panic!("Unsupported GPIO port: {}", port), | ||
| 85 | }; | ||
| 86 | |||
| 87 | if pin > max_pin { | ||
| 88 | panic!( | ||
| 89 | "Invalid pin {} for PORT{}, max pin is {}", | ||
| 90 | pin, port, max_pin | ||
| 91 | ); | ||
| 92 | } | ||
| 93 | |||
| 94 | // Get the base address for the port | ||
| 95 | let port_base: *mut u32 = match port { | ||
| 96 | 0 => pac::Port0::ptr() as *mut u32, | ||
| 97 | 1 => pac::Port1::ptr() as *mut u32, | ||
| 98 | 2 => pac::Port2::ptr() as *mut u32, | ||
| 99 | 3 => pac::Port3::ptr() as *mut u32, | ||
| 100 | 4 => pac::Port4::ptr() as *mut u32, | ||
| 101 | _ => panic!("Unsupported GPIO port: {}", port), | ||
| 102 | }; | ||
| 103 | |||
| 104 | // PCR registers are 4 bytes apart, starting at offset 0 for PCR0 | ||
| 105 | let pcr_addr = port_base.add(pin as usize); | ||
| 106 | |||
| 107 | // Read current PCR value | ||
| 108 | let current_val = pcr_addr.read_volatile(); | ||
| 109 | |||
| 110 | // Clear mux bits (bits 8-11) and set new mux value | ||
| 111 | let new_val = (current_val & !(0xF << 8)) | ((mux as u32) << 8); | ||
| 112 | |||
| 113 | // Write back the new value | ||
| 114 | pcr_addr.write_volatile(new_val); | ||
| 115 | |||
| 116 | core::arch::asm!("dsb sy; isb sy"); | ||
| 117 | } | ||
| 118 | |||
| 119 | /// Configure a pin for GPIO mode (ALT0). | ||
| 120 | /// This is a convenience function that calls set_pin_mux with mux=0. | ||
| 121 | /// | ||
| 122 | /// # Arguments | ||
| 123 | /// * `port` - Port number (0-4) | ||
| 124 | /// * `pin` - Pin number (varies by port: PORT0=0-7, PORT1=0-19, PORT2=0-26, PORT3=0-31, PORT4=0-7) | ||
| 125 | pub unsafe fn set_pin_mux_gpio(port: u8, pin: u8) { | ||
| 126 | set_pin_mux(port, pin, 0); | ||
| 127 | } | ||
diff --git a/src/reset.rs b/src/reset.rs new file mode 100644 index 000000000..1c131d1cc --- /dev/null +++ b/src/reset.rs | |||
| @@ -0,0 +1,112 @@ | |||
| 1 | //! Reset control helpers built on PAC field writers. | ||
| 2 | use crate::pac; | ||
| 3 | |||
| 4 | /// Trait describing a reset line that can be asserted/deasserted. | ||
| 5 | pub trait ResetLine { | ||
| 6 | /// Drive the peripheral out of reset. | ||
| 7 | unsafe fn release(mrcc: &pac::mrcc0::RegisterBlock); | ||
| 8 | |||
| 9 | /// Drive the peripheral into reset. | ||
| 10 | unsafe fn assert(mrcc: &pac::mrcc0::RegisterBlock); | ||
| 11 | |||
| 12 | /// Check whether the peripheral is currently released. | ||
| 13 | fn is_released(mrcc: &pac::mrcc0::RegisterBlock) -> bool; | ||
| 14 | } | ||
| 15 | |||
| 16 | /// Release a reset line for the given peripheral set. | ||
| 17 | #[inline] | ||
| 18 | pub unsafe fn release<R: ResetLine>(peripherals: &pac::Peripherals) { | ||
| 19 | R::release(&peripherals.mrcc0); | ||
| 20 | } | ||
| 21 | |||
| 22 | /// Assert a reset line for the given peripheral set. | ||
| 23 | #[inline] | ||
| 24 | pub unsafe fn assert<R: ResetLine>(peripherals: &pac::Peripherals) { | ||
| 25 | R::assert(&peripherals.mrcc0); | ||
| 26 | } | ||
| 27 | |||
| 28 | /// Pulse a reset line (assert then release) with a short delay. | ||
| 29 | #[inline] | ||
| 30 | pub unsafe fn pulse<R: ResetLine>(peripherals: &pac::Peripherals) { | ||
| 31 | let mrcc = &peripherals.mrcc0; | ||
| 32 | R::assert(mrcc); | ||
| 33 | cortex_m::asm::nop(); | ||
| 34 | cortex_m::asm::nop(); | ||
| 35 | R::release(mrcc); | ||
| 36 | } | ||
| 37 | |||
| 38 | macro_rules! impl_reset_line { | ||
| 39 | ($name:ident, $reg:ident, $field:ident) => { | ||
| 40 | pub struct $name; | ||
| 41 | |||
| 42 | impl ResetLine for $name { | ||
| 43 | #[inline] | ||
| 44 | unsafe fn release(mrcc: &pac::mrcc0::RegisterBlock) { | ||
| 45 | mrcc.$reg().modify(|_, w| w.$field().enabled()); | ||
| 46 | } | ||
| 47 | |||
| 48 | #[inline] | ||
| 49 | unsafe fn assert(mrcc: &pac::mrcc0::RegisterBlock) { | ||
| 50 | mrcc.$reg().modify(|_, w| w.$field().disabled()); | ||
| 51 | } | ||
| 52 | |||
| 53 | #[inline] | ||
| 54 | fn is_released(mrcc: &pac::mrcc0::RegisterBlock) -> bool { | ||
| 55 | mrcc.$reg().read().$field().is_enabled() | ||
| 56 | } | ||
| 57 | } | ||
| 58 | }; | ||
| 59 | } | ||
| 60 | |||
| 61 | pub mod line { | ||
| 62 | use super::*; | ||
| 63 | |||
| 64 | impl_reset_line!(Port2, mrcc_glb_rst1, port2); | ||
| 65 | impl_reset_line!(Port3, mrcc_glb_rst1, port3); | ||
| 66 | impl_reset_line!(Gpio3, mrcc_glb_rst2, gpio3); | ||
| 67 | impl_reset_line!(Lpuart2, mrcc_glb_rst0, lpuart2); | ||
| 68 | impl_reset_line!(Ostimer0, mrcc_glb_rst1, ostimer0); | ||
| 69 | impl_reset_line!(Port1, mrcc_glb_rst1, port1); | ||
| 70 | impl_reset_line!(Adc1, mrcc_glb_rst1, adc1); | ||
| 71 | } | ||
| 72 | |||
| 73 | #[inline] | ||
| 74 | pub unsafe fn release_reset_port2(peripherals: &pac::Peripherals) { | ||
| 75 | release::<line::Port2>(peripherals); | ||
| 76 | } | ||
| 77 | |||
| 78 | #[inline] | ||
| 79 | pub unsafe fn release_reset_port3(peripherals: &pac::Peripherals) { | ||
| 80 | release::<line::Port3>(peripherals); | ||
| 81 | } | ||
| 82 | |||
| 83 | #[inline] | ||
| 84 | pub unsafe fn release_reset_gpio3(peripherals: &pac::Peripherals) { | ||
| 85 | release::<line::Gpio3>(peripherals); | ||
| 86 | } | ||
| 87 | |||
| 88 | #[inline] | ||
| 89 | pub unsafe fn release_reset_lpuart2(peripherals: &pac::Peripherals) { | ||
| 90 | release::<line::Lpuart2>(peripherals); | ||
| 91 | } | ||
| 92 | |||
| 93 | #[inline] | ||
| 94 | pub unsafe fn release_reset_ostimer0(peripherals: &pac::Peripherals) { | ||
| 95 | release::<line::Ostimer0>(peripherals); | ||
| 96 | } | ||
| 97 | |||
| 98 | /// Convenience shim retained for existing call sites. | ||
| 99 | #[inline] | ||
| 100 | pub unsafe fn reset_ostimer0(peripherals: &pac::Peripherals) { | ||
| 101 | pulse::<line::Ostimer0>(peripherals); | ||
| 102 | } | ||
| 103 | |||
| 104 | #[inline] | ||
| 105 | pub unsafe fn release_reset_port1(peripherals: &pac::Peripherals) { | ||
| 106 | release::<line::Port1>(peripherals); | ||
| 107 | } | ||
| 108 | |||
| 109 | #[inline] | ||
| 110 | pub unsafe fn release_reset_adc1(peripherals: &pac::Peripherals) { | ||
| 111 | release::<line::Adc1>(peripherals); | ||
| 112 | } | ||
diff --git a/src/rtc.rs b/src/rtc.rs new file mode 100644 index 000000000..f83baab5e --- /dev/null +++ b/src/rtc.rs | |||
| @@ -0,0 +1,281 @@ | |||
| 1 | //! RTC DateTime driver. | ||
| 2 | use crate::pac; | ||
| 3 | use crate::pac::rtc0::cr::{Um}; | ||
| 4 | use core::sync::atomic::{AtomicBool, Ordering}; | ||
| 5 | |||
| 6 | type Regs = pac::rtc0::RegisterBlock; | ||
| 7 | |||
| 8 | static ALARM_TRIGGERED: AtomicBool = AtomicBool::new(false); | ||
| 9 | |||
| 10 | // Token-based instance pattern like embassy-imxrt | ||
| 11 | pub trait Instance { | ||
| 12 | fn ptr() -> *const Regs; | ||
| 13 | } | ||
| 14 | |||
| 15 | /// Token for RTC0 | ||
| 16 | #[cfg(feature = "rtc0")] | ||
| 17 | pub type Rtc0 = crate::peripherals::RTC0; | ||
| 18 | #[cfg(feature = "rtc0")] | ||
| 19 | impl Instance for crate::peripherals::RTC0 { | ||
| 20 | #[inline(always)] | ||
| 21 | fn ptr() -> *const Regs { | ||
| 22 | pac::Rtc0::ptr() | ||
| 23 | } | ||
| 24 | } | ||
| 25 | |||
| 26 | // Also implement Instance for the Peri wrapper type | ||
| 27 | #[cfg(feature = "rtc0")] | ||
| 28 | impl Instance for embassy_hal_internal::Peri<'_, crate::peripherals::RTC0> { | ||
| 29 | #[inline(always)] | ||
| 30 | fn ptr() -> *const Regs { | ||
| 31 | pac::Rtc0::ptr() | ||
| 32 | } | ||
| 33 | } | ||
| 34 | |||
| 35 | const DAYS_IN_A_YEAR: u32 = 365; | ||
| 36 | const SECONDS_IN_A_DAY: u32 = 86400; | ||
| 37 | const SECONDS_IN_A_HOUR: u32 = 3600; | ||
| 38 | const SECONDS_IN_A_MINUTE: u32 = 60; | ||
| 39 | const YEAR_RANGE_START: u16 = 1970; | ||
| 40 | |||
| 41 | #[derive(Debug, Clone, Copy)] | ||
| 42 | pub struct RtcDateTime { | ||
| 43 | pub year: u16, | ||
| 44 | pub month: u8, | ||
| 45 | pub day: u8, | ||
| 46 | pub hour: u8, | ||
| 47 | pub minute: u8, | ||
| 48 | pub second: u8, | ||
| 49 | } | ||
| 50 | #[derive(Copy, Clone)] | ||
| 51 | pub struct RtcConfig { | ||
| 52 | #[allow(dead_code)] | ||
| 53 | wakeup_select: bool, | ||
| 54 | update_mode: Um, | ||
| 55 | #[allow(dead_code)] | ||
| 56 | supervisor_access: bool, | ||
| 57 | compensation_interval: u8, | ||
| 58 | compensation_time: u8, | ||
| 59 | } | ||
| 60 | |||
| 61 | #[derive(Copy, Clone)] | ||
| 62 | pub struct RtcInterruptEnable; | ||
| 63 | impl RtcInterruptEnable { | ||
| 64 | pub const RTC_TIME_INVALID_INTERRUPT_ENABLE: u32 = 1 << 0; | ||
| 65 | pub const RTC_TIME_OVERFLOW_INTERRUPT_ENABLE: u32 = 1 << 1; | ||
| 66 | pub const RTC_ALARM_INTERRUPT_ENABLE: u32 = 1 << 2; | ||
| 67 | pub const RTC_SECONDS_INTERRUPT_ENABLE: u32 = 1 << 4; | ||
| 68 | } | ||
| 69 | |||
| 70 | pub fn convert_datetime_to_seconds(datetime: &RtcDateTime) -> u32 { | ||
| 71 | let month_days: [u16; 13] = [0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]; | ||
| 72 | |||
| 73 | let mut seconds = (datetime.year as u32 - 1970) * DAYS_IN_A_YEAR; | ||
| 74 | seconds += (datetime.year as u32 / 4) - (1970 / 4); | ||
| 75 | seconds += month_days[datetime.month as usize] as u32; | ||
| 76 | seconds += datetime.day as u32 - 1; | ||
| 77 | |||
| 78 | if (datetime.year & 3 == 0) && (datetime.month <= 2) { | ||
| 79 | seconds -= 1; | ||
| 80 | } | ||
| 81 | |||
| 82 | seconds = seconds * SECONDS_IN_A_DAY | ||
| 83 | + (datetime.hour as u32 * SECONDS_IN_A_HOUR) | ||
| 84 | + (datetime.minute as u32 * SECONDS_IN_A_MINUTE) | ||
| 85 | + datetime.second as u32; | ||
| 86 | |||
| 87 | seconds | ||
| 88 | } | ||
| 89 | |||
| 90 | |||
| 91 | pub fn convert_seconds_to_datetime(seconds: u32) -> RtcDateTime { | ||
| 92 | let mut seconds_remaining = seconds; | ||
| 93 | let mut days = seconds_remaining / SECONDS_IN_A_DAY + 1; | ||
| 94 | seconds_remaining %= SECONDS_IN_A_DAY; | ||
| 95 | |||
| 96 | let hour = (seconds_remaining / SECONDS_IN_A_HOUR) as u8; | ||
| 97 | seconds_remaining %= SECONDS_IN_A_HOUR; | ||
| 98 | let minute = (seconds_remaining / SECONDS_IN_A_MINUTE) as u8; | ||
| 99 | let second = (seconds_remaining % SECONDS_IN_A_MINUTE) as u8; | ||
| 100 | |||
| 101 | let mut year = YEAR_RANGE_START; | ||
| 102 | let mut days_in_year = DAYS_IN_A_YEAR; | ||
| 103 | |||
| 104 | while days > days_in_year { | ||
| 105 | days -= days_in_year; | ||
| 106 | year += 1; | ||
| 107 | |||
| 108 | days_in_year = if year % 4 == 0 { | ||
| 109 | DAYS_IN_A_YEAR + 1 | ||
| 110 | } else { | ||
| 111 | DAYS_IN_A_YEAR | ||
| 112 | }; | ||
| 113 | } | ||
| 114 | |||
| 115 | let mut days_per_month = [0u8, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; | ||
| 116 | if year % 4 == 0 { | ||
| 117 | days_per_month[2] = 29; | ||
| 118 | } | ||
| 119 | |||
| 120 | let mut month = 1; | ||
| 121 | for m in 1..=12 { | ||
| 122 | if days <= days_per_month[m] as u32 { | ||
| 123 | month = m; | ||
| 124 | break; | ||
| 125 | } else { | ||
| 126 | days -= days_per_month[m] as u32; | ||
| 127 | } | ||
| 128 | } | ||
| 129 | |||
| 130 | let day = days as u8; | ||
| 131 | |||
| 132 | RtcDateTime { | ||
| 133 | year, | ||
| 134 | month: month as u8, | ||
| 135 | day, | ||
| 136 | hour, | ||
| 137 | minute, | ||
| 138 | second, | ||
| 139 | } | ||
| 140 | } | ||
| 141 | |||
| 142 | pub fn get_default_config() -> RtcConfig { | ||
| 143 | RtcConfig { | ||
| 144 | wakeup_select: false, | ||
| 145 | update_mode: Um::Um0, | ||
| 146 | supervisor_access: false, | ||
| 147 | compensation_interval: 0, | ||
| 148 | compensation_time: 0, | ||
| 149 | } | ||
| 150 | } | ||
| 151 | /// Minimal RTC handle for a specific instance I (store the zero-sized token like embassy) | ||
| 152 | pub struct Rtc<I: Instance> { | ||
| 153 | _inst: core::marker::PhantomData<I>, | ||
| 154 | } | ||
| 155 | |||
| 156 | impl<I: Instance> Rtc<I> { | ||
| 157 | /// initialize RTC | ||
| 158 | pub fn new(_inst: impl Instance, config: RtcConfig) -> Self { | ||
| 159 | let rtc = unsafe { &*I::ptr() }; | ||
| 160 | |||
| 161 | /* RTC reset */ | ||
| 162 | rtc.cr().modify(|_, w| w.swr().set_bit()); | ||
| 163 | rtc.cr().modify(|_, w| w.swr().clear_bit()); | ||
| 164 | rtc.tsr().write(|w| unsafe { w.bits(1) }); | ||
| 165 | |||
| 166 | rtc.cr().modify(|_, w| w.um().variant(config.update_mode)); | ||
| 167 | |||
| 168 | rtc.tcr().modify(|_, w| unsafe { | ||
| 169 | w.cir().bits(config.compensation_interval) | ||
| 170 | .tcr().bits(config.compensation_time) | ||
| 171 | }); | ||
| 172 | |||
| 173 | Self { _inst: core::marker::PhantomData } | ||
| 174 | } | ||
| 175 | |||
| 176 | pub fn set_datetime(&self, datetime: RtcDateTime) { | ||
| 177 | let rtc = unsafe { &*I::ptr() }; | ||
| 178 | let seconds = convert_datetime_to_seconds(&datetime); | ||
| 179 | rtc.tsr().write(|w| unsafe { w.bits(seconds) }); | ||
| 180 | } | ||
| 181 | |||
| 182 | pub fn get_datetime(&self) -> RtcDateTime { | ||
| 183 | let rtc = unsafe { &*I::ptr() }; | ||
| 184 | let seconds = rtc.tsr().read().bits(); | ||
| 185 | convert_seconds_to_datetime(seconds) | ||
| 186 | } | ||
| 187 | |||
| 188 | pub fn set_alarm(&self, alarm: RtcDateTime) { | ||
| 189 | let rtc = unsafe { &*I::ptr() }; | ||
| 190 | let seconds = convert_datetime_to_seconds(&alarm); | ||
| 191 | |||
| 192 | rtc.tar().write(|w| unsafe { w.bits(0) }); | ||
| 193 | let mut timeout = 10000; | ||
| 194 | while rtc.tar().read().bits() != 0 && timeout > 0 { | ||
| 195 | timeout -= 1; | ||
| 196 | } | ||
| 197 | |||
| 198 | rtc.tar().write(|w| unsafe { w.bits(seconds) }); | ||
| 199 | |||
| 200 | let mut timeout = 10000; | ||
| 201 | while rtc.tar().read().bits() != seconds && timeout > 0 { | ||
| 202 | timeout -= 1; | ||
| 203 | } | ||
| 204 | } | ||
| 205 | |||
| 206 | pub fn get_alarm(&self) -> RtcDateTime{ | ||
| 207 | let rtc = unsafe { &*I::ptr() }; | ||
| 208 | let alarm_seconds = rtc.tar().read().bits(); | ||
| 209 | convert_seconds_to_datetime(alarm_seconds) | ||
| 210 | } | ||
| 211 | |||
| 212 | pub fn start(&self) { | ||
| 213 | let rtc = unsafe { &*I::ptr() }; | ||
| 214 | rtc.sr().modify(|_, w| w.tce().set_bit()); | ||
| 215 | } | ||
| 216 | |||
| 217 | pub fn stop(&self) { | ||
| 218 | let rtc = unsafe { &*I::ptr() }; | ||
| 219 | rtc.sr().modify(|_, w| w.tce().clear_bit()); | ||
| 220 | } | ||
| 221 | |||
| 222 | pub fn set_interrupt(&self, mask: u32) { | ||
| 223 | let rtc = unsafe { &*I::ptr() }; | ||
| 224 | |||
| 225 | if (RtcInterruptEnable::RTC_TIME_INVALID_INTERRUPT_ENABLE & mask) != 0 { | ||
| 226 | rtc.ier().modify(|_, w| w.tiie().tiie_1()); | ||
| 227 | } | ||
| 228 | if (RtcInterruptEnable::RTC_TIME_OVERFLOW_INTERRUPT_ENABLE & mask) != 0 { | ||
| 229 | rtc.ier().modify(|_, w| w.toie().toie_1()); | ||
| 230 | } | ||
| 231 | if (RtcInterruptEnable::RTC_ALARM_INTERRUPT_ENABLE & mask) != 0 { | ||
| 232 | rtc.ier().modify(|_, w| w.taie().taie_1()); | ||
| 233 | } | ||
| 234 | if (RtcInterruptEnable::RTC_SECONDS_INTERRUPT_ENABLE & mask) != 0 { | ||
| 235 | rtc.ier().modify(|_, w| w.tsie().tsie_1()); | ||
| 236 | } | ||
| 237 | |||
| 238 | ALARM_TRIGGERED.store(false, Ordering::SeqCst); | ||
| 239 | } | ||
| 240 | |||
| 241 | pub fn disable_interrupt(&self, mask: u32) { | ||
| 242 | let rtc = unsafe { &*I::ptr() }; | ||
| 243 | |||
| 244 | if (RtcInterruptEnable::RTC_TIME_INVALID_INTERRUPT_ENABLE & mask) != 0 { | ||
| 245 | rtc.ier().modify(|_, w| w.tiie().tiie_0()); | ||
| 246 | } | ||
| 247 | if (RtcInterruptEnable::RTC_TIME_OVERFLOW_INTERRUPT_ENABLE & mask) != 0 { | ||
| 248 | rtc.ier().modify(|_, w| w.toie().toie_0()); | ||
| 249 | } | ||
| 250 | if (RtcInterruptEnable::RTC_ALARM_INTERRUPT_ENABLE & mask) != 0 { | ||
| 251 | rtc.ier().modify(|_, w| w.taie().taie_0()); | ||
| 252 | } | ||
| 253 | if (RtcInterruptEnable::RTC_SECONDS_INTERRUPT_ENABLE & mask) != 0 { | ||
| 254 | rtc.ier().modify(|_, w| w.tsie().tsie_0()); | ||
| 255 | } | ||
| 256 | } | ||
| 257 | |||
| 258 | pub fn clear_alarm_flag(&self) { | ||
| 259 | let rtc = unsafe { &*I::ptr() }; | ||
| 260 | rtc.ier().modify(|_, w| w.taie().clear_bit()); | ||
| 261 | } | ||
| 262 | |||
| 263 | pub fn is_alarm_triggered(&self) -> bool { | ||
| 264 | ALARM_TRIGGERED.load(Ordering::Relaxed) | ||
| 265 | } | ||
| 266 | } | ||
| 267 | |||
| 268 | pub fn on_interrupt() { | ||
| 269 | let rtc = unsafe { &*pac::Rtc0::ptr() }; | ||
| 270 | // Check if this is actually a time alarm interrupt | ||
| 271 | let sr = rtc.sr().read(); | ||
| 272 | if sr.taf().bit_is_set() { | ||
| 273 | rtc.ier().modify(|_, w| w.taie().clear_bit()); | ||
| 274 | ALARM_TRIGGERED.store(true, Ordering::SeqCst); | ||
| 275 | } | ||
| 276 | } | ||
| 277 | |||
| 278 | pub struct RtcHandler; | ||
| 279 | impl crate::interrupt::typelevel::Handler<crate::interrupt::typelevel::RTC> for RtcHandler { | ||
| 280 | unsafe fn on_interrupt() { on_interrupt(); } | ||
| 281 | } \ No newline at end of file | ||
diff --git a/src/uart.rs b/src/uart.rs new file mode 100644 index 000000000..45b6b2be3 --- /dev/null +++ b/src/uart.rs | |||
| @@ -0,0 +1,308 @@ | |||
| 1 | //! Minimal polling UART2 bring-up replicating MCUXpresso hello_world ordering. | ||
| 2 | //! WARNING: This is a narrow implementation only for debug console (115200 8N1). | ||
| 3 | |||
| 4 | use crate::pac; | ||
| 5 | use core::cell::RefCell; | ||
| 6 | use cortex_m::interrupt::Mutex; | ||
| 7 | use embassy_sync::signal::Signal; | ||
| 8 | |||
| 9 | // svd2rust defines the shared LPUART RegisterBlock under lpuart0; all instances reuse it. | ||
| 10 | type Regs = pac::lpuart0::RegisterBlock; | ||
| 11 | |||
| 12 | // Token-based instance pattern like embassy-imxrt | ||
| 13 | pub trait Instance { | ||
| 14 | fn ptr() -> *const Regs; | ||
| 15 | } | ||
| 16 | |||
| 17 | /// Token for LPUART2 provided by embassy-hal-internal peripherals macro. | ||
| 18 | #[cfg(feature = "lpuart2")] | ||
| 19 | pub type Lpuart2 = crate::peripherals::LPUART2; | ||
| 20 | #[cfg(feature = "lpuart2")] | ||
| 21 | impl Instance for crate::peripherals::LPUART2 { | ||
| 22 | #[inline(always)] | ||
| 23 | fn ptr() -> *const Regs { | ||
| 24 | pac::Lpuart2::ptr() | ||
| 25 | } | ||
| 26 | } | ||
| 27 | |||
| 28 | // Also implement Instance for the Peri wrapper type | ||
| 29 | #[cfg(feature = "lpuart2")] | ||
| 30 | impl Instance for embassy_hal_internal::Peri<'_, crate::peripherals::LPUART2> { | ||
| 31 | #[inline(always)] | ||
| 32 | fn ptr() -> *const Regs { | ||
| 33 | pac::Lpuart2::ptr() | ||
| 34 | } | ||
| 35 | } | ||
| 36 | |||
| 37 | /// UART configuration (explicit src_hz; no hardcoded frequencies) | ||
| 38 | #[derive(Copy, Clone)] | ||
| 39 | pub struct Config { | ||
| 40 | pub src_hz: u32, | ||
| 41 | pub baud: u32, | ||
| 42 | pub parity: Parity, | ||
| 43 | pub stop_bits: StopBits, | ||
| 44 | } | ||
| 45 | |||
| 46 | #[derive(Copy, Clone)] | ||
| 47 | pub enum Parity { | ||
| 48 | None, | ||
| 49 | Even, | ||
| 50 | Odd, | ||
| 51 | } | ||
| 52 | #[derive(Copy, Clone)] | ||
| 53 | pub enum StopBits { | ||
| 54 | One, | ||
| 55 | Two, | ||
| 56 | } | ||
| 57 | |||
| 58 | impl Config { | ||
| 59 | pub fn new(src_hz: u32) -> Self { | ||
| 60 | Self { | ||
| 61 | src_hz, | ||
| 62 | baud: 115_200, | ||
| 63 | parity: Parity::None, | ||
| 64 | stop_bits: StopBits::One, | ||
| 65 | } | ||
| 66 | } | ||
| 67 | } | ||
| 68 | |||
| 69 | /// Compute a valid (OSR, SBR) tuple for given source clock and baud. | ||
| 70 | /// Uses a functional fold approach to find the best OSR/SBR combination | ||
| 71 | /// with minimal baud rate error. | ||
| 72 | fn compute_osr_sbr(src_hz: u32, baud: u32) -> (u8, u16) { | ||
| 73 | let (best_osr, best_sbr, _best_err) = (8u32..=32).fold( | ||
| 74 | (16u8, 4u16, u32::MAX), // (best_osr, best_sbr, best_err) | ||
| 75 | |(best_osr, best_sbr, best_err), osr| { | ||
| 76 | let denom = baud.saturating_mul(osr); | ||
| 77 | if denom == 0 { | ||
| 78 | return (best_osr, best_sbr, best_err); | ||
| 79 | } | ||
| 80 | |||
| 81 | let sbr = (src_hz + denom / 2) / denom; // round | ||
| 82 | if sbr == 0 || sbr > 0x1FFF { | ||
| 83 | return (best_osr, best_sbr, best_err); | ||
| 84 | } | ||
| 85 | |||
| 86 | let actual = src_hz / (osr * sbr); | ||
| 87 | let err = actual.abs_diff(baud); | ||
| 88 | |||
| 89 | // Update best if this is better, or same error but higher OSR | ||
| 90 | if err < best_err || (err == best_err && osr as u8 > best_osr) { | ||
| 91 | (osr as u8, sbr as u16, err) | ||
| 92 | } else { | ||
| 93 | (best_osr, best_sbr, best_err) | ||
| 94 | } | ||
| 95 | }, | ||
| 96 | ); | ||
| 97 | (best_osr, best_sbr) | ||
| 98 | } | ||
| 99 | |||
| 100 | /// Minimal UART handle for a specific instance I (store the zero-sized token like embassy) | ||
| 101 | pub struct Uart<I: Instance> { | ||
| 102 | _inst: core::marker::PhantomData<I>, | ||
| 103 | } | ||
| 104 | |||
| 105 | impl<I: Instance> Uart<I> { | ||
| 106 | /// Create and initialize LPUART (reset + config). Clocks and pins must be prepared by the caller. | ||
| 107 | pub fn new(_inst: impl Instance, cfg: Config) -> Self { | ||
| 108 | let l = unsafe { &*I::ptr() }; | ||
| 109 | // 1) software reset pulse | ||
| 110 | l.global().write(|w| w.rst().reset()); | ||
| 111 | cortex_m::asm::delay(3); // Short delay for reset to take effect | ||
| 112 | l.global().write(|w| w.rst().no_effect()); | ||
| 113 | cortex_m::asm::delay(10); // Allow peripheral to stabilize after reset | ||
| 114 | // 2) BAUD | ||
| 115 | let (osr, sbr) = compute_osr_sbr(cfg.src_hz, cfg.baud); | ||
| 116 | l.baud().modify(|_, w| { | ||
| 117 | let w = match cfg.stop_bits { | ||
| 118 | StopBits::One => w.sbns().one(), | ||
| 119 | StopBits::Two => w.sbns().two(), | ||
| 120 | }; | ||
| 121 | // OSR field encodes (osr-1); use raw bits to avoid a long match on all variants | ||
| 122 | let raw_osr = osr.saturating_sub(1) as u8; | ||
| 123 | unsafe { w.osr().bits(raw_osr).sbr().bits(sbr) } | ||
| 124 | }); | ||
| 125 | // 3) CTRL baseline and parity | ||
| 126 | l.ctrl().write(|w| { | ||
| 127 | let w = w.ilt().from_stop().idlecfg().idle_2(); | ||
| 128 | let w = match cfg.parity { | ||
| 129 | Parity::None => w.pe().disabled(), | ||
| 130 | Parity::Even => w.pe().enabled().pt().even(), | ||
| 131 | Parity::Odd => w.pe().enabled().pt().odd(), | ||
| 132 | }; | ||
| 133 | w.re().enabled().te().enabled().rie().disabled() | ||
| 134 | }); | ||
| 135 | // 4) FIFOs and WATER: keep it simple for polling; disable FIFOs and set RX watermark to 0 | ||
| 136 | l.fifo().modify(|_, w| { | ||
| 137 | w.txfe() | ||
| 138 | .disabled() | ||
| 139 | .rxfe() | ||
| 140 | .disabled() | ||
| 141 | .txflush() | ||
| 142 | .txfifo_rst() | ||
| 143 | .rxflush() | ||
| 144 | .rxfifo_rst() | ||
| 145 | }); | ||
| 146 | l.water() | ||
| 147 | .modify(|_, w| unsafe { w.txwater().bits(0).rxwater().bits(0) }); | ||
| 148 | Self { _inst: core::marker::PhantomData } | ||
| 149 | } | ||
| 150 | |||
| 151 | /// Enable RX interrupts. The caller must ensure an appropriate IRQ handler is installed. | ||
| 152 | pub unsafe fn enable_rx_interrupts(&self) { | ||
| 153 | let l = &*I::ptr(); | ||
| 154 | l.ctrl().modify(|_, w| w.rie().enabled()); | ||
| 155 | } | ||
| 156 | |||
| 157 | #[inline(never)] | ||
| 158 | pub fn write_byte(&self, b: u8) { | ||
| 159 | let l = unsafe { &*I::ptr() }; | ||
| 160 | // Timeout after ~10ms at 12MHz (assuming 115200 baud, should be plenty) | ||
| 161 | const DATA_OFFSET: usize = 0x1C; // DATA register offset inside LPUART block | ||
| 162 | let data_ptr = unsafe { (I::ptr() as *mut u8).add(DATA_OFFSET) }; | ||
| 163 | for _ in 0..120000 { | ||
| 164 | if l.water().read().txcount().bits() == 0 { | ||
| 165 | unsafe { core::ptr::write_volatile(data_ptr, b) }; | ||
| 166 | return; | ||
| 167 | } | ||
| 168 | } | ||
| 169 | // If timeout, skip the write to avoid hanging | ||
| 170 | } | ||
| 171 | |||
| 172 | #[inline(never)] | ||
| 173 | pub fn write_str_blocking(&self, s: &str) { | ||
| 174 | for &b in s.as_bytes() { | ||
| 175 | if b == b'\n' { | ||
| 176 | self.write_byte(b'\r'); | ||
| 177 | } | ||
| 178 | self.write_byte(b); | ||
| 179 | } | ||
| 180 | } | ||
| 181 | pub fn read_byte_blocking(&self) -> u8 { | ||
| 182 | let l = unsafe { &*I::ptr() }; | ||
| 183 | while !l.stat().read().rdrf().is_rxdata() {} | ||
| 184 | (l.data().read().bits() & 0xFF) as u8 | ||
| 185 | } | ||
| 186 | } | ||
| 187 | |||
| 188 | // Simple ring buffer for UART RX data | ||
| 189 | const RX_BUFFER_SIZE: usize = 256; | ||
| 190 | pub struct RingBuffer { | ||
| 191 | buffer: [u8; RX_BUFFER_SIZE], | ||
| 192 | read_idx: usize, | ||
| 193 | write_idx: usize, | ||
| 194 | count: usize, | ||
| 195 | } | ||
| 196 | |||
| 197 | impl RingBuffer { | ||
| 198 | pub const fn new() -> Self { | ||
| 199 | Self { | ||
| 200 | buffer: [0; RX_BUFFER_SIZE], | ||
| 201 | read_idx: 0, | ||
| 202 | write_idx: 0, | ||
| 203 | count: 0, | ||
| 204 | } | ||
| 205 | } | ||
| 206 | |||
| 207 | pub fn push(&mut self, data: u8) -> bool { | ||
| 208 | if self.count >= RX_BUFFER_SIZE { | ||
| 209 | return false; // Buffer full | ||
| 210 | } | ||
| 211 | self.buffer[self.write_idx] = data; | ||
| 212 | self.write_idx = (self.write_idx + 1) % RX_BUFFER_SIZE; | ||
| 213 | self.count += 1; | ||
| 214 | true | ||
| 215 | } | ||
| 216 | |||
| 217 | pub fn pop(&mut self) -> Option<u8> { | ||
| 218 | if self.count == 0 { | ||
| 219 | return None; | ||
| 220 | } | ||
| 221 | let data = self.buffer[self.read_idx]; | ||
| 222 | self.read_idx = (self.read_idx + 1) % RX_BUFFER_SIZE; | ||
| 223 | self.count -= 1; | ||
| 224 | Some(data) | ||
| 225 | } | ||
| 226 | |||
| 227 | pub fn is_empty(&self) -> bool { | ||
| 228 | self.count == 0 | ||
| 229 | } | ||
| 230 | |||
| 231 | pub fn len(&self) -> usize { | ||
| 232 | self.count | ||
| 233 | } | ||
| 234 | } | ||
| 235 | |||
| 236 | // Global RX buffer shared between interrupt handler and UART instance | ||
| 237 | static RX_BUFFER: Mutex<RefCell<RingBuffer>> = Mutex::new(RefCell::new(RingBuffer::new())); | ||
| 238 | static RX_SIGNAL: Signal<embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex, ()> = | ||
| 239 | Signal::new(); | ||
| 240 | |||
| 241 | // Debug counter for interrupt handler calls | ||
| 242 | static mut INTERRUPT_COUNT: u32 = 0; | ||
| 243 | |||
| 244 | impl<I: Instance> Uart<I> { | ||
| 245 | /// Read a byte asynchronously using interrupts | ||
| 246 | pub async fn read_byte_async(&self) -> u8 { | ||
| 247 | loop { | ||
| 248 | // Check if we have data in the buffer | ||
| 249 | let byte = cortex_m::interrupt::free(|cs| { | ||
| 250 | let mut buffer = RX_BUFFER.borrow(cs).borrow_mut(); | ||
| 251 | buffer.pop() | ||
| 252 | }); | ||
| 253 | |||
| 254 | if let Some(byte) = byte { | ||
| 255 | return byte; | ||
| 256 | } | ||
| 257 | |||
| 258 | // Wait for the interrupt signal | ||
| 259 | RX_SIGNAL.wait().await; | ||
| 260 | } | ||
| 261 | } | ||
| 262 | |||
| 263 | /// Check if there's data available in the RX buffer | ||
| 264 | pub fn rx_data_available(&self) -> bool { | ||
| 265 | cortex_m::interrupt::free(|cs| { | ||
| 266 | let buffer = RX_BUFFER.borrow(cs).borrow(); | ||
| 267 | !buffer.is_empty() | ||
| 268 | }) | ||
| 269 | } | ||
| 270 | |||
| 271 | /// Try to read a byte from RX buffer (non-blocking) | ||
| 272 | pub fn try_read_byte(&self) -> Option<u8> { | ||
| 273 | cortex_m::interrupt::free(|cs| { | ||
| 274 | let mut buffer = RX_BUFFER.borrow(cs).borrow_mut(); | ||
| 275 | buffer.pop() | ||
| 276 | }) | ||
| 277 | } | ||
| 278 | } | ||
| 279 | |||
| 280 | /// Type-level handler for LPUART2 interrupts, compatible with bind_interrupts!. | ||
| 281 | pub struct UartInterruptHandler; | ||
| 282 | |||
| 283 | impl crate::interrupt::typelevel::Handler<crate::interrupt::typelevel::LPUART2> | ||
| 284 | for UartInterruptHandler | ||
| 285 | { | ||
| 286 | unsafe fn on_interrupt() { | ||
| 287 | INTERRUPT_COUNT += 1; | ||
| 288 | |||
| 289 | let lpuart = &*pac::Lpuart2::ptr(); | ||
| 290 | |||
| 291 | // Check if we have RX data | ||
| 292 | if lpuart.stat().read().rdrf().is_rxdata() { | ||
| 293 | // Read the data byte | ||
| 294 | let data = (lpuart.data().read().bits() & 0xFF) as u8; | ||
| 295 | |||
| 296 | // Store in ring buffer | ||
| 297 | cortex_m::interrupt::free(|cs| { | ||
| 298 | let mut buffer = RX_BUFFER.borrow(cs).borrow_mut(); | ||
| 299 | if buffer.push(data) { | ||
| 300 | // Data added successfully, signal waiting tasks | ||
| 301 | RX_SIGNAL.signal(()); | ||
| 302 | } | ||
| 303 | }); | ||
| 304 | } | ||
| 305 | // Always clear any error flags that might cause spurious interrupts | ||
| 306 | let _ = lpuart.stat().read(); | ||
| 307 | } | ||
| 308 | } | ||
