Shield template properties
Every shield,* and plug,* property a .shield file may declare —
what it means, whether it may be absent, and what refuses it if it is
misused. The authority is the loader itself,
scripts/rigc/loader/shields.py; every example below is copied verbatim (only
re-indented for this page) from a real .shield file in this tree.
This page is reference, not narrative — Writing a shield template
teaches the concept; this page looks up the facts. See
Board socket properties for the socket,* vocabulary a shield’s bus proxies
and exposed sockets resolve against.
Note
Every node a rig, or another part of the same shield, may reference by
name — a device, a pad, a strap, a jumper, an exposed socket — must
carry a DTS label. A node with none is refused
(lang-shield-label, naming the node and what it needs) rather than
silently falling back to its node name: rig-facing references
(config:/wires:/socket:) resolve by label only.
Declaring the connector
A shield names the connector type it plugs on the plug node itself, one per plug. There is one shape, whether the shield has one plug or four: plug plurality is a count, never a separate authored form.
compatible = "shield,plug" / shield,plugs
- Where:
a child node of the template root, one per plug the shield declares.
- Type:
the child’s
compatibleis the literal string"shield,plug"; its ownshield,plugsproperty is a string naming that plug’s connector type ("arduino-r3","grove","mikrobus","i2c-port").- Required or optional:
at least one is required — a template with no
shield,plug-compatible child declares no connector at all and is refused. The child node’s own name is the slot name other properties reference it by;plugis the conventional name for a shield with one and is reserved for nothing.- Refuses:
lang-shield-plug(noshield,plugchild at all; also ashield,plugson the TEMPLATE node, the retired spelling — the message says where the property moved);lang-shield-type(a plug declares noshield,plugsof its own, or names a connector type the registry does not know).
Example — boards/shields/adafruit_data_logger/adafruit_data_logger.shield
(one plug):
dl_plug: plug {
compatible = "shield,plug";
shield,plugs = "arduino-r3";
};
Example — boards/shields/can_span_click/can_span_click.shield (two
mikroBUS plugs on one shield, declared exactly the same way):
left_plug: left {
compatible = "shield,plug";
shield,plugs = "mikrobus";
};
right_plug: right {
compatible = "shield,plug";
shield,plugs = "mikrobus";
};
The plug node
- Where:
a child of the template root — one per plug, named whatever the shield calls that slot.
- Type:
not a property but a node: the plug itself, the position reference frame every
gpios/pwms/io-channelsproperty on this shield’s devices resolves a phandle against, and the parent of this plug’s own bus groups (see “Where a group goes” below).- Required or optional:
at least one, by construction — a plug node is what a
shield,plug-compatible child is.- Refuses:
lang-shield-plug-cells— a plug node may not declare#gpio-cells/#pwm-cells/#io-channel-cells. A position reference through a plug carries the generic count for its function (2 for gpio, 3 for pwm, 1 for adc,scripts/rigc/loader/shields.py::_FUNCTION_DEFAULT_CELLS), and so does the parent side of an exposed socket’sgpio-map/pwm-map/io-channel-maprow. Only a node whose arity genuinely differs says so — a routing jumper, which supplies the position itself and therefore declares#gpio-cells = <1>.
Where a group goes
One placement rule, at any plug count:
Bus groups nest under their owning plug. A group named after one of
that plug’s plug,bus-proxies (i2c/spi/uart, bare or
role-qualified) is a child of the plug node. The nesting is what
distinguishes two same-kind buses on a shield with two plugs, and it is
also what gives each plug’s bus its own chip-select pool namespace. A
bus-shaped group at template level is refused
(lang-shield-proxy, naming the plugs it could have nested under).
Plain groups stay at template level. A group that is not a bus proxy
(gpio, pwm, adc, or any other non-reserved name) is
plug-agnostic: its devices’ own references each name the plug they
resolve through, by phandle. Nesting one under a plug is refused
(lang-shield-proxy). With exactly one plug such a device is
attributed to it; with more, to none.
pads and config are template level too, whatever the count — they
are shield-level facts, not per-plug ones.
dl_plug: plug {
compatible = "shield,plug";
shield,plugs = "arduino-r3";
i2c { dl_rtc: rtc@68 { /* ... */ }; }; /* bus: under the plug */
spi { dl_sd: sdhc { /* ... */ }; };
};
gpio { dl_led1: led-1 { /* ... */ }; }; /* plain: template level */
pads { dl_sq: sq { /* ... */ }; };
Devices
A device is any node under a bus-proxy group (i2c/spi/uart,
named after the connector type’s plug,bus-proxies) or a plain,
plug-agnostic group (gpio, or any other non-reserved group name). Its
own gpio/pwm/adc references are documented in
Writing a shield template and
Adding a second socket; the properties below are this
project’s own address, chip-select and parameter vocabulary.
shield,cs-position
- Where:
a device (semantically, one that will end up on a SPI bus).
- Type:
<u32>— one connector-type position index (a#definefrom the connector type’s own header, e.g.ARDUINO_HEADER_R3_D10).- Required or optional:
optional. Absence means the device’s chip-select is pool-allocated — the analyzer draws it from the mated socket’s (or connector type’s default)
socket,cs-poolcandidates, in allocation order, skipping positions already claimed. Presence means the CS is copper-fixed at that exact position: allocated outright, never drawn from the pool, and checked that the position actually resolves to a real SoC pin on the socket the device ends up on.- Refuses:
phys-cs(the pool is exhausted for a pool-allocated device; or a copper-fixed position has nogpio-mapentry on the resolved socket, so there is no pin to route it to).
Example — copper-fixed, boards/shields/adafruit_data_logger/adafruit_data_logger.shield
(SD-card CS):
dl_sd: sdhc {
compatible = "zephyr,sdhc-spi-slot";
shield,cs-position = <ARDUINO_HEADER_R3_D10>;
spi-max-frequency = <24000000>;
};
Contrast boards/shields/adafruit_winc1500/adafruit_winc1500.shield’s
w_wifi device, which authors no shield,cs-position at all — its own
comment states the consequence directly: “no shield,cs-position -> CS
pool-allocated”.
shield,collect
- Where:
a device.
- Type:
string — the
compatibleof a shared collection parent ("gpio-leds","gpio-keys", …).- Required or optional:
optional. Absence means the device emits as its own standalone node. Presence means the device is instead one entry merged into a board-wide node of that
compatible— so several shield instances contribute LEDs or buttons to one sharedgpio-leds/gpio-keysparent instead of each fighting over its own. The string is emitted verbatim as the collection’scompatible; nothing in the loader restricts it to a fixed set of known collection names.- Refuses:
nothing at parse time — an unrecognized collection compatible is not diagnosed, it simply produces its own (unusual) collection node.
Example — boards/shields/adafruit_data_logger/adafruit_data_logger.shield:
dl_led1: led-1 { shield,collect = "gpio-leds";
gpios = <&dl_plug ARDUINO_HEADER_R3_D3 GPIO_ACTIVE_HIGH>; };
shield,params / shield,param-includes
- Where:
a device;
shield,param-includesis always a sibling ofshield,paramson the same node.- Type:
both are string arrays.
shield,paramslists property names this device instance’s rig placement may (or must) assign (params: {<device>: {<name>: <value>}}).shield,param-includeslists header paths the assigned value’s tokens are allowed to resolve against.- Required or optional:
both optional; a device with neither accepts no per-instance parameters. Once
shield,paramsnames a property with no default authored in the template itself (no matching entry among the device’s other properties), assigning it becomes required — every rig instantiating this shield must supply it, or the per-stage invariant fails.shield,param-includesis the contract for what a non-literal assigned value is allowed to name: a macro-only header (say, an event-code header) contributes no node or property of its own, so the vocabulary it defines cannot be recovered from the template’s own#includes and must be declared here, explicitly, on the same device the parameter belongs to.- Refuses:
lang-param(a rig assigns a property the device does not declare inshield,params; a rig names a device the shield does not have; a required, no-default parameter is left unassigned by every stage);lang-dt-include(a header inshield,param-includesis missing or fails to preprocess; an assigned non-literal token does not resolve against any declared header).
Example — boards/shields/grove_btn/grove_btn.shield:
gb_key: button {
shield,collect = "gpio-keys";
shield,params = "zephyr,code";
shield,param-includes = "zephyr/dt-bindings/input/input-event-codes.h";
gpios = <&gb_plug GROVE_SIG0 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>;
};
shield,channel
- Where:
an exposed socket node (see “Carriers and exposed sockets” below) whose bus proxy is a new scope rather than a pass-through.
- Type:
<u32>— a mux/interposer channel index.- Required or optional:
optional. Absence is the ordinary case for a pass-through exposed socket. Presence marks this exposed socket as one channel of a multiplexer or similar interposer device on this shield — paired with a
socket,<bus> = <&device>reference naming that device rather than a plug (see Board socket properties’ssocket,<bus>entry for the pass-through-versus-new-scope distinction).- Refuses:
nothing directly — a malformed or missing pairing with the bus proxy surfaces as
lang-exposedinstead (documented on Board socket properties).
Example — boards/shields/i2c_mux/i2c_mux.shield (four channels of one
TCA9548A mux, each its own scope):
ch0: ch0 { compatible = "socket,i2c-port"; socket,i2c = <&mux>; shield,channel = <0>; };
ch1: ch1 { compatible = "socket,i2c-port"; socket,i2c = <&mux>; shield,channel = <1>; };
Pads
A pad is an arity-1 connector: a signal that belongs to a device but is
not itself claimable through any plug — an RTC’s square-wave output, say.
Pads live under a pads group at template level, whatever the shield’s
plug count.
shield,role
- Where:
a pad node, under
pads.- Type:
string enum — one of
"driver","listener","bidir".- Required or optional:
optional. Absence defaults to
"bidir". The role governs how the net-conflict checker treats signals reaching this pad: a driver pad may not share its net with another driver, a listener never drives.- Refuses:
lang-pad-role(any value outside the three-way enum).
Example — boards/shields/adafruit_data_logger/adafruit_data_logger.shield:
dl_sq: sq { shield,role = "driver"; shield,of = <&dl_rtc>; };
shield,of
- Where:
a pad node.
- Type:
phandle to a device of the same shield.
- Required or optional:
optional. Absence means the pad is not associated with any particular device of this shield. Presence records which device the pad belongs to (its node name, unit-address stripped) — this is bookkeeping only; nothing in the loader currently checks the phandle resolves to a real device, so a
shield,ofnaming a non-device node is accepted silently rather than refused.- Refuses:
nothing — see the note above.
Example — the same dl_sq pad, one entry above (RTC square-wave output
belonging to the RTC device), boards/shields/adafruit_data_logger/adafruit_data_logger.shield:
dl_sq: sq { shield,role = "driver"; shield,of = <&dl_rtc>; };
Config elements
A config element is a strap or a jumper: something a rig, or the
allocator, selects a value for. Both live under a config group at
template level; which one a child node becomes is decided by presence or
absence of shield,position-domain.
shield,domain (config strap)
- Where:
a config-group child with no
shield,position-domain— this absence is what makes it a strap rather than a jumper.- Type:
an array of
(address, strap-state)<u32>pairs —<addr0 state0>, <addr1 state1>, ....- Required or optional:
required to make the node parse as a strap at all; a config child authoring neither
shield,domainnorshield,position-domainis not a documented shape (see the note below).- Refuses:
nothing named — see the note below.
Example — boards/shields/temp_click/temp_click.shield (a
strap-selectable I2C address, referenced by shield,addr-from above):
tc_addr_strap: addr-strap {
shield,domain = <0x48 0>, <0x49 1>; /* (address, strap) */
shield,sheet-label = "ADDR jumper";
};
Note
A real defect found while re-deriving this page, reported rather than
fixed (this is a docs slice): scripts/rigc/loader/shields.py::_parse_strap
reads node.props["shield,domain"] unconditionally, with no presence
check. A config-group child authoring neither
shield,position-domain nor shield,domain is routed to
_parse_strap (the “else” branch of the jumper/strap dispatch in
_parse_shield) and raises a raw KeyError there instead of a
Diagnostic — a crash, not a rejected rig.
shield,position-domain (config jumper)
- Where:
a config-group child. Its presence, not its value, is the discriminator: a config child that has it is a jumper; one that lacks it is a strap.
- Type:
an array of
(connector-position index, jumper-state)<u32>pairs — the position-side twin of a strap’s address domain.- Required or optional:
presence itself makes the node a jumper. On a shield with more than one plug, declaring one at all is refused outright — the position domain has no plug axis to resolve through, so a jumper only makes sense where there is exactly one plug.
- Refuses:
lang-shield-plurality(a shield with two or more plugs declares a routing jumper).A jumper is also the one node in a template that legitimately declares a cell count:
#gpio-cells = <1>, because it supplies the position itself and leaves only the flags to the referring property. A plug never does (see “The plug node” above).
Example — boards/shields/adafruit_winc1500/adafruit_winc1500.shield
(IRQ routed to D7 by default, or D2 via a solder jumper):
w_irq_jmp: irq-jmp {
#gpio-cells = <1>; /* supplies the position; flags stay on the signal */
shield,position-domain = <ARDUINO_HEADER_R3_D7 0>, /* default */
<ARDUINO_HEADER_R3_D2 1>; /* alt */
shield,sheet-label = "IRQ select (SJ2)";
};
shield,sheet-label
- Where:
a strap or jumper node.
- Type:
string.
- Required or optional:
optional. Absence means the emitted config sheet carries an empty label for that element (
scripts/rigc/loader/shields.py::_sheet_labelreturns""when the property is absent) — the element still functions, but the human-facing wiring instructions have nothing readable to call it. Presence supplies that label (a silkscreen name, typically).- Refuses:
nothing.
Example — the same w_irq_jmp node above,
boards/shields/adafruit_winc1500/adafruit_winc1500.shield:
shield,sheet-label = "IRQ select (SJ2)";.
Carriers and exposed sockets
A shield with no devices of its own that instead re-exports one or more
sockets — an I2C multiplexer, a click-adapter — is a carrier. Its
exposed sockets are ordinary nodes with a compatible = "socket,<type>",
authored inside the .shield file: they carry the identical
vocabulary a real board socket does (gpio-map, socket,<bus>,
socket,cs-pool, …), documented in full on Board socket properties, plus
this shield-only shield,channel above. A carrier with several plugs may
compose one exposed socket’s rows from any of them, exactly as a
device’s own cross-plug references do.
Note the asymmetry in a map row’s two halves: the CHILD side carries the
count the exposed socket declares for itself (#gpio-cells and friends,
on the socket node), while the PARENT side is a plug and therefore always
carries the generic count for that function — 2 for gpio, 3 for pwm, 1 for
adc. A plug declares no counts of its own, so there is nothing there to
vary.
Example — pass-through, boards/shields/arduino_uno_click/arduino_uno_click.shield
(an Arduino R3 shield re-exporting two mikroBUS sockets, SPI/I2C passed
through the carrier’s own plug):
mb1: mb1 {
compatible = "socket,mikrobus";
#gpio-cells = <2>;
gpio-map = <MIKROBUS_CS 0 &auc_plug ARDUINO_HEADER_R3_D10 0>,
<MIKROBUS_RST 0 &auc_plug ARDUINO_HEADER_R3_D6 0>,
<MIKROBUS_INT 0 &auc_plug ARDUINO_HEADER_R3_D2 0>;
socket,spi = <&auc_plug>;
socket,i2c = <&auc_plug>;
};
Example — new scope, boards/shields/i2c_mux/i2c_mux.shield (a TCA9548A
mux; each channel is its own scope rooted on the mux device, so a fixed
I2C address may repeat behind different channels) — see shield,channel
above for the same snippet.