This is the engineering reference for the model: what it implements, register by register, for people writing firmware against it. For using the board in the editor, start with The Seeed XIAO SAMD21 and the board page.

Seeed XIAO SAMD21 board contract (Phase 9)

The XIAO SAMD21 is the smallest board here and the third Cortex-M0+. One kernel component (xiao in crates/parts) wraps an ATSAMD21G18A SoC model (crates/atsamd21) on the same core as the Raspberry Pi Pico (crates/cortexm-core, Profile::V6M). The core is shared; the chip around it is not. This document is the contract, and it wins over any comment in the code.

The catalog type is xiao.

Read section 7 first if you are taking a build from here to real silicon. Every base address in this model is one this document can state from the SAM D21 Family Datasheet, and most register offsets are too, but the TC block's offsets and the peripheral multiplexing table are this model's reading rather than a page quoted back, and section 7 names each one. Nothing here is guessed at quietly.

1. Board pins (catalog order)

Top view, the USB-C socket at the top, the ATSAMD21G18A under the silkscreen in the middle. The board is 21 x 17.8 mm and its two rows of seven castellated pads are 15.24 mm (exactly 0.6 inch) apart, so it straddles a breadboard's center channel.

Left column, top to bottom (7 pads): D0, D1, D2, D3, D4, D5, D6.

Right column, top to bottom (7 pads): 5V, GND, 3V3, D10, D9, D8, D7.

14 pads. Eleven are I/O and three are power. Every one of the eleven is also an analog input, and A0 to A10 are the same pads as D0 to D10; the silkscreen prints both.

The pad behind each pin, and what else it can be:

pin pad AIN EXTINT PWM (function E) other
D0 / A0 PA02 0 2 none the DAC's output
D1 / A1 PA04 4 4 TCC0 CC0
D2 / A2 PA10 18 10 TCC1 CC0
D3 / A3 PA11 19 11 TCC1 CC1
D4 / A4 PA08 16 NMI TCC0 CC0 SDA
D5 / A5 PA09 17 9 TCC0 CC1 SCL
D6 / A6 PB08 2 8 TC4 CC0 Serial1 TX
D7 / A7 PB09 3 9 TC4 CC1 Serial1 RX
D8 / A8 PA07 7 7 TCC1 CC1 SCK
D9 / A9 PA05 5 5 TCC0 CC1 MISO
D10 / A10 PA06 6 6 TCC1 CC0 MOSI

Three things in that table are worth reading twice, and all three are the hardware rather than the model:

  • D0 has no PWM. PA02 is the DAC's pin and reaches no waveform output at all. analogWrite(0, x) here drives the pad on or off at the halfway mark rather than pretending to fade.
  • D4 has no ordinary external interrupt. PA08 is the chip's NMI pin. attachInterrupt(4, ...) does nothing, here and on a desk.
  • D5 and D7 share EXTINT9, so only one of the two can carry an interrupt at a time; the second attachInterrupt takes the line from the first. And because TCC1 and TCC2 have two compare registers each, some pins share a duty: writing analogWrite on one of D1/D4, D9/D5, D10/D2 or D8/D3 moves both.

Three LEDs are on the board and on no pad: the user LED on PA17, the RX LED on PA18 and the TX LED on PA19. All three are active low, so digitalWrite(LED_BUILTIN, LOW) lights the user one. They are seen through probe and the visual, and nowhere else. The runtime numbers them 13, 12 and 11 and defines LED_BUILTIN as 13.

Electrical. Every I/O pad drives push-pull at 3.3 V through R_STRONG, sits high-Z, or pulls up or down through 40 k. An input reads high at 1.65 V; a floating or contended net reads 0, because a real pin reads whatever noise is on it and a simulator that invented a value would be lying about a wire nobody connected. 3V3 drives 3.3 V at R_SUPPLY, 5V drives 5 V and GND drives 0 V, always, because the board is plugged into USB. There is no reset pad on the header: the two reset pads are on the underside of a real XIAO, and here reset is a poke (section 5).

2. Memory map

