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:
D0has 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.D4has no ordinary external interrupt. PA08 is the chip's NMI pin.attachInterrupt(4, ...)does nothing, here and on a desk.D5andD7shareEXTINT9, so only one of the two can carry an interrupt at a time; the secondattachInterrupttakes the line from the first. And because TCC1 and TCC2 have two compare registers each, some pins share a duty: writinganalogWriteon one ofD1/D4,D9/D5,D10/D2orD8/D3moves 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.DFLLSYNCreads 0. Sowhile (!(SYSCTRL->PCLKSR.reg & DFLLRDY)) {}falls straight through. - GCLK (
0x4000_0C00):CTRLat0x00(SWRSTbit 0),STATUSat0x01(SYNCBUSYbit 7, never set),CLKCTRLat0x02,GENCTRLat0x04,GENDIVat0x08. 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):AHBMASK0x14,APBAMASK0x18,APBBMASK0x1C,APBCMASK0x20, all stored and read back at their reset values, andRCAUSEat0x38, 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
PCKSIZEand setsEPSTATUS.BK1RDY. The model reads those bytes out of SRAM, hands them to the serial monitor, clearsBK1RDYand raisesEPINTFLAG.TRCPT1. - OUT. Firmware clears
EPSTATUS.BK0RDYto say bank 0 is free. The model copies whatever the monitor has typed into bank 0'sADDR, writes the length into itsPCKSIZE, setsBK0RDYand raisesEPINTFLAG.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:
CTRLA0x00,READREQ0x02,CTRLBCLR/CTRLBSET0x04/0x05,CTRLC0x06,DBGCTRL0x08,EVCTRL0x0A,INTENCLR/INTENSET0x0C/0x0D,INTFLAG0x0E,STATUS0x0F,COUNT0x10,PER0x14,CC0x18. The TCC's offsets this model can state; the TC's are its best reading of a block whose layout changes withMODE, and they are the numbersfirmware/xiao/include/samd21.hbuilds against, so firmware built here agrees with the model whether or not it agrees with silicon. - The peripheral multiplexing table of section 1: which
PMUXcolumn on each pad carries which SERCOM pad, whichEXTINTline 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.rsis 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
WireandSPIare 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'
EVCTRLregisters 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.