Hello everyone,
I am writing an application with three sensors on the same SPI1 interface on my nRF52840. See the following devicetree section:
&spi1 {
compatible = "nordic,nrf-spim";
status = "okay";
pinctrl-0 = <&spi1_default>;
pinctrl-1 = <&spi1_sleep>;
pinctrl-names = "default", "sleep";
cs-gpios = <&gpio0 22 GPIO_ACTIVE_LOW>,
<&gpio0 14 GPIO_ACTIVE_LOW>,
<&gpio0 23 GPIO_ACTIVE_LOW>;
iis2dh: iis2dh@0 {
compatible = "st,iis2dh-ext";
reg = <0>;
spi-max-frequency = <8000000>;
drdy-gpios = <&gpio1 13 GPIO_ACTIVE_HIGH>;
zephyr,deferred-init;
};
kx134: kx134@1 {
compatible = "rohm,kx134";
reg = <1>;
spi-max-frequency = <8000000>;
drdy-gpios = <&gpio0 30 GPIO_ACTIVE_HIGH>;
drdy-int = <2>;
zephyr,deferred-init;
};
h3lis200dl: h3lis200dl@2 {
compatible = "st,h3lis200dl";
reg = <2>;
spi-max-frequency = <8000000>;
drdy-gpios = <&gpio1 11 GPIO_ACTIVE_HIGH>;
drdy-int = <1>;
zephyr,deferred-init;
};
};
For all sensors, I use a custom driver to fetch accelerometer data at high sample rates. As shown above, the spi-max-frequency is set to the same value (8 MHz) for all sensors. The zephyr,deferred-init; property is used to fully turn off the sensor (its supply voltage) to save power when required. Furthermore, all sensors use the same SPI operation, for example:
#define H3LIS200DL_SPI_OPERATION (SPI_OP_MODE_MASTER | SPI_MODE_CPOL | SPI_MODE_CPHA | SPI_WORD_SET(8))
While debugging, I noticed that each sensor readout took a lot of processing time - but only when a different sensor had just been read before. For two consecutive reads from the same sensor, the SPI readout was much faster.
After looking deeper into the NCS, I found the issue:
zephyr/drivers/spi/spi_context.h:
static inline bool spi_context_configured(struct spi_context *ctx,
const struct spi_config *config)
{
return !!(ctx->config == config);
}
zephyr/drivers/spi/spi_nrfx_spim.c:
static int configure(const struct device *dev,
const struct spi_config *spi_cfg)
{
...
if (dev_data->initialized && spi_context_configured(ctx, spi_cfg)) {
/* Already configured. No need to do it again. */
return 0;
}
...
nrfx_spim_init(&dev_config->spim, &config,
event_handler, (void *)dev);
...
}
The configure function is called for every new SPI transaction, and spi_context_configured compares the SPI configs. However, the comparison is based on pointer values, which are different for each sensor. This always evaluates to false and causes a time-consuming nrfx_spim_init(...) call.
This re-initialization takes about a millisecond, which is significant in my case, given the high sample rates.
However, the re-initialization is not actually required because the sensors all use the exact same hardware configuration - the only difference is the CS lines.
To solve that issue I came up with the following workaround:
static int configure(const struct device *dev,
const struct spi_config *spi_cfg)
{
...
/* Skip nrfx_spim_init if same spi params and only cs is different for spi1 */
if (dev == DEVICE_DT_GET(DT_NODELABEL(spi1))) {
if (dev_data->initialized &&
ctx->config->frequency == spi_cfg->frequency &&
ctx->config->operation == spi_cfg->operation) {
ctx->config = spi_cfg;
return 0;
}
} else {
if (dev_data->initialized && spi_context_configured(ctx, spi_cfg)) {
/* Already configured. No need to do it again. */
return 0;
}
}
...
nrfx_spim_init(&dev_config->spim, &config,
event_handler, (void *)dev);
...
}
With this, I was able to reduce processor load from almost 100% down to about 30%.
Now I am wondering: is there a better solution than this? In particular, modifying the NCS directly doesn’t feel like the best approach.
Best regards,
Bernhard