Integration of MAX30001 with Ezurio nRF54L15U DK: Application Hangs after TF-M Boot & SPI/Jumper Configuration

  • Hi Nordic Support Team,

    I am currently working on integrating the MAX30001 ECG/Bio-impedance sensor with the Ezurio nRF54L15U DK (nRF54L series). I am using the nRF Connect SDK V3.1.1

    I have two main issues: the application seems to hang/stop outputting logs after TF-M initialization, and I need clarification on the hardware configuration for this specific third-party DK.

    1. The Issue (Boot Log): I have configured the SPI driver for the MAX30001. When I flash the board, I see the Trusted Firmware-M (TF-M) boot up, but I get no output from my main application. The logs stop here:

    Plaintext
    Pins have been configured as secure.
    GPIO port: 0x00000000
    Pin: 0x00000000
    Pin: 0x00000001
    Booting TF-M v2.1.2
    [Sec Thread] Secure image initializing!
    TF-M Float ABI: Hard
    Lazy stacking enabled
    [INF][PS] Encryption alg: 0x550020
    

    I suspect this is a TrustZone configuration issue where the SPI pins or the GPIOs used for the sensor (CS, INT) are defined as Secure and the Non-Secure application is faulting when trying to access them.

    2. Hardware Configuration Questions (Ezurio DK): Since this is the Ezurio nRF54L15U DK, the documentation is slightly different from the standard Nordic DKs.

    • SPI Pins: Could you confirm the recommended default SPI pins (MOSI, MISO, SCK) and Chip Select (CS) for this board overlay?

    • SPI Mode: For the MAX30001 on nRF54, should I be strictly using SPI Mode 0 (CPOL=0, CPHA=0)?

    • Jumper Configuration: The board has several jumpers (Jxx). To isolate the nRF54L15 for external SPI communication with the sensor, are there specific jumpers I need to remove to disconnect on-board peripherals that might share the same bus?

    • GPIO Security: Do I need to explicitly set the SPI pins to non-secure in the spm.conf or via Kconfig to allow the application to use them?

    My Current Setup:

    • Board: Ezurio nRF54L15U DK

    • Sensor: MAX30001 (MAX30001EVKIT or custom breakout)

    • Interface: SPI

    Any guidance on the correct Device Tree Overlay configuration for the pins and how to pass the execution past the TF-M stage would be helpful.

Parents Reply Children
  • it is not hanging, in the rtt terminal i can see the log 

    SEGGER J-Link V8.66 - Real time terminal output
    SEGGER J-Link (unknown) V1.0, SN=1059980505
    Process: JLink.exe
    MAX30001 ID: 0 0 0
    *** Booting nRF Connect SDK v3.1.1-e2a97fe2578a ***
    *** Using Zephyr OS v4.1.99-ff8f0c579eeb ***


    *** BOOTING NRF54L15 ECG APP ***
    Waiting for MAX30001 (Attempt 1)...
    Waiting for MAX30001 (Attempt 2)...
    Waiting for MAX30001 (Attempt 3)...
    Waiting for MAX30001 (Attempt 4)...
    Waiting for MAX30001 (Attempt 5)...
    Waiting for MAX30001 (Attempt 6)...

  • #include <stdio.h>
    #include <zephyr/kernel.h>
    #include <zephyr/drivers/gpio.h>
    #include <zephyr/drivers/sensor.h>
    #include "max30001.h" 
    
    #define LED0_NODE DT_ALIAS(led0)
    static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios);
    
    #define THREAD_STACKSIZE 4096
    #define THREAD_PRIORITY 7
    
    K_THREAD_STACK_DEFINE(sensor_thread_stack, THREAD_STACKSIZE);
    static struct k_thread sensor_thread;
    
    /* --- BLINKY OF LIFE --- */
    void blink_status_led(int times) {
        if (!gpio_is_ready_dt(&led)) return;
        for (int i=0; i<times; i++) {
            gpio_pin_toggle_dt(&led);
            k_msleep(100);
            gpio_pin_toggle_dt(&led);
            k_msleep(100);
        }
    }
    
    static void thread_entry(void *p1, void *p2, void *p3)
    {
        int ret;
        struct sensor_value ecg_val, bioz_val, rtor_val, leadoff_val;
        const struct device *sensor_dev = (const struct device *)p1;
        
        printk("--- SENSOR THREAD STARTED ---\n");
    
        while (1) {
            // Fetch sample from driver
            ret = sensor_sample_fetch(sensor_dev); 
    
            if (ret == 0) {
                // Valid Data Found
                sensor_channel_get(sensor_dev, SENSOR_CHAN_ECG_UV, &ecg_val);
                sensor_channel_get(sensor_dev, SENSOR_CHAN_BIOZ_UV, &bioz_val);
                sensor_channel_get(sensor_dev, SENSOR_CHAN_RTOR, &rtor_val);
                sensor_channel_get(sensor_dev, SENSOR_CHAN_LDOFF, &leadoff_val);
    
                double ecg_uv = sensor_value_to_double(&ecg_val);
                double bioz_ohm = sensor_value_to_double(&bioz_val);
                
                // Print format: ECG, BioZ, RTOR, LeadOff
                printk("%.0f,%.2f,%d,%d\n", ecg_uv, bioz_ohm, rtor_val.val1, leadoff_val.val1);
                
                // Toggle LED on every successful sample
                gpio_pin_toggle_dt(&led);
            } else {
                // Error handling
                if (ret == -11) {
                    // -11 is EAGAIN (No data yet), just wait a tiny bit
                    k_usleep(100); 
                } else {
                    // Actual Error (e.g., SPI fail)
                    printk("Fetch Error: %d\n", ret);
                    k_msleep(1000); 
                }
            }
        }
    }
    
    void sensor_init(void)
    {
        const struct device *sensor_dev = DEVICE_DT_GET(DT_NODELABEL(max30001));	    
        
        // RETRY LOOP: Don't give up if sensor isn't ready immediately
        int retry_count = 0;
        while (!device_is_ready(sensor_dev)) {
            printk("Waiting for MAX30001 (Attempt %d)...\n", ++retry_count);
            blink_status_led(2); // Double blink = Error/Waiting
            k_msleep(2000);
        }
        
        printk("MAX30001 is READY! Starting thread.\n");
        blink_status_led(5); // 5 Fast blinks = Success
        
        k_thread_create(&sensor_thread, sensor_thread_stack,
                K_THREAD_STACK_SIZEOF(sensor_thread_stack),
                thread_entry, (void *)sensor_dev, NULL, NULL,
                THREAD_PRIORITY, 0, K_FOREVER);
        k_thread_start(&sensor_thread);
    }    
    
    int main(void)
    {
        // 1. Setup LED
        if (gpio_is_ready_dt(&led)) {
            gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE);
            gpio_pin_set_dt(&led, 0);
        }
    
        // 2. Prove Life immediately
        printk("\n\n*** BOOTING NRF54L15 ECG APP ***\n");
        blink_status_led(3); // 3 Slow blinks on startup
        
        // 3. Init Sensor
        sensor_init(); 
    
        while (1) {
            k_sleep(K_FOREVER);
        }
        return 0;
    }


    attached my MAIN.C also

  • device_is_ready() is returning false because the driver initialisation failed on boot before main(), so there is no point in calling this function in a loop. You can probe the I2C bus with a Logic analyser or oscilloscope to find try find why the initialisation is failing. Maybe it's something simple as an address NAK.

  • i am not having a proper driver file also, the driver file which i am using now is based on a git repo and that too with lot of changes in it, so i dont know whether that is the problem, can you tell me how to access the logic analyser in the nrf for this particular issue? 

  • What I meant to suggest was using a logic analyser or oscilloscope to inspect the TWI transactions between the nRF and the MAX30001 slave to determine why the initialization is failing.

Related