This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

Issues in reading analog mic data over SAADC pin of nRF52810.

I have an expansion board by ST mounting an analog mic (MP23ABS1). The mic out pin (via a diff. amplifier) is given on a connector. I take that signal and feed it to the AIN5 (P0.29) on nrf52810. I want to continuously sample the analog mic data at 8000Hz and store the data in a buffer and transmit that over BLE. I've implemented a solution but I'm not getting the right values (already tested analog signal independently over oscilloscope). What I have done is, I initialized the ADC and the configured the channel with default values in single ended mode. Created an application timer with repeat mode and period of timer as 0.125ms(1/8000Hz) and in the timer callback function I sample the ADC(nrf_drv_saadc_sample()). After buffer (96 bytes of int16_t) is full, I check for NRFX_SAADC_EVT_DONE  and convert the buffer, in the saadc handler. And this process repeats after every 0.125ms.

The issue is that the buffer is filled with starting 4-5 bytes as 0x0000 and some random values in between as 0x0000 which get filled gradually. Also the digital values don't change much with changes in the sound I make (while I check in the buffer in the debug mode). Can you help me identify where I am going wrong with the implementation ?

Init Routine:

bool SDL_Mic_Init(void)
{
  uint32_t ret_code=false;
  
  
  ret_code=nrf_drv_saadc_init(NULL, mic_callback);  //ADC Initialization
  
  if(ret_code==NRFX_SUCCESS)
  { 
    nrf_saadc_channel_config_t adc_channel=NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(MIC_INPUT_DATA_PIN);
    
    ret_code=nrf_drv_saadc_channel_init(MIC_ADC_CHANNEL,&adc_channel);
    if(ret_code==NRFX_SUCCESS)
      ret_code=true;
    
   app_timer_create(&test_timer,APP_TIMER_MODE_REPEATED,sampling_callback);
    
    nrf_saadc_continuous_mode_enable(2000); //for 8K sample rate (16000000/2000=8000Hz)
  }
  
  return ret_code;
}

When I send a start streaming command from app

bool Mic_DataAcq_Start(void)
{
  uint32_t ret_code=false;
  //Set the buffer in RAM that will acquire adc data(raw datamic)
    ret_code=nrf_drv_saadc_buffer_convert(mic_handler.audio_sample , AUDIO_SAMPLES_BUFFER_SIZE);
        if(ret_code==NRFX_SUCCESS)
    {
        ret_code=true;
    }
    
    app_timer_start(test_timer, ticks, NULL); //ticks=APP_TIMER_TICKS(0.125)

  return ret_code;
}

After 0.125ms, timer handler will call nrf_drv_saadc_sample() and after buffer is full the following will be called

void mic_callback(const nrf_drv_saadc_evt_t  * p_event)
{
  if(p_event->type==NRFX_SAADC_EVT_DONE)
  {
    //copy mic_handler.audio_samples buffer into a copy buffer
   // memcpy(SRV_DATA_state.txLogFrame3.txStreamFrame.mic_rawaudio.audio_raw_val, mic_handler.audio_sample, AUDIO_SAMPLES_BUFFER_SIZE);
    //Set a flag to indicate FSM process that data is ready to be sent over BLE
    //MSG_SetFlag(MSG_TYPE_SRV_DATA, DATA_FLAG_AMIC_DATA);
    
    nrf_drv_saadc_buffer_convert(p_event->data.done.p_buffer , AUDIO_SAMPLES_BUFFER_SIZE);
  }
  
}

