Skip to content

core: pinmux: improve dynamic pinmux configuration at runtime#415

Draft
dani-clo wants to merge 4 commits intoarduino:mainfrom
dani-clo:pinmux_pinctrl_arduino
Draft

core: pinmux: improve dynamic pinmux configuration at runtime#415
dani-clo wants to merge 4 commits intoarduino:mainfrom
dani-clo:pinmux_pinctrl_arduino

Conversation

@dani-clo
Copy link
Copy Markdown

@dani-clo dani-clo commented Apr 2, 2026

DRAFT – work in progress: additional fixes may still come from https://github.com/maidnl/ArduinoCore-zephyr/tree/dynamic_pinctrl_fix

This PR improves the initial dynamic pinmux solution introduced in #269

What changes:

  • Marks all relevant nodes as zephyr,deferred-init.

  • Generates a flash-resident map from each node pinctrl devicetree configuration.

  • Configures peripheral pin routing at runtime using pinctrl_apply_state or pinctrl_configure_pins.

  • For channel-based peripherals (for example ADC and PWM), configures only the required channel pin instead of the full peripheral block.

Why:

  • Avoids early/static pin assignment for peripherals that are configured dynamically.

  • Reduces unnecessary pin configuration for multi-channel peripherals.

Documentation:

See dynamic_pinmux.md for design details.

@dani-clo dani-clo added the enhancement New feature or request label Apr 2, 2026
Copy link
Copy Markdown

@pillo79 pillo79 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be a huge leap forward! 🚀 First review found a few scattered nits and improvements.

Overall, I think there are two big changes in this commit that are orthogonal:

  • the new PINCTRL_STATE_ARDUINO => you call apply_dev_pinctrl() on any device, if it has that state, it resets the GPIOs.
  • devices are set to deferred_init => any device like that must be configured (if !ready(dev) { init(dev) }).

I think splitting these two topics in separate commits would make the implementation easier to get right.

Comment thread cores/arduino/zephyrCommon.cpp Outdated
Comment thread cores/arduino/zephyrCommon.cpp Outdated
*
* Map deferred-init peripherals with their pinctrl configuration from devicetree.
*/
#define PINCTRL_NODE_DEFERRED(node_id) DT_PROP_OR(node_id, zephyr_deferred_init, 0)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is unrelated to pin control. Also, IIRC there's no need for the "or", COND_CODE_1 should not error on empty.

Suggested change
#define PINCTRL_NODE_DEFERRED(node_id) DT_PROP_OR(node_id, zephyr_deferred_init, 0)
#define IS_DEFERRED(node_id) DT_PROP(node_id, zephyr_deferred_init)

Comment thread cores/arduino/zephyrCommon.cpp Outdated
* the peripheral's PSEL register must be written via the device base
* address stored in pcfg->reg.
* On platforms without CONFIG_PINCTRL_STORE_REG (e.g. STM32) the reg
* argument is ignored by their pinctrl driver, so passing PINCTRL_REG_NONE is safe.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the issue is the field is not present, otherwise you would not have an #ifdef...

Comment thread cores/arduino/zephyrCommon.cpp Outdated
* Switches peripheral pins back to ARDUINO pinctrl state (alternate function),
* typically after a temporary transition to GPIO mode.
*
* @param idx Index of the pin within the device's ARDUINO pinctrl state
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parameter name mismatch

Comment thread cores/arduino/zephyrCommon.cpp Outdated
* @param dev Target peripheral device to acquire pin for
* @param target_state Desired pinctrl state (typically PINCTRL_STATE_DEFAULT or SLEEP)
*/
int apply_dev_pinctrl(const struct device *dev, uint8_t target_state) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for target_state (the above function only works on PINCTRL_STATE_ARDUINO anyway)

