Outils › Cartes

Outils

Cette page n’est pas encore traduite : voici la version anglaise.

Boards: the store and your own collections

A project is usually built on boards someone else designed: an ESP32 devkit, an STM32 Nucleo, a Raspberry Pi, a Jetson. archec ships a store of known boards (arche-hw-store), and you keep your own in collections of the same shape. A board says what it is, the processors on it (what archec gen --target embassy builds for), and its connectors with the pins they are wired to.

Using a board#

archec board list                          # the store, and your collections
archec board show nucleo-g474re            # processors, connectors, pins
archec board add main.arche nucleo-g474re --as ctl

board add writes three things, and keeps them only if the project builds as well as before:

project Rover {
  uses arche-hw-store "^0.1"               // the store, built into archec
}
include "arche-hw-store/boards/nucleo-g474re/nucleo-g474re.arche"

system RoverHw "Hardware" {                // the deployment's platform, or this one
  part ctl: NucleoG474RE
}

A binary on the board runs on its processor; a board with more than one (the Kria KR260: apu, rpu) asks which:

deployment D {
  system Rover
  platform RoverHw
  binary fw on ctl { thread t { runs controller } }      // ctl.mcu, the STM32G474RE
  route controller.bus -> motor.bus over can_bus { id 0x120 }
}

The bus is linked to the board's connector (ctl.can0 -> can_bus), and archec gen --target embassy drives the CAN controller on the board's pins — FDCAN1 on PA11/PA12 here, an MCP2515 on the nRF52840 DK's Arduino header (P1_15, P1_13, P1_14, CS P1_12, INT P1_03) — instead of its defaults.

--copy copies the board's folder into components/ instead, when you want to change it; --into <System> picks the system; --as names the part.

What a board is#

/// ST NUCLEO-G474RE: an STM32G474RE (Cortex-M4F with FDCAN) on a Nucleo-64.
component NucleoG474RE "NUCLEO-G474RE" {
  kind board
  description "One 170 MHz Cortex-M4F, 128 KB SRAM, three FDCAN controllers…"
  product { vendor "STMicroelectronics"; part-number "NUCLEO-G474RE"; url "https://…" }
  component mcu "STM32G474RE" {
    kind processor
    compute { cores 1; core-type "Cortex-M4F"; hal stm32; chip "stm32g474re"; freq 170MHz; ram 128KiB; os bare-metal }
  }
  inout can0: can "FDCAN1, CAN FD (add a transceiver)" { pins { tx PA12; rx PA11 } }
  inout uart2: uart "USART2 (ST-LINK virtual COM)" { pins { tx PA2; rx PA3 } }
  in pwr: power "5 V USB"
}
  • kind board is built in, like processor and bus.
  • product { vendor; part-number; url } is what you would order.
  • A processor says hal and chip (bare metal: what the Embassy crate builds for) or os (Linux: a host crate).
  • pins { role pin; … } on a connector names the chip's pins, as the HAL crate does: GPIO4 (esp32), PA11 (stm32), PIN_16 (rp), P1_15 (nrf). The roles archec reads are tx, rx for a CAN controller and sck, mosi, miso, cs, int for an SPI one (the MCP2515), plus sda, scl.

A board folder is a component folder: it uses only what it declares, the built-in port types and the standard library, so it works where it is and as a copy.

Pin planning#

