This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

Startup time and GPIO interrupts with bootloader

Hello,

I use a custom board with nRF51822QFAA, with two buttons (left, right) and uart for printf/debug.
The idea of the project is to have the board always sleeping, it wakes up when a button is pressed, sends the corresponding command over BLE and back to sleep.
To illustrate my problem I reduced the code to only "sleep > wake up > printf command > sleep" (see main.c attached).

Here are my questions, see below for more details on the problem :
 - Why does a bootloader add time at startup ? And why this added time is longer when the app code is bigger in size ?
 - How to reduce / eliminate the startup time added by the bootloader ?
 - Is it normal that the example of bootloader from SDK12 (\examples\dfu\bootloader_secure) does not propagate gpio interrupts to the app ?
 - With a bootloader, and even with a long startup time and no interrupt, how can I know which button was pressed when the chip wakes up ?

 

Scenario 1a :
The nRF51822 is loaded with softdevice S130 + app + bootloader (all merged together with mergehex, loaded with nrfjprog).
The bootloader comes from SDK12\examples\dfu\bootloader_secure with only the public_key changed. The app is the .hex from main.c attached, code size ~13k.
The board is asleep, one button is pressed, almost instantly, the chip wakes up and prints "start" on the console, the 'nrf_gpio_pin_read' sees the good pin at '1' and the button/GPIO interrupt is triggered so it is possible to know which button was pressed. The board is back asleep.
(I believe the function 'nrf_gpio_pin_read' reads 1 and the interrupt is triggered because the code runs much faster than the finger pushing the button).

 Scenario 1b :
Same as '1a' (softdevice S130 + app + bootloader) but with a bigger code size. The "big_function" is modified so now the code is about ~90k.
The board is asleep, one button is pressed, about 1s later, the chip wakes up and prints "start" on the console but the 'nrf_gpio_pin_read' function does not reads 1 for the gpio and the interrupt is not triggered. A second push on the button triggers the interrupt, and puts the board back to sleep.
This scenario with a code size of ~45k takes about 1/2s to start.
(This correspond to my final project, so right now I need to press the button two times in order to send the right command...) 

Scenario 2 :
The nRF51822 is loaded with the softdevice (using nRFgo Studio) then with the app using Keil µVision (exactly the same main.c from scenario 1). 
When a button is pressed, the "start" in printed instantly on the console, 'nrf_gpio_pin_read' reads 1 and interrupt is triggered.
(This scenario was repeated with both a small size code (13k) and a big size code (90k), same result in both case).

SDK : nRF5_SDK_12.1.0_0d23e2a
Softdevice : s130_nrf51_2.0.1_softdevice.hex
Computer : Windows 10 - nRFgo Studio v1.21 - Keil µVision v5.21

/* 		REMOTE TEST
 *
 */
#include "softdevice_handler.h"
#include "nrf_drv_gpiote.h"
#include "app_timer.h"
#include "app_uart.h"
#include "nrf_delay.h"

#define RX_PIN_NUMBER 						2
#define TX_PIN_NUMBER 						3
#define BUTTON_L							10
#define BUTTON_R							8
#define NRF_CLOCK_LFCLKSRC      {.source        = NRF_CLOCK_LF_SRC_RC ,  			\
                                 .rc_ctiv       = 16,                    			\
                                 .rc_temp_ctiv  = 0,                     		 	\
                                 .xtal_accuracy = 0}

void UART_init(uint32_t baud_rate);
void gpiote_interrupt_init(uint32_t int_pin);
void gpiote_interrupt_enable(uint32_t int_pin);
void gpiote_interrupt_disable(uint32_t int_pin);
void ble_central_stack_init(void);
void big_function(void);

bool off_flag = false;
int not_used = 0;


int main(void)
{
	UART_init(UART_BAUDRATE_BAUDRATE_Baud115200); printf("\r\n\r\nstart\r\n");
	printf("(A) %d : %d\r\n", nrf_gpio_pin_read(BUTTON_L), nrf_gpio_pin_read(BUTTON_R));
	
    APP_TIMER_INIT(1, 5, false);
	ble_central_stack_init();
	
	gpiote_interrupt_init(BUTTON_L);
	gpiote_interrupt_init(BUTTON_R);
	
    while(1)
    {
		sd_app_evt_wait();
		
		if(not_used)
		{
			big_function();
		}
    
		if(off_flag == true)
		{
			printf("end\r\n");
			nrf_delay_ms(500);
			
			sd_power_system_off();
		}
	}	
	
}



void gpio_event_handler(nrf_drv_gpiote_pin_t pin, nrf_gpiote_polarity_t action)
{
	printf("(%d) %d : %d : ", pin, nrf_gpio_pin_read(BUTTON_L), nrf_gpio_pin_read(BUTTON_R));
	
	if(pin == BUTTON_L)
	{
		gpiote_interrupt_disable(BUTTON_L);
		nrf_delay_ms(500);
		printf("L\n");
		gpiote_interrupt_enable(BUTTON_L);
		off_flag = true;
	}
	else if(pin == BUTTON_R)
	{
		gpiote_interrupt_disable(BUTTON_R);
		nrf_delay_ms(500);
		printf("R\n");
		gpiote_interrupt_enable(BUTTON_R);
		off_flag = true;
	}
}


