Hello,
I am experiencing a similar issue to the the following post where the channels sampled from SAADC buffer are becoming out of order (i.e {channels 0 , channel1, channel 6 ... } are becoming miss-aligned as {channel 6, channel 0, channel 1 .. } and so on .. ) after a a few hundred samples.
The differences between my code and the solution posted is that my code leverages DPPI channels (instead of PPI channels) so that the saadc is sampled at a rate of approx 6Khz for the 3 channels driven by the hardware timer.
See code below:
/*
API for reading MFTM ADC Channels
- battery is measured 1/sec
- Force sensors are measured 6000Hz
*/
#include <logging/log.h>
#include "mftm_adc_init.h"
#include "msg_nrf_sensors.h"
#include "msg_npb.h"
#include "msg_pkt.h"
#include "mftm_fsm.h"
#include "mftm_adc_init.h"
#include "mftm_gpio_init.h"
#include <zephyr.h>
#include <helpers/nrfx_gppi.h>
#include <nrfx_dppi.h>
#include <nrfx_saadc.h>
#include <nrfx_timer.h>
#include <logging/log.h>
#if defined(SEND_FORCE_DATA) || defined (SEND_BAT_DATA)
LOG_MODULE_REGISTER(mftm_adc, CONFIG_LOG_DEFAULT_LEVEL);
msg_buffer_t msg_bat;
msg_buffer_t msg_force;
static bool force_fsm_enabled = true; //variable to syncronize temp with fsm
// //at present we want to sample battery 1/sec.
//At 165us samples x 150 sample buffers = 24.750 ms~ 25ms
//therefore we will call the battery function 1 every 40 saadc interrupts
#define BATTERY_ADC_NUM_ITERATIONS (40)
#define SAMPLE_RATE_DEFAULT 165UL //165uS sampling desired
nrfx_timer_t timer1 = NRFX_TIMER_INSTANCE(1); //timer instance
//global bool for timer syncronization between api calls
bool timer_running = false;
uint8_t channel;
int16_t saadc_buf1[SAADC_BUF_SIZE];
int16_t saadc_buf2[SAADC_BUF_SIZE];
int channels_to_read = 0; // actual number of channles we detect
bool send_force_raw = false; //guard for sending force measurments once stop command has been sent to fsm
static bool saadc_prev_enabled = false;
static bool timer_prev_enabled = false;
static nrfx_saadc_channel_t channels[ADC_NUM_CHANNELS] = {NRFX_SAADC_DEFAULT_CHANNEL_SE(NRF_SAADC_INPUT_AIN0, 0),
NRFX_SAADC_DEFAULT_CHANNEL_SE(NRF_SAADC_INPUT_AIN1, 1),
NRFX_SAADC_DEFAULT_CHANNEL_SE(NRF_SAADC_INPUT_AIN2, 2),
NRFX_SAADC_DEFAULT_CHANNEL_SE(NRF_SAADC_INPUT_AIN3, 3),
NRFX_SAADC_DEFAULT_CHANNEL_SE(NRF_SAADC_INPUT_AIN4, 4),
NRFX_SAADC_DEFAULT_CHANNEL_SE(NRF_SAADC_INPUT_AIN5, 5),
NRFX_SAADC_DEFAULT_CHANNEL_SE(NRF_SAADC_INPUT_AIN6, 6),
NRFX_SAADC_DEFAULT_CHANNEL_SE(NRF_SAADC_INPUT_AIN7, 7)};
#if defined(SEND_BAT_DATA)
// function to send bettery messages to TX buffer, since it will be different than the force sensor messages
// these will also be used in the BLE advertisment
static int send_battery_messages(int32_message_t raw_data){
#if defined(TEST_DECODE_BATTERY)
//send raw bytes to be serialized and then queued up
queue_data(MessageID_BAT, &raw_data.bytes, sizeof(int32_message_t),true, &msg_bat);
#else
queue_data(MessageID_BAT, &raw_data.bytes, sizeof(int32_message_t), false, &msg_bat);
#endif //defined(TEST_DECODE_BATTERY)
} // defined(SEND_BAT_DATA)
#endif
#if defined(SEND_FORCE_DATA)
/*
Description: PB Serializes Force messages and puts them on the bt_tx fifo
Note: for the mftm we are sending the 16 bit force values together, this is to conserve throughput
*/
static int send_force_messages(adc_chan_t * forceData){
#if defined(TEST_DECODE_FORCE)
//send raw bytes to be serialized and then queued up
queue_data(MessageID_FORCE, &forceData->bytes,sizeof(Force), true, &msg_force);
#else
queue_data(MessageID_FORCE, &forceData->bytes,sizeof(Force), false, &msg_force);
#endif //defined(TEST_DECODE_FORCE)
}
#endif //defined(SEND_FORCE_DATA)
void saadc_evt_handler(nrfx_saadc_evt_t const * p_event)
{
adc_chan_t * p_raw_adc;
static int32_t sample_adc_count = 0;
nrfx_err_t err = NRFX_SUCCESS;
static uint8_t counter = 0;
if(p_event->type == NRFX_SAADC_EVT_DONE)
{
sample_adc_count++;
if(send_force_raw ){
//at present, we only have 2 16 bit adc force samples that need to be transmitted at 6000Hz
//minimum size of protobuffer is 32 bits, se we will currently send them both out as a combined adc value to reduce throughput by half
//send force messages
p_raw_adc = (adc_chan_t*)p_event->data.done.p_buffer;
send_force_messages(p_raw_adc);
}
}
else if(p_event->type == NRFX_SAADC_EVT_CALIBRATEDONE)
{
LOG_INF("SAADC calibrated.");
}
else if(p_event->type == NRFX_SAADC_EVT_BUF_REQ)
{
counter++;
if(counter%2)
{
err = nrfx_saadc_buffer_set(saadc_buf1, SAADC_BUF_SIZE);
if(err != NRFX_SUCCESS)
{
LOG_ERR("saadc_evt_handler(). Could not set buffer2: %d", err);
}
}
else
{
err = nrfx_saadc_buffer_set(saadc_buf2, SAADC_BUF_SIZE);
if(err != NRFX_SUCCESS)
{
LOG_ERR("saadc_evt_handler(). Could not set buffer1: %d", err);
}
}
}
else if(p_event->type == NRFX_SAADC_EVT_FINISHED)
{
LOG_INF("SAADC finished sampling");
}
}
void timer1_evt_handler(nrf_timer_event_t event_type, void * p_context) //called when timer is triggered
{
// Nothing to do here, the timer's IRQ is not enabled
}
void timer_init(uint32_t sample_rate)
{
nrfx_err_t err = NRFX_SUCCESS;
uint32_t time_ticks;
nrfx_timer_config_t timer_cfg = NRFX_TIMER_DEFAULT_CONFIG;
timer_cfg.frequency = NRF_TIMER_FREQ_16MHz;
timer_cfg.mode = NRF_TIMER_MODE_TIMER;
timer_cfg.bit_width = NRF_TIMER_BIT_WIDTH_32;
timer_cfg.interrupt_priority = NRFX_TIMER_DEFAULT_CONFIG_IRQ_PRIORITY;
//if the timer has already been enabled, we want to disable the instance
if(timer_prev_enabled){
nrfx_timer_uninit(&timer1);
}
err = nrfx_timer_init(&timer1, &timer_cfg, timer1_evt_handler);
if(err != NRFX_SUCCESS)
{
LOG_ERR("timer_init(). Could not initialize TIMER1: %d", err);
return; //return from the function if something went wrong
}else{
timer_prev_enabled = true;
}
time_ticks = nrfx_timer_us_to_ticks(&timer1, (unsigned long)sample_rate);
nrfx_timer_extended_compare(&timer1, NRF_TIMER_CC_CHANNEL0, time_ticks, NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK, false);
}
/*
Decsription: Initalization function to parse the saadcCondig struct and create a bit mask to set the corresponding saadc channels
Input: Pointer to saadc struct
Ouput: bit mask of enabled channels
*/
uint32_t init_chan_cfgs(saadcConfig *saadcConfig){
uint32_t chan_bit_mask = 0;
nrfx_err_t err;
int8_t i = 0;
bool *pchan_cfg = &saadcConfig->enableChan0; //set pointer to address of battery channel
//initalize channels chronologically
for ( i = 0; i <= ADC_NUM_CHANNELS; i++) {
//if paramter is set to true, initalize that channnel
if (*pchan_cfg){
err = nrfx_saadc_channel_config(&channels[i]);
if(err != NRFX_SUCCESS)
{
LOG_ERR("init_chan_cfgs(). Could not configure SAADC channel: %d on error: %d",i, err);
}else{
chan_bit_mask |= (1 << i);
channels_to_read++;
}
}
pchan_cfg++;
}
return chan_bit_mask;
}
/*
Description: saadc initalzation function
input: saadcConfig struct
*/
void saadc_init(saadcConfig *saadc_cfg)
{
nrfx_err_t err = NRFX_SUCCESS;
uint32_t chan_bit_mask = 0;
nrfx_saadc_adv_config_t saadc_adv_cfg = NRFX_SAADC_DEFAULT_ADV_CONFIG;
saadc_adv_cfg.start_on_end = true;
//disable saadc if there already exists an instance
if(saadc_prev_enabled){
nrfx_saadc_uninit();
k_sleep(K_MSEC(100));
}
err = nrfx_saadc_init(IRQ_PRIO_LOWEST);
if(err != NRFX_SUCCESS)
{
LOG_ERR("saadc_init(). Could not initialize SAADC: %x", err);
return; //return from the function if something went wrong
}else{
saadc_prev_enabled = true;
}
err = nrfx_saadc_offset_calibrate(NULL);
if(err != NRFX_SUCCESS)
{
LOG_ERR("saadc_init(). Could not calibrate offset: %d", err);
}
// set up ADC channels
chan_bit_mask = init_chan_cfgs(saadc_cfg);
err = nrfx_saadc_advanced_mode_set(chan_bit_mask, NRF_SAADC_RESOLUTION_12BIT, &saadc_adv_cfg, saadc_evt_handler);
if(err != NRFX_SUCCESS)
{
LOG_ERR("saadc_init() Could not set advanced SAADC mode: %d", err);
}
err = nrfx_saadc_buffer_set(saadc_buf1, SAADC_BUF_SIZE);
if(err != NRFX_SUCCESS)
{
LOG_ERR("saadc_init(). Could not set buffer1: %d", err);
}
err = nrfx_saadc_buffer_set(saadc_buf2, SAADC_BUF_SIZE);
if(err != NRFX_SUCCESS)
{
LOG_ERR("saadc_init(). Could not set buffer2: %d", err);
}
err = nrfx_saadc_mode_trigger();
if(err != NRFX_SUCCESS)
{
LOG_ERR("saadc_init(). Could trigger mode: %d", err);
}else{
LOG_INF("saadc_init(). Saadc initalizated");
}
}
/*
Description: Initalization function for saadc channels. This function initalizes the dppi channels to allow for non-cpu bound hardware timer interrupts for the saadc channels
Input: void pointer to saadc struct
Output: number of channels initalized
Note: in order to meet the 7.5ms BLE connection interval time, the cpu could not be bound for less than 7.5ms in the ISR so
the sample buffer size had to be hard coded as 150 (corresponding to ~25ms interupt latency) to provide us with some margin
*/
int saadc_driver_config(saadcConfig *saadc_cfg)
{
if(saadc_cfg == NULL){
return 0 ;
}
nrfx_err_t err;
force_fsm_enabled = saadc_cfg->enabled;
if(!force_fsm_enabled){
stop_mftm_adc(); // stop adc sample collection
LOG_INF("ssaadc_driver_config(). driver disabled");
return 0; // return 0 force sensors have been enabled
}
/* Connect SAADC IRQ to nrfx_saadc_irq_handler */
IRQ_CONNECT(SAADC_IRQn, IRQ_PRIO_LOWEST, nrfx_isr, nrfx_saadc_irq_handler, 0);
//initalize hardware timer
timer_init(saadc_cfg->odr);
//initalize saadc driver
saadc_init(saadc_cfg);
//allocate dppi channels
err = nrfx_dppi_channel_alloc(&channel);
if (err != NRFX_SUCCESS) {
LOG_ERR("mftm_adc_channels_init(). DPPI channel allocation error: %08x", err);
return 0 ;
}
/* Configure endpoints of the channel so that the TIMER1 CAPTURE0
event is connected with the SAADC SAMPLE task. This means that each time
TIMER1 reaches it's set compare value, the SAADC will sample all
enabled channel once.
*/
nrfx_gppi_channel_endpoints_setup(channel,
nrfx_timer_event_address_get(&timer1, NRF_TIMER_EVENT_COMPARE0),
nrf_saadc_task_address_get(NRF_SAADC, NRF_SAADC_TASK_SAMPLE));
nrfx_dppi_channel_disable(channel);
nrfx_timer_disable(&timer1);
LOG_INF("mftm_adc_channels_init(). nrfx_saadc initalized on %s", CONFIG_BOARD);
return channels_to_read;
}
void start_mftm_adc(bool send_force){
/* Trigger offset calibration
As this generates a _DONE and _RESULT event
the first result will be incorrect.
*/
send_force_raw = send_force;
int err;
if(!timer_running){
err = nrfx_dppi_channel_enable(channel);
if (err != NRFX_SUCCESS) {
LOG_ERR("Failed to enable (D)PPI channel, error: %08x", err);
return;
}
nrfx_timer_enable(&timer1);
timer_running = true;
}
}
void stop_mftm_adc(){
//kill the timer to stop sample measurments
if(timer_running){
nrfx_dppi_channel_disable(channel);
nrfx_timer_disable(&timer1);
timer_running = false;
}
}
#endif //defined(SEND_FORCE_DATA)
According to the solution that the above post, I could resolve this issue by "Using PPI to trigger START task on an END event". I am wondering how that could be implemented using the DPPI driver considering the triggering is already issued by the hardware timer through the DPPI endpoints.
Any help would be appreciated.
Thanks,
Matt