High power consumption of nRF52832 using TWI 100KHz ?

Hi, we are using nRF52832 for our project. SDK version is nRF5_SDK_17.1.0_ddde560. SoftDevice is S132.

The project requires TWI to read data from a sensor and send that data via BLE. The sensor will output 9 bytes every 2.5ms, or 3600B/s data rate. Currently, BLE consumes 0.1mA and it matches the estimated number. When data transfer via BLE is going on, our board consumes 3.8mA. We are trying to lower this number by determining if any part of the system consumes more power than usual.

So we first remove the code that sends data via BLE, which means only connect via BLE, and read data via TWI, but do not send it to the peer. We get a consumption of around 3.2mA
Then we remove the code that reads data from sensor via TWI. This time consumption reduces to 1.5mA. The sensor is still in its working state, just the code to read data in the sensor buffer is removed.

That means reading sensor data via TWI consumes 3.2-1.5=1.7mA. This is so high, especially for a quite low data rate. TWI clock is 100KHz. We check the sensor buffer every 60ms to see if any data is available. The buffer check is simply just 2 registers (head & tail) being read from the sensor. From this, we know how many bytes we can read from the sensor, and we read a burst of that many bytes from the sensor.

Is this number normal for TWI usage? Is there any way we can optimize the power consumption when using TWI? Please let us know soon. We really appreciate your speedy help.

Best regards,

