my k_work or main loop seem to be starved by BLE

I'm using nRF connect SDK2.9.0. I don't create my threads. Instead, I have a GPIO interrupt and a k_timer interrupt, each doing their own job.

In my timer expiry function, I submit a work to the workqueue that do temperature sensing and PID control.

// Timer structure
static struct k_timer my_timer;

// thread context: temperature control and data transmission (temperature and ad5940)
static void temprt_work_handler(struct k_work *work);
K_WORK_DEFINE(temprt_work, temprt_work_handler);

// thread context: read ad5940 data
static void ECAFE_work_handler(struct k_work *work);
K_WORK_DEFINE(ECAFE_work, ECAFE_work_handler);




// Timer callback function
// timer callback should not do too much operation, otherwise will block other functions like ADC read
void timer_expiry_function(struct k_timer *timer_id)
{
	// Do minimal, non-blocking things here, then submit work. 
	k_work_submit(&temprt_work);
}

static void temprt_work_handler(struct k_work *work)
{

	uint64_t time_stamp;
	int64_t delta_time;

	time_stamp = k_uptime_get();

    log_count++;
	// LOG_INF("Timer callback triggered! Count = %d\n", log_count);
    // Add your processing here (e.g., reading the ADC)
	float adc_temp_reading = fetch_adc_value();
	
	// PID operations...
	k_yield();
}

static void ECAFE_work_handler(struct k_work *work){
	DataCount = APPBUFF_SIZE;
	//Deal with it and provide a buffer to store data we got
	AppAMPISR(AppBuff, &DataCount); 

	//Show the results to UART
	AMPShowResult((float*)AppBuff, DataCount); 

	k_yield();
}

In contrast, I didn't submit my work directly in my GPIO interrupt callback handler. Instead, I set a global flag to 1 interrupt callback handler, and inspect the flag in the main loop:

	while (1) {
		

		/* Check if interrupt flag which will be set when interrupt occurred. */
		if(AD5940_GetMCUIntFlag())
		{
		  AD5940_ClrMCUIntFlag(); /* Clear this flag */
		  LOG_INF("AD5940 INT flag on\n");
		  k_work_submit(&ECAFE_work);

		}

		//k_sleep(K_MSEC(delay_in_ms));
		k_usleep(200);

	}

Now, my problem is that if I enable BLE, my k_timer work is executed properly, but my ECAFE_work is never executed, although I can see the GPIO interrupt triggered and flag is set to 1. It seems that the flag inspection part in my main-loop is never executed. 

