aboutsummaryrefslogtreecommitdiff
path: root/src/uart.rs
diff options
context:
space:
mode:
authorBogdan Petru Chircu Mare <[email protected]>2025-10-31 21:44:48 -0700
committerBogdan Petru Chircu Mare <[email protected]>2025-10-31 21:44:48 -0700
commit0443134bc47918d2f8f0ede1b292b372629f8894 (patch)
tree823f569464a6e001b4eb4246c9ad8083c8c20847 /src/uart.rs
parent47e383545f4aac3bfaec0563429cc721540e665a (diff)
feat(mcxa276): initial HAL import
Diffstat (limited to 'src/uart.rs')
-rw-r--r--src/uart.rs308
1 files changed, 308 insertions, 0 deletions
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
4use crate::pac;
5use core::cell::RefCell;
6use cortex_m::interrupt::Mutex;
7use embassy_sync::signal::Signal;
8
9// svd2rust defines the shared LPUART RegisterBlock under lpuart0; all instances reuse it.
10type Regs = pac::lpuart0::RegisterBlock;
11
12// Token-based instance pattern like embassy-imxrt
13pub trait Instance {
14 fn ptr() -> *const Regs;
15}
16
17/// Token for LPUART2 provided by embassy-hal-internal peripherals macro.
18#[cfg(feature = "lpuart2")]
19pub type Lpuart2 = crate::peripherals::LPUART2;
20#[cfg(feature = "lpuart2")]
21impl 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")]
30impl 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)]
39pub 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)]
47pub enum Parity {
48 None,
49 Even,
50 Odd,
51}
52#[derive(Copy, Clone)]
53pub enum StopBits {
54 One,
55 Two,
56}
57
58impl 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.
72fn 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)
101pub struct Uart<I: Instance> {
102 _inst: core::marker::PhantomData<I>,
103}
104
105impl<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
189const RX_BUFFER_SIZE: usize = 256;
190pub struct RingBuffer {
191 buffer: [u8; RX_BUFFER_SIZE],
192 read_idx: usize,
193 write_idx: usize,
194 count: usize,
195}
196
197impl 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
237static RX_BUFFER: Mutex<RefCell<RingBuffer>> = Mutex::new(RefCell::new(RingBuffer::new()));
238static RX_SIGNAL: Signal<embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex, ()> =
239 Signal::new();
240
241// Debug counter for interrupt handler calls
242static mut INTERRUPT_COUNT: u32 = 0;
243
244impl<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!.
281pub struct UartInterruptHandler;
282
283impl 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}