archec pins main.arche
Hw.ctl — NUCLEO-G474RE · STM32G474RE (stm32)
  PA2   can0 tx   -> can_bus  (the part's)
  PA2   uart2 tx  -> esp.uart0
  PA3   uart2 rx  -> esp.uart0
  PA11  can0 rx   -> can_bus

prints each board's pins: the port and role on each, where the port is linked, and whether the part wrote it (part ctl: NucleoG474RE { inout can0 { pins { tx PA2 } } } over the board's PA12). Then come the pin plan's warnings, which archec check and the editor give too:

  • pin-conflict — one pin used by two ports of a board, among those the system links or whose pins a part writes (a board's connectors are alternatives until then: the Pico's SPI0 and its MCP2515 share pins), or by two roles of one port. A component that extends a board is told when a pin it writes is one of its board's connectors';
  • pin-function — a pin its chip cannot use so: on the RP2040, UART TX only on GPIO 0, 4, 8…, RX on 1, 5, 9…, I2C SDA on even pins, SPI SCK/MOSI/MISO on their own, and one port's pins on one peripheral (TX on UART1, RX on UART0 is wrong); on the ESP32, GPIO34–39 are inputs, and the pins wired to the module's flash (GPIO6–11; 12–17 on the C3, 26–32 on the S3) are taken. The STM32 and nRF chips route their functions through tables (or any pin) archec does not hold: nothing is said there;
  • pin-pair — a link whose ends do not pair: tx meets the other end's rx (SCK, MOSI, MISO, SDA, SCL meet the same role), and a CAN controller on a bus needs both tx and rx;
  • pin-level — a link between ports whose pins { level … } differ: pins { tx PIN_0; rx PIN_1; level 3.3V } on one side, level 5V on the other. level is not a pin; the store's boards do not say theirs yet, so write it on the part (or in your own board) where it matters.

Zephyr devicetree#

archec gen main.arche --target devicetree -o generated

writes a devicetree overlay for each binary of the deployment that runs on a board's microcontroller, named as west names the board (generated/devicetree/steer_fw/boards/nucleo_g474re.overlay): copy its boards/ into the Zephyr application. In it, each CAN, UART, I2C and SPI connector of the board that the platform links (steer_mcu.can0 -> chassis_can):

/ {
	aliases {
		arche-chassis-can = &fdcan1;
	};
	chosen {
		zephyr,canbus = &fdcan1;
	};
};

/* steer_mcu.can0 — FDCAN1, CAN FD (add a transceiver): tx PA12, rx PA11 */
&fdcan1 {
	status = "okay";
	pinctrl-0 = <&fdcan1_tx_pa12 &fdcan1_rx_pa11>;
	pinctrl-names = "default";
	bitrate = <500000>;
};
  • the pins are the port's (a part's own pins over the board's), as each HAL names them: hal_stm32's pinctrl nodes, NRF_PSEL(…) groups, the RP2040 and ESP32 pinmux macros. The peripheral is the one the connector's name says (USART2, FDCAN1, TWAI0), on the RP2040 the one its pins belong to;
  • the speed is the link's bitrate, else the bus's: bitrate for CAN (1 Mbit/s at most, a CAN FD bus adds bitrate-data), current-speed for a UART, clock-frequency for I2C;
  • a CAN on SPI (the nRF52840 DK's and the Pico's MCP2515) is an mcp2515 node under the SPI, its CS and INT as GPIOs; an I2C device linked to the board is a node at its address, the compatible yours to add;
  • each bus has an alias, arche-<bus>: under Zephyr (__ZEPHYR__), the drivers.c of archec gen --target c sends a CAN or UART bus's frames on DEVICE_DT_GET(DT_ALIAS(arche_<bus>)); its main.c returns and leaves the threads running. The C's platform layer is arche_rt_posix.c on Zephyr's POSIX API (CONFIG_POSIX_API, in the prj.conf written beside the overlay, once).

The overlays are checked with dtc in the tests, against stand-ins for the board's own nodes; they have not been built with west against a Zephyr tree.

KiCad netlist#

archec kicad main.arche [CarHw] -o car.net

