Battery level reading using ADC pin from nRF chip shows significant fluctuation

Hi everyone,

We're reading BAT level by reading data from ADC pin and the setup diagram is shown below. 

We tested our device at hospital, and we observed significant fluctuation highlighted in red regarding its BAT level. For example, it went from 65% to 20%, then jumped to 60% again. 

We then took the device and battery back to the office and tried to reproduce the issue on benchtop. However, we couldn't reproduce it. In contrast, the following is what we got, and it worked as expected.

Our hypothesis is that it could be due to the impedance mismatch between ADC input pin and the output of voltage divider. However, based on our calculation, the input impedance of the voltage divider is equivalent to R13//R15 (50kOhm) while the input impedance of ADC pin is more than 1MOhm. This would not cause the impedance mismatch. 

I greatly appreciate if anyone has any suggestions for this issue. Thanks.

Parents
  • Hi Tai,

    I dare not claim to be an expert, but I have been assigned the case, and will try to help.

    This behavior is indeed abnormal. The SAADC on the nRF52 series doesn't have any limitation that would explain that.

    Could you please elaborate what you did in the bench test? I notice that the discharge curve in the field test isn't similar to that in the bench test, assuming that the drain is constant in both scenarios.

    Is it possible that the device underwent any special mode, or was put under abnormal environmental condition during the time with the measurement dip?
    For example, extreme EM noises requiring multiple retries, keeping the radio on for longer, increasing the current draw.

    Is this observed multiple times or just once, and with one particular unit, or multiple units?

    Hieu

  • Hi Hieu. 

    Could you please elaborate what you did in the bench test? I notice that the discharge curve in the field test isn't similar to that in the bench test, assuming that the drain is constant in both scenarios.

    I used exact hardware device and battery used in hospital. I basically turned the device on, connected to our mobile application, then let it run till the battery is fully depleted. I left the device on my desk, which is normal working environment in the office. 

    Side note: The first discharge curve was only 1.5 hr long measurement while the second one conducted on bench test was 8 hr long. 

    Is it possible that the device underwent any special mode, or was put under abnormal environmental condition during the time with the measurement dip?

    It was running under normal mode. For environmental condition at hospital, it has many medical equipment and there is presence of 2.4 GHz frequency component emitted by this equipment. In other words, it is pretty noisy, and we observed several BLE disconnects due to that before.

    However, for this case I did check the RSSI values recorded in our application. It looked normal.

    Is this observed multiple times or just once, and with one particular unit, or multiple units?

    It happened for us twice in different hospitals. For another case, we put a branch new battery, the HW device showed low BAT right away and BLE disconnect happened after a couple minutes.

  • Best if you could share the SAADC and % calculation code; maybe we'll spot something ..

  • Of course. Here they are. There are pretty standard one for SAADC feature.

    void saadc_sampling_event_init(void)
    {
        ret_code_t err_code;
    
        err_code = nrf_drv_ppi_init();
        APP_ERROR_CHECK(err_code);
    
        nrf_drv_timer_config_t timer_cfg = NRF_DRV_TIMER_DEFAULT_CONFIG;
        timer_cfg.bit_width = NRF_TIMER_BIT_WIDTH_32;
        err_code = nrf_drv_timer_init(&m_timer, &timer_cfg, timer_handler);
        APP_ERROR_CHECK(err_code);
    
        /* setup m_timer for compare event every 1000ms */
        uint32_t ticks = nrf_drv_timer_ms_to_ticks(&m_timer, 1000);
        nrf_drv_timer_extended_compare(&m_timer,
                                       NRF_TIMER_CC_CHANNEL0,
                                       ticks,
                                       NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK,
                                       false);
        nrf_drv_timer_enable(&m_timer);
    
        uint32_t timer_compare_event_addr = nrf_drv_timer_compare_event_address_get(&m_timer,
                                                                                    NRF_TIMER_CC_CHANNEL0);
        uint32_t saadc_sample_task_addr   = nrf_drv_saadc_sample_task_get();
    
        /* setup ppi channel so that timer compare event is triggering sample task in SAADC */
        err_code = nrf_drv_ppi_channel_alloc(&m_ppi_channel);
        APP_ERROR_CHECK(err_code);
    
        err_code = nrf_drv_ppi_channel_assign(m_ppi_channel,
                                              timer_compare_event_addr,
                                              saadc_sample_task_addr);
        APP_ERROR_CHECK(err_code);
    }
    
    
    void saadc_sampling_event_enable(void)
    {
        ret_code_t err_code = nrf_drv_ppi_channel_enable(m_ppi_channel);
    
        APP_ERROR_CHECK(err_code);
    }

    Since I wanted to normalize the BAT level from 0 - 100%, I rescaled it using a linear equation. There has been never an issue before, except for two cases recently.

    // Function to initialize the SAADC (Successive Approximation Analog-to-Digital Converter) module
    static void saadc_init(void)
    {
        ret_code_t err_code;
        nrf_saadc_channel_config_t channel_config = NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN1);
    
       
        nrf_drv_saadc_config_t saadc_config = NRF_DRV_SAADC_DEFAULT_CONFIG;
        saadc_config.resolution = NRF_SAADC_RESOLUTION_12BIT;
        err_code = nrf_drv_saadc_init(&saadc_config, saadc_callback);
        APP_ERROR_CHECK(err_code);
    
        err_code = nrf_drv_saadc_channel_init(0, &channel_config);
        APP_ERROR_CHECK(err_code);
    
        err_code = nrf_drv_saadc_buffer_convert(m_buffer_pool[0], SAMPLES_IN_BUFFER);
        APP_ERROR_CHECK(err_code);
    
        err_code = nrf_drv_saadc_buffer_convert(m_buffer_pool[1], SAMPLES_IN_BUFFER);
        APP_ERROR_CHECK(err_code);
    }
    
    void saadc_callback(nrf_drv_saadc_evt_t const * p_event)
    {
        if (p_event->type == NRF_DRV_SAADC_EVT_DONE)
        {
            ret_code_t err_code;
            uint8_t i;
            nrf_saadc_value_t temp_adc_value = 0;
            nrf_saadc_value_t adc_result = 0;
    
            err_code = nrf_drv_saadc_buffer_convert(p_event->data.done.p_buffer, SAMPLES_IN_BUFFER);
            APP_ERROR_CHECK(err_code);
    
            for (i = 0; i < SAMPLES_IN_BUFFER; i++)
            {
                //NRF_LOG_INFO("temp_adc_%d: %d", i, p_event->data.done.p_buffer[i]);
                temp_adc_value += p_event->data.done.p_buffer[i];
            }
    
            /****************************************************************************
            *                             Convert ADC value to voltage                   *
            * // V(N) = 0;
            // GAIN = 1/6;
            // REFERENCE Voltage = internal (0.6V);
            // RESOLUTION : 14 bit;
            // 14bit
            // ADC_RESULT = [V(P) – V(N) ] x GAIN / REFERENCE x 2^(RESOLUTION - m)
            // (m=0) if CONFIG.MODE=SE, or (m=1) if CONFIG.MODE=Diff
            // V(P) = ADC_RESULT x REFERENCE / ( GAIN x RESOLUTION) = RESULT x (600 / (1/6 x 2^(14)) =  ADC_RESULT x 0.2197265625;
            ****************************************************************************/
            adc_result = temp_adc_value/SAMPLES_IN_BUFFER;
            voltage = adc_result * 0.87890625;
            first_voltage = 805;// 0.5*Vmax from Duracell Optimum (1.69V MAX);
    
            #if RESCALE_BAT
            float temp_voltage_percent = 281.69* voltage/1000 - 126.76;
            if (temp_voltage_percent > 100){
                temp_voltage_percent = 100;
            } else if (temp_voltage_percent <0){
                temp_voltage_percent =0;
            } 
            #else
            float temp_voltage_percent = 100* voltage/ first_voltage;
            #endif
            voltage_level_percent = (uint8_t)temp_voltage_percent;
            
            if(voltage_level_percent <= 20 && isRedLEDBlink != true){
              isRedLEDBlink = true;
              red_led_timers_start();
            }
            // Print or use the voltage value as needed
            NRF_LOG_INFO("ADC value: %d Reference Voltage: %d Current Voltage: %d", adc_result, first_voltage, voltage);
        }
    }

  • Also SAADC init code? Looks like a lot of work within an interrupt callback complete with floating point calculations not clearly defined; also the compiler needs to know if the hardware FPU is being used to correctly handle the FPU interrupt registers, plus there are known hardware FPU power consumption issues which may or may not be handled by the version of SDK .. often best to simply use fixed-point 32-bit integers with appropriate scaling to avoid those issues

  • Ops. I just added the SAADC init code to my previous comment. Thanks!

  • Using fixed-point 32-bit integers sounds a good idea. I'm thinking of sending the raw ADC values and convert it to BAT level in % on the app side, which would reduce the complexity of what interrupt callback needs to be handled. 

Reply Children
No Data
Related