System Details:
- Board: Custom
- Chip: nRF52840/nRF52833
- PCA Number: PCA10056/10100
- SoftDevice: S140, v7
- nRF5 SDK: Version 16.0.0
- mdk version: 8.46.0
- nrfjprog version: 10.15.4 external
- JLinkARM.dll version: 7.64d
Hello,
I have a BLE UART app that streams on-board sensor data to my central BLE device (laptop) every 50ms (20hz). I visualize this incoming data continuously, and I noticed that every 5 seconds, there is an I2C/TWI communication failure and all sensor data momentarily goes to 0s as the I2C lines become unavailable.
I was able to confirm that this periodic error comes from my code that performs SAADC sampling to read the on-board battery level. When I disabled this SAADC module, the error goes away completely. For context, my code uses a timer, which triggers the SAADC sampling by calling nrf_drv_saadc_sample(). This timer is triggered every BATTERY_SAMPLE_PERIOD milliseconds. I began experimenting with this timer frequency, and I noticed that if this timer is triggered every 900ms (or longer), then the I2C communication error will arise. However, if I set this BATTERY_SAMPLE_PERIOD to something shorter than that, say 800, then this issue never occurs. However, the end result is never actually calculated in only 700ms...generally takes a couple seconds.
Ideally, we want to be able to sample this battery level every 20 seconds or something, but it seems this communication error arises anytime we set the value above 900. My hunch makes me feel like this issue comes about when the SAADC module finishes sampling & sends an interrupt to the main program thread, which blocks the i2c/twi communication somehow. Is there some explanation for this behavior? Would you recommend a way to deal with these types of SAADC timing-related issues?
Here is my battery.c file which contains the battery sampling code for your reference:
#include "app_error.h"
#include "app_scheduler.h"
#include "app_timer.h"
#include "battery.h"
#include "board.h"
#include "nrf_drv_saadc.h"
#include "nrf_log.h"
#include "nrf_gpio.h"
#define BATTERY_SAMPLE_PERIOD 700
#define SAADC_BUFFER_SIZE 3
#define ADC_VOLTAGE_DIV_COMPENSATION 2 // 10M / (10M + 10M)
#define ADC_REF_VOLTAGE_IN_MILLIVOLTS 600
#define ADC_PRE_SCALING_COMPENSATION 6
#define ADC_RESULT_IN_MILLI_VOLTS(ADC_VALUE) ((((ADC_VALUE) * ADC_REF_VOLTAGE_IN_MILLIVOLTS) / 1023) * ADC_PRE_SCALING_COMPENSATION * ADC_VOLTAGE_DIV_COMPENSATION)
APP_TIMER_DEF(m_batteryTimerId);
static nrf_saadc_value_t m_buffer_pool[2][SAADC_BUFFER_SIZE];
static uint16_t m_vBat;
void saadc_callback(nrf_drv_saadc_evt_t const * p_event)
{
ret_code_t err_code;
uint16_t adc_sum_value = 0;
uint16_t adc_average_value;
uint16_t adc_result_millivolts;
if (p_event->type == NRF_DRV_SAADC_EVT_DONE)
{
int i;
for (i = 0; i < SAADC_BUFFER_SIZE; i++)
{
adc_sum_value += p_event->data.done.p_buffer[i];
}
adc_average_value = adc_sum_value / p_event->data.done.size;
//NRF_LOG_INFO("[battery] ADC avg value: %d \r\n", adc_average_value);
adc_result_millivolts = ADC_RESULT_IN_MILLI_VOLTS(adc_average_value);
m_vBat = adc_result_millivolts;
NRF_LOG_INFO("[battery] ADC result: %dmV\r\n", adc_result_millivolts);
err_code = nrf_drv_saadc_buffer_convert(p_event->data.done.p_buffer, SAADC_BUFFER_SIZE);
APP_ERROR_CHECK(err_code);
}
}
static void vBatSampleHelper(void)
{
// Trigger sampling
ret_code_t ret = nrf_drv_saadc_sample();
APP_ERROR_CHECK(ret);
}
uint16_t batteryGetVbat(void)
{
return m_vBat;
}
void batteryTimerHandler(void * p_context)
{
app_sched_event_put(NULL, 0, (app_sched_event_handler_t)vBatSampleHelper);
}
void batterySamplingStart(void)
{
// Start timer
uint32_t ret = app_timer_start(m_batteryTimerId, APP_TIMER_TICKS(BATTERY_SAMPLE_PERIOD), NULL);
APP_ERROR_CHECK(ret);
#ifdef NRF52832_XXAA
// Initialize SAADC hardware to produce correct VBAT readings
nrf_gpio_pin_set(PIN_VBAT_EN);
#endif
}
void batterySamplingStop(void)
{
app_timer_stop(m_batteryTimerId);
#ifdef NRF52832_XXAA
nrf_gpio_pin_clear(PIN_VBAT_EN);
#endif
}
void batteryInit(void)
{
ret_code_t ret;
#ifdef NRF52832_XXAA
/* Configure PIN_VBAT_EN as an OUTPUT */
nrf_gpio_cfg_output(PIN_VBAT_EN);
/* Write PIN_VBAT_EN to a LOW value (disables SAADC VBAT Reading Voltage Divider until BLE connects) */
nrf_gpio_pin_clear(PIN_VBAT_EN);
#endif
// Configure SAADC
nrf_saadc_channel_config_t channel_config = NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(PIN_VBAT_SENSE); // SELECT CHANNEL
channel_config.reference = NRF_SAADC_REFERENCE_INTERNAL; // _REFERENCE_VDD4;
channel_config.gain = NRF_SAADC_GAIN1_6; // _GAAIN1_4;
ret = nrf_drv_saadc_init(NULL, saadc_callback);
APP_ERROR_CHECK(ret);
ret = nrf_drv_saadc_channel_init(0, &channel_config);
APP_ERROR_CHECK(ret);
ret = nrf_drv_saadc_buffer_convert(m_buffer_pool[0], SAADC_BUFFER_SIZE);
APP_ERROR_CHECK(ret);
ret = nrf_drv_saadc_buffer_convert(m_buffer_pool[1], SAADC_BUFFER_SIZE);
APP_ERROR_CHECK(ret);
// Create timer for periodic battery sampling
ret = app_timer_create(&m_batteryTimerId, APP_TIMER_MODE_REPEATED, batteryTimerHandler);
APP_ERROR_CHECK(ret);
}
Many thanks in advance,
Corten