writes the wiring of a system (the one named, else the deployment's platform) as a KiCad netlist, which pcbnew reads (File → Import → Netlist) or eeschema compares with a schematic:

  • each part whose ports carry signals is a symbol with no footprint: a board is A1, A2…, anything else U1… (what is on a board is the board's). Its pins are its ports' signals: the board's pins by role (uart2.TX/PA2), else the port type's wires (wire canh "CAN_H"), else the usual ones of a built-in type (CAN_H/CAN_L, TX/RX, SDA/SCL, SCK/MOSI/MISO/CS, V+/GND). A part whose pins are its own (part pico: RaspberryPiPico { inout i2c0 { pins { … } } }) gets a symbol of its own;
  • a link is a net per signal: TX to RX crossed (named after the TX end, pico_uart0_TX), SDA to SDA; everything linked to a kind bus component shares its nets (chassis_can_CAN_H), and two TX/RX ends on one bus cross; every GND is one net;
  • a CAN connector with controller pins (tx/rx, or an MCP2515's SPI) puts CAN_H/CAN_L on the bus side, and a note says the transceiver goes between.

archec kicad checks its output by reading it back in the tests (every net's pins exist, no pin is on two nets); KiCad itself (kicad-cli) was not run on it.

Your own collection#

archec board new carrier-v2 --in ~/arche-boards --from nucleo-g474re --name "Carrier v2"
archec board check ~/arche-boards
archec board extract main.arche RoverHw.ctl --in ~/arche-boards   # a board the model defines

A collection is a library: a folder with a library.arche (board new makes it) and boards/<id>/<id>.arche, with pictures in boards/<id>/assets/. A project sees it through libraries "<folder>" (board add writes it), and every project on the machine does when it is in ARCHE_BOARDS (a path list) — the VS Code setting arche.boardCollections sets that for the editor and its language server. arche.lock records its version and fingerprint like any library's.

board check builds each board on its own and checks that its processors say hal/chip or os, that its pins are pins of its chip, and that its picture is there.

In VS Code#

The Boards view of the Arche side bar lists the boards the model uses (click: go to the part), the store, and your collections:

  • on a board: Add to Model… (from the collection or as a copy, into which system), Show Board Details (processors, connectors, pins), Open Definition, Duplicate to My Collection…;
  • on a collection: New Board…, Check Board Collection (the Problems view), Reveal Collection Folder;
  • in the title bar: add, filter (name, vendor, chip, HAL, core), refresh, New Board…, Add Board Collection…;
  • on a component of the Model view: Save as Board in My Collection…;
  • drag a board from the view into a .arche editor: part esp32_c3_devkitm_1: Esp32C3DevKitM1 is written where it is dropped (the part's name selected, to change it), in a new system <Project>Hw "Hardware" when it is dropped outside any block, and the board's include and the project's uses are written as board add writes them. Dropped in another editor, it is the part line as text.

The editor knows the boards too:

  • completion after part x: (and extends) offers the boards of the store and of your collections, with vendor and part number; choosing one the model does not include yet also writes its include (and uses arche-hw-store "^0.1", or libraries and uses for a collection of yours, in the file that has the project block). In pins { … } on a port it offers the roles, each with the pin the board gives it on a port of that type (tx PA12), then, after a role, the board's pins with what they are wired to (PA11 — can0 rx, FDCAN1), the one of the same role and port type first. In a part of a board (part ctl: NucleoG474RE { … }, or a component that extends one) the board's connectors come with their type, name and pins, and choosing one writes it with its pins, to change them: inout uart2 { pins { tx PA2; rx PA3 } }.
  • hover on a board's name, or on a part of one, shows its vendor, part number and URL, its processors (core, clock, RAM, hal and chip), its connectors with their pins, and the collection it comes from; on a pin in pins { … }, the chip it is on and what the board wires to it (PA11 — can0 rx, FDCAN1); on a role, what it is for.
  • a pin that the board's chip does not name so, on a board, a part of one or a component that extends one, is a warning (board-pin, also in archec check), with the nearest pin as a quick fix and the pins the board's connectors use: NUCLEO-G474RE has no pin 'PB99': its STM32G474RE (stm32) names them like PA11. Did you mean 'PB9'?. So is a role archec does not read (tz). The check knows how each HAL names its pins, not each chip's pinout: PK15 passes on a chip that has no port K.

The store#

Board Processor Builds for CAN
ESP32-C3-DevKitM-1 ESP32-C3, RISC-V hal esp32, esp32c3 TWAI on GPIO2/GPIO3
ESP32-S3-DevKitC-1 ESP32-S3, 2× Xtensa LX7 hal esp32, esp32s3 TWAI on GPIO4/GPIO5
ESP32-DevKitC ESP32, 2× Xtensa LX6 hal esp32, esp32 TWAI on GPIO21/GPIO22
NUCLEO-G474RE STM32G474RE, Cortex-M4F hal stm32, stm32g474re FDCAN1 on PA12/PA11
NUCLEO-F446RE STM32F446RE, Cortex-M4F hal stm32, stm32f446re CAN1 on PA12/PA11
NUCLEO-H743ZI2 STM32H743ZI, Cortex-M7 hal stm32, stm32h743zi FDCAN1 on PD1/PD0
Raspberry Pi Pico, Pico W RP2040, 2× Cortex-M0+ hal rp, rp2040 MCP2515 on SPI0
nRF52840 DK nRF52840, Cortex-M4F hal nrf, nrf52840 MCP2515 on the Arduino header
Raspberry Pi 4, Pi 5 BCM2711 / BCM2712 Linux (std) an MCP2515 HAT, SocketCAN
Jetson Orin Nano Developer Kit 6× Cortex-A78AE Linux (std) —
Kria KR260 K26: 4× Cortex-A53, 2× Cortex-R5F, PL Linux (apu), FreeRTOS (rpu) —
Raspberry Pi 4 and 5 (Go driver) BCM2711 / BCM2712 Linux (Go: raspi.NewAdaptor) —
Jetson Nano Developer Kit Tegra X1, 4× Cortex-A57 Linux (Go: jetson.NewAdaptor) —
BeagleBone Black, PocketBeagle AM3358, Cortex-A8 Linux (Go: beaglebone, pocketbeagle) —
ASUS Tinker Board, Tinker Board 2 RK3288 / RK3399 Linux (Go) —
FriendlyELEC NanoPi NEO, NanoPC-T6 Allwinner H3 / RK3588 Linux (Go) —
Orange Pi 5 Pro RK3588S Linux (Go) —
PINE64 ROCK64, Radxa ROCK Pi 4, Radxa Zero RK3328 / RK3399 / S905Y2 Linux (Go) —

None of these boards has a CAN transceiver: wire one between the pins and the bus.

In Renode#

archec gen main.arche --target renode --deployment D runs a deployment's microcontrollers in Renode (1.17), without the boards: a machine per processor with a binary, Renode's own description of its chip, the binary's ELF loaded, a CANHub per CAN bus and a UARTHub per UART link of the platform. archec board show says it per processor (Renode: …).

Chip (boards) Renode platform CAN Notes
stm32h743zi (NUCLEO-H743ZI2) platforms/cpus/stm32h743.repl FDCAN1 (MCAN) Renode's RCC does not keep FDCANSEL: the .resc makes it read back PLL1_Q, the clock the generated main.rs picks
stm32f446re (NUCLEO-F446RE) platforms/cpus/stm32f4.repl CAN1 (bxCAN) an STM32F407: more flash and RAM, the same CAN and USARTs at the same addresses
nrf52840 (nRF52840 DK) platforms/cpus/nrf52840.repl — Renode has no MCP2515: the board's CAN is not on the bus
stm32g474re (NUCLEO-G474RE) — no STM32G4 in Renode
rp2040 (Pico, Pico W) — no RP2040 in Renode
esp32, esp32c3, esp32s3 — no Espressif chip in Renode
Linux processors (Pi, Jetson, Kria's APU…) — not emulated: their binary runs on a host; the .resc has a SocketCAN bridge for it, commented (Linux hosts)

A board's CAN or UART port is the Renode peripheral its text names ("FDCAN1, CAN FD…", "USART2 (ST-LINK virtual COM)"); a CAN port that names none is the first controller, the one the generated firmware drives. A UART that names none is said, not guessed.

archec gen main.arche --target embassy --deployment Bench -o out
archec gen main.arche --target renode --deployment Bench -o out
(cd out/embassy/brake_fw && cargo build --release)
renode out/renode/Bench/Bench.resc              # the machines, running
renode-test out/renode/Bench/Bench.robot        # the checks

The ELF paths are variables at the top of machines.resc ($brake_fw), for a firmware built elsewhere or in C. The Robot suite checks that each binary that talks on a bus has started its CAN controller, and, for each test case, what Renode can see: a send from a part it does not run is put on the bus (encoded as the codec lays the message out) and must be read by the firmware; an expect sent("MSG") counts the frames the machines sent with that message's CAN id. A part's state, inside the firmware, is not checked there. examples/car-bench is the car's actuator firmware run this way.

Devices and their drivers#

The store also has devices, one folder each under devices/ (87 of them: sensors, displays — below —, motors, LEDs, ADCs…), and the Linux boards above have their driver in Go under boards/<board>/drivers/go/. Both come from Gobot v2.6.0 (Apache-2.0, see LICENSE-gobot and NOTICE), adapted so they import nothing of Gobot:

/// BME280 temperature, pressure and humidity sensor.
component Bme280 "BME280" {
  kind device
  product { vendor "Bosch Sensortec"; part-number "BME280"; driver "i2c.NewBME280Driver" }
  inout i2c: i2c "I²C" { address 0x77 }
  fn temperature -> float32 "Temperature (°C)"
  fn humidity -> float32 "Humidity (%RH)"
}
  • driver names the device's Go constructor in devices/go/<family>/ (i2c, spi, gpio, aio, onewire); a board's names its adaptor.
  • address is the device's I²C address. Two devices at the same address on one bus is an error (bus-address); a part can move one (part env2: Bme280 { inout i2c { address 0x76 } }).
  • A Linux board exposes each header pin its driver knows as a port (pin11, or p8_07 on a BeagleBone), so a device's pin is linked to the board's:
system Bench {
  part pi: RaspberryPi4
  part env: Bme280
  part led: Led
  pi.i2c1 -> env.i2c
  pi.pin11 -> led.pin
}

The Go code lives in one module, archehw (lib/arche-hw-store/go.mod): the runtime in runtime/go/ (Gobot's interfaces as archert, the Linux access for I²C, SPI, GPIO, PWM, analog and 1-Wire), the device drivers in devices/go/, the boards' drivers beside each board. It needs nothing outside Go. Gobot's own tests come with it (go test ./... in lib/arche-hw-store; npm test runs them).

Two tools keep it in step with Gobot:

go run ./tools/gobot-port <gobot checkout> lib/arche-hw-store "v2.6.0 (798be8c)"   # the Go code
go run ./tools/gobot-catalog lib/arche-hw-store                                     # devices/ and the boards' .arche

The vendors, part numbers, units and boards' processors are not in Gobot's code: they are in tools/gobot-catalog/known.go and known_boards.go. The Raspberry Pi adaptor was written before the Pi 5 and has not been tried on one.

Displays#

The screens of the store say what a ui on them takes: their hardware {display: {…}} — the size in pixels, the density, the input, the controller.

Device Display Input Bus
ili9341 2.8" 320×240, 143 dpi, RGB565 touch (XPT2046) SPI, DC, reset, backlight, touch IRQ
st7789 2.0" 240×320, 200 dpi keys SPI, DC, reset, backlight
st7789-240x240 1.3" 240×240, 261 dpi keys SPI, DC, reset, backlight
ssd1306, spi-ssd1306 0.96" 128×64 mono, 149 dpi keys I²C 0x3C, SPI (Gobot's drivers)
hdmi-display 1920×1080, 92 dpi pointer HDMI, USB
archec device list --displays
archec device add main.arche ili9341 --into RoverHw

A component with a ui { } and no display of its own takes the one display part beside it — in a system it is a part of, else in the platform of a deployment of that system (archec ui says "from the hardware"); the ui's own input wins over the display's. A display of your own is a component with the same record:

component MyPanel "7\" panel" {
  kind device
  hardware {display: {width: 1024, height: 600, dpi: 170, input: "touch"}}
  in hdmi: hdmi
}

archec gen --target ui-lvgl and ui-slint write the ui for it (ui). The SSD1306's record is in tools/gobot-catalog/known.go, so the catalog writes it again.