flash 256 KB at 0x0000_0000
SRAM 32 KB at 0x2000_0000 to 0x2000_7FFF
stack top 0x2000_8000
PM 0x4000_0400, section 3.2
SYSCTRL 0x4000_0800, section 3.2
GCLK 0x4000_0C00, section 3.2
EIC 0x4000_1800, section 3.6
PORT 0x4100_4400, section 3.1
USB 0x4100_5000, section 3.7
SERCOM0..5 0x4200_0800, 0x400 apart, section 3.3
TCC0..2 0x4200_2000, 0x400 apart, section 3.4
TC3..TC7 0x4200_2C00, 0x400 apart, section 3.4
ADC 0x4200_4000, section 3.5

Every PT_LOAD segment must land inside the flash or the SRAM; anything else is refused with the offending address in the message, and the chip is left with no program.

The vector table goes at zero and nothing goes before it. A real XIAO ships with the UF2 bootloader in the first 8 KB and the application linked at 0x2000; an image built here therefore runs in Mokxi and under a debugger, and needs relinking at 0x2000 to go in over that bootloader. That is a one-line change to ORIGIN in firmware/xiao/link.ld, not a code change.

Blocks the datasheet has and this model does not (PAC0-2, DSU, NVMCTRL, DMAC, MTB, WDT, RTC, EVSYS, AC, DAC, PTC, I2S) are mapped and inert: reading one gives 0 and counts as a stray access, rather than faulting. A startup that pokes the NVM controller's wait states should not crash the board. Anything outside every block is an access fault, which on ARMv6-M is a HardFault and which the board reports as a crash on the host channel.

3. Peripheral registers

Only the subset below is implemented. A SAM D21 register is eight, sixteen or thirty-two bits wide, and the bus serves each at its own width: an access narrower than its register is the read-modify-write the bus matrix would have done, and one wider is answered from the register it starts in. So firmware written against the datasheet's own types works unchanged.

3.1 PORT (0x4100_4400)

Two groups, PA and PB, 0x80 apart. Pads are numbered 0..63 in this model: 0..31 are PA00..PA31 and 32..63 PB00..PB31.

offset register
0x00 DIR
0x04 DIRCLR
0x08 DIRSET
0x0C DIRTGL
0x10 OUT
0x14 OUTCLR
0x18 OUTSET
0x1C OUTTGL
0x20 IN
0x24 CTRL (stored, read back)
0x28 WRCONFIG
0x30 + n PMUXn, one byte per pair of pins
0x40 + n PINCFGn, one byte per pin

PINCFG bit 0 PMUXEN, bit 1 INEN, bit 2 PULLEN, bit 6 DRVSTR (stored, electrically ignored).

OUT does double duty and it is this part's one real trap. With DIR set it is the driven level; with DIR clear it is the direction of the pull. So INPUT_PULLUP is DIRCLR, then OUTSET, then PINCFG.PULLEN, and PULLEN without OUT gives a pull-down. Two of the three and the pin does the wrong thing quietly.

WRCONFIG writes sixteen pins' PINCFG and PMUX in one store: PINMASK in bits 0..15, PMUXEN 16, INEN 17, PULLEN 18, DRVSTR 22, PMUX 24..27, WRPMUX 28, WRPINCFG 30, HWSEL 31 (which shifts the mask to pins 16..31).

With PMUXEN set, PMUX picks one of the eight peripheral functions A..H and the pad leaves the PORT's hands. The model acts on the ones it has (SERCOM in USART mode and the TC/TCC waveform outputs, section 1's table) and treats every other selection as a peripheral it does not have, which asks for nothing and leaves the pad high-Z rather than inventing a level. The pad's pull is still the pad's, which is how a SERCOM RX pin keeps its pull-up.

IN reads 0 on a pin whose INEN is clear, which is what the hardware does. A pad this chip is driving reads back what it is driving.

3.2 PM, GCLK and SYSCTRL: the clock tree is not modeled