Parents
  • Hi 

    The problem is that callback configured through the app_timer will be affected by other interrupts and latencies in the system, and is not suited for scheduling very rapid events. 

    The app_timer is normally only used for event that happen at a slower rate, and where you can accept some delays in the execution. 

    For triggering ADC samples it is recommended to use a separate TIMER or RTC module, and have it trigger the ADC in the background using the PPI module. Then you don't have to run any code to trigger ADC sampling, and it will not be affected by other interrupts in the system. 

    For an example of how to implement this please have a look at the saadc example in the SDK:
    \nRF5_SDK_17.0.2_d674dde\examples\peripheral\saadc

    Best regards
    Torbjørn

  • Okay I'll try to do that and take a look at the example as well. But I still don't understand one thing, why do I need to use timer when I want to do continuous sampling. Why can't I just set the sample rate in the register as 8KHz (16Mhz/2000) and trigger ADC_START_TASK (NRF_SAADC_TASK_START)

    As I understand (and also from the comment documentation) , it should convert samples at the specified sample rate till RAM buffer is full and once the RAM buffer is full I should receive NRFX_SAADC_EVT_DONE event and upon this event I just transmit the buffer. 

    When I try to do that, it just converts one sample and stops. Can you please explain that ?

  • Hey,

    Sure and please do share your entire code with me . Thanks !

  • Hi Manish

    You will find the example attached:

    nrfx_saadc_continuous_sampling.zip

    It is based on a set of ADC examples made by one of my colleagues, but adjusted to use the SAMPLERATE feature. 

    Best regards
    Torbjørn

  • Thanks overbekk, for the example. I had a quick glance at the code and was curious as to why have you enabled the LFCLK ? Wouldn't the internal ADC timer use the HFCLK(16MHz) ?
    Also, 

    For triggering ADC samples it is recommended to use a separate TIMER or RTC module

    Can you please explain me how I can use a separate RTC module to trigger ADC sample (using PPI only) given that nRF52810 has only RTC0(used by softdevice) and RTC1(used by app_timer) ?

  • Hi Manish

    Manish Kaul said:
    I had a quick glance at the code and was curious as to why have you enabled the LFCLK

    This was a remnant from the original example I based it on. After I shared the example with you I cleaned it up a bit, and asked my colleague to include it in his repository. 

    It seems he hasn't included it there yet, but you can find the cleaned up version here:

    nrfx_saadc_continuous_sampling_v2.zip

    Manish Kaul said:
    Can you please explain me how I can use a separate RTC module to trigger ADC sample (using PPI only) given that nRF52810 has only RTC0(used by softdevice) and RTC1(used by app_timer) ?

    There is no need to use a separate timer if you want to sample at 8kHz, then you can use the SAMPLERATE register as the example shows. Is this not the frequency you need?

    If you don't have any RTC's available you can also use a TIMER module to trigger ADC sampling. Generally the TIMER modules use more current than RTC's (while offering better accuracy), but if you are running the ADC already the difference is unlikely to be that large (unless you want to sample the ADC at a slow rate, then an RTC or the app_timer module is preferred).  

    Best regards
    Torbjørn

  • but you can find the cleaned up version here

    Thank you very much.

    Is this not the frequency you need?

    Yes, still need to sample at 8KHz but that's not the only sample rate I need to work on. Will go as low as 1KHz or even 500Hz as well.

    but if you are running the ADC already the difference is unlikely to be that large (unless you want to sample the ADC at a slow rate, then an RTC or the app_timer module is preferred). 

    I see. But I really need to lower current consumption while sampling at the same rate (8KHz) and lower. So I was thinking of using Low power timer (RTC) for ADC. In case I want to confirm how much difference do I get with TIMER vs. RTC (since I do intend to also lower the sample rate as mentioned above), can I use RTC1 (already used by app_timer) by modifying the app_timer.c by, for example, adding a function to it which set's the CC1 register (CC0 used already by app timer) and ties the COMPARE1 register to PPI ? Or any other way you suggest I can use the RTC1 timer to not interrupt the app timer routines in my application and at the same time be able to use it for sampling via PPI ?

Reply
  • but you can find the cleaned up version here

    Thank you very much.

    Is this not the frequency you need?

    Yes, still need to sample at 8KHz but that's not the only sample rate I need to work on. Will go as low as 1KHz or even 500Hz as well.

    but if you are running the ADC already the difference is unlikely to be that large (unless you want to sample the ADC at a slow rate, then an RTC or the app_timer module is preferred). 

    I see. But I really need to lower current consumption while sampling at the same rate (8KHz) and lower. So I was thinking of using Low power timer (RTC) for ADC. In case I want to confirm how much difference do I get with TIMER vs. RTC (since I do intend to also lower the sample rate as mentioned above), can I use RTC1 (already used by app_timer) by modifying the app_timer.c by, for example, adding a function to it which set's the CC1 register (CC0 used already by app timer) and ties the COMPARE1 register to PPI ? Or any other way you suggest I can use the RTC1 timer to not interrupt the app timer routines in my application and at the same time be able to use it for sampling via PPI ?

Children
  • Hi Manish

    The slower the sample rate the larger the relative difference will be, since it is the sleep currents (not the active currents) that is most affected by running a timer. 

    If you want to compare the two you could always use the app_timer itself to sample the ADC, as long as the frequency is not too large. At 1kHz or lower the app_timer should be able to schedule callbacks fast enough. 

    Then you can compare the current consumption either when using the app_timer or when using a TIMER module, and see if it is big enough to warrant concern. 

    Best regards
    Torbjørn

Related