Beware that this post is related to an SDK in maintenance mode
More Info: Consider nRF Connect SDK for new designs
This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

Odd SAADC readings

Hello, I am using the heart rate collector example on one NRF52 DK (central), and the heart rate service example on another NRF52 DK (peripheral).

I replaced the sensor sim on the peripheral, and replaced it with saadc to send actual readings to the collector.

With both DK's plugged in to my PC, I - as expected - received a constant reading of 100% battery from the peripheral. But when i unplugged the peripheral from USB power to sample the percentage level of a brand new Energizer cr2032 coin cell battery, the readings did not go as expected.

The initial percentage of the coin cell read from the peripheral started at 100%, but within a few seconds, the battery level began rapidly decreasing (about 2% every 20 seconds). After a few minutes I noticed that the percentage of the battery had halted at around 16-17%, and wouldn''t decrease any further from that level. I took that battery out, and tested another new cr2032. The same thing happened where it drained to about 16% and stopped within a few minutes.

Every 2 seconds (app timer), the battery_level_meas_timeout_handler is called on the peripheral

static void adc_configure(void)
{
    ret_code_t err_code = nrf_drv_saadc_init(NULL, saadc_event_handler);
    APP_ERROR_CHECK(err_code);

    nrf_saadc_channel_config_t config =
        NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_VDD);
    err_code = nrf_drv_saadc_channel_init(0, &config);
    APP_ERROR_CHECK(err_code);

    err_code = nrf_drv_saadc_buffer_convert(&adc_buf[0], 1);
    APP_ERROR_CHECK(err_code);

    err_code = nrf_drv_saadc_buffer_convert(&adc_buf[1], 1);
    APP_ERROR_CHECK(err_code);
}

static void battery_level_meas_timeout_handler(void * p_context)
{
    UNUSED_PARAMETER(p_context);
    battery_level_update();
}

static void battery_level_update(void)
{
    ret_code_t err_code;
    err_code = nrf_drv_saadc_sample();
    APP_ERROR_CHECK(err_code);
}

void saadc_event_handler(nrf_drv_saadc_evt_t const * p_event)
{
    if (p_event->type == NRF_DRV_SAADC_EVT_DONE)
    {
        nrf_saadc_value_t adc_result;
        uint16_t          batt_lvl_in_milli_volts;
        uint8_t           percentage_batt_lvl;
        uint32_t          err_code;

        adc_result = p_event->data.done.p_buffer[0];

        err_code = nrf_drv_saadc_buffer_convert(p_event->data.done.p_buffer, 1);
        APP_ERROR_CHECK(err_code);

        batt_lvl_in_milli_volts = ADC_RESULT_IN_MILLI_VOLTS(adc_result) +
                                  DIODE_FWD_VOLT_DROP_MILLIVOLTS;
        percentage_batt_lvl = battery_level_in_percent(batt_lvl_in_milli_volts);

        printf("Battery level is: %d%%", percentage_batt_lvl);

        err_code = ble_bas_battery_level_update(&m_bas, percentage_batt_lvl, BLE_CONN_HANDLE_ALL);
        if ((err_code != NRF_SUCCESS) &&
            (err_code != NRF_ERROR_INVALID_STATE) &&
            (err_code != NRF_ERROR_RESOURCES) &&
            (err_code != NRF_ERROR_BUSY) &&
            (err_code != BLE_ERROR_GATTS_SYS_ATTR_MISSING)
           )
        {
            APP_ERROR_HANDLER(err_code);
        }
    }
}

I also used the defines that where provided in the proximity example:

#define ADC_REF_VOLTAGE_IN_MILLIVOLTS 600 /**< Reference voltage (in milli volts) used by ADC while doing conversion. */
#define ADC_PRE_SCALING_COMPENSATION 6 /**< The ADC is configured to use VDD with 1/3 prescaling as input. And hence the result of conversion is to be multiplied by 3 to get the actual value of the battery voltage.*/
#define DIODE_FWD_VOLT_DROP_MILLIVOLTS 270 /**< Typical forward voltage drop of the diode . */
#define ADC_RES_10BIT 1024 /**< Maximum digital value for 10-bit ADC conversion. */
#define ADC_RESULT_IN_MILLI_VOLTS(ADC_VALUE)\
((((ADC_VALUE) * ADC_REF_VOLTAGE_IN_MILLIVOLTS) / ADC_RES_10BIT) * ADC_PRE_SCALING_COMPENSATION)

Any idea as to what could cause these weird battery percentage readings?

(I don't plan on using the heart rate example in the future, and I'm aware that not uninitializing ADC in between samples consumes large amounts of current, I simply wanted to test the results of saadc for use in other programs.)

Thanks, Jeremy 

Parents
  • Hi,

    The proximity example (which you have based the battery measurement code on) does not meassure the battery immediately, but rather waits until the first app timer timeout. Until then, the default value (initial_batt_level) of 100% is used. The subsequent percentage number is calculated from the SAADC measurements by battery_level_in_percent(), so this has to be updated to match your battery.

  • Thanks for the reply Einar.

    That makes sense that the default value is used until an actual reading is given. What still doesn't make sense is that the first saadc reading starts around 90% and reaches about 16% in a matter of minutes (what appears to be a rapid drain), yet doesn't drain any further than that point. I don't see how this could ever actually occur.

    Are the new battery's I'm buying actually that depleted, and it's taking saadc a few minutes to give the accurate representation of their battery percentage, or could something be off with the way I'm implementing the saadc reading?

    Going from 90% to 16% in a matter of minutes, but not draining any further doesn't make sense.

  • I see your point. It might be that the battery is depleted quickly for some reason, and probably the discharge curve used in battery_level_in_percent() does not match the battery. Can measure some points with a multimeter as well, and do the battery_level_in_percent() calculations to see if you get the same numbers?

    The only other factor I can think of is that you have not calibrated the SAADC, but that should just give you a fixed offset.

  • So I checked the voltage on SB10 of the USB powered DK and got 3.3v, just to confirm i got the right reading.

    Then I measured the voltage of SB11 on the peripheral that was powered by a cr2032. My multi-meter read 2.64V (the battery_level_in_percent() was returning 13%). 

    If we plug 2.64 into the "else if (mvolts > 2440)" :

    battery_level = 18 - ((2740 - 2640) * 12) / 300 = 14.... so only one percent off.

    Then i took the coin cell out of the peripheral and measured it's voltage at 2.70V. It had previously measured 3.1V only a couple hours before use.

    So it seems that saadc is relatively accurate, but I just don't understand how a cr2032 could go from 3.1V to 2.7V in less than an hour. Then not drain any further (at the same rate), running the heart rate sampler example. This scenario can be duplicated with any new cr2032 I put into the DK.

    Any ideas as to what could be going on?

  • Hi,

    The discharge curve for typical CR2032 batteries is such that there is a steep decline in battery voltage initially, then the voltage is quite stable, before it starts to fall of rapidly again. You can for instance refer to the graph here.

    If your firmware is not power optimized, then it might draw a couple of mA on average, and that together with the typical discharge curve and a bad discharge curve model probably explain what you are seeing.

Reply Children
Related