How to maximize WIFI throughput when using SPI4 between nRF5340 and nRF7002

We are trying to max out the wifi throughput via nRF7002 when connected to SPI4. Currently we only achieve around 6.3Mbits/sec (TCP Upload to peer).

When measuring the SPI4 CLK we only see 16Mhz despite configuring 32Mhz in app overlay.

Question: What can we do to configure SPI4 for 32Mhz? Will this lead to higher throughput?

Hardware:

  • nRF5340-DK
  • nRF7002-EK
    • Modifikations: CLK, MISO and MOSI pins are connected to P0.08, P0.10 and P0.09 

Software:

  • nrf connect sdk version: 2.9.1
  • sample: wifi/throughput
  • EXTRA_CONF_FILE: overlay-high-performance.conf
  • SHIELD: nrf7002ek
  • custom app.overlay:

&pinctrl {
	spi4_default: spi4_default {
		group1 {
			psels = <NRF_PSEL(SPIM_SCK, 0, 8)>,
					<NRF_PSEL(SPIM_MISO, 0, 10)>,
					<NRF_PSEL(SPIM_MOSI, 0, 9)>;
			nordic,drive-mode = <NRF_DRIVE_H0H1>;
		};
	};

	spi4_sleep: spi4_sleep {
		group1 {
			psels = <NRF_PSEL(SPIM_SCK, 0, 8)>,
					<NRF_PSEL(SPIM_MISO, 0, 10)>,
					<NRF_PSEL(SPIM_MOSI, 0, 9)>;
			low-power-enable;
		};
	};
};

&spi4 {
	max-frequency = <33000000>;
	clock-frequency = <32000000>;

	nrf70: nrf7002-spi@0 {
		spi-max-frequency = <33000000>;
	};
};

Commandline:

west build -p -b nrf5340dk/nrf5340/cpuapp -- -DSHIELD=nrf7002ek -DEXTRA_CONF_FILE=overlay-high-performance.conf

Zperf:

zperf tcp upload XXX.XXX.XXX.XXX 5001 60 1024

Update 20.03.2025:

  • Setting either &spi4 max-frequency or nrf70 spi-max-frequency  to <8000000> leads to 8Mhz frequency on the oscilloscope
  • Setting &spi4 clock-frequency to <8000000> has no effect

This was done only to verify the setup. We are still unable to set the SPI4 frequency to 32Mhz as desired.

If we are commenting out lines 160-163 in ~/ncs/v2.9.1/zephyr/drivers/spi/spi_nrfx_spim.c we are actually seeing roughly 32Mhz and have a better throughput of 7.9Mbits/sec (TCP Upload to peer). This is however a brutal hack as it reconfigures the SPI on every transceive operation.

