Skip to content

Commit

Permalink
cmake: modules: shields: Make it possible to give options to shield
Browse files Browse the repository at this point in the history
This allows you to adapt the shield to your board at compile time.

Signed-off-by: TOKITA Hiroshi <[email protected]>
  • Loading branch information
soburi committed Jan 9, 2025
1 parent 43efd24 commit 7cc63ca
Show file tree
Hide file tree
Showing 4 changed files with 488 additions and 22 deletions.
79 changes: 59 additions & 20 deletions cmake/modules/shields.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,15 @@ include(extensions)
# Check that SHIELD has not changed.
zephyr_check_cache(SHIELD WATCH)

set(GEN_SHIELD_DERIVED_OVERLAY_SCRIPT ${ZEPHYR_BASE}/scripts/build/gen_shield_derived_overlay.py)
set(GENERATED_SHIELDS_DIR ${PROJECT_BINARY_DIR}/include/generated/shields)

# Add the directory derived overlay files to the SHIELD_DIRS output variable.
list(APPEND
SHIELD_DIRS
${GENERATED_SHIELDS_DIR}
)

if(SHIELD)
message(STATUS "Shield(s): ${SHIELD}")
endif()
Expand All @@ -44,7 +53,7 @@ if(DEFINED SHIELD)
endif()
# SHIELD-NOTFOUND is a real CMake list, from which valid shields can be popped.
# After processing all shields, only invalid shields will be left in this list.
set(SHIELD-NOTFOUND ${SHIELD_AS_LIST})
string(REGEX REPLACE "([@:][^;]*)" "" SHIELD-NOTFOUND "${SHIELD}")

foreach(root ${BOARD_ROOT})
set(shield_dir ${root}/boards/shields)
Expand Down Expand Up @@ -74,43 +83,73 @@ endforeach()

# Process shields in-order
if(DEFINED SHIELD)
foreach(s ${SHIELD_AS_LIST})
foreach(shld ${SHIELD_AS_LIST})
string(REGEX MATCH [[^([^@:]*)([@:].*)?$]] matched "${shld}")
set(s ${CMAKE_MATCH_1}) # name part
set(shld_opts ${CMAKE_MATCH_2}) # options part

if(NOT ${s} IN_LIST SHIELD_LIST)
continue()
endif()

list(REMOVE_ITEM SHIELD-NOTFOUND ${s})

# Add <shield>.overlay to the shield_dts_files output variable.
list(APPEND
shield_dts_files
${SHIELD_DIR_${s}}/${s}.overlay
)

# Add the shield's directory to the SHIELD_DIRS output variable.
list(APPEND
SHIELD_DIRS
${SHIELD_DIR_${s}}
)

# Search for shield/shield.conf file
if(EXISTS ${SHIELD_DIR_${s}}/${s}.conf)
list(APPEND
shield_conf_files
${SHIELD_DIR_${s}}/${s}.conf
)
endif()

# Add board-specific .conf and .overlay files to their
# respective output variables.
zephyr_file(CONF_FILES ${SHIELD_DIR_${s}}/boards
DTS shield_dts_files
KCONF shield_conf_files
DTS board_overlay_file
KCONF board_conf_file
)

zephyr_file(CONF_FILES ${SHIELD_DIR_${s}}/boards/${s}
DTS shield_dts_files
KCONF shield_conf_files
DTS board_shield_overlay_file
KCONF board_shield_conf_file
)

get_filename_component(board_overlay_stem "${board_overlay_file}" NAME_WE)
get_filename_component(shield_overlay_stem "${board_shield_overlay_file}" NAME_WE)

set(shld_conf_srcs
"${SHIELD_DIR_${s}}/${s}.conf"
"${board_conf_file}"
"${board_shield_conf_file}")

set(shld_overlay_srcs
"${SHIELD_DIR_${s}}/${s}.overlay"
"${board_overlay_file}"
"${board_shield_overlay_file}")

set(shld_overlay_dsts
"${GENERATED_SHIELDS_DIR}/${shld}.overlay"
"${GENERATED_SHIELDS_DIR}/${shld}_${board_overlay_stem}.overlay"
"${GENERATED_SHIELDS_DIR}/${shld}_${board_overlay_stem}_${shield_overlay_stem}.overlay")

foreach(src_conf ${shld_conf_srcs})
# Search for shield/shield.conf file
if(EXISTS ${src_conf})
list(APPEND shield_conf_files ${src_conf})
endif()
endforeach()

foreach(src_overlay dst_overlay IN ZIP_LISTS shld_overlay_srcs shld_overlay_dsts)
if (EXISTS ${src_overlay})
execute_process(COMMAND
${PYTHON_EXECUTABLE}
${GEN_SHIELD_DERIVED_OVERLAY_SCRIPT}
"${src_overlay}"
"--shield-options=${shld_opts}"
"--output-filename=${dst_overlay}")