The core runs at 48 MHz from the first instruction. There is no clock tree here: no DFLL, no oscillators, no dividers, no generators that actually generate anything. These three blocks exist so that a startup written against the real registers runs to the end instead of hanging on a ready bit.

  • SYSCTRL (0x4000_0800) is a shadow of thirty-two words, with two exceptions. PCLKSR (0x0C) reads every ready and lock bit set (XOSCRDY, XOSC32KRDY, OSC32KRDY, OSC8MRDY, DFLLRDY, DFLLLCKF, DFLLLCKC, DFLLRCS, BOD33RDY, DPLLLCKF, DPLLLCKT) and every failure bit clear. DFLLSYNC reads 0. So while (!(SYSCTRL->PCLKSR.reg & DFLLRDY)) {} falls straight through.
  • GCLK (0x4000_0C00): CTRL at 0x00 (SWRST bit 0), STATUS at 0x01 (SYNCBUSY bit 7, never set), CLKCTRL at 0x02, GENCTRL at 0x04, GENDIV at 0x08. The last three are addressed by the ID in the value written, as on the part, and a read answers for whichever ID was written last. Nothing is gated on them.
  • PM (0x4000_0400): AHBMASK 0x14, APBAMASK 0x18, APBBMASK 0x1C, APBCMASK 0x20, all stored and read back at their reset values, and RCAUSE at 0x38, which is read-only here and carries the power-on bit, the external-reset bit or the system-reset bit depending on what last reset the part. Nothing is gated on the clock masks: a peripheral works whether or not firmware turned its clock on. The runtime turns them on anyway, because a real part needs it.

Time itself comes from SysTick, which is in cortexm-core at its architectural address. The runtime reloads it every 48 000 cycles (exactly a millisecond at 48 MHz), and SYST_CALIB reports that as an exact ten-millisecond tenth.

3.3 SERCOM as a USART (0x4200_0800 + 0x400 n)

Six SERCOMs, each modeled in USART mode with an internal clock (CTRLA.MODE = 1), asynchronous 8N1, and in no other mode. SPI and I2C are not modeled; a SERCOM put in one of those modes does nothing.

offset register
0x00 CTRLA
0x04 CTRLB
0x08 DBGCTRL (stored)
0x0C BAUD
0x0E RXPL (stored)
0x14 INTENCLR
0x16 INTENSET
0x18 INTFLAG
0x1A STATUS (stored; no error is ever raised)
0x1C SYNCBUSY (always 0)
0x28 DATA

CTRLA bit 0 SWRST, bit 1 ENABLE, bits 2..4 MODE, bits 16..17 TXPO (0 drives PAD[0], anything else PAD[2]), bits 20..21 RXPO (the pad the receiver listens on), bit 30 DORD. CTRLB bit 16 TXEN, bit 17 RXEN. INTFLAG bit 0 DRE, bit 1 TXC (latched, write 1 to clear), bit 2 RXC.

BAUD is the asynchronous arithmetic generator: BAUD = 65536 (1 - 16 f / f_ref), so a bit time is 16 x 65536 / (65536 - BAUD) reference clocks. The reference is taken to be the 48 MHz core clock, because there is no clock tree to ask (section 3.2). A BAUD of 0 (what a program that has not called begin() leaves behind) falls back to 115200. There is no fractional mode.

A transmitter that is enabled and has TXEN set owns its pad and shifts real bits through it: a start bit low, eight data bits least significant first, a stop bit high, each one bit time long, each edge stamped with the cycle the hardware would have moved it. A receiver with RXEN set starts a frame on a falling edge and samples each data bit in the middle of its bit time. So a jumper from D6 to D7 carries a character, and a mismatched baud rate garbles text here in the same way it does on a desk.

On this board Serial1 is SERCOM4 with TXPO 0 and RXPO 1, which is PAD[0] on PB08 and PAD[1] on PB09 (D6 and D7), both on peripheral function C.

3.4 TCC0-2 and TC3-7, as far as PWM

Three TCCs and five TCs. A counter here is never stepped: it is a closed-form function of the CPU cycle, and so is the time of its next edge, which is what lets a core parked in WFI cost nothing through a whole delay() while a PWM keeps running.