In my prj.conf I didn't set anything related to thread and priority. I'm wondering what's the default priority for my work queue and for the BLE? Could you help me identify the problem? Thank you very much.

  • It does not sound to me that this have anything to do with priorities or starvation. It should be enough processing time available to do the work you want to do.

    Perhaps as a first step just do this and see if it works with and without Bluetooth enabled:

    while (1) {
            const bool flag = AD5940_GetMCUIntFlag();
            LOG_INF("AD5940 INT flag: %d", flag);
    		if (flag)
    		{
    		  AD5940_ClrMCUIntFlag();
    		  k_work_submit(&ECAFE_work);
    		}
    
    		k_sleep(K_MSEC(1000));
    	}

    Perhaps share how the interrupt and the functions that gets and sets the flag are implemented.

    In order to log the priorities of all the threads in your system I recommend having a look at the reply I did for this question:

    Zephyr interrupt-thread synchronization - improving real time performance - Nordic Q&A - Nordic DevZone - Nordic DevZone

  • Hi Jakob, 

    Thank you for the quick response.

    Below is how I implement the GPIO interrupt and flags: Note that I didn't submit work directly in the GPIO callback because I need to create a flag that is compatible with AD5940 AFE chip library

    /* GPIO interrupt */
    static const struct device *gpio_dev = DEVICE_DT_GET(DT_NODELABEL(gpio1));
    static struct gpio_callback gpio_cb;
    
    
    
    /* Global flag to indicate an interrupt from the AD5940.
     * Marking it as volatile ensures that the compiler does not optimize
     * accesses to it away, as it can be modified in an interrupt context.
     */
    volatile uint32_t AD5940IntFlag = 0;
    
    
    /* Callback function for handling interrupts */
    static void gpio_int_callback(const struct device *dev, struct gpio_callback *cb,
      uint32_t pins)
    {
      printk("Interrupt detected on pin %d\n", INT_PIN);
      /* Check if our pin triggered the interrupt */
      if (pins & BIT(INT_PIN)) {
        printk("Interrupt detected on pin %d\n", INT_PIN);
        /* Set your interrupt flag or perform additional processing here */
        AD5940IntFlag = 1;
      }
    }
    
    
    
    
    
    
    /* library interface for ad5940: MCU dependent functions */
    /* Declared in ad5940.h, need to put in ad5940_port.c */
    /* cannot use a separate porting code because need to use some global variable?*/
    
    // Other functions...
    
    
    /**
     * @brief This function returns the status of the interrupt flag that is set in the external interrupt handler to indicate the AD5940 has generated an interrupt.
     *
     * @return uint32_t  The current flag value.
     */
    uint32_t AD5940_GetMCUIntFlag(void)
    {
        return AD5940IntFlag;
    }
    
    /**
     * @brief This function clears the interrupt status flag.
     *
     * @return uint32_t  The value of the flag prior to clearing.
     */
    uint32_t AD5940_ClrMCUIntFlag(void)
    {
        uint32_t flag = AD5940IntFlag;
        AD5940IntFlag = 0;
        return flag;
    }
    
    /**
     * @brief This function initializes and configures the main peripherals for the functions defined above.
    * The GPIOs are configured accordingly for the SPI interface, Reset pin and external interrupt pin
    * The SPI is configured according to the AD5940 datasheet
    * The external interrupt source is enable in the NVIC on the cortex core
    * The CS pin and Reset pin are set high.
    */
    uint32_t AD5940_MCUResourceInit(void *pCfg)
    {
        int err;
        int ret;
        
        // GPIO init and config
        err = gpio_is_ready_dt(&rst_spec);
        if (!err) {
          LOG_ERR("Error: RESET is not ready, err: %d", err);
          
        }
    
    
        // return 0 means successful
        ret = gpio_pin_configure_dt(&rst_spec, GPIO_OUTPUT_INACTIVE);
        if (ret < 0) {
            LOG_ERR("Error: Unable to configure AD5940 RESET pin, err: %d\n", ret);
        }
    
        LOG_INF("RESET pin is ready\n");
    
    
    
    
    
    
    
        /* initialize and configure GPIO interrupt */
    
    
    
        if(!device_is_ready(gpio_dev)) {
          LOG_ERR("GPIO_0 device not ready!\n");
        }
    
        /* Configure the pin as an input with a pull-down resistor.
         * Adjust the pull configuration as needed based on your external circuit.
         */
        ret = gpio_pin_configure(gpio_dev, INT_PIN, GPIO_INPUT | GPIO_PULL_UP);
        if (ret != 0) {
            LOG_ERR("Error %d: failed to configure pin %d\n", ret, INT_PIN);
            return -ENODEV;;
        }
    
        /* Configure the interrupt on a rising edge (when the signal goes high).
         * If you need level detection (i.e. the flag is set as long as the signal is high),
         * note that most systems rely on edge-triggering and then check the pin state
         * within the callback.
         */
        ret = gpio_pin_interrupt_configure(gpio_dev, INT_PIN, GPIO_INT_EDGE_FALLING);
        if (ret != 0) {
          LOG_ERR("Error %d: failed to configure interrupt on pin %d\n", ret, INT_PIN);
            return -ENODEV;;
        }
    
        /* Initialize the callback structure and add it */
        gpio_init_callback(&gpio_cb, gpio_int_callback, BIT(INT_PIN));
        gpio_add_callback(gpio_dev, &gpio_cb);
    
        LOG_INF("Configured GPIO pin %d as interrupt input \n", INT_PIN);
    
    
    
    
    
    
    
    
        /* SPI init */
        /*get device address*/
        spi_dev = DEVICE_DT_GET(DT_NODELABEL(my_spi_master));
        
        if(!device_is_ready(spi_dev)) {
          LOG_ERR("SPI master device not ready!\n");
        }
        if(!device_is_ready(spi_cs.gpio.port)){
          LOG_ERR("SPI master chip select device not ready!\n");
        }
    
        LOG_INF("SPI device ready\n");
    
        AD5940_CsSet(); // SETTING THE CS PIN TO HIGH
        AD5940_RstSet();// ITS A ACTIVE HIGH AT AD5940 SO WE SET THE PIN IT TO HIGH
    
        /* Return 0 on success */
        return 0;
    }
    

  • OK, and what output do you get when you run your application? Do you see the "Interrupt detected on pin" messages generated in `gpio_int_callback`?

    Perhaps fix the code so that you do not write the exact same message before and inside the `if` block in that function.

    I am not sure that the checking of `pins` is actually correct in that function. Why not just set the variable to "1" always? But you claim that the code is working without Bluetooth enabled?

    Note that you are configuring the interrupt with GPIO_INT_EDGE_FALLING but the very verbose comment says:

    Configure the interrupt on a rising edge (when the signal goes high)

    By logging and examining a bit more what is actually happening you should be able to get this working. Good luck!

Related