list(APPEND shield_dts_files "${dst_overlay}")
endif()
endforeach()

endforeach()
endif()

Expand Down
110 changes: 108 additions & 2 deletions doc/hardware/porting/shields.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,15 @@ under :zephyr_file:`boards/shields`:
.. code-block:: none
boards/shields/<shield>
├── shield.yml
├── <shield>.overlay
├── Kconfig.shield
└── Kconfig.defconfig
These files provides shield configuration as follows:

* ***shield.yml**: This file defines the name of the shield, its variant
information, and the information it accepts if parameterized.
* **<shield>.overlay**: This file provides a shield description in devicetree
format that is merged with the board's :ref:`devicetree <dt-guide>`
before compilation.
Expand All @@ -48,6 +51,100 @@ to provide a device nodelabel is the form <device>_<shield>, for instance:
...
};
shield.yml format
*****************

Shield parametrization
**********************

The Zephyr shield framework supports parameterized configuration, allowing users to
customize shield behavior and connections directly from the command line.
This feature is particularly useful for multi-connector shields or when multiple
instances of the same shield need to coexist with different configurations.

Overview of Shield Parametrization
==================================

Shield parametrization extends the ``--shield`` option in the west build command to
accept key-value pairs that define configuration parameters.
These parameters can include connector names, I2C addresses, GPIO pins, and more.
Additionally, the framework supports default values, ensuring functionality
even when no explicit parameters are provided.

Specifying Parameters
---------------------

Parameters are specified in the --shield option using the following syntax:

::

<shield_identifier>[:<option>{=<value>}]{:<option>{=<value>}}

<shield_identifier>: The name of the shield.
<option>: A configuration parameter for the shield.
<value>: (Optional) The value for the parameter. If omitted, a default value is used.

Example
=======

To use two instances of a shield with different configurations:

First instance connected to grove_i2c with an I2C address of 0x1F and an interrupt on GPIO pin 7.
Second instance connected to grove_i2c1 with default settings.


Derived Overlay Mechanism
=========================

When parameters are specified, the build system automatically generates a "derived overlay" file for each configuration. These overlays:

Include the original shield configuration.
Modify it to reflect the specified parameters.

Example of a Derived Overlay
============================

For the first shield in the above example, the derived overlay file
seeed_grove_lis3dhtr:addr=1f:irq_gpio_pin=7.overlay might contain:

.. code-block:: c
#define SHIELD_OPTION_SEEED_GROVE_LIS3DHTR_ADDR 0x1F
#define SHIELD_OPTION_SEEED_GROVE_LIS3DHTR_IRQ_GPIO_PIN 7
#define SHIELD_BASE_NAME SEEED_GROVE_LIS3DHTR
#define SHIELD_DERIVED_NAME SEEED_GROVE_LIS3DHTR_ADDR_1F_IRQ_GPIO_PIN_7
#include </path/to/original/seeed_grove_lis3dhtr.overlay>
#undef SHIELD_BASE_NAME
#undef SHIELD_DERIVED_NAME
This ensures context isolation and prevents conflicts when multiple instances of the same shield are used.
Using Context in Overlays

Shields can use helper macros to incorporate parameterized options into their overlays. For instance:

Default values can be defined as:

.. code-block:: c
#define SHIELD_OPTION_DEFAULT_SEEED_GROVE_LIS3DHTR_ADDR 0x19
#define SHIELD_OPTION_DEFAULT_SEEED_GROVE_LIS3DHTR_CONN grove_i2c
Options can be directly referenced using macros like:

.. code-block:: c
#if SHIELD_OPTION_DEFINED(IRQ_GPIO_PIN)
irq-gpios = <&SHIELD_OPTION(IRQ_GPIO_PORT)
SHIELD_OPTION(IRQ_GPIO_PIN)
SHIELD_OPTION(IRQ_GPIO_FLAG)>;
#endif
Adding Source Code
******************

Expand Down Expand Up @@ -86,8 +183,7 @@ This should be done at two different level:
arduino_i2c: &i2c1 {};
Board specific shield configuration
-----------------------------------
**Board specific shield configuration**

If modifications are needed to fit a shield to a particular board or board
revision, you can override a shield description for a specific board by adding
Expand Down Expand Up @@ -116,6 +212,14 @@ to the west command:
:goals: build


We can use parameterized options if the shield is supporting.
Specify the shield name followed by either ``:`` or ``@``.

.. zephyr-app-commands::
:app: your_app
:shield: seeed_grove_lis3dhtr@1:addr=1f:irq_gpio_pin=7
:goals: build

Alternatively, it could be set by default in a project's CMakeLists.txt:

.. code-block:: cmake
Expand All @@ -131,6 +235,7 @@ possible to provide multiple version of the shields description:
.. code-block:: none
boards/shields/<shield>
├── shield.yml
├── <shield_v1>.overlay
├── <shield_v1>.defconfig
├── <shield_v2>.overlay
Expand All @@ -149,6 +254,7 @@ revision:
.. code-block:: none
boards/shields/<shield>
├── shield.yml
├── <shield_v1>.overlay
├── <shield_v1>.defconfig
├── <shield_v2>.overlay
Expand Down
118 changes: 118 additions & 0 deletions dts/common/shield_utils.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/*
* Copyright (c) 2025 TOKITA Hiroshi
* SPDX-License-Identifier: Apache-2.0
*/

#ifndef __DT_SHIELD_UTILS_H
#define __DT_SHIELD_UTILS_H

#include <zephyr/dt-bindings/dt-util.h>

/**
* @defgroup dts_shield_option_apis Shield option APIs
*
* Options included in a shield specifier are converted to a defined name
* and value by the framework's rules.
* This macro determine it is defined or not by the option name.
*
* @ingroup dts_apis
* @{
*/

/**
* Determines whether a specified option is defined.
*
* @param o option name
*/
#define SHIELD_OPTION_EXISTS(o) \
IS_ENABLED( \
UTIL_CAT(UTIL_CAT(SHIELD_OPTION_, UTIL_CAT(SHIELD_DERIVED_NAME, \
UTIL_CAT(_, UTIL_CAT(o, _EXISTS))))))

/**
* Get an option which is specified on the command line.
*
* Options included in a shield specifier are converted to a defined name
* and value by the framework's rules.
* This macro references it by the option name and gets the value.
*
* @param o option name
*/
#define SHIELD_OPTION(o) \
COND_CODE_1(SHIELD_OPTION_EXISTS(o), \
(UTIL_CAT(SHIELD_OPTION_, \
UTIL_CAT(SHIELD_DERIVED_NAME, \
UTIL_CAT(_, o)))), \
(UTIL_CAT(SHIELD_OPTION_DEFAULT_, \
UTIL_CAT(SHIELD_BASE_NAME, \
UTIL_CAT(_, o)))))

/*
* Aliases for common options...
*/

/**
* Get an index parameter which is specified in the shield specifier.
*/
#define SHIELD_INDEX COND_CODE_1(SHIELD_OPTION_EXISTS(__INDEX), \
(SHIELD_OPTION(__INDEX)), ())

#define SHIELD_CONN_EXISTS SHIELD_OPTION_EXISTS(CONN_TOKEN)

/**
* Get the `conn` option value.
*/
#define SHIELD_CONN UTIL_CAT(SHIELD_OPTION(CONN_TOKEN), SHIELD_INDEX)

#define SHIELD_LABEL_EXISTS SHIELD_OPTION_EXISTS(LABEL_TOKEN)

/**
* Get the `label` option value
*/
#define SHIELD_LABEL SHIELD_OPTION(LABEL_TOKEN)

#define SHIELD_ADDR_EXISTS SHIELD_OPTION_EXISTS(ADDR)

/**
* Get `addr` option value
*/
#define SHIELD_ADDR SHIELD_OPTION(ADDR)

/**
* Get hexadecimal representation of `addr` option value
*/
#define SHIELD_ADDR_HEX SHIELD_OPTION(ADDR_HEX)

/**
* Get addr option value with '0x' prefix
*/
#define SHIELD_0X_ADDR UTIL_CAT(0x, SHIELD_OPTION(ADDR_HEX))

/*
* Various utility macros...
*/

#define SHIELD_CONVENTIONAL_LABEL_(stem) \
COND_CODE_1(SHIELD_OPTION_EXISTS(ADDR), \
(COND_CODE_1(SHIELD_OPTION_EXISTS(CONN), \
(UTIL_CAT(stem, \
UTIL_CAT(_, \
UTIL_CAT(SHIELD_ADDR, \
UTIL_CAT(_, SHIELD_CONN))))), \
(UTIL_CAT(stem, \
UTIL_CAT(_, SHIELD_ADDR))))), \
(COND_CODE_1(SHIELD_OPTION_EXISTS(CONN), \
(UTIL_CAT(stem, \
UTIL_CAT(_, SHIELD_CONN))), \
(stem))))

#define SHIELD_CONVENTIONAL_LABEL(stem) \
COND_CODE_1(SHIELD_OPTION_EXISTS(LABEL), \
(SHIELD_LABEL), \
(SHIELD_CONVENTIONAL_LABEL_(stem)))

/**
* @}
*/

#endif /* __DT_SHIELD_UTILS_H */
Loading

0 comments on commit 7cc63ca

Please sign in to comment.