Skip to content

Commit 6e8795f

Browse files
committed
revised text and formatting for CPU guide
1 parent 25671f5 commit 6e8795f

1 file changed

Lines changed: 87 additions & 64 deletions

File tree

doc/guides/advanced_tutorials/porting_cpus.md

Lines changed: 87 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ usually consists of:
1818
- documentation and tests that make the port maintainable.
1919

2020
This document focuses on the CPU side. For the board side, also refer to the
21-
existing RIOT guide on [porting boards](https://guide.riot-os.org/advanced_tutorials/porting_boards/).
21+
existing RIOT guide on [porting boards](/advanced_tutorials/porting_boards/).
2222

2323
:::note
2424
A CPU port defines what the MCU family can do. A board port defines which of
@@ -32,26 +32,26 @@ A typical CPU port has a file structure similar to the following:
3232
Entries marked with `*` are optional and depend on the CPU family and reuse
3333
strategy. Entries without `*` are typically required for a minimal CPU port.
3434

35-
```text
36-
cpu/foo
37-
├── include
38-
├── cpu_conf.h
39-
├── periph_cpu.h
40-
└── vendor*
41-
├── ldscripts*
42-
├── periph*
43-
├── gpio.c
44-
├── i2c.c
45-
├── spi.c
46-
└── ...
47-
├── vectors
48-
└── vectors_<model>.c
49-
├── Makefile
50-
├── Makefile.dep*
51-
├── Makefile.features*
52-
├── Makefile.include
53-
└── cpu.c
54-
```
35+
{% filetree %}
36+
- cpu/foo
37+
- include
38+
- cpu_conf.h
39+
- periph_cpu.h
40+
- vendor*
41+
- ldscripts*
42+
- periph*
43+
- gpio.c
44+
- i2c.c
45+
- spi.c
46+
-
47+
- vectors
48+
- vectors_<model>.c
49+
- Makefile
50+
- Makefile.dep*
51+
- Makefile.features*
52+
- Makefile.include
53+
- cpu.c
54+
{% /filetree %}
5555

5656
Not every subdirectory is mandatory; use the `*` markers above to distinguish
5757
optional components.
@@ -61,7 +61,7 @@ optional components.
6161
A practical CPU porting workflow usually looks like this:
6262

6363
1. create the CPU directory and build-system files,
64-
2. add startup support and a working `cpu_init()`,
64+
2. add startup support and a working `cpu_init()` function,
6565
3. provide CPU and peripheral configuration headers,
6666
4. add the interrupt vector table,
6767
5. implement the required peripherals one by one,
@@ -72,12 +72,13 @@ The following sections describe the individual files and their purpose.
7272

7373
## Build System Files
7474

75-
### `Makefile`
75+
### Makefile
7676

7777
The top-level CPU `Makefile` defines the CPU module and may include common
78-
subdirectories that are shared with other CPUs.
78+
subdirectories that are shared with other CPUs. `$(RIOTCPU)` is the `cpu/`
79+
subdirectory of the RIOT basefolder `$(RIOTBASE)`.
7980

80-
Example:
81+
#### Example:
8182

8283
```makefile
8384
MODULE = cpu
@@ -86,13 +87,13 @@ DIRS = $(RIOTCPU)/cortexm_common periph
8687
include $(RIOTBASE)/Makefile.base
8788
```
8889

89-
### `Makefile.dep`
90+
### Makefile.dep
9091

9192
This file expresses build-time dependencies for the CPU. It is the right place
9293
for conditional module additions that depend on the selected CPU model,
9394
`USEMODULE`, or board features.
9495

95-
Example:
96+
#### Example:
9697

9798
```makefile
9899
include $(RIOTCPU)/cortexm_common/Makefile.dep
@@ -102,13 +103,13 @@ ifneq (,$(filter periph_uart,$(USEMODULE)))
102103
endif
103104
```
104105

105-
### `Makefile.features`
106+
### Makefile.features
106107

107108
This file declares what the CPU provides to the rest of RIOT by defining the
108109
CPU architecture, including common family feature files as well as defining
109-
the implemented peripherals with `FEATURES_PROVIDED`.
110+
the implemented peripherals with `FEATURES_PROVIDED` in alphabetical order.
110111

111-
Example:
112+
#### Example:
112113

113114
```makefile
114115
CPU_CORE = cortex-m4f
@@ -124,27 +125,32 @@ Only advertise features that are actually implemented and usable. Since the
124125
availability of some features can depend on the specific `CPU_MODEL`,
125126
conditional statements might be required here as well.
126127

127-
### `Makefile.include`
128+
### Makefile.include
128129

129-
This file contains low-level build configuration for the CPU.
130+
This file contains low-level build configuration and additional compiler and
131+
linker search paths for the CPU.
130132

131-
Example:
133+
#### Example:
132134

133135
```makefile
134136
INCLUDES += -I$(RIOTCPU)/foo/include
137+
INCLUDES += -I$(RIOTCPU)/vendor/include
135138
LINKER_SCRIPT = $(RIOTCPU)/foo/ldscripts/foo.ld
136139
VECTORS_O = $(CPU_OBJ)/vectors/vectors_$(CPU_MODEL).o
137140
```
138141

139142
For Cortex-M devices, this file often also ties the CPU port to the generic
140-
`cortexm_common` startup and linker support.
143+
`cortexm_common` startup and linker support. The same pattern is common on
144+
other architectures as well (for example `riscv_common` for RISC-V based
145+
ports).
141146

142147
## Core CPU Files
143148

144-
### `cpu.c`
149+
### cpu.c
145150

146-
`cpu.c` contains `cpu_init()`, which performs the minimum hardware and runtime
147-
initialization needed before RIOT starts normal execution.
151+
The `cpu.c` file contains the `cpu_init()` function, which performs the
152+
minimum hardware and runtime initialization needed before RIOT starts its
153+
normal operation.
148154

149155
A minimal shape often looks like this:
150156

@@ -167,7 +173,7 @@ Refer to the Technical Reference Manual of the target CPU for details on
167173
implementing the boot process.
168174
:::
169175
170-
### `vectors/`
176+
### vectors/
171177
172178
This directory contains the interrupt vector table for the supported CPU model.
173179
Without a correct vector table, the CPU will usually fault immediately after
@@ -179,9 +185,9 @@ Typical responsibilities:
179185
- define the `Reset_Handler`,
180186
- provide weak default handlers for all supported interrupts,
181187
- match the IRQ numbering expected by
182-
`CPU_IRQ_NUMOF`.
188+
`CPU_IRQ_NUMOF` defined in `include/cpu_conf.h`.
183189
184-
### `ldscripts/`
190+
### ldscripts/
185191
186192
This directory holds CPU-specific linker scripts if the CPU cannot directly use
187193
an existing common linker script.
@@ -192,7 +198,7 @@ Typical responsibilities:
192198
- place sections such as `.text`, `.data`, `.bss`, interrupt vectors, and stack,
193199
- cooperate with RIOT's startup assumptions.
194200
195-
### `include/cpu_conf.h`
201+
### include/cpu_conf.h
196202
197203
This header provides CPU-wide configuration.
198204
@@ -219,8 +225,10 @@ Example structure:
219225
The exact contents depend on the architecture and MCU family, but this file is
220226
usually where RIOT learns which vendor header to use and how many interrupts the
221227
CPU exposes.
228+
Remember that only vendor headers included in the search paths defined by
229+
the `Makefile.include` can be found by the compiler!
222230

223-
### `include/periph_cpu.h`
231+
### include/periph_cpu.h
224232

225233
This header provides CPU-specific peripheral types and helper macros.
226234

@@ -264,7 +272,7 @@ The exact contents depend on the CPU family, but this file usually contains the
264272
CPU-local building blocks that make the generic RIOT peripheral API usable for a
265273
specific MCU implementation.
266274

267-
### `include/vendor/`
275+
### include/vendor/
268276

269277
Vendor HAL headers can be placed here or integrated via RIOT's package
270278
mechanism. Vendor SDKs can range from thousands to millions of lines of code,
@@ -279,7 +287,7 @@ EFM32, which keep vendor content modular and reused across multiple boards.
279287
The CPU should be added to `features.yaml` so the build system can resolve the
280288
feature name consistently.
281289

282-
Example:
290+
#### Example:
283291

284292
```yaml
285293
- title: Example MCU Grouping
@@ -326,7 +334,8 @@ The GPIO driver provides the basic digital pin interface for RIOT.
326334

327335
- `gpio_init()` for input/output modes,
328336
- `gpio_init_int()` for edge-triggered interrupts,
329-
- `gpio_read()`, `gpio_set()`, `gpio_clear()`, `gpio_toggle()`,
337+
- `gpio_read()`, `gpio_set()`, `gpio_clear()`, `gpio_toggle()` for discrete
338+
pin state manipulation,
330339
- `gpio_irq_enable()` and `gpio_irq_disable()` if interrupt support is enabled,
331340
- pin-to-port decoding helpers if the CPU uses packed GPIO identifiers.
332341

@@ -341,7 +350,8 @@ The GPIO driver provides the basic digital pin interface for RIOT.
341350
- preload the output latch if needed before switching a pin to output mode,
342351
- configure interrupt trigger type and store callback context for
343352
`gpio_init_int()`,
344-
- dispatch the registered callback from the ISR.
353+
- dispatch the registered callback from the ISR. Keep the ISR short. It should
354+
typically acknowledge the interrupt and call the registered callback.
345355

346356
### Example register-level init sketch
347357

@@ -359,25 +369,23 @@ int gpio_init(gpio_t pin, gpio_mode_t mode)
359369
}
360370
```
361371

362-
Keep the ISR short. It should typically acknowledge the interrupt and call the
363-
registered callback.
364-
365372
## UART
366373

367374
**RIOT API:** `drivers/include/periph/uart.h`
368375

369-
UART is used for standard I/O, shell access, and device communication. In RIOT,
370-
receive handling is interrupt-driven.
376+
The UART is used for standard I/O, shell access, and device communication.
377+
In RIOT, receive handling is interrupt-driven.
371378

372-
As a side note, UART setup usually reuses parts of the GPIO implementation:
373-
pin muxing, GPIO direction defaults, and optional RTS/CTS handling all rely on
379+
As a side note, the UART setup usually reuses parts of the GPIO implementation:
380+
Pin muxing, GPIO direction defaults, and optional RTS/CTS handling all rely on
374381
the underlying pin configuration support being correct.
375382

376383
### Typical functionality to implement
377384

378-
- `uart_init()`,
379-
- `uart_write()`,
380-
- `uart_poweron()` and `uart_poweroff()`,
385+
- `uart_init()` for the peripheral initialization,
386+
- `uart_write()`, `uart_read()` for input and output,
387+
- `uart_poweron()` and `uart_poweroff()` for enabling and disabling the
388+
peripheral,
381389
- optional helpers such as reconfiguration or RX start interrupt support if the
382390
corresponding modules are enabled.
383391

@@ -393,7 +401,7 @@ the underlying pin configuration support being correct.
393401
- unmask the peripheral interrupt and connect it to the NVIC if receive support
394402
is enabled,
395403
- enable RX/TX and keep `uart_poweroff()` and `uart_poweron()` transparent to
396-
upper layers.
404+
the upper layers.
397405

398406
### Example register-level init sketch
399407

@@ -421,9 +429,17 @@ external RIOT API should remain unchanged.
421429

422430
**RIOT API:** `drivers/include/periph/timer.h`
423431

424-
Timers are foundational in RIOT because higher layers such as `ztimer` and
425-
scheduler services depend on them. A reliable timer driver is therefore one of
426-
the most important parts of a CPU port.
432+
Timers are foundational in RIOT because higher level modules such as `ztimer`
433+
and scheduler services depend on them. A reliable timer driver is therefore one
434+
of the most important parts of a CPU port.
435+
436+
Most Cortex-M MCUs expose multiple timer classes: core `SysTick`, general
437+
purpose timers (often 16-bit or 32-bit), and sometimes low-power timers running
438+
from a slower always-on clock. For `periph_timer`, prefer a dedicated general
439+
purpose timer, compare/interrupt support, and enough width for the expected
440+
timing range. `SysTick` is usually better kept for core/system use and is often
441+
too limited (single instance, 24-bit counter, core-clock coupling) for robust
442+
peripheral timing.
427443

428444
### Typical functionality to implement
429445

@@ -473,8 +489,8 @@ Mistakes here often break `ztimer`, sleeping, or scheduling in subtle ways.
473489
**RIOT API:** `drivers/include/periph/spi.h`
474490

475491
RIOT's SPI API is transaction-based because an SPI bus is a shared resource.
476-
This means the driver is expected to configure and power the peripheral around
477-
`spi_acquire()` / `spi_release()`.
492+
This means the peripheral driver is expected to configure and power the
493+
peripheral within `spi_acquire()` / `spi_release()`.
478494

479495
GPIO support is also important here: SPI buses reuse GPIO handling for pin mux
480496
setup, optional manual chip-select lines, and some reconfiguration paths.
@@ -500,6 +516,10 @@ setup, optional manual chip-select lines, and some reconfiguration paths.
500516

501517
### Generic high-level shape
502518

519+
The following snippet is an example of how the SPI API is called from
520+
application or device driver code. It is not CPU-port implementation code
521+
from `cpu/<cpu>/periph/spi.c`.
522+
503523
```c
504524
spi_init(bus);
505525
spi_init_cs(bus, cs);
@@ -575,16 +595,19 @@ Bring up the CPU with the smallest possible configuration first:
575595
- boot to `cpu_init()`,
576596
- confirm the vector table works,
577597
- get `examples/basic/hello-world` running over UART,
598+
- if UART output is missing, toggle an LED at key checkpoints in startup and
599+
`cpu_init()` to localize where execution stops,
578600
- validate one timer instance so `ztimer` works,
579601
- then implement the remaining peripherals incrementally.
580602

581603
### Reuse common code
582604

583605
If the MCU belongs to an already supported architecture or family, reuse the
584-
existing common code where possible. For this case, a base directory
606+
existing common code where possible. In this case, a base directory
585607
(e.g. `cpu/<CPU_FAM>_common`) should be created that contains all shared/common
586608
configurations. This drastically reduces maintenance and lowers the risk of
587-
subtle startup or interrupt bugs.
609+
subtle startup or interrupt bugs, as well as code divergation issues in the
610+
future.
588611

589612
### Refer to the manual of the target CPU
590613

0 commit comments

Comments
 (0)