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

Triggering SAADC sampling in synchronous with GPIO HITOLOW transition.

Product: nrf52840 DK

Softdevice: 17.0.2

Hello,

I would like to start with a very humble statement that I am new to this nrf52 SoCs and would appreciate any of your help.

I am working on a project with a photodiode and two LEDs with different wavelengths. I would like to read the analog values from the photodiode let's say every 0.1ms. There are two gpio outputs from the nrf which toggles the LED each 10 ms. So each GPIO pins are producing 100 Hhz 50% duty cycle PWM signals but are opposite to each other. So once the LED1 is ON, LED2 is OFF and so on.

For the application, synchronization of SAADC SAMPLE START and GPIO transitions are very important. 

So I have managed to toggle both the GPIOs with 100 Hz frequency with opposite polarity with the help of a single external timer instance. Let's say NRF_DRV_TIMER_INSTANCE(1). I have used two different PPI channels to assign tasks and events. So the idea was to set the GPIO2 at a different initial state(HIGH) to obtain the result. So GPIO1 start from LOTOHIGH and GPIO2 starts from HITOLOW. Now I have assigned a different timer (NRF_DRV_TIMER_INSTANCE(2)) for the SAADC. This timer ticks at every 0.1ms to trigger the sampling. Also here I have assigned PPI channel to assign the sampling task and timer event. I am reading the value from the saadc_callback handler. 

Now I would like to know how to synchronize the operation. That is to trigger the clock or to start the sampling at the rising edge of GPIO1. 

1. How can I use the interrupt at the rising edge of the GPIO1 to trigger the SAADC sampling? This is crucial because the first 100 readings from the SAADC will be the analog value from the photosensor caused due to LED1 and the next 100 readings will be generated due to LED2.  

2. Is this approach the correct method or is there any other better method?

3. This question covers a different issue but still, I would like to ask. My application is using a BLE features to send the analog value to a central device. Values are sent to the central device inside the saadc_callback handler. If I had to start the sampling and the GPIO output state transition only after a successful connection is established with the central device, what would be the right way to program the firmware? Are there existing example codes explaining similar usage? (I believe the answer is YES. But I was not able to find any. Please help me out with this.)

Again thank you for your time and would appreciate any of your valuable suggestions. 

Karlz