PRESCALER is bits 8..10 of CTRLA on both kinds, and it is not a power of two all the way up: 0 to 4 are DIV1, DIV2, DIV4, DIV8 and DIV16, then 5 is DIV64, 6 DIV256 and 7 DIV1024. That jump is a real trap and getting it wrong makes a PWM four times too slow.

TCC (0x4200_2000 + 0x400 n):

offset register
0x00 CTRLA: bit 0 SWRST, bit 1 ENABLE, bits 8..10 PRESCALER
0x04/0x05 CTRLBCLR/CTRLBSET (stored)
0x08 SYNCBUSY (always 0)
0x24/0x28 INTENCLR/INTENSET
0x2C INTFLAG: bit 0 OVF
0x30 STATUS (always 0)
0x34 COUNT
0x3C WAVE: bits 0..2 WAVEGEN, of which 2 is normal PWM
0x40 PER, the period
0x44 + 4c CC0..CC3, the duties
0x68/0x6C/0x70 + 4c WAVEB/PERB/CCBn, which take effect at once here

TCC0 has four compare channels; TCC1 and TCC2 have two, so their WO[2] and WO[3] repeat WO[0] and WO[1]. That is why section 1's table pairs some pins.

TC (0x4200_2C00 + 0x400 n, for TC3 to TC7):

offset register
0x00 CTRLA: bit 0 SWRST, bit 1 ENABLE, bits 2..3 MODE (0 COUNT16, 1 COUNT8, 2 COUNT32), bits 5..6 WAVEGEN, bits 8..10 PRESCALER
0x02 READREQ (accepted, does nothing: the count is always current)
0x04/0x05 CTRLBCLR/CTRLBSET (stored)
0x0C/0x0D INTENCLR/INTENSET
0x0E INTFLAG: bit 0 OVF
0x0F STATUS (always 0)
0x10 COUNT, at the width MODE says
0x14 PER, in COUNT8 only
0x18 + c CC0, CC1, one byte apart in COUNT8, two in COUNT16, four in COUNT32

In normal PWM (WAVEGEN = 2) a TC's top is PER in COUNT8 and the full counter width otherwise; in match PWM (WAVEGEN = 3) CC0 is the period and CC1 the duty. A waveform output is high from the start of the period until the compare match and low after it, so a compare of 0 is a solid low and a compare past the top a solid high, with no edge at all. That is the shape analogWrite wants and the only shape this model produces.

The overflow flag reaches the NVIC when its INTENSET bit is set. Capture, the event system, one-shot and the ramp, dithering, dead-time insertion, pattern generation and the fault inputs are all absent.

3.5 ADC (0x4200_4000)

Software-triggered single conversions, which is the one path analogRead takes.

offset register
0x00 CTRLA bit 0 SWRST, bit 1 ENABLE
0x01 REFCTRL stored, read back
0x02 AVGCTRL stored, read back
0x03 SAMPCTRL stored, read back
0x04 CTRLB bits 4..5 RESSEL: 0 twelve bits, 2 ten, 3 eight
0x06 WINCTRL stored, read back
0x08 SWTRIG bit 0 FLUSH, bit 1 START
0x0C INPUTCTRL bits 0..4 MUXPOS; MUXNEG and GAIN stored
0x10 EVCTRL stored
0x12/0x13 INTENCLR/INTENSET
0x14 INTFLAG bit 0 RESRDY, bit 1 OVERRUN
0x15 STATUS always 0
0x16 RESULT reading it clears RESRDY
0x18..0x22 WINLT, WINUT, GAINCORR, OFFSETCORR, CALIB, DBGCTRL stored, read back

One conversion: point MUXPOS at the channel, write SWTRIG.START, wait for RESRDY, read RESULT. It takes a fixed 6 microseconds of simulated time, which firmware really does wait for. The result is round(4095 x Vin / 3.3) of the pad's voltage at the moment the conversion started, shifted down for a RESSEL below twelve bits. A channel with nothing holding its pin reads 0.

The reference and the gain do nothing here. REFCTRL.REFSEL and INPUTCTRL.GAIN are stored and read back, and the model always measures against the 3.3 V rail, which is what the Arduino cores' own settings, INTVCC1 with DIV2 gain, come out to on this board. A sketch that switches to the 1.0 V internal reference gets the same numbers here that it would get at INTVCC1, and this document says so rather than inventing a scale. Averaging, the window monitor, the corrections, free-run mode, differential mode and the event system are absent.

Channels 0..19 exist. Section 1's table maps them to pads.

3.6 EIC (0x4000_1800)

Sixteen EXTINT lines, which is what attachInterrupt is built on. A pad reaches the controller by taking peripheral function A, and which line it lands on is fixed by the pad (section 1).

offset register
0x00 CTRL: bit 0 SWRST, bit 1 ENABLE
0x01 STATUS: bit 7 SYNCBUSY, always 0
0x02/0x03 NMICTRL/NMIFLAG (stored; the NMI itself is not raised)
0x04 EVCTRL (stored)
0x08/0x0C INTENCLR/INTENSET
0x10 INTFLAG
0x14 WAKEUP (stored)
0x18/0x1C CONFIG0/CONFIG1: four bits of SENSE and one FILTEN per line

SENSE: 0 none, 1 rising, 2 falling, 3 both, 4 high level, 5 low level. An edge latches in INTFLAG and is written to clear. A level holds INTFLAG set for as long as the pin is at that level, so a handler that does not remove the condition runs again, exactly as on the part. FILTEN is stored and filters nothing.

All sixteen lines share one NVIC vector; the runtime reads INTFLAG and calls whichever handlers are attached.

3.7 The USB device (0x4100_5000): a stub that carries bytes

On a XIAO the board's only socket goes to the SAM D21's own USB, and the serial monitor on your desk is talking to a CDC ACM interface that the sketch's firmware enumerates. That is the fact that makes the board what it is, and it is the fact that makes it awkward: there is no Serial until the firmware has answered a dozen control transfers.

What is here is a pipe with the register names on it, and the transfer path is the part's real one.

offset register
0x00 CTRLA: bit 0 SWRST, bit 1 ENABLE, bit 7 MODE
0x02 SYNCBUSY (always 0)
0x03 QOSCTRL (stored)
0x08 CTRLB: bit 0 DETACH, set at reset
0x0A DADD (stored)
0x0C/0x0D/0x10 STATUS, FSMSTATUS, FNUM: all read 0; there is no bus to have a state
0x14/0x18/0x1C INTENCLR/INTENSET/INTFLAG (stored; nothing is ever raised)
0x20 EPINTSMRY
0x24 DESCADD
0x28 PADCAL (stored)
0x100 + 0x20 n endpoint n: EPCFG +0, EPSTATUSCLR +4, EPSTATUSSET +5, EPSTATUS +6, EPINTFLAG +7, EPINTENCLR +8, EPINTENSET +9

The descriptor table at DESCADD is the real one: two banks per endpoint, sixteen bytes each, ADDR at +0 and PCKSIZE at +4 with BYTE_COUNT in bits 0..13 and SIZE in bits 28..30.

  • IN. Firmware fills a buffer, writes the length into bank 1's PCKSIZE and sets EPSTATUS.BK1RDY. The model reads those bytes out of SRAM, hands them to the serial monitor, clears BK1RDY and raises EPINTFLAG.TRCPT1.
  • OUT. Firmware clears EPSTATUS.BK0RDY to say bank 0 is free. The model copies whatever the monitor has typed into bank 0's ADDR, writes the length into its PCKSIZE, sets BK0RDY and raises EPINTFLAG.TRCPT0.

Nothing else about USB is here. There is no host, no bus, no reset, no SETUP packet, no descriptor, no enumeration, no addressing, no frame counter, no 1 ms start-of-frame, no NAK, no STALL and no LPM. INTFLAG never raises and the USB interrupt never reaches the NVIC, so the runtime polls. A sketch that drives this the way the Arduino core drives real silicon will still work (it polls the bank-ready bits, which behave), but a sketch that waits for enumeration will wait forever, so while (!Serial) {} returns at once here and waits on a desk. Keyboard and Mouse, which are the other half of what a native-USB board is for, are not available at all.

