Hi,
I need to enable the ADC read and GPIO pin toggling to happen simultaneously at high precision, without using the MCU resources, approximately 15 us interval. After searching through this forum, I realized the only way to achieve this precision is to use DPPI in the nrfx drivers directly, instead of using the Zephyr drivers.
I successfully enabled the timer1 that triggers the ADC read configured in the DPPI, and another set of code to use the same timer that toggles the GPIO pin also configured in the DPPI but I cannot set them together. Here are snippets of my code for configuring DPPI:
/* Includes ------------------------------------------------------------------*/ #include <errno.h> #include <logging/log.h> #include <nrfx.h> #include <nrfx_saadc.h> #include <nrfx_timer.h> #include <nrfx_gpiote.h> #include <helpers/nrfx_gppi.h> #include <nrfx_dppi.h> #define SAMPLES_IN_BUFFER 4 #define SAMPLE_RATE 16000000//1000000UL #define IRQ_PRIO_LOWEST 0 #define GPIOTE_INTERRUPT_PRIORITY 1 #define GPIOTE_MCLK_SYNC_PIN_OUT 0 #define DIVIDER_COUNT 20 #define PIN 3 // READ1 channel #define AIN5_READ1 1 #define AIN4_VCM_CHANNEL_INPUT NRF_SAADC_INPUT_AIN4 #define AIN5_READ1_MCU_CHANNEL_INPUT NRF_SAADC_INPUT_AIN5 LOG_MODULE_REGISTER(nrfx_sample, LOG_LEVEL_INF); static void saadc_evt_handler(nrfx_saadc_evt_t const * p_event); static void timer1_evt_handler(nrf_timer_event_t event_type, void * p_context); static void timer_init(void); static void saadc_init(void); int16_t saadc_buf1[SAMPLES_IN_BUFFER]; nrfx_timer_t timer1 = NRFX_TIMER_INSTANCE(1); void main(void) { printk("nrfx_saadc sample on %s\n", CONFIG_BOARD); nrfx_err_t err; /* Connect SAADC IRQ to nrfx_saadc_irq_handler */ IRQ_CONNECT(SAADC_IRQn, IRQ_PRIO_LOWEST, nrfx_isr, nrfx_saadc_irq_handler, 0); timer_init(); saadc_init(); // gpiote_init(); /* Allocate a (D)PPI channel. */ uint8_t dppi_channel_1; err = nrfx_dppi_channel_alloc(&dppi_channel_1); if (err != NRFX_SUCCESS) { LOG_ERR("(D)PPI channel 1 allocation error: %08x", err); return; } /* 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(dppi_channel_1, nrfx_timer_event_address_get(&timer1, NRF_TIMER_EVENT_COMPARE0), nrf_saadc_task_address_get(NRF_SAADC, NRF_SAADC_TASK_SAMPLE)); /* Enable (D)PPI channel. */ err = nrfx_dppi_channel_enable(dppi_channel_1); if (err != NRFX_SUCCESS) { LOG_ERR("Failed to enable (D)PPI channel 1, error: %08x", err); return; } LOG_INF("(D)PPI configured, leaving main()"); nrfx_timer_enable(&timer1); printk("TIMER1 started\n"); } static void saadc_evt_handler(nrfx_saadc_evt_t const * p_event) { nrfx_err_t err = NRFX_SUCCESS; if(p_event->type == NRFX_SAADC_EVT_DONE) { printk("SAADC sampled: \nREAD1\tREAD2\tREAD3\tREAD4\n"); for(uint8_t i = 0; i < p_event->data.done.size; i++) { printk("%hi\t", p_event->data.done.p_buffer[i]); } printk("\n"); } else if(p_event->type == NRFX_SAADC_EVT_CALIBRATEDONE) { printk("SAADC calibrated.\n"); } else if(p_event->type == NRFX_SAADC_EVT_BUF_REQ) { printk("SAADC buffer requested\n"); err = nrfx_saadc_buffer_set(saadc_buf1, SAMPLES_IN_BUFFER); if(err != NRFX_SUCCESS) { LOG_ERR("Error! Could not set buffer1: %d\n", err); } } else if(p_event->type == NRFX_SAADC_EVT_FINISHED) { printk("SAADC finished sampling\n\n"); } } static void timer1_evt_handler(nrf_timer_event_t event_type, void * p_context) { // Nothing to do here, the timer's IRQ is not enabled } static void timer_init(void) { nrfx_err_t err = NRFX_SUCCESS; nrfx_timer_config_t timer_cfg = NRFX_TIMER_DEFAULT_CONFIG; timer_cfg.bit_width = NRF_TIMER_BIT_WIDTH_32; err = nrfx_timer_init(&timer1, &timer_cfg, timer1_evt_handler); if(err != NRFX_SUCCESS) { LOG_ERR("Error! Could not initialize TIMER1: %d\n", err); } nrfx_timer_extended_compare(&timer1, NRF_TIMER_CC_CHANNEL0, nrfx_timer_us_to_ticks(&timer1, 1000000), NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK, false); } static void saadc_init(void) { nrfx_err_t err = NRFX_SUCCESS; nrfx_saadc_channel_t saadc_channel_read1 = NRFX_SAADC_DEFAULT_CHANNEL_DIFFERENTIAL(AIN5_READ1_MCU_CHANNEL_INPUT, AIN4_VCM_CHANNEL_INPUT, AIN5_READ1); nrfx_saadc_adv_config_t saadc_adv_cfg = NRFX_SAADC_DEFAULT_ADV_CONFIG; saadc_adv_cfg.start_on_end = true; err = nrfx_saadc_init(IRQ_PRIO_LOWEST); if(err != NRFX_SUCCESS) { LOG_ERR("Error! Could not initialize SAADC: %d\n", err); } err = nrfx_saadc_offset_calibrate(NULL); if(err != NRFX_SUCCESS) { LOG_ERR("Error! Could not calibrate offset: %d\n", err); } err = nrfx_saadc_channel_config(&saadc_channel_read1); if(err != NRFX_SUCCESS) { LOG_ERR("Error! Could not configure SAADC channel 1: %d\n", err); } err = nrfx_saadc_advanced_mode_set((1 << AIN5_READ1), NRF_SAADC_RESOLUTION_10BIT, &saadc_adv_cfg, saadc_evt_handler); if(err != NRFX_SUCCESS) { LOG_ERR("Error! Could not set advanced SAADC mode: %d\n", err); } err = nrfx_saadc_buffer_set(saadc_buf1, SAMPLES_IN_BUFFER); if(err != NRFX_SUCCESS) { LOG_ERR("Error! Could not set buffer1: %d\n", err); } err = nrfx_saadc_mode_trigger(); if(err != NRFX_SUCCESS) { LOG_ERR("Error! Could trigger mode: %d\n", err); } } void gpiote_init(void) { LOG_INF("nrfx_gpiote sample on %s", CONFIG_BOARD); nrfx_err_t err; err = nrfx_gpiote_init(GPIOTE_INTERRUPT_PRIORITY); if (err != NRFX_SUCCESS) { LOG_ERR("nrfx_gpiote_init error: %08x", err); return; } nrfx_gpiote_out_config_t const out_config = NRFX_GPIOTE_CONFIG_OUT_TASK_TOGGLE(false); /* Initialize output pin. SET task will turn the LED on, * CLR will turn it off and OUT will toggle it. */ err = nrfx_gpiote_out_init(PIN, &out_config); if (err != NRFX_SUCCESS ) { LOG_ERR("nrfx_gpiote_out_init error: %08x", err); } nrfx_timer_config_t timer_cfg = NRFX_TIMER_DEFAULT_CONFIG; timer_cfg.mode = NRF_TIMER_MODE_TIMER; timer_cfg.bit_width = NRF_TIMER_BIT_WIDTH_32; err = nrfx_timer_init(&timer1, &timer_cfg, timer1_evt_handler); nrfx_timer_extended_compare(&timer1, NRF_TIMER_CC_CHANNEL0, DIVIDER_COUNT/2, NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK, false); uint8_t channel; err = nrfx_dppi_channel_alloc(&channel); nrfx_gpiote_pin_t gpiote_mclk_sync_pin_out = PIN; nrfx_gppi_channel_endpoints_setup(channel, nrfx_timer_event_address_get(&timer1, NRF_TIMER_EVENT_COMPARE0), nrfx_gpiote_out_task_addr_get(gpiote_mclk_sync_pin_out)); nrfx_gppi_channels_enable(BIT(channel)); nrfx_gpiote_out_task_enable(PIN); nrfx_timer_enable(&timer1); }
In the API reference, DPPI does not allow one task or event to associate with more than one channel. In my case, ideally, I would like to toggle the GPIO pin based on the timer1 and also triggers the ADC read every time the GPIO toggles, at the rising edge and falling edge.
Can you suggest a method to seamlessly enable the ADC read and GPIO pin toggling with the timer1 configured in DPPI? Maybe there is a way to set a GPIO pin to the PWM based on the timer1 while triggering the ADC read in DPPI?
Thanks!