Parents
  • Hi,

    1. How can I use the interrupt at the rising edge of the GPIO1 to trigger the SAADC sampling? This is crucial because the first 100 readings from the SAADC will be the analog value from the photosensor caused due to LED1 and the next 100 readings will be generated due to LED2.  

    2. Is this approach the correct method or is there any other better method?

    If GPIO1 is configured as an output in GPIOTE, you will not get any interrupt on the rising edge of this GPIO. You either need to use the event that toggles the GPIO to start the TIMER/SAADC sampling through PPI, or setup a separate GPIOTE channel as input (configured as watcher), and physically connect the output and input pins together externally.

    3. This question covers a different issue but still, I would like to ask. My application is using a BLE features to send the analog value to a central device. Values are sent to the central device inside the saadc_callback handler. If I had to start the sampling and the GPIO output state transition only after a successful connection is established with the central device, what would be the right way to program the firmware? Are there existing example codes explaining similar usage? (I believe the answer is YES. But I was not able to find any. Please help me out with this.)

    The simplest solution would be to enable the PPI channel(s) when you receive the CONNECTED event in the application, and similarly, disable the PPI channel(s) when you receive the DISCONNECTED event.

    Best regards,
    Jørgen

  • Hello Jorgen,

    Thank for the reply.

    So let me see if I understood you correctly. Yes the GPIOs are configured in the output of GPIOTE. 

        nrf_drv_gpiote_out_config_t config1 = GPIOTE_CONFIG_OUT_TASK_TOGGLE(true);
        nrf_drv_gpiote_out_config_t config2 = GPIOTE_CONFIG_OUT_TASK_TOGGLE(true);
       
        uint32_t err_code;
    
        err_code = nrfx_gpiote_out_init(GPIO_OUTPUT_PIN_NUMBER1, &config1);
        APP_ERROR_CHECK(err_code);
        err_code = nrfx_gpiote_out_init(GPIO_OUTPUT_PIN_NUMBER2, &config2);
        APP_ERROR_CHECK(err_code);

    I have two PPI channels allocated for task and event mapping.

        nrf_ppi_channel_t ppi_channel1;
        nrf_ppi_channel_t ppi_channel2;
        
        err_code = nrf_drv_ppi_channel_alloc(&ppi_channel1);
        APP_ERROR_CHECK(err_code);
        err_code = nrf_drv_ppi_channel_alloc(&ppi_channel2);
        APP_ERROR_CHECK(err_code);

    Then I use a timer to trigger at each 20ms. With the use of the code below, I manage to toggle the GPIOs alternatively. 

    uint32_t ticks_RED = nrfx_timer_ms_to_ticks(&timer, 20);
    
        nrf_drv_timer_compare(&timer, NRF_TIMER_CC_CHANNEL0, (ticks_RED/2), true);
        nrf_drv_timer_extended_compare(&timer, NRF_TIMER_CC_CHANNEL1, ticks_RED, NRF_TIMER_SHORT_COMPARE1_CLEAR_MASK, true);

    Then I get the address of both event and task and assign them to each PPI channel and finally enable the channels and gpiote. PPI, timer and GPIOTE are initialized inside the main()

        compare_evt_addr1 = nrf_drv_timer_event_address_get(&timer, NRF_TIMER_EVENT_COMPARE0);
        gpiote_task_addr1 = nrf_drv_gpiote_out_task_addr_get(GPIO_OUTPUT_PIN_NUMBER1);
    
        compare_evt_addr2 = nrf_drv_timer_event_address_get(&timer, NRF_TIMER_EVENT_COMPARE1);
        gpiote_task_addr2 = nrf_drv_gpiote_out_task_addr_get(GPIO_OUTPUT_PIN_NUMBER2);
        
        err_code = nrf_drv_ppi_channel_assign(ppi_channel1, compare_evt_addr1, gpiote_task_addr1);
        APP_ERROR_CHECK(err_code);
        err_code = nrf_drv_ppi_channel_assign(ppi_channel2, compare_evt_addr2, gpiote_task_addr2);
        APP_ERROR_CHECK(err_code);
        
        err_code = nrf_drv_ppi_channel_enable(ppi_channel1);
        APP_ERROR_CHECK(err_code);
        err_code = nrf_drv_ppi_channel_enable(ppi_channel2);
        APP_ERROR_CHECK(err_code);
    
        nrf_drv_gpiote_out_task_enable(GPIO_OUTPUT_PIN_NUMBER1);
        nrf_drv_gpiote_out_task_enable(GPIO_OUTPUT_PIN_NUMBER2);

    /***************************SAADC*****************************/

    For the SAADC right now I am using a single channel with 12-bit resolution. 

    Here in SAADC I need to sample every 1ms or even lesser than that. Let's say 1ms. So I have a separate timer instance initialized for triggering sampling. Here I use PPI where the timer compare event of timer instance (2) is triggering sample task in SAADC.

    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 1ms */
        uint32_t ticks = nrf_drv_timer_ms_to_ticks(&m_timer, 1);
        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_callback(nrf_drv_saadc_evt_t const * p_event)
    {
    
        //NRF_LOG_INFO("SAADC event is %d", p_event->type);
        if (p_event->type == NRF_DRV_SAADC_EVT_DONE)
        {
            ret_code_t err_code;
    
            err_code = nrf_drv_saadc_buffer_convert(p_event->data.done.p_buffer, SAMPLES_IN_BUFFER);
            APP_ERROR_CHECK(err_code);
            
            uint16_t value;
            value = p_event->data.done.p_buffer[];
            for(i=0; i<SAMPLES_IN_BUFFER; i++)
            {
            NRF_LOG_INFO("%d", p_event->data.done.p_buffer[i]);
            }
        }
    }

    You either need to use the event that toggles the GPIO to start the TIMER/SAADC sampling through PPI,

    If I am not wrong, the event that toggles the GPIO is 

    compare_evt_addr1 = nrf_drv_timer_event_address_get(&timer, NRF_TIMER_EVENT_COMPARE0);

    So should I use the same timer for both SAADC and GPIOs? ( which i believe is not the solution). Or should I use a separate PPI channel to assign the two timers (timer instance 1 and 2) to each other and then use another PPI channel to assign the timer instance 2 with SAADC for triggering sample? I don't quite understand this. So can you please elaborate?

    separate GPIOTE channel as input (configured as watcher), and physically connect the output and input pins together externally.

    At the moment, I would prefer any other method without an additional external physical connection.

    Thanks in advance.

