aboutsummaryrefslogtreecommitdiff
path: root/src/uart.rs
diff options
context:
space:
mode:
authorFelipe Balbi <[email protected]>2025-11-07 11:00:15 -0800
committerGitHub <[email protected]>2025-11-07 11:00:15 -0800
commit5632acec18cc5906b1625a8facf530db56c73300 (patch)
treeabf2897f4b2f9814069c64611896be2d42cf2ce8 /src/uart.rs
parent47e383545f4aac3bfaec0563429cc721540e665a (diff)
parent9590d94ee9ba016f65a13100c429fc56ffe58e40 (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.rs306
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
4use core::cell::RefCell;
5
6use cortex_m::interrupt::Mutex;
7use embassy_sync::signal::Signal;
8
9use crate::pac;
10
11// svd2rust defines the shared LPUART RegisterBlock under lpuart0; all instances reuse it.
12type Regs = pac::lpuart0::RegisterBlock;
13
14// Token-based instance pattern like embassy-imxrt
15pub trait Instance {
16 fn ptr() -> *const Regs;
17}
18
19/// Token for LPUART2 provided by embassy-hal-internal peripherals macro.
20pub type Lpuart2 = crate::peripherals::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
29impl 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)]
38pub 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)]
46pub enum Parity {
47 None,
48 Even,
49 Odd,
50}
51#[derive(Copy, Clone)]
52pub enum StopBits {
53 One,
54 Two,
55}
56
57impl 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.
71fn 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)
100pub struct Uart<I: Instance> {
101 _inst: core::marker::PhantomData<I>,
102}
103
104impl<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
190const RX_BUFFER_SIZE: usize = 256;
191pub struct RingBuffer {
192 buffer: [u8; RX_BUFFER_SIZE],
193 read_idx: usize,
194 write_idx: usize,
195 count: usize,
196}
197
198impl 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
238static RX_BUFFER: Mutex<RefCell<RingBuffer>> = Mutex::new(RefCell::new(RingBuffer::new()));
239static RX_SIGNAL: Signal<embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex, ()> = 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> 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}