diff options
| author | Felipe Balbi <[email protected]> | 2025-11-07 11:00:15 -0800 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-11-07 11:00:15 -0800 |
| commit | 5632acec18cc5906b1625a8facf530db56c73300 (patch) | |
| tree | abf2897f4b2f9814069c64611896be2d42cf2ce8 /src/uart.rs | |
| parent | 47e383545f4aac3bfaec0563429cc721540e665a (diff) | |
| parent | 9590d94ee9ba016f65a13100c429fc56ffe58e40 (diff) | |
Merge pull request #1 from bogdan-petru/import/mcxa276-initial
feat(mcxa276): initial HAL import
Diffstat (limited to 'src/uart.rs')
| -rw-r--r-- | src/uart.rs | 306 |
1 files changed, 306 insertions, 0 deletions
diff --git a/src/uart.rs b/src/uart.rs new file mode 100644 index 000000000..3209a318d --- /dev/null +++ b/src/uart.rs | |||
| @@ -0,0 +1,306 @@ | |||
| 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 core::cell::RefCell; | ||
| 5 | |||
| 6 | use cortex_m::interrupt::Mutex; | ||
| 7 | use embassy_sync::signal::Signal; | ||
| 8 | |||
| 9 | use crate::pac; | ||
| 10 | |||
| 11 | // svd2rust defines the shared LPUART RegisterBlock under lpuart0; all instances reuse it. | ||
| 12 | type Regs = pac::lpuart0::RegisterBlock; | ||
| 13 | |||
| 14 | // Token-based instance pattern like embassy-imxrt | ||
| 15 | pub trait Instance { | ||
| 16 | fn ptr() -> *const Regs; | ||
| 17 | } | ||
| 18 | |||
| 19 | /// Token for LPUART2 provided by embassy-hal-internal peripherals macro. | ||
| 20 | pub type Lpuart2 = crate::peripherals::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 | impl Instance for embassy_hal_internal::Peri<'_, crate::peripherals::LPUART2> { | ||
| 30 | #[inline(always)] | ||
| 31 | fn ptr() -> *const Regs { | ||
| 32 | pac::Lpuart2::ptr() | ||
| 33 | } | ||
| 34 | } | ||
| 35 | |||
| 36 | /// UART configuration (explicit src_hz; no hardcoded frequencies) | ||
| 37 | #[derive(Copy, Clone)] | ||
| 38 | pub struct Config { | ||
| 39 | pub src_hz: u32, | ||
| 40 | pub baud: u32, | ||
| 41 | pub parity: Parity, | ||
| 42 | pub stop_bits: StopBits, | ||
| 43 | } | ||
| 44 | |||
| 45 | #[derive(Copy, Clone)] | ||
| 46 | pub enum Parity { | ||
| 47 | None, | ||
| 48 | Even, | ||
| 49 | Odd, | ||
| 50 | } | ||
| 51 | #[derive(Copy, Clone)] | ||
| 52 | pub enum StopBits { | ||
| 53 | One, | ||
| 54 | Two, | ||
| 55 | } | ||
| 56 | |||
| 57 | impl Config { | ||
| 58 | pub fn new(src_hz: u32) -> Self { | ||
| 59 | Self { | ||
| 60 | src_hz, | ||
| 61 | baud: 115_200, | ||
| 62 | parity: Parity::None, | ||
| 63 | stop_bits: StopBits::One, | ||
| 64 | } | ||
| 65 | } | ||
| 66 | } | ||
| 67 | |||
| 68 | /// Compute a valid (OSR, SBR) tuple for given source clock and baud. | ||
| 69 | /// Uses a functional fold approach to find the best OSR/SBR combination | ||
| 70 | /// with minimal baud rate error. | ||
| 71 | fn compute_osr_sbr(src_hz: u32, baud: u32) -> (u8, u16) { | ||
| 72 | let (best_osr, best_sbr, _best_err) = (8u32..=32).fold( | ||
| 73 | (16u8, 4u16, u32::MAX), // (best_osr, best_sbr, best_err) | ||
| 74 | |(best_osr, best_sbr, best_err), osr| { | ||
| 75 | let denom = baud.saturating_mul(osr); | ||
| 76 | if denom == 0 { | ||
| 77 | return (best_osr, best_sbr, best_err); | ||
| 78 | } | ||
| 79 | |||
| 80 | let sbr = (src_hz + denom / 2) / denom; // round | ||
| 81 | if sbr == 0 || sbr > 0x1FFF { | ||
| 82 | return (best_osr, best_sbr, best_err); | ||
| 83 | } | ||
| 84 | |||
| 85 | let actual = src_hz / (osr * sbr); | ||
| 86 | let err = actual.abs_diff(baud); | ||
| 87 | |||
| 88 | // Update best if this is better, or same error but higher OSR | ||
| 89 | if err < best_err || (err == best_err && osr as u8 > best_osr) { | ||
| 90 | (osr as u8, sbr as u16, err) | ||
| 91 | } else { | ||
| 92 | (best_osr, best_sbr, best_err) | ||
| 93 | } | ||
| 94 | }, | ||
| 95 | ); | ||
| 96 | (best_osr, best_sbr) | ||
| 97 | } | ||
| 98 | |||
| 99 | /// Minimal UART handle for a specific instance I (store the zero-sized token like embassy) | ||
| 100 | pub struct Uart<I: Instance> { | ||
| 101 | _inst: core::marker::PhantomData<I>, | ||
| 102 | } | ||
| 103 | |||
| 104 | impl<I: Instance> Uart<I> { | ||
| 105 | /// Create and initialize LPUART (reset + config). Clocks and pins must be prepared by the caller. | ||
| 106 | pub fn new(_inst: impl Instance, cfg: Config) -> Self { | ||
| 107 | let l = unsafe { &*I::ptr() }; | ||
| 108 | // 1) software reset pulse | ||
| 109 | l.global().write(|w| w.rst().reset()); | ||
| 110 | cortex_m::asm::delay(3); // Short delay for reset to take effect | ||
| 111 | l.global().write(|w| w.rst().no_effect()); | ||
| 112 | cortex_m::asm::delay(10); // Allow peripheral to stabilize after reset | ||
| 113 | // 2) BAUD | ||
| 114 | let (osr, sbr) = compute_osr_sbr(cfg.src_hz, cfg.baud); | ||
| 115 | l.baud().modify(|_, w| { | ||
| 116 | let w = match cfg.stop_bits { | ||
| 117 | StopBits::One => w.sbns().one(), | ||
| 118 | StopBits::Two => w.sbns().two(), | ||
| 119 | }; | ||
| 120 | // OSR field encodes (osr-1); use raw bits to avoid a long match on all variants | ||
| 121 | let raw_osr = osr.saturating_sub(1) as u8; | ||
| 122 | unsafe { w.osr().bits(raw_osr).sbr().bits(sbr) } | ||
| 123 | }); | ||
| 124 | // 3) CTRL baseline and parity | ||
| 125 | l.ctrl().write(|w| { | ||
| 126 | let w = w.ilt().from_stop().idlecfg().idle_2(); | ||
| 127 | let w = match cfg.parity { | ||
| 128 | Parity::None => w.pe().disabled(), | ||
| 129 | Parity::Even => w.pe().enabled().pt().even(), | ||
| 130 | Parity::Odd => w.pe().enabled().pt().odd(), | ||
| 131 | }; | ||
| 132 | w.re().enabled().te().enabled().rie().disabled() | ||
| 133 | }); | ||
| 134 | // 4) FIFOs and WATER: keep it simple for polling; disable FIFOs and set RX watermark to 0 | ||
| 135 | l.fifo().modify(|_, w| { | ||
| 136 | w.txfe() | ||
| 137 | .disabled() | ||
| 138 | .rxfe() | ||
| 139 | .disabled() | ||
| 140 | .txflush() | ||
| 141 | .txfifo_rst() | ||
| 142 | .rxflush() | ||
| 143 | .rxfifo_rst() | ||
| 144 | }); | ||
| 145 | l.water() | ||
| 146 | .modify(|_, w| unsafe { w.txwater().bits(0).rxwater().bits(0) }); | ||
| 147 | Self { | ||
| 148 | _inst: core::marker::PhantomData, | ||
| 149 | } | ||
| 150 | } | ||
| 151 | |||
| 152 | /// Enable RX interrupts. The caller must ensure an appropriate IRQ handler is installed. | ||
| 153 | pub unsafe fn enable_rx_interrupts(&self) { | ||
| 154 | let l = &*I::ptr(); | ||
| 155 | l.ctrl().modify(|_, w| w.rie().enabled()); | ||
| 156 | } | ||
| 157 | |||
| 158 | #[inline(never)] | ||
| 159 | pub fn write_byte(&self, b: u8) { | ||
| 160 | let l = unsafe { &*I::ptr() }; | ||
| 161 | // Timeout after ~10ms at 12MHz (assuming 115200 baud, should be plenty) | ||
| 162 | const DATA_OFFSET: usize = 0x1C; // DATA register offset inside LPUART block | ||
| 163 | let data_ptr = unsafe { (I::ptr() as *mut u8).add(DATA_OFFSET) }; | ||
| 164 | for _ in 0..120000 { | ||
| 165 | if l.water().read().txcount().bits() == 0 { | ||
| 166 | unsafe { core::ptr::write_volatile(data_ptr, b) }; | ||
| 167 | return; | ||
| 168 | } | ||
| 169 | } | ||
| 170 | // If timeout, skip the write to avoid hanging | ||
| 171 | } | ||
| 172 | |||
| 173 | #[inline(never)] | ||
| 174 | pub fn write_str_blocking(&self, s: &str) { | ||
| 175 | for &b in s.as_bytes() { | ||
| 176 | if b == b'\n' { | ||
| 177 | self.write_byte(b'\r'); | ||
| 178 | } | ||
| 179 | self.write_byte(b); | ||
| 180 | } | ||
| 181 | } | ||
| 182 | pub fn read_byte_blocking(&self) -> u8 { | ||
| 183 | let l = unsafe { &*I::ptr() }; | ||
| 184 | while !l.stat().read().rdrf().is_rxdata() {} | ||
| 185 | (l.data().read().bits() & 0xFF) as u8 | ||
| 186 | } | ||
| 187 | } | ||
| 188 | |||
| 189 | // Simple ring buffer for UART RX data | ||
| 190 | const RX_BUFFER_SIZE: usize = 256; | ||
| 191 | pub struct RingBuffer { | ||
| 192 | buffer: [u8; RX_BUFFER_SIZE], | ||
| 193 | read_idx: usize, | ||
| 194 | write_idx: usize, | ||
| 195 | count: usize, | ||
| 196 | } | ||
| 197 | |||
| 198 | impl RingBuffer { | ||
| 199 | pub const fn new() -> Self { | ||
| 200 | Self { | ||
| 201 | buffer: [0; RX_BUFFER_SIZE], | ||
| 202 | read_idx: 0, | ||
| 203 | write_idx: 0, | ||
| 204 | count: 0, | ||
| 205 | } | ||
| 206 | } | ||
| 207 | |||
| 208 | pub fn push(&mut self, data: u8) -> bool { | ||
| 209 | if self.count >= RX_BUFFER_SIZE { | ||
| 210 | return false; // Buffer full | ||
| 211 | } | ||
| 212 | self.buffer[self.write_idx] = data; | ||
| 213 | self.write_idx = (self.write_idx + 1) % RX_BUFFER_SIZE; | ||
| 214 | self.count += 1; | ||
| 215 | true | ||
| 216 | } | ||
| 217 | |||
| 218 | pub fn pop(&mut self) -> Option<u8> { | ||
| 219 | if self.count == 0 { | ||
| 220 | return None; | ||
| 221 | } | ||
| 222 | let data = self.buffer[self.read_idx]; | ||
| 223 | self.read_idx = (self.read_idx + 1) % RX_BUFFER_SIZE; | ||
| 224 | self.count -= 1; | ||
| 225 | Some(data) | ||
| 226 | } | ||
| 227 | |||
| 228 | pub fn is_empty(&self) -> bool { | ||
| 229 | self.count == 0 | ||
| 230 | } | ||
| 231 | |||
| 232 | pub fn len(&self) -> usize { | ||
| 233 | self.count | ||
| 234 | } | ||
| 235 | } | ||
| 236 | |||
| 237 | // Global RX buffer shared between interrupt handler and UART instance | ||
| 238 | static RX_BUFFER: Mutex<RefCell<RingBuffer>> = Mutex::new(RefCell::new(RingBuffer::new())); | ||
| 239 | static RX_SIGNAL: Signal<embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex, ()> = 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> for UartInterruptHandler { | ||
| 284 | unsafe fn on_interrupt() { | ||
| 285 | INTERRUPT_COUNT += 1; | ||
| 286 | |||
| 287 | let lpuart = &*pac::Lpuart2::ptr(); | ||
| 288 | |||
| 289 | // Check if we have RX data | ||
| 290 | if lpuart.stat().read().rdrf().is_rxdata() { | ||
| 291 | // Read the data byte | ||
| 292 | let data = (lpuart.data().read().bits() & 0xFF) as u8; | ||
| 293 | |||
| 294 | // Store in ring buffer | ||
| 295 | cortex_m::interrupt::free(|cs| { | ||
| 296 | let mut buffer = RX_BUFFER.borrow(cs).borrow_mut(); | ||
| 297 | if buffer.push(data) { | ||
| 298 | // Data added successfully, signal waiting tasks | ||
| 299 | RX_SIGNAL.signal(()); | ||
| 300 | } | ||
| 301 | }); | ||
| 302 | } | ||
| 303 | // Always clear any error flags that might cause spurious interrupts | ||
| 304 | let _ = lpuart.stat().read(); | ||
| 305 | } | ||
| 306 | } | ||