Reply
  • Hello Jorgen,

    Thank for the reply.

    So let me see if I understood you correctly. Yes the GPIOs are configured in the output of GPIOTE. 

        nrf_drv_gpiote_out_config_t config1 = GPIOTE_CONFIG_OUT_TASK_TOGGLE(true);
        nrf_drv_gpiote_out_config_t config2 = GPIOTE_CONFIG_OUT_TASK_TOGGLE(true);
       
        uint32_t err_code;
    
        err_code = nrfx_gpiote_out_init(GPIO_OUTPUT_PIN_NUMBER1, &config1);
        APP_ERROR_CHECK(err_code);
        err_code = nrfx_gpiote_out_init(GPIO_OUTPUT_PIN_NUMBER2, &config2);
        APP_ERROR_CHECK(err_code);

    I have two PPI channels allocated for task and event mapping.

        nrf_ppi_channel_t ppi_channel1;
        nrf_ppi_channel_t ppi_channel2;
        
        err_code = nrf_drv_ppi_channel_alloc(&ppi_channel1);
        APP_ERROR_CHECK(err_code);
        err_code = nrf_drv_ppi_channel_alloc(&ppi_channel2);
        APP_ERROR_CHECK(err_code);

    Then I use a timer to trigger at each 20ms. With the use of the code below, I manage to toggle the GPIOs alternatively. 

    uint32_t ticks_RED = nrfx_timer_ms_to_ticks(&timer, 20);
    
        nrf_drv_timer_compare(&timer, NRF_TIMER_CC_CHANNEL0, (ticks_RED/2), true);
        nrf_drv_timer_extended_compare(&timer, NRF_TIMER_CC_CHANNEL1, ticks_RED, NRF_TIMER_SHORT_COMPARE1_CLEAR_MASK, true);

    Then I get the address of both event and task and assign them to each PPI channel and finally enable the channels and gpiote. PPI, timer and GPIOTE are initialized inside the main()

        compare_evt_addr1 = nrf_drv_timer_event_address_get(&timer, NRF_TIMER_EVENT_COMPARE0);
        gpiote_task_addr1 = nrf_drv_gpiote_out_task_addr_get(GPIO_OUTPUT_PIN_NUMBER1);
    
        compare_evt_addr2 = nrf_drv_timer_event_address_get(&timer, NRF_TIMER_EVENT_COMPARE1);
        gpiote_task_addr2 = nrf_drv_gpiote_out_task_addr_get(GPIO_OUTPUT_PIN_NUMBER2);
        
        err_code = nrf_drv_ppi_channel_assign(ppi_channel1, compare_evt_addr1, gpiote_task_addr1);
        APP_ERROR_CHECK(err_code);
        err_code = nrf_drv_ppi_channel_assign(ppi_channel2, compare_evt_addr2, gpiote_task_addr2);
        APP_ERROR_CHECK(err_code);
        
        err_code = nrf_drv_ppi_channel_enable(ppi_channel1);
        APP_ERROR_CHECK(err_code);
        err_code = nrf_drv_ppi_channel_enable(ppi_channel2);
        APP_ERROR_CHECK(err_code);
    
        nrf_drv_gpiote_out_task_enable(GPIO_OUTPUT_PIN_NUMBER1);
        nrf_drv_gpiote_out_task_enable(GPIO_OUTPUT_PIN_NUMBER2);

    /***************************SAADC*****************************/

    For the SAADC right now I am using a single channel with 12-bit resolution. 

    Here in SAADC I need to sample every 1ms or even lesser than that. Let's say 1ms. So I have a separate timer instance initialized for triggering sampling. Here I use PPI where the timer compare event of timer instance (2) is triggering sample task in SAADC.

    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 1ms */
        uint32_t ticks = nrf_drv_timer_ms_to_ticks(&m_timer, 1);
        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_callback(nrf_drv_saadc_evt_t const * p_event)
    {
    
        //NRF_LOG_INFO("SAADC event is %d", p_event->type);
        if (p_event->type == NRF_DRV_SAADC_EVT_DONE)
        {
            ret_code_t err_code;
    
            err_code = nrf_drv_saadc_buffer_convert(p_event->data.done.p_buffer, SAMPLES_IN_BUFFER);
            APP_ERROR_CHECK(err_code);
            
            uint16_t value;
            value = p_event->data.done.p_buffer[];
            for(i=0; i<SAMPLES_IN_BUFFER; i++)
            {
            NRF_LOG_INFO("%d", p_event->data.done.p_buffer[i]);
            }
        }
    }

    You either need to use the event that toggles the GPIO to start the TIMER/SAADC sampling through PPI,

    If I am not wrong, the event that toggles the GPIO is 

    compare_evt_addr1 = nrf_drv_timer_event_address_get(&timer, NRF_TIMER_EVENT_COMPARE0);

    So should I use the same timer for both SAADC and GPIOs? ( which i believe is not the solution). Or should I use a separate PPI channel to assign the two timers (timer instance 1 and 2) to each other and then use another PPI channel to assign the timer instance 2 with SAADC for triggering sample? I don't quite understand this. So can you please elaborate?

    separate GPIOTE channel as input (configured as watcher), and physically connect the output and input pins together externally.

    At the moment, I would prefer any other method without an additional external physical connection.

    Thanks in advance.

Children
No Data
Related