Xander

  • Hi Ovrebekk,

    I currently don't have a tool to check the bus yet.

    In particular, how long will each transaction typically take, and what is the worst case time?

    We have 2 sensors. 1 sensor that will be read every 60ms. Each time I estimate 300 bytes were being transmitted between sensor and MCU. Another sensor will be read every 10 seconds, each time at most 10 bytes were being transmitted. I don't think this low number of bytes transaction can bottleneck the TWI bus. Especially, we never had any issue when we used non-blocking mode.

    Have you tried to increase the interval just to see if the crashes go away? 

    I have increased this interval to 70 and 80ms and it even crashed immediately, rather than after around 20 seconds like 60ms.

    However, I think I found 1 clear problem and if I can solve this, I think I can solve other issues. In our application, if users send a BLE command, a BLE event will set a flag. In main, we check if the flag is set, we will turn on sensor 1, and start the 60ms repeating timer (I removed all code for any other sensor to simplify the issue).

    // Enter main loop.
    for (;;)
    {
        idle_state_handle();
        if (startSensor1Flag){
            sensor1_start();
            ret_code_t err_code = app_timer_start(m_poll_sensor1_timer_id, APP_TIMER_TICKS(60), NULL);
            APP_ERROR_CHECK(err_code);
            startSensor1Flag = false;
        }
    }
    Below is the handler when this timer timeout.

    static void poll_sensor1_timeout_handler(void *p_context){
        UNUSED_PARAMETER(p_context);
        sensor1_checkNewData();   // mix of blocking & non-blocking TWI calls
    }

    sensor1_checkNewData() read Read & Write pointer of sensor internal buffer (uses TWI blocking mode) to determine how many samples are available from sensors. Then it will read a burst of those many samples from the sensor (TWI non-blocking mode).

    Now looking back at the main loop, especially at the sensor1_start() function. This function sets 1 bit of a register to HIGH to start the sensor. But because I don't want it to modify any other bits in the same register, I must first read the register content, then only modify that specific bit and rewrite the modified register content into the register.

    Previously, I made this function in blocking mode as followed.

    void sensor1_start(){
        static sensor1_reg addr_reg = SENSOR1_SYSCONTROL;
        static uint8_t data_enable = 0x04;
        sensor1_write_8_mask(&addr_reg, &data_enable, SENSOR1_FIFO_EN_MASK);
    }
    
    void sensor1_write_8_mask(sensor1_reg *p_reg_addr, uint8_t *p_buffer, uint8_t bitmask){
        uint8_t original_data = 0;
        nrf_twi_mngr_transfer_t const transfer_read[] = {SENSOR1_READ(p_reg_addr, &original_data, 1)};
        nrf_twi_mngr_perform(sensor1_p_nrf_twi_mngr, sensor1_p_twi_config, transfer_read, 2, NULL);
        if ((original_data & ~bitmask) != *p_buffer){
            original_data = (original_data & bitmask) | *p_buffer;
            uint8_t p_write_buffer[] = {*p_reg_addr, original_data};
            nrf_twi_mngr_transfer_t const transfers[] = {SENSOR1_WRITE(p_write_buffer, 2)};
            nrf_twi_mngr_perform(sensor1_p_nrf_twi_mngr, sensor1_p_twi_config, transfers, 1, NULL);
        }
    }
    
    #define SENSOR1_READ(p_reg_addr, p_buffer, byte_cnt) \
        NRF_TWI_MNGR_WRITE(SENSOR1_ADDRESS, p_reg_addr, 1,        NRF_TWI_MNGR_NO_STOP), \
        NRF_TWI_MNGR_READ (SENSOR1_ADDRESS, p_buffer,   byte_cnt, 0)
        
    #define SENSOR1_WRITE(p_buffer, byte_cnt) \
        NRF_TWI_MNGR_WRITE(SENSOR1_ADDRESS, p_buffer, byte_cnt, 0)

    This worked flawlessly before, we even ran it for half an hour and there wasn't any issue. Now I want to make this function also in non-blocking mode to save power consumption as shown below.

    void sensor1_start{
        static readWriteBitMask data = {
            .original_data = 0,
            .new_data = 0x04,
            .bitmask = SENSOR1_FIFO_EN_MASK,
            .addr_reg = SENSOR1_SYSCONTROL
        };
        nrf_twi_mngr_transfer_t const transfers[] =  SENSOR1_READ(&data.addr_reg, &data.original_data, 1)};
        send_i2c sensor1(twi_en_callback, &data, transfers, sizeof(transfers) / sizeof(transfers[0]));    
    }
    
    void send_i2c_sensor1(void (*callback)(ret_code_t result, void *p_user_data), void* p_user_data, const nrf_twi_mngr_transfer_t* transfers, uint8_t num_of_transfer){
        const nrf_twi_mngr_transaction_t p_transaction = {
            .callback = callback,
            .p_user_data = p_user_data,
            .p_transfers = transfers,
            .number_of_transfers = num_of_transfer,
            .p_required_twi_cfg = NULL
        };
        APP_ERROR_CHECK(nrf_twi_mngr_schedule(sensor1_p_nrf_twi_mngr, &p_transaction));
    }
    
    static void twi_en_callback(ret_code_t result, void *p_user_data){
        if (result == NRF_SUCCESS){
            readWriteBitMask* data = (readWriteBitMask*)(p_user_data);
            if ((data->original_data & ~(data->bitmask)) != data->new_data){
                data->original_data = (data->original_data & data->bitmask) | data->new_data;
                uint8_t p_write_buffer[] = {data->addr_reg, data->original_data};
                nrf_twi_mngr_transfer_t const transfers[] = {SENSOR1_WRITE(p_write_buffer, 2)};
                send_i2c_sensor1(NULL, NULL, transfers, sizeof(transfers) / sizeof(transfers[0]));
            }
        }
    }
    
    #define SENSOR1_READ(p_reg_addr, p_buffer, byte_cnt) \
        NRF_TWI_MNGR_WRITE(SENSOR1_ADDRESS, p_reg_addr, 1,        NRF_TWI_MNGR_NO_STOP), \
        NRF_TWI_MNGR_READ (SENSOR1_ADDRESS, p_buffer,   byte_cnt, 0)
        
    #define SENSOR1_WRITE(p_buffer, byte_cnt) \
        NRF_TWI_MNGR_WRITE(SENSOR1_ADDRESS, p_buffer, byte_cnt, 0)

    This will make the app crash immediate as soon as I send the BLE command to start sensor 1. I wonder if there is any conflict between sensor1_start() and sensor1_checkNewData(), so I do not start the 60ms when BLE command is received. It still crashed. So the only culprit now should be the non-blocking sensor1_start() function. I suspect something goes out of scope, so I test my theory by setting callback and pointer to user data to NULL

    void sensor1_start{
        static readWriteBitMask data = {
            .original_data = 0,
            .new_data = 0x04,
            .bitmask = SENSOR1_FIFO_EN_MASK,
            .addr_reg = SENSOR1_SYSCONTROL
        };
        nrf_twi_mngr_transfer_t const transfers[] =  SENSOR1_READ(&data.addr_reg, &data.original_data, 1)};
        send_i2c sensor1(NULL, NULL, transfers, sizeof(transfers) / sizeof(transfers[0]));    
    }

    This time the app won't crash. I still don't understand why and what is the solution now

    EDITED: after several testing, I realize the above non-blocking function if being placed in a separate file (not main.c), app will crash. But if I place these functions inside main.c, crash no longer happens. I don't understand why

  • Hi 

    A common issue with non blocking API's like these is to forget to make the right variables static or const. Then the variables will be put on the stack, and when you exit the function that declared them they will be overwritten with other data. 

    It seems you make most variables static or const, but possibly some are not (such as the p_write_buffer[] array). 

    Can you double check that this is handled correctly? 

    Putting functions in a different file can change where variables are put in memory, which could make a difference if there are some buffer overrun issues or similar, but exactly why it makes a difference in your case I can't spot from the attached code snippets.  

    Best regards
    Torbjørn

  • Thank you Ovrebekk for your help. It helps me solve the issue for most functions, except in this place I needs to calculate the number of bytes to read from TWI on the fly. So I cannot use static const for the transfers array. How should I deal with this case?

  • Hi 

    You don't need to use both static and const, just static is sufficient. 

    Just be aware that you won't be able to declare the variable and assign the data values on the same line, since the declaration of a static value only happens once and can not contain dynamic data. 

    Instead you can declare the static variable at the top of the function, without assigning a value to it, and assign values to it later on where the variable is to be used. 

    Best regards
    Torbjørn

  • Hi Ovrebekk,

    I tried your suggestion but the compiler doesn't allow it.

Related