Suggested change
int apply_dev_pinctrl(const struct device *dev, uint8_t target_state) {
int apply_dev_pinctrl(const struct device *dev) {

Comment thread cores/arduino/zephyrCommon.cpp Outdated
// TODO: add reverse map to find pin name from DAC* define
// In the meantime, consider A0 == DAC0
_reinit_peripheral_if_needed((pin_size_t)(dacName + A0), dac_dev);
apply_dev_pinctrl(dac_dev, PINCTRL_STATE_DEFAULT);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just "apply", without specifying a state.

Comment thread cores/arduino/zephyrInternal.h Outdated
void disableInterrupt(pin_size_t);
void _reinit_peripheral_if_needed(pin_size_t pin, const struct device *dev);

int apply_dev_pinctrl(const struct device *dev, uint8_t target_state);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing forward declaration for the channel one?

Comment thread cores/arduino/zephyrSerial.cpp Outdated
/* Re-apply DEFAULT pinctrl state so shared pins
* are remuxed back to Serial after temporary GPIO/PWM usage.
*/
(void)apply_dev_pinctrl(uart, PINCTRL_STATE_DEFAULT);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't this mean UARTs cannot be deferred?
I think a fix should be to have smarter stuff in the dev table and just call apply_dev_pinctrl 🙁

Comment on lines +270 to +282
template <typename DT_SPEC, size_t N>
static size_t state_pin_index_from_spec_index(const DT_SPEC (&specs)[N], size_t spec_idx) {
const struct device *dev = specs[spec_idx].dev;
size_t state_pin_idx = 0;

for (size_t i = 0; i < spec_idx; i++) {
if (specs[i].dev == dev) {
state_pin_idx++;
}
}

return state_pin_idx;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⁉️ this information is not helpful. You're looking for the peripheral channel, which is completely independent of how the entries are sequenced in the board pinout.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, the goal here is not to find a peripheral channel number; it is to find the correct pinctrl entry for the selected Arduino pin: for example on GIGA, A0 maps to adc1_inp4_pc4, and D3 maps to tim2_ch3_pa2.

There isn't a Zephyr generic API to map an arbitrary peripheral channel back to the specific pinctrl entry required by pinctrl_configure_pins(), so we derive the pinctrl entry from the board DT ordering.
This approach is peripheral and driver agnostic - works for all Arduino adc and pwm.

Comment on lines +41 to +42
#define PINCTRL_MAP_ENTRY_IF_PRESENT(node_id) \
COND_CODE_1(PINCTRL_NODE_DEFERRED(node_id), (PINCTRL_MAP_ENTRY(node_id)), ())
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly defining all entries here, or those that have a PINCTRL_STATE_ARDUINO, is better because it allows other devices (e.g serial, DAC) to be used in the same way.

@KurtE
Copy link
Copy Markdown

KurtE commented Apr 2, 2026

@pillo79 and @dani-clo

  • devices are set to deferred_init => any device like that must be configured (if !ready(dev) { init(dev) }).

Wondering, if the devices are marked deferred_init - if it would also make sense to maybe not allocate some of the
resources to those devices, unless the device is initialized. Examples: Camera buffers, or buffers for USARTS, etc.

Obviously that could be a follow on update instead as part of this one. But looks good

@dani-clo dani-clo force-pushed the pinmux_pinctrl_arduino branch from 3fd16e0 to 1aba51a Compare April 8, 2026 08:55
@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 8, 2026

Built 0.54.2-0.dev+1aba51af

CI run FAILED: 🔥 giga, 🔥 nano33ble, 🔥 portentac33, 🔥 portentah7, 🔥 unoq

ArtifactBoardCoreTestsRAMSketchesWarningsErrors
zephyr_contrib ek_ra8d1 📗

11.9%

2--
frdm_mcxn947 3 🏷️

58.6%

2--
frdm_rw612 1 🏷️

83.0%

2--
🔥 zephyr_main giga 🔥Core build failed!
nano33ble 🔥Core build failed!
nano_matter 📗 ✔️*

⚠️ 85.7%

208(2*)
niclasense 2 🏷️ ✅*

⚠️ 87.4%

208-
opta 4 🏷️ ✔️*

46.7%

548(2*)
portentac33 🔥Core build failed!
portentah7 🔥Core build failed!
🔥 zephyr_unoq unoq 🔥Core build failed!
Legend

BoardTestStatus description
🔥 🔥 Test run failed to complete.
🔴 Test completed with unexpected errors.
✔️* 🚫 Test completed with errors, but all are known/expected.
✅* 🟡 Test completed with some warnings; no errors detected.
🟢 Test passed successfully, with no warnings or errors.
🌑 🌑 Test was skipped.

Caution

zephyr_main is blocked due to failures on giga, nano33ble, portentac33, portentah7!

Caution

zephyr_unoq is blocked due to failures on unoq!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants