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
  • Hello,

    I you haven't already, I would recommend you try to run this without TF-M first to help narrow down the problem. The TF-M log is not reporting any violations or errors, at least. It would also be good if you could try to debug this in VS code to see where the program hangs. 

    Best regards,

    Vidar

  • 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.

Reply Children
Related