There is no baud rate on this channel, and that is not a shortcut: a CDC ACM port has none of its own. Serial.begin(115200) passes a number that would reach the host as a line coding request, and the host is free to ignore it.

3.8 Interrupts

The NVIC is in the core at its architectural address, with two priority bits. The vector table is the SAM D21's: 0 PM, 1 SYSCTRL, 2 WDT, 3 RTC, 4 EIC, 5 NVMCTRL, 6 DMAC, 7 USB, 8 EVSYS, 9..14 SERCOM0..5, 15..17 TCC0..2, 18..22 TC3..TC7, 23 ADC, 24 AC, 25 DAC, 26 PTC, 27 I2S.

Lines this model actually raises: EIC, SERCOM0..5, the ADC, TCC0..2 and TC3..TC7. The USB's is not raised (section 3.7) and neither is anything else's.

4. Host channel

host_write puts bytes in the USB's receive queue; host_read takes the bytes that have gone out of it. That is Serial in a sketch and the serial monitor in the UI. Bytes are UTF-8 as far as the UI is concerned and opaque to the model.

Serial1 is not the host channel. It goes to D6 and D7 and nowhere else, so what a sketch prints there reaches the serial monitor only if something on the breadboard carries it back.

5. Time model and the part

Picoseconds in a u64, like the kernel. The core runs at 48 MHz, so a cycle is 20833.33 ps, not a whole number, which is why time is never accumulated: it is always recomputed from a cycle count as cycles x 1_000_000 / 48. A 10 us slice is exactly 480 cycles and exactly 10,000,000 ps, so slice boundaries never drift. The cycle counter is anchored to kernel time at the start of every slice, so a timer can never drift away from the circuit however the slices fall, and a parked core catches up on the cycles it slept through, SysTick included, which is what millis() counts.

The slice runs in two gears, as the STM32 models do: coarse batches bounded by the next peripheral deadline, and one instruction at a time for 100 us after any store that lands on a PORT, SERCOM, timer or USB register. So a program that touches a pin runs in fine and a program that computes runs in coarse.

probe is a bitmask: 1 running, 2 parked in WFI, 4 the user LED lit, 8 the RX LED, 16 the TX LED, 32 serial transmit in the last 20 ms, 64 serial receive; 0 with no program. The three LED bits are already the right way around: the pads are active low and the part has turned them over.

poke: 1 holds reset, 0 releases it.

6. Firmware runtime (firmware/xiao/)

An Arduino-shaped runtime written for this board: setup() and loop(), pinMode, digitalWrite, digitalRead, shiftOut, analogRead (with analogReadResolution), analogWrite, millis, micros, micros64, delay, delayMicroseconds, attachInterrupt, detachInterrupt, interrupts, noInterrupts, Serial and Serial1. No Atmel Software Framework, no Arduino core, no CMSIS, no newlib, no libgcc and no compiler-rt.

delay() parks: it executes WFI and the next SysTick interrupt wakes it. A parked core retires no instructions and the kernel jumps straight to the wake time, so blink spends its second of simulated time on a thousand interrupts rather than forty-eight million cycles. A delay inside noInterrupts() busy-waits instead, because a parked core with PRIMASK set would never run the handler that advances the clock.

analogWrite is eight bits at 735 Hz (255 counts of the 48 MHz clock divided by 256) on the ten pads that reach a timer. 0 and 255 are not PWM: they hand the pad back to the PORT and drive it low or high, so analogWrite(pin, 0) really is off.

