1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
|
//! The HP UART controllers: UART0-4.
//!
//! Out of scope on purpose: UHCI/DMA, RS485, IrDA, hardware and software flow control, the wakeup
//! machinery, and LP_UART (which is a different block behind a different clock tree, not an
//! instance of this one).
//!
//! Three things about this peripheral cost real debugging time, and all three are structural rather
//! than incidental:
//!
//! **Half the configuration registers are shadowed.** The registers whose macro name ends `_SYNC` -
//! UART_CLKDIV_SYNC, UART_CONF0_SYNC, and a dozen more - are not the live configuration. A write
//! lands in a shadow that the core clock domain ignores until UART_REG_UPDATE is set, at which
//! point the hardware copies the shadow across and clears the bit itself. Reads come back from the
//! shadow, so a read-modify-write composes correctly and a read-back proves nothing about what the
//! transmitter is currently using. Every mutator here therefore ends in `update()`, which is
//! exactly what ESP-IDF does: `uart_ll_update` (uart_ll.h:85-89) sets the bit and spins on it, and
//! every `*_sync` writer in that file calls it (set_stop_bits at uart_ll.h:793, set_parity at 828,
//! set_data_bit_num at 1023, set_loop_back at 1439, the FIFO resets at 735 and 750). Omitting it
//! does not fail loudly: the register reads back as asked and the wire keeps the old setting.
//!
//! **Reading offset 0x000 pops the RX FIFO.** `UART_FIFO_REG`'s only field is annotated `RO` in
//! uart_reg.h:18 and that annotation is wrong in the way that matters - the read is the pop. A
//! generic "snapshot the block" loop therefore eats received bytes, which is why the differential
//! harness carries a per-peripheral deny-list of offsets. Writes to the same address push a byte,
//! and must be full 32-bit stores: a byte store on this bus is a read-modify-write, so it would pop
//! a byte in order to push one (uart_ll.h:716-724 says so and is the reason `pushByte` uses
//! `writeRaw`).
//!
//! **UART0 is the console.** Resetting it clears UART_CLKDIV, the console turns to garbage
//! mid-sentence and the board dies on a watchdog reset with nothing readable to explain it. That was
//! measured on this board. Nothing here resets UART0 implicitly, `reset()` refuses instance 0, and
//! the differential suite uses UART1.
//!
//! The clock path is two dividers in series and they live in different blocks: HP_SYS_CLKRST holds
//! the integer pre-divider (`REG_UARTn_SCLK_DIV_NUM`) and the source select, the UART itself holds
//! the 12.4 fixed-point divider. `setBaudrate` drives both, because neither alone spans the range.
const std = @import("std");
const regs = @import("regs");
const mmio = @import("mmio");
const gpio = @import("gpio.zig");
const clkrst = @import("clkrst.zig");
const Reg = mmio.Reg;
const Field = mmio.Field;
/// UART0-4. LP_UART (ESP-IDF's port 5) is a separate peripheral and not modelled here.
pub const count = 5;
/// SOC_UART_FIFO_LEN, soc_caps.h:655. Both directions; the TX count register reports how many bytes
/// are queued, so free space is this minus that.
pub const fifo_len = 128;
// ------------------------------------------------------------------------------ register blocks
// One 0x1000-byte block per instance (soc.h:20, `REG_UART_BASE(i) = DR_REG_UART_BASE + i*0x1000`).
// Every register is reached through a RegArray so the stride is checked against the headers rather
// than assumed, and a wrong instance index is a bounds assert rather than a write into UART2.
fn regArray(comptime offset: u32) type {
return mmio.RegArray(
regs.DR_REG_UART0_BASE + offset,
regs.DR_REG_UART0_BASE + 0x1000 + offset,
count,
);
}
const fifo = regArray(0x00);
const clkdiv_sync = regArray(0x14);
const status = regArray(0x1c);
const conf0_sync = regArray(0x20);
const clk_conf = regArray(0x88);
const reg_update = regArray(0x98);
// CLKDIV_SYNC: a 12.4 fixed-point divider, with the fraction not adjacent to the integer part.
const clkdiv = Field.of(regs.UART_CLKDIV_S, regs.UART_CLKDIV_V);
const clkdiv_frag = Field.of(regs.UART_CLKDIV_FRAG_S, regs.UART_CLKDIV_FRAG_V);
// CONF0_SYNC: the data format, the FIFO resets and the loopback switch all share this word, which is
// why every one of them is a read-modify-write and not a `write`.
const parity = Field.of(regs.UART_PARITY_S, regs.UART_PARITY_V);
const parity_en = Field.of(regs.UART_PARITY_EN_S, regs.UART_PARITY_EN_V);
const bit_num = Field.of(regs.UART_BIT_NUM_S, regs.UART_BIT_NUM_V);
const stop_bit_num = Field.of(regs.UART_STOP_BIT_NUM_S, regs.UART_STOP_BIT_NUM_V);
const loopback = Field.of(regs.UART_LOOPBACK_S, regs.UART_LOOPBACK_V);
const rxfifo_rst = Field.of(regs.UART_RXFIFO_RST_S, regs.UART_RXFIFO_RST_V);
const txfifo_rst = Field.of(regs.UART_TXFIFO_RST_S, regs.UART_TXFIFO_RST_V);
// STATUS: live counters, so read-only and never worth comparing between two runs.
const rxfifo_cnt = Field.of(regs.UART_RXFIFO_CNT_S, regs.UART_RXFIFO_CNT_V);
const txfifo_cnt = Field.of(regs.UART_TXFIFO_CNT_S, regs.UART_TXFIFO_CNT_V);
const tx_sclk_en = Field.of(regs.UART_TX_SCLK_EN_S, regs.UART_TX_SCLK_EN_V);
const rx_sclk_en = Field.of(regs.UART_RX_SCLK_EN_S, regs.UART_RX_SCLK_EN_V);
/// UART_REG_UPDATE, the commit bit for the whole `_SYNC` family. `R/W/SC`: the hardware clears it
/// when the copy is done.
const reg_update_bit = Field.of(regs.UART_REG_UPDATE_S, regs.UART_REG_UPDATE_V);
// -------------------------------------------------------------------------------- clock control
// The source select and the integer pre-divider are one register apart, and not in the register the
// names suggest: for UARTn the select is in PERI_CLK_CTRL(110+n) and the pre-divider is in
// PERI_CLK_CTRL(111+n). That is not a typo in this file - uart_ll.h:463-475 writes
// `peri_clk_ctrl110.reg_uart0_clk_src_sel` while uart_ll.h:558-568 writes
// `peri_clk_ctrl111.reg_uart0_sclk_div_num`, so ctrl111 holds UART0's divider *and* UART1's select.
const peri_clk_ctrl = mmio.RegArray(
regs.HP_SYS_CLKRST_PERI_CLK_CTRL110_REG,
regs.HP_SYS_CLKRST_PERI_CLK_CTRL111_REG,
6, // ctrl110..ctrl115: five selects and five dividers, overlapping by one
);
// All five instances place these fields at the same shifts in their respective registers
// (hp_sys_clkrst_reg.h: every REG_UARTn_CLK_SRC_SEL_S is 24, every REG_UARTn_SCLK_DIV_NUM_S is 0,
// every REG_UARTn_CLK_EN_S is 26), so one macro triple each describes all of them.
const clk_src_sel = Field.of(regs.HP_SYS_CLKRST_REG_UART0_CLK_SRC_SEL_S, regs.HP_SYS_CLKRST_REG_UART0_CLK_SRC_SEL_V);
const sclk_div_num = Field.of(regs.HP_SYS_CLKRST_REG_UART0_SCLK_DIV_NUM_S, regs.HP_SYS_CLKRST_REG_UART0_SCLK_DIV_NUM_V);
const sclk_en = Field.of(regs.HP_SYS_CLKRST_REG_UART0_CLK_EN_S, regs.HP_SYS_CLKRST_REG_UART0_CLK_EN_V);
/// The three clock sources an HP UART can take, with the encoding from uart_ll.h:447-461.
pub const ClockSource = enum(u2) {
/// The 40 MHz crystal. The only source whose frequency is exact, which is why it is the default
/// for anything that has to interoperate.
xtal = 0,
/// RC_FAST, the always-on oscillator. Nominally 20 MHz and uncalibrated - it varies with
/// temperature and part, so a baud rate derived from `nominalHz` here is approximate.
rtc = 1,
/// A fixed 80 MHz tap off the system PLL. Needed for the high rates: the 12-bit integer divider
/// runs out below about 5 kBd from XTAL.
pll_f80m = 2,
/// The nominal frequency to hand `setBaudrate`. Nominal is exact for `xtal` and `pll_f80m` and a
/// datasheet typical for `rtc`; the real clock tree can be reconfigured, so a caller that has
/// changed it must pass its own number instead.
pub fn nominalHz(self: ClockSource) u32 {
return switch (self) {
.xtal => 40_000_000,
.rtc => 20_000_000,
.pll_f80m => 80_000_000,
};
}
};
pub const WordLength = enum(u2) {
// uart_types.h:57-60. The encoding is (bits - 5), which is why it starts at zero.
bits5 = 0,
bits6 = 1,
bits7 = 2,
bits8 = 3,
};
pub const StopBits = enum(u2) {
// uart_types.h:68-70. There is no encoding for zero stop bits, so the enum starts at 1 and 0 is
// reserved by the hardware.
one = 1,
one_and_half = 2,
two = 3,
};
pub const Parity = enum(u2) {
// uart_types.h:78-80: bit 1 is "parity enabled", bit 0 is odd/even. `disable` is 0, so the
// odd/even bit is not part of it - see `setParity` for why that matters.
disable = 0,
even = 2,
odd = 3,
};
/// One UART instance. A value type holding nothing but the index, so it costs nothing at runtime and
/// every register access folds to a constant address when the index is known.
pub const Uart = struct {
num: u8,
pub fn init(num: u8) Uart {
std.debug.assert(num < count);
return .{ .num = num };
}
// ----------------------------------------------------------------------------- the commit bit
/// Copy the `_SYNC` shadow registers into the core clock domain and wait for the hardware to
/// acknowledge by clearing the bit (uart_ll.h:85-89).
///
/// Bounded, where ESP-IDF's `while (hw->reg_update.reg_update);` is not: a UART whose core clock
/// is gated off never clears the bit, and on a board with no debugger an infinite spin is
/// indistinguishable from a crash. 4096 spins is several thousand times the observed cost of a
/// commit, which takes a handful of core-clock cycles. Returns false rather than panicking so a
/// caller can report the peripheral instead of losing the console.
pub fn update(self: Uart) bool {
const r = reg_update.at(self.num);
r.modify(.{reg_update_bit.is(1)});
return r.waitFor(reg_update_bit, 0, 4096);
}
// ---------------------------------------------------------------------------- clocks and reset
/// Reset the block. Refuses UART0.
///
/// UART0 carries this board's console. A reset clears UART_CLKDIV to its power-on 694, the
/// console's output becomes garbage part-way through whatever it was printing, and the board
/// takes a watchdog reset a moment later - measured, not theorised. There is no "and then put
/// the divider back" version of this that is safe, because the damage is done between the two
/// stores.
pub fn reset(self: Uart) void {
std.debug.assert(self.num != 0);
switch (self.num) {
1 => clkrst.resetPeripheral(.uart1),
2 => clkrst.resetPeripheral(.uart2),
3 => clkrst.resetPeripheral(.uart3),
4 => clkrst.resetPeripheral(.uart4),
else => unreachable,
}
}
/// The core (baud-generating) clock, as distinct from the APB bus clock that
/// `clkrst.setClockEnabled` handles. Both are needed: the bus clock makes the registers
/// answer, this one makes the shift registers move - and `update()` is one of the things that
/// stops working without it.
///
/// Two gates in two blocks, per uart_ll.h:379-397: HP_SYS_CLKRST's per-instance `CLK_EN`, which
/// sits in the *select* register PERI_CLK_CTRL(110+n) and not the divider one next to it, and
/// the UART's own TX and RX enables in UART_CLK_CONF. Interrupts are masked over the first
/// because PERI_CLK_CTRL is shared with unrelated peripherals.
pub fn setCoreClockEnabled(self: Uart, on: bool) void {
const v: u32 = @intFromBool(on);
{
const guard = clkrst.maskInterrupts();
defer guard.release();
self.selectReg().modify(.{sclk_en.is(v)});
}
clk_conf.at(self.num).modify(.{ tx_sclk_en.is(v), rx_sclk_en.is(v) });
}
/// Select the clock the baud generator divides down. Read-modify-write of a register shared with
/// other peripherals, so interrupts are masked (uart_ll.h:477-481 makes the equivalent point by
/// refusing to compile outside `PERIPH_RCC_ATOMIC`).
pub fn setClockSource(self: Uart, src: ClockSource) void {
const guard = clkrst.maskInterrupts();
defer guard.release();
self.selectReg().modify(.{clk_src_sel.is(@intFromEnum(src))});
}
pub fn clockSource(self: Uart) ClockSource {
// Encoding 3 is not defined; IDF's getter (uart_ll.h:509-524) maps `default` to RTC, so
// reporting the same thing keeps a round-trip through both implementations consistent.
return switch (self.selectReg().get(clk_src_sel)) {
0 => .xtal,
2 => .pll_f80m,
else => .rtc,
};
}
/// PERI_CLK_CTRL(110+n): where this instance's source select and core clock gate live.
inline fn selectReg(self: Uart) Reg {
return peri_clk_ctrl.at(self.num);
}
/// PERI_CLK_CTRL(111+n): where this instance's integer pre-divider lives. One register above
/// the select, which is the trap this pair of accessors exists to contain.
inline fn dividerReg(self: Uart) Reg {
return peri_clk_ctrl.at(self.num + 1);
}
// ------------------------------------------------------------------------------------- baud
/// The two dividers a baud rate decomposes into, computed exactly as
/// `_uart_ll_set_baudrate` (uart_ll.h:532-588) does.
pub const Divider = struct {
/// HP_SYS_CLKRST's integer pre-divider, 1-256. Stored as `sclk - 1` in an 8-bit field.
sclk: u32,
/// The UART's own divider, integer part, 12 bits.
int: u32,
/// The UART's own divider, sixteenths.
frag: u32,
};
/// Decompose a baud rate, or fail if the hardware cannot express it.
///
/// The arithmetic, line by line against uart_ll.h:
///
/// 541 max_div = UART_CLKDIV_V = 0xfff - the UART divider's integer part is 12 bits
/// 542 sclk = ceil(sclk_freq / (max_div * baud)) the smallest pre-divide that brings
/// the remaining ratio inside 12 bits
/// 545 reject sclk == 0 or sclk > 256 256 = SCLK_DIV_NUM_V + 1
/// 549 clk_div = (sclk_freq << 4) / (baud * sclk) the ratio in sixteenths
/// 551 int = clk_div >> 4
/// 552 frag = clk_div & 0xf
/// 555+ the field written is sclk - 1
///
/// The `<< 4` is IDF's fixed-point scale, not a fudge: CLKDIV_FRAG is a count of sixteenths of a
/// source-clock period added to every bit time, so `clk_div` is the exact ratio rounded down to
/// 1/16 of a tick. Two deliberate departures from the C, neither of which changes a result:
///
/// * The `ceil` denominator is 64-bit here as it is there (uart_ll.h:542 casts `max_div` to
/// `uint64_t`), and `sclk_freq << 4` is *also* computed in 64 bits. In C that shift is
/// `uint32_t` and overflows above 268.4 MHz; no P4 UART source is anywhere near that (the
/// fastest is PLL_F80M at 80 MHz), so the two agree on every reachable input while this one
/// has no undefined case.
/// * `baud == 0` returns null rather than false-with-registers-untouched; same outcome, but the
/// caller cannot ignore it by accident.
pub fn divider(baud: u32, sclk_freq: u32) ?Divider {
if (baud == 0) return null;
const max_div: u64 = clkdiv.max(); // UART_CLKDIV_V
const denom = max_div * baud;
const sclk: u64 = (@as(u64, sclk_freq) + denom - 1) / denom;
if (sclk == 0 or sclk > @as(u64, sclk_div_num.max()) + 1) return null;
const clk_div: u64 = (@as(u64, sclk_freq) << 4) / (@as(u64, baud) * sclk);
return .{
.sclk = @intCast(sclk),
.int = @intCast(clk_div >> 4),
.frag = @intCast(clk_div & 0xf),
};
}
/// Program a baud rate. Returns false, having touched nothing, if it is unreachable from this
/// source frequency.
///
/// Store order follows uart_ll.h:550-576 exactly - integer part, fraction, pre-divider, commit -
/// because the intermediate states are visible to the transmitter of a UART that is already
/// running, and because a write-trace comparison against IDF would otherwise differ on ordering
/// while agreeing on the final registers. The two CLKDIV_SYNC stores are separate for the same
/// reason: IDF's two bitfield assignments are two read-modify-writes of that word.
pub fn setBaudrate(self: Uart, baud: u32, sclk_freq: u32) bool {
const d = divider(baud, sclk_freq) orelse return false;
const div = clkdiv_sync.at(self.num);
div.modify(.{clkdiv.is(d.int)});
div.modify(.{clkdiv_frag.is(d.frag)});
{
const guard = clkrst.maskInterrupts();
defer guard.release();
self.dividerReg().modify(.{sclk_div_num.is(d.sclk - 1)});
}
_ = self.update();
return true;
}
/// The baud rate the registers currently describe, by inverting the above
/// (uart_ll.h:590-615). Integer division both ways, so this is not exactly the value passed to
/// `setBaudrate` - it is what the hardware will actually produce, which is the more useful
/// number.
pub fn baudrate(self: Uart, sclk_freq: u32) u32 {
const div = clkdiv_sync.at(self.num).raw();
const int = (div >> clkdiv.shift) & clkdiv.unshiftedMask();
const frag = (div >> clkdiv_frag.shift) & clkdiv_frag.unshiftedMask();
const sclk = self.dividerReg().get(sclk_div_num) + 1;
const ticks = ((@as(u64, int) << 4) | frag) * sclk;
if (ticks == 0) return 0;
return @intCast((@as(u64, sclk_freq) << 4) / ticks);
}
// ------------------------------------------------------------------------------ data format
/// uart_ll.h:1020-1024.
pub fn setWordLength(self: Uart, w: WordLength) void {
conf0_sync.at(self.num).modify(.{bit_num.is(@intFromEnum(w))});
_ = self.update();
}
/// uart_ll.h:790-794.
pub fn setStopBits(self: Uart, s: StopBits) void {
conf0_sync.at(self.num).modify(.{stop_bit_num.is(@intFromEnum(s))});
_ = self.update();
}
/// uart_ll.h:817-832.
///
/// Note what IDF does *not* do: disabling parity leaves UART_PARITY - the odd/even select bit -
/// at whatever it was, because the value 0 for "disabled" carries no odd/even information and
/// writing bit 0 of it would be writing a zero the caller never asked for. So `.disable` clears
/// `parity_en` only. Reproduced here because otherwise a differential run diverges by one bit
/// after any sequence that sets odd parity and then disables it.
pub fn setParity(self: Uart, p: Parity) void {
const c = conf0_sync.at(self.num);
const v = @intFromEnum(p);
if (p != .disable) c.modify(.{parity.is(v & 1)});
c.modify(.{parity_en.is((v >> 1) & 1)});
_ = self.update();
}
/// All three format fields, in IDF's order. Three commits rather than one, matching what
/// calling IDF's three setters does: the format of a UART mid-transmission is not atomic on
/// this hardware either way, and diverging here would be a difference with no benefit.
pub fn setFormat(self: Uart, w: WordLength, p: Parity, s: StopBits) void {
self.setWordLength(w);
self.setParity(p);
self.setStopBits(s);
}
pub fn wordLength(self: Uart) WordLength {
return @enumFromInt(conf0_sync.at(self.num).get(bit_num));
}
pub fn stopBits(self: Uart) StopBits {
// Encoding 0 is not a legal stop-bit count. The hardware's reset value is 1, and nothing
// here can write 0, so an out-of-range read means the block is unclocked or was reset
// under us - reported as `one` rather than an illegal enum value, which would be UB.
return switch (conf0_sync.at(self.num).get(stop_bit_num)) {
2 => .one_and_half,
3 => .two,
else => .one,
};
}
/// uart_ll.h:834-841: parity is only meaningful when enabled, so the odd/even bit is not
/// reported unless it is.
pub fn parityMode(self: Uart) Parity {
const c = conf0_sync.at(self.num).raw();
if ((c >> parity_en.shift) & 1 == 0) return .disable;
return if ((c >> parity.shift) & 1 == 1) .odd else .even;
}
// ------------------------------------------------------------------------------------- FIFO
/// Bytes waiting in the RX FIFO (uart_ll.h:763-766).
pub fn rxCount(self: Uart) u32 {
return status.at(self.num).get(rxfifo_cnt);
}
/// Bytes queued in the TX FIFO.
pub fn txCount(self: Uart) u32 {
return status.at(self.num).get(txfifo_cnt);
}
/// Free space in the TX FIFO (uart_ll.h:775-780: the total, minus what is queued).
pub fn txFree(self: Uart) u32 {
return fifo_len - self.txCount();
}
/// Push one byte. A full 32-bit store, because a narrower one becomes a read-modify-write on
/// this bus and the read would pop a received byte (uart_ll.h:716-724).
pub inline fn pushByte(self: Uart, byte: u8) void {
fifo.at(self.num).writeRaw(byte);
}
/// Pop one byte. The read *is* the pop - see this file's header on why offset 0x000 is on the
/// differential harness's no-read list.
pub inline fn popByte(self: Uart) u8 {
return @truncate(fifo.at(self.num).raw());
}
/// Discard everything received. Assert, commit, deassert, commit: `rxfifo_rst` lives in a
/// shadow register, so without the commits the hardware never sees either edge
/// (uart_ll.h:733-739).
pub fn resetRxFifo(self: Uart) void {
const c = conf0_sync.at(self.num);
c.modify(.{rxfifo_rst.is(1)});
_ = self.update();
c.modify(.{rxfifo_rst.is(0)});
_ = self.update();
}
/// uart_ll.h:748-754. Same shape, and the same reason for it.
pub fn resetTxFifo(self: Uart) void {
const c = conf0_sync.at(self.num);
c.modify(.{txfifo_rst.is(1)});
_ = self.update();
c.modify(.{txfifo_rst.is(0)});
_ = self.update();
}
// --------------------------------------------------------------------------------- loopback
/// Tie TX back to RX inside the block (uart_ll.h:1437-1441). The pads are not involved, which
/// makes it the only way to exercise a UART end to end with nothing wired to the board - it is
/// how the FIFO and format paths can be tested at all here.
pub fn setLoopback(self: Uart, on: bool) void {
conf0_sync.at(self.num).modify(.{loopback.is(@intFromBool(on))});
_ = self.update();
}
pub fn loopbackEnabled(self: Uart) bool {
return conf0_sync.at(self.num).get(loopback) == 1;
}
// ------------------------------------------------------------------------------ pin routing
/// This instance's TX signal index in the GPIO matrix. The names in IDF's map are
/// `UARTn_TXD_PAD_OUT_IDX` (gpio_sig_map.h:28-52) and they are consecutive in steps of three,
/// but the step is not relied on: each is named.
pub fn txSignal(self: Uart) u32 {
return switch (self.num) {
0 => regs.UART0_TXD_PAD_OUT_IDX,
1 => regs.UART1_TXD_PAD_OUT_IDX,
2 => regs.UART2_TXD_PAD_OUT_IDX,
3 => regs.UART3_TXD_PAD_OUT_IDX,
4 => regs.UART4_TXD_PAD_OUT_IDX,
else => unreachable,
};
}
/// This instance's RX signal index. Numerically equal to the TX one - the matrix's input and
/// output signal spaces are separate namespaces that happen to share indices for a duplex
/// peripheral - which is exactly why routing RX with `matrixOut` silently does nothing useful.
pub fn rxSignal(self: Uart) u32 {
return switch (self.num) {
0 => regs.UART0_RXD_PAD_IN_IDX,
1 => regs.UART1_RXD_PAD_IN_IDX,
2 => regs.UART2_RXD_PAD_IN_IDX,
3 => regs.UART3_RXD_PAD_IN_IDX,
4 => regs.UART4_RXD_PAD_IN_IDX,
else => unreachable,
};
}
/// Route TX to a pad through the GPIO matrix.
pub fn routeTx(self: Uart, pin: u8) void {
gpio.matrixOut(pin, self.txSignal());
}
/// Route a pad to RX through the GPIO matrix, and enable that pad's input buffer - without
/// which the routed signal reads as a constant and the UART receives nothing, which is the
/// single most common way this goes wrong.
pub fn routeRx(self: Uart, pin: u8) void {
gpio.setInputEnable(pin, true);
gpio.matrixIn(pin, self.rxSignal());
}
// --------------------------------------------------------------------------------- transfers
/// Send every byte, blocking until each fits. Bounded only by the FIFO draining, which always
/// progresses while the core clock is on - so unlike a blocking *read* this cannot wait on an
/// event that may never happen.
pub fn write(self: Uart, bytes: []const u8) void {
for (bytes) |b| {
while (self.txFree() == 0) {}
self.pushByte(b);
}
}
/// Drain up to `buf.len` received bytes and report how many there were. Does not block.
///
/// Deliberately not blocking: nothing on the other end of a UART is obliged to send, so a
/// blocking read is an unbounded wait, and there is no timer in this HAL's dependency set to
/// bound it with. A caller that wants to wait writes the loop, and owns the decision about what
/// to do when the bytes never come.
pub fn read(self: Uart, buf: []u8) usize {
var n: usize = 0;
const available = self.rxCount();
while (n < buf.len and n < available) : (n += 1) buf[n] = self.popByte();
return n;
}
/// Whether the transmitter has finished: nothing queued in the FIFO.
///
/// Not the same as "the last bit is on the wire" - the shift register still holds up to one
/// character after the FIFO empties. UART_FSM_STATUS reports that, and this HAL does not model
/// it, so a caller about to cut the clock or reconfigure the format must allow for one more
/// character time.
pub fn txIdle(self: Uart) bool {
return self.txCount() == 0;
}
};
// ------------------------------------------------------------------------------------ host tests
// The divider arithmetic is the only part of this file that can be checked without the chip, and it
// is the part most worth checking: every value below is IDF's formula evaluated by hand, so a
// transcription error in `divider` fails here rather than as a garbled console.
test "40 MHz XTAL, 115200 Bd: one source tick, 12.4 divider does the work" {
// 40e6/(4095*115200) = 0.085 -> ceil = 1. clk_div = (40e6<<4)/115200 = 5555 (5555.55 floored).
// 5555 = 347*16 + 3.
const d = Uart.divider(115200, 40_000_000).?;
try std.testing.expectEqual(@as(u32, 1), d.sclk);
try std.testing.expectEqual(@as(u32, 347), d.int);
try std.testing.expectEqual(@as(u32, 3), d.frag);
// 347 + 3/16 = 347.1875 ticks per bit -> 115,213 Bd in real arithmetic, and 115,211 as the
// hardware's own truncating inverse reports it (see the round-trip test): 0.01% fast either way.
}
test "80 MHz PLL, 115200 Bd: the fraction differs from the XTAL case, which is the point of it" {
// 80e6/(4095*115200) = 0.17 -> 1. clk_div = (80e6<<4)/115200 = 11111 = 694*16 + 7.
const d = Uart.divider(115200, 80_000_000).?;
try std.testing.expectEqual(@as(u32, 1), d.sclk);
try std.testing.expectEqual(@as(u32, 694), d.int);
try std.testing.expectEqual(@as(u32, 7), d.frag);
}
test "a rate low enough to need the pre-divider" {
// 300 Bd from 40 MHz: 40e6/300 = 133,333 ticks per bit, far past 12 bits.
// ceil(40e6/(4095*300)) = ceil(32.6) = 33. clk_div = (40e6<<4)/(300*33) = 64,646 = 4040*16 + 6.
const d = Uart.divider(300, 40_000_000).?;
try std.testing.expectEqual(@as(u32, 33), d.sclk);
try std.testing.expectEqual(@as(u32, 4040), d.int);
try std.testing.expectEqual(@as(u32, 6), d.frag);
try std.testing.expect(d.int <= 0xfff);
try std.testing.expect(d.sclk <= 256);
}
test "unreachable rates are rejected rather than rounded" {
// Zero is IDF's explicit early return (uart_ll.h:538).
try std.testing.expectEqual(@as(?Uart.Divider, null), Uart.divider(0, 40_000_000));
// 10 Bd from 40 MHz needs a pre-divide of ceil(40e6/40950) = 977, past the 8-bit field's 256.
try std.testing.expectEqual(@as(?Uart.Divider, null), Uart.divider(10, 40_000_000));
}
test "the sclk == 0 rejection is unreachable except from a zero source frequency" {
// Worth pinning down, because the obvious reading of uart_ll.h:545 is wrong. `sclk` is a
// *ceiling*, so for any non-zero source frequency it is at least 1 - asking for 4 MBd from a
// 1 kHz clock does NOT fail here, it yields sclk = 1 and a divider of zero, and IDF programs
// that just as happily. The only input that trips the branch is sclk_freq == 0.
const absurd = Uart.divider(4_000_000, 1000).?;
try std.testing.expectEqual(@as(u32, 1), absurd.sclk);
try std.testing.expectEqual(@as(u32, 0), absurd.int);
try std.testing.expectEqual(@as(u32, 0), absurd.frag);
try std.testing.expectEqual(@as(?Uart.Divider, null), Uart.divider(115200, 0));
}
test "the pre-divider field stores sclk - 1, so the reachable rates stop at 256 ticks" {
// The boundary IDF checks at uart_ll.h:545: sclk may be 256 because the field holds sclk-1.
// From 40 MHz the last rate inside it is 39 Bd, at a pre-divide of 251; 38 Bd needs 257.
const ok = Uart.divider(39, 40_000_000).?;
try std.testing.expectEqual(@as(u32, 251), ok.sclk);
try std.testing.expectEqual(@as(u32, 4086), ok.int);
try std.testing.expectEqual(@as(?Uart.Divider, null), Uart.divider(38, 40_000_000));
}
test "the divider round-trips through the baud rate the hardware will really produce" {
// What `baudrate()` computes, without a chip: the inverse of the same arithmetic.
const d = Uart.divider(115200, 40_000_000).?;
const ticks = ((@as(u64, d.int) << 4) | d.frag) * d.sclk;
const actual: u32 = @intCast((@as(u64, 40_000_000) << 4) / ticks);
// 347 + 3/16 = 347.1875 ticks per bit, and 40e6*16/5555 truncates to 115,211 Bd: 0.01% fast.
try std.testing.expectEqual(@as(u32, 115_211), actual);
}
|