void gpiote_interrupt_init(uint32_t int_pin)
{
	uint32_t err_code ;
	
	nrf_drv_gpiote_in_config_t in_config = GPIOTE_CONFIG_IN_SENSE_LOTOHI(false);    	//Configure to generate interrupt and wakeup on pin signal low. "false" means that gpiote will use the PORT event, which is low power, i.e. does not add any noticable current consumption (<<1uA). Setting this to "true" will make the gpiote module use GPIOTE->IN events which add ~8uA for nRF52 and ~1mA for nRF51.
	in_config.pull = NRF_GPIO_PIN_PULLDOWN;                                         	//Configure pullup for input pin to prevent it from floting. Pin is pulled down when button is pressed on nRF5x-DK boards, see figure two in http://infocenter.nordicsemi.com/topic/com.nordic.infocenter.nrf52/dita/nrf52/development/dev_kit_v1.1.0/hw_btns_leds.html?cp=2_0_0_1_4
	
	if(!nrf_drv_gpiote_is_init())
	{
		err_code = nrf_drv_gpiote_init();
		APP_ERROR_CHECK(err_code);
	}
	err_code = nrf_drv_gpiote_in_init(int_pin, &in_config, gpio_event_handler);       	//Initialize the wake-up pin
	APP_ERROR_CHECK(err_code);                                                    		//Check error code returned

	gpiote_interrupt_enable(int_pin);
	
}

void gpiote_interrupt_enable(uint32_t int_pin)
{
	nrf_drv_gpiote_in_event_enable(int_pin, true);
}


void gpiote_interrupt_disable(uint32_t int_pin)
{
	nrf_drv_gpiote_in_event_disable(int_pin);
}


void uart_event_handle(app_uart_evt_t * p_event)
{

}

void UART_init(uint32_t baud_rate)
{
    uint32_t                     err_code;

    const app_uart_comm_params_t comm_params =
    {
    	(uint32_t)RX_PIN_NUMBER,
        (uint32_t)TX_PIN_NUMBER,
		(uint32_t)NULL,
        (uint32_t)NULL,
        APP_UART_FLOW_CONTROL_DISABLED,
        false,
        baud_rate
    };

	APP_UART_FIFO_INIT( &comm_params,
                       1,
                       256,
                       uart_event_handle,
                       APP_IRQ_PRIORITY_LOW,
                       err_code);

    APP_ERROR_CHECK(err_code);
}


void ble_central_stack_init(void)
{
    uint32_t err_code;

    nrf_clock_lf_cfg_t clock_lf_cfg = NRF_CLOCK_LFCLKSRC;
    SOFTDEVICE_HANDLER_INIT(&clock_lf_cfg, NULL);
    ble_enable_params_t ble_enable_params;
    err_code = softdevice_enable_get_default_config(0, 1, &ble_enable_params);
    APP_ERROR_CHECK(err_code);
    CHECK_RAM_START_ADDR(0, 1);

    err_code = softdevice_enable(&ble_enable_params);
    APP_ERROR_CHECK(err_code);
}


void big_function(void)
{
	// With only one 'printf' line as below the size of th program is small (Program Size: Code=13400 RO-data=308 RW-data=176 ZI-data=4152).
	// Copy-paste the 'printf' line below about 3000 times to get a bigger program (Program Size: Code=91820 RO-data=308 RW-data=176 ZI-data=4152).
	// By changing the code size, the increase of start-up time can be observed (when using bootloader).
	printf("(A) %d : %d\r\n", nrf_gpio_pin_read(BUTTON_L), nrf_gpio_pin_read(BUTTON_R));
}

  • Hi,

    Why does a bootloader add time at startup ? And why this added time is longer when the app code is bigger in size ?

    The bootloader will calculate the CRC of the application at every boot, and compare it with the expected CRC in the bootloader settings page. The large the application firmware, the longer it takes to calculate the CRC. The application is started if the CRC match. If not, the bootloader enters DFU mode.

    How to reduce / eliminate the startup time added by the bootloader ?

     You can see how this is handled in the nrf_dfu_app_is_valid() implementation in nrf_dfu_utils.c. In most cases removing the CRC check should be safe - you would anyway not be any worse off than in the case where there is no bootloader. 

    Is it normal that the example of bootloader from SDK12 (\examples\dfu\bootloader_secure) does not propagate gpio interrupts to the app ?

     Yes.

    With a bootloader, and even with a long startup time and no interrupt, how can I know which button was pressed when the chip wakes up ?

    If you use the sense mechanism for GPIO pins (for low power), then you cannot know without reading the state of the pins. Doing so from the application might be too late if CRC validation takes a long time. You could device a application specific way of reading the pin state and storing it at a specific location in RAM which is read by the application. However, I would argue that a better and simpler approach is to skip CRC validation and just read the pins in the application.

Related