diff --git a/drivers/spi/spi_nrfx_spim.c b/drivers/spi/spi_nrfx_spim.c
index aacf863e3b4..2fae79168b1 100644
--- a/drivers/spi/spi_nrfx_spim.c
+++ b/drivers/spi/spi_nrfx_spim.c
@@ -157,10 +157,10 @@ static int configure(const struct device *dev,
        nrfx_spim_config_t config;
        nrfx_err_t result;
 
-       if (dev_data->initialized && spi_context_configured(ctx, spi_cfg)) {
-               /* Already configured. No need to do it again. */
-               return 0;
-       }
+       // if (dev_data->initialized && spi_context_configured(ctx, spi_cfg)) {
+       //      /* Already configured. No need to do it again. */
+       //      return 0;
+       // }
 
        if (spi_cfg->operation & SPI_HALF_DUPLEX) {
                LOG_ERR("Half-duplex not supported");

Update:

We are running in the following condition (lines 196-205 in ~/ncs/v2.9.1/zephyr/drivers/spi/spi_nrfx_spim.c) that limits SPI4 frequency to 16Mhz

#if defined(CONFIG_SOC_NRF5340_CPUAPP)
	/* On nRF5340, the 32 Mbps speed is supported by the application core
	 * when it is running at 128 MHz (see the Timing specifications section
	 * in the nRF5340 PS).
	 */
	if (max_freq > 16000000 &&
	    nrf_clock_hfclk_div_get(NRF_CLOCK) != NRF_CLOCK_HFCLK_DIV_1) {
		max_freq = 16000000;
	}
#endif

This happens despite setting the clock divider to 1 first thing in main():

int main(void)
{
#if NRFX_CLOCK_ENABLED && (defined(CLOCK_FEATURE_HFCLK_DIVIDE_PRESENT) || NRF_CLOCK_HAS_HFCLK192M)
	/* For now hardcode to 128MHz */
	nrfx_clock_divider_set(NRF_CLOCK_DOMAIN_HFCLK,
			       NRF_CLOCK_HFCLK_DIV_1);
#endif
	printk("Starting %s with CPU frequency: %d MHz\n", CONFIG_BOARD, SystemCoreClock/MHZ(1));

	return 0;
}

Parents
  • Hi Moritz,

    Thanks for reaching out with your questions.

    I recall that it is also critical to use a 128MHz system clock to make SPI4 work with 32MHz. Could you confirm if you have made this change?

    Jørgen is currently unavailable. I will try this myself tomorrow to see if it is possible and get back to you.

    Best regards,
    Charlie

  • Hi Charlie,

    I think we are running at 128Mhz:

    SystemCoreClock / MHZ(1) = 128
  • Hi Moritz,

    I observed the same results as you. I suspect this could be due to long wiring causing the SPI timing requirements to not be met.

    We have had customers successfully use SPI4 at 32MHz on their custom boards before. I will spend more time investigating this next week and update you accordingly.

    Best regards,
    Charlie

  • Hi Charlie,

    thanks for looking into the problem!

    When forcing a reconfiguration from main.c after the clock has been successfully set to 128Mhz we are able to see 32Mhz on the SPI4 CLK via oscilloscope as well as observing 7.78 Mbps throughput. The modification to spi_nrfx_spim.c look like the following:

    diff --git a/drivers/spi/spi_nrfx_spim.c b/drivers/spi/spi_nrfx_spim.c
    index aacf863e3b4..9b0c8b2a2d6 100644
    --- a/drivers/spi/spi_nrfx_spim.c
    +++ b/drivers/spi/spi_nrfx_spim.c
    @@ -157,9 +157,16 @@ static int configure(const struct device *dev,
            nrfx_spim_config_t config;
            nrfx_err_t result;
     
    -       if (dev_data->initialized && spi_context_configured(ctx, spi_cfg)) {
    -               /* Already configured. No need to do it again. */
    -               return 0;
    +       extern bool UGLY_FORCE_RECONFIGURE_SPIM;
    +
    +       if (!UGLY_FORCE_RECONFIGURE_SPIM) {
    +               if (dev_data->initialized && spi_context_configured(ctx, spi_cfg)) {
    +                       /* Already configured. No need to do it again. */
    +                       return 0;
    +               }
    +       } else {
    +               LOG_ERR("Reconfiguring SPIM %s", dev->name);
    +               UGLY_FORCE_RECONFIGURE_SPIM = false;
            }
     
            if (spi_cfg->operation & SPI_HALF_DUPLEX) {

    From our point of view it looks like the SPI4 is configured before entering `main()` so spi_nrfx_spim.c:160-163

    if (dev_data->initialized && spi_context_configured(ctx, spi_cfg)) {
    	/* Already configured. No need to do it again. */
    	return 0;
    }

    always leads to an early exit, even though the clock might have been reconfigured after SPIM4's initial initialization.

    I hope this helps,

    best regards,

    Moritz

  • Hi Moritz,

    Thanks for sharing your finding.

    32MHz is not a safe choice at current moment, you may encounter an issue mentioned here (+) Nordic DevZone.

    Best regards,

    Charlie

  • Hi Charlie,

    I found a way boost the SPI4 frequency to 32Mhz without hacking. I can use https://docs.zephyrproject.org/apidoc/latest/group__sys__init.html to set the system clock to 128Mhz before SPIM4 is configured.

    diff --git a/src/main.c b/src/main.c
    index 231f206..5d2db35 100644
    --- a/src/main.c
    +++ b/src/main.c
    @@ -15,14 +15,20 @@
     #endif
     #include <stdio.h>
     
    -int main(void)
    -{
     #if NRFX_CLOCK_ENABLED && (defined(CLOCK_FEATURE_HFCLK_DIVIDE_PRESENT) || NRF_CLOCK_HAS_HFCLK192M)
    +static int clock_set_128Mhz(void)
    +{
            /* For now hardcode to 128MHz */
    -       nrfx_clock_divider_set(NRF_CLOCK_DOMAIN_HFCLK,
    -                              NRF_CLOCK_HFCLK_DIV_1);
    +       nrfx_clock_divider_set(NRF_CLOCK_DOMAIN_HFCLK, NRF_CLOCK_HFCLK_DIV_1);
    +
    +       return 0;
    +}
    +SYS_INIT(clock_set_128Mhz, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
     #endif
    -       printk("Starting %s with CPU frequency: %d MHz\n", CONFIG_BOARD, SystemCoreClock/MHZ(1));
    +
    +int main(void)
    +{
    +       printk("Starting %s with CPU frequency: %d MHz\n", CONFIG_BOARD, SystemCoreClock / MHZ(1));
     
            return 0;
     }
    

    I consider this however just a workaround.

    I did not understand the connection to the ticket you linked, am I missing something?

    Specifically I'm interested as to why "32Mhz is not a safe choice at current moment":

    • will it be in the future?
    • besides from difficulties in setting it up, are there known problems further down the road?

    Best regards,

    Moritz

Reply
  • Hi Charlie,

    I found a way boost the SPI4 frequency to 32Mhz without hacking. I can use https://docs.zephyrproject.org/apidoc/latest/group__sys__init.html to set the system clock to 128Mhz before SPIM4 is configured.

    diff --git a/src/main.c b/src/main.c
    index 231f206..5d2db35 100644
    --- a/src/main.c
    +++ b/src/main.c
    @@ -15,14 +15,20 @@
     #endif
     #include <stdio.h>
     
    -int main(void)
    -{
     #if NRFX_CLOCK_ENABLED && (defined(CLOCK_FEATURE_HFCLK_DIVIDE_PRESENT) || NRF_CLOCK_HAS_HFCLK192M)
    +static int clock_set_128Mhz(void)
    +{
            /* For now hardcode to 128MHz */
    -       nrfx_clock_divider_set(NRF_CLOCK_DOMAIN_HFCLK,
    -                              NRF_CLOCK_HFCLK_DIV_1);
    +       nrfx_clock_divider_set(NRF_CLOCK_DOMAIN_HFCLK, NRF_CLOCK_HFCLK_DIV_1);
    +
    +       return 0;
    +}
    +SYS_INIT(clock_set_128Mhz, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
     #endif
    -       printk("Starting %s with CPU frequency: %d MHz\n", CONFIG_BOARD, SystemCoreClock/MHZ(1));
    +
    +int main(void)
    +{
    +       printk("Starting %s with CPU frequency: %d MHz\n", CONFIG_BOARD, SystemCoreClock / MHZ(1));
     
            return 0;
     }
    

    I consider this however just a workaround.

    I did not understand the connection to the ticket you linked, am I missing something?

    Specifically I'm interested as to why "32Mhz is not a safe choice at current moment":

    • will it be in the future?
    • besides from difficulties in setting it up, are there known problems further down the road?

    Best regards,

    Moritz

Children
  • Hi Moritz,

    Thanks for sharing your new solution.

    The corncern seems to be solved here nrf70: Implement SPI clock switching by krish2718 · Pull Request #87706 · zephyrproject-rtos/zephyr

    We plan to include this fix with the coming release NCS v3.0.0.

    Best regards,

    Charlie

  • Hi Charlie,

    i saw the mentioned change being incorporated into NCS v.3.0.0. I can confirm that it is now possible to configure SPI4 to 32MHz via app.overlay.

    However, it seems that there is some form of regression in terms of stability and throughput when connected via 2.4 GHz. We did some measurements using the NCS v2.9.1 version + 32 MHz SPI4 workaround and there is no noticeable difference between 2.4 GHz and 5 GHz. Both give an average throughput of 8 Mbits/s.

    Using the NCS v3.0.0 version (no 32 MHz SPI4 workaround needed) there is a different picture:

    for 5 GHz everything is as expected:

    Host:

    $ wifi connect -s########### -k1 -p######## -b5
    ...
    $ zperf tcp upload 192.168.0.202 5001 20 1024
    Remote port is 5001
    Connecting to 192.168.0.202
    Duration:       20.00 s
    Packet size:    1024 bytes
    Rate:           10 kbps
    Starting...
    -
    Upload completed!
    Duration:       20.00 s
    Num packets:    18690
    Num errors:     0 (retry or fail)
    Rate:           7.65 Mbps

    Peer:

    $ iperf -s -i 1
    ------------------------------------------------------------
    Server listening on TCP port 5001
    TCP window size:  128 KByte (default)
    ------------------------------------------------------------
    [  1] local 192.168.0.202 port 5001 connected with 192.168.0.201 port 61423 (icwnd/mss/irtt=14/1460/6060)
    [ ID] Interval       Transfer     Bandwidth
    [  1] 0.0000-1.0000 sec   825 KBytes  6.76 Mbits/sec
    [  1] 1.0000-2.0000 sec   928 KBytes  7.60 Mbits/sec
    [  1] 2.0000-3.0000 sec   952 KBytes  7.80 Mbits/sec
    [  1] 3.0000-4.0000 sec   930 KBytes  7.62 Mbits/sec
    [  1] 4.0000-5.0000 sec   947 KBytes  7.76 Mbits/sec
    [  1] 5.0000-6.0000 sec   942 KBytes  7.72 Mbits/sec
    [  1] 6.0000-7.0000 sec   941 KBytes  7.71 Mbits/sec
    [  1] 7.0000-8.0000 sec   941 KBytes  7.71 Mbits/sec
    [  1] 8.0000-9.0000 sec   935 KBytes  7.66 Mbits/sec
    [  1] 9.0000-10.0000 sec   948 KBytes  7.77 Mbits/sec
    [  1] 10.0000-11.0000 sec   941 KBytes  7.71 Mbits/sec
    [  1] 11.0000-12.0000 sec   932 KBytes  7.64 Mbits/sec
    [  1] 12.0000-13.0000 sec   931 KBytes  7.63 Mbits/sec
    [  1] 13.0000-14.0000 sec   922 KBytes  7.56 Mbits/sec
    [  1] 14.0000-15.0000 sec   930 KBytes  7.62 Mbits/sec
    [  1] 15.0000-16.0000 sec   917 KBytes  7.51 Mbits/sec
    [  1] 16.0000-17.0000 sec   927 KBytes  7.59 Mbits/sec
    [  1] 17.0000-18.0000 sec   915 KBytes  7.50 Mbits/sec
    [  1] 18.0000-19.0000 sec   928 KBytes  7.60 Mbits/sec
    [  1] 19.0000-20.0000 sec   934 KBytes  7.65 Mbits/sec
    [  1] 0.0000-20.0130 sec  18.1 MBytes  7.60 Mbits/sec

    for 2.4 GHz the throughput is less than satisfactory:

    Host:

    $ wifi connect -s########### -k1 -p######## -b2
    ...
    $ zperf tcp upload 192.168.0.202 5001 20 1024
    Remote port is 5001
    Connecting to 192.168.0.202
    Duration:       20.00 s
    Packet size:    1024 bytes
    Rate:           10 kbps
    Starting...
    -
    Upload completed!
    Duration:       20.00 s
    Num packets:    4817
    Num errors:     0 (retry or fail)
    Rate:           1.97 Mbps

    Peer:

    $ iperf -s -i 1
    ------------------------------------------------------------
    Server listening on TCP port 5001
    TCP window size:  128 KByte (default)
    ------------------------------------------------------------
    [  1] local 192.168.0.202 port 5001 connected with 192.168.0.201 port 40620 (icwnd/mss/irtt=14/1460/14986)
    [ ID] Interval       Transfer     Bandwidth
    [  1] 0.0000-1.0000 sec   159 KBytes  1.30 Mbits/sec
    [  1] 1.0000-2.0000 sec   322 KBytes  2.64 Mbits/sec
    [  1] 2.0000-3.0000 sec   331 KBytes  2.71 Mbits/sec
    [  1] 3.0000-4.0000 sec   289 KBytes  2.37 Mbits/sec
    [  1] 4.0000-5.0000 sec   312 KBytes  2.56 Mbits/sec
    [  1] 5.0000-6.0000 sec   305 KBytes  2.50 Mbits/sec
    [  1] 6.0000-7.0000 sec   354 KBytes  2.90 Mbits/sec
    [  1] 7.0000-8.0000 sec   305 KBytes  2.50 Mbits/sec
    [  1] 8.0000-9.0000 sec  44.2 KBytes   362 Kbits/sec
    [  1] 9.0000-10.0000 sec   286 KBytes  2.34 Mbits/sec
    [  1] 10.0000-11.0000 sec   361 KBytes  2.96 Mbits/sec
    [  1] 11.0000-12.0000 sec   374 KBytes  3.06 Mbits/sec
    [  1] 12.0000-13.0000 sec   368 KBytes  3.01 Mbits/sec
    [  1] 13.0000-14.0000 sec   141 KBytes  1.16 Mbits/sec
    [  1] 14.0000-15.0000 sec  2.85 KBytes  23.4 Kbits/sec
    [  1] 15.0000-16.0000 sec   143 KBytes  1.17 Mbits/sec
    [  1] 16.0000-17.0000 sec  87.4 KBytes   716 Kbits/sec
    [  1] 17.0000-18.0000 sec  4.28 KBytes  35.0 Kbits/sec
    [  1] 18.0000-19.0000 sec   278 KBytes  2.28 Mbits/sec
    [  1] 19.0000-20.0000 sec   348 KBytes  2.85 Mbits/sec
    [  1] 0.0000-20.0240 sec  4.70 MBytes  1.97 Mbits/sec

    Looking at the oscilloscope it seems that on multiple occasions during the stream the clock signal disappears and the reappears for short intervals.

    Do you have an opinion about that? Or do you need further information about our setup? Is there any way we can get more information about what happens when the data rate is reduced so much?

    Update:

    Just doing a quick check using SPI4 at 16 MHz we can also see the regression:

    - 6 Mbit/s for 5 GHz

    - 2.6 Mbits/ for 2.4 GHz with short periods of clock signal loss

    Update2:

    Same with SPI4 at 8 MHz:

    - 4.1 Mbits/s for 5 GHz

    - 2.7 Mbits/s for 2.4 GHz with short periods of clock signal loss

Related