Built with clang --target=thumbv6m-none-eabi -mcpu=cortex-m0plus -mthumb, linked against firmware/xiao/link.ld with ld.lld. Five of the runtime's files are the Raspberry Pi Pico's, compiled again for this board rather than copied: the number formatting, the math helpers, the freestanding floor and Print. Both parts are Cortex-M0+ and nothing in any of those files depends on where a peripheral lives. This board's own are the register header, the pin table, the startup code, the linker script, the two serial classes and the GPIO, analog, time, SERCOM, USB and interrupt drivers. The AEABI division, multiply and shift helpers in crt0.S are the Pico's instruction for instruction, because their register contracts are the ABI's rather than the chip's.

The catalog program string is elf32 arm samd21. It differs from the Pico's elf32 arm rp2040 because the ELF really does: the same instruction set, a different memory map, and an image linked for one will not run on the other.

Four examples: blink (the user LED and one on D10), button (a switch on D2, on EXTINT10), serial (both ports, with a jumper from D6 to D7) and analog (a knob on A10 and a fade on D9). An example is budgeted at 32 KB, and the real ceiling the link script enforces is the 32 KB of SRAM.

7. What this model is sure of, and what it is not

Three groups. This section exists because a board model that quietly guesses an address is worse than one that says it did not know.

Taken from the datasheet. The memory map of section 2: 256 KB of flash at zero, 32 KB of SRAM at 0x2000_0000, and the base addresses of PM, SYSCTRL, GCLK, EIC, PORT, USB, the six SERCOMs, TCC0-2, TC3-7 and the ADC, with the 0x400 strides between instances. The PORT's register layout and its 0x80 group stride, including the OUT-picks-the-pull rule and the WRCONFIG field positions. The SERCOM USART layout and the arithmetic baud formula. The TCC layout. The ADC layout. The EIC layout and its four-bit SENSE fields. The USB device registers, the 0x20 endpoint stride and the sixteen-byte descriptor bank with ADDR and PCKSIZE. The PRESCALER encoding, including the jump from DIV16 to DIV64. The interrupt vector numbers of section 3.8. Two NVIC priority bits.

Believed, not verified. Two things, and a program being taken to real silicon should check both:

  • The TC block's register offsets in section 3.4: CTRLA 0x00, READREQ 0x02, CTRLBCLR/CTRLBSET 0x04/0x05, CTRLC 0x06, DBGCTRL 0x08, EVCTRL 0x0A, INTENCLR/INTENSET 0x0C/0x0D, INTFLAG 0x0E, STATUS 0x0F, COUNT 0x10, PER 0x14, CC 0x18. The TCC's offsets this model can state; the TC's are its best reading of a block whose layout changes with MODE, and they are the numbers firmware/xiao/include/samd21.h builds against, so firmware built here agrees with the model whether or not it agrees with silicon.
  • The peripheral multiplexing table of section 1: which PMUX column on each pad carries which SERCOM pad, which EXTINT line and which waveform output. The pad numbers behind each XIAO pin are the board's and are what firmware depends on; which timer channel a pad can reach is what a PWM sketch depends on, and an error there shows up as a pin that does not fade rather than as a wrong number. crates/atsamd21/src/mux.rs is the whole table in one place.

The generic clock client IDs in samd21.h (GCLK_ID_EIC and the rest) are in the same group. Nothing is gated on them here (section 3.2), so an error in one is invisible in Mokxi and would stop a peripheral dead on a desk.

Deliberately absent. Each of these was left out rather than invented:

  • The clock tree. No DFLL, no oscillators, no PLL, no generators. Section 3.2 says what stands in for it.
  • SPI and I2C on the SERCOMs. Section 3.3 is USART mode and nothing else, so Wire and SPI are not available.
  • The DAC, which is what PA02 is really for, the analog comparators, the PTC and I2S.
  • DMAC and EVSYS. No DMA and no event links between peripherals, which is what the timers' EVCTRL registers would have used.
  • NVMCTRL and flash programming, the watchdog, the RTC and the DSU. Mapped and inert (section 2).
  • The whole of USB above a byte pipe, which section 3.7 sets out at length, and with it Keyboard and Mouse.
  • The UF2 bootloader. An image here starts at zero; a real XIAO's starts at 0x2000 (section 2).

That is why vendor Arduino core binaries do not run here, and firmware built for this document does.