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

Toggle gpio certain number of times with PPI

Using nRF5_SDK_15.3.0, soft device 132 (Rigado BMD-300 eval kit), segger embedded studio

I'm trying to control ADS1231 by listening for DRDY to go low and pulse an output to SCLK 25 times (shift 24bits and one more to get the data line to go high as per documentation).

I was planning on using the PPI library to accomplish this.  I have a timer compare event that is hooked up with a gpio toggle task:

/**@brief Initialize ads1231
*/
static void ads_init()
{
uint32_t compare_evt_addr;
uint32_t gpiote_task_addr;
nrf_ppi_channel_t ppi_channel;
ret_code_t err_code;

// initialize gpiote if not already
if (!nrfx_gpiote_is_init())
{
err_code = nrfx_gpiote_init();
APP_ERROR_CHECK(err_code);
}

// configure timer using defaults in sdk_config. Takes a timer, timer config, and handler
nrfx_timer_config_t timer_cfg = NRFX_TIMER_DEFAULT_CONFIG;
err_code = nrfx_timer_init(&ads_sclk_timer, &timer_cfg, adsclk_handler);
APP_ERROR_CHECK(err_code);

// configure gpiote with the task to toggle the bit. Takes the pin to toggle and the task config
nrfx_gpiote_out_config_t config = NRFX_GPIOTE_CONFIG_OUT_TASK_TOGGLE(false);
err_code = nrfx_gpiote_out_init(ADS_SCLK, &config);
APP_ERROR_CHECK(err_code);

// set a compare channel 0 with value 2000UL. Timer will clear based on compare 0. Interrupt for compare channel is disabled
nrfx_timer_extended_compare(&ads_sclk_timer, (nrf_timer_cc_channel_t)0, 1000UL, NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK, true);

// allocate a ppi (programmable peripheral interconnect)
err_code = nrfx_ppi_channel_alloc(&ppi_channel);
APP_ERROR_CHECK(err_code);

// get the event address of compare 0. Get the gpiote address associated with the ADS_SCLK pin
compare_evt_addr = nrfx_timer_event_address_get(&ads_sclk_timer, NRF_TIMER_EVENT_COMPARE0);
gpiote_task_addr = nrfx_gpiote_out_task_addr_get(ADS_SCLK);

// hook up the compare event to trigger the toggle task
err_code = nrfx_ppi_channel_assign(ppi_channel, compare_evt_addr, gpiote_task_addr);
APP_ERROR_CHECK(err_code);

// enable the ppi channel
err_code = nrfx_ppi_channel_enable(ppi_channel);
APP_ERROR_CHECK(err_code);
}

In the timer callback I keep a count of how many times I've interrupted and disable the timer and gpio task when I've pulsed 25 times.  

int numBits = 24;

void adsclk_handler(nrf_timer_event_t event_type, void * p_context){
// internal counter
static int count = 0;

count++;
NRF_LOG_INFO("%d", count);

if (count > 2*numBits) {
// TODO: read bit
}

// shift out one more time to reset drdy
if(count > 2*(numBits+1)) {
// reset count and disable the gpio output pin task
nrfx_gpiote_out_task_disable(ADS_SCLK);
nrfx_timer_disable(&ads_sclk_timer);
count = 0;
}

}

When the DRDY line goes low, I enable the gpiote task and timer:

nrfx_gpiote_out_task_enable(ADS_SCLK); // start sclk to shift data out
nrfx_timer_enable(&ads_sclk_timer);

This produces the following waveform:

There's a little blip at the end of the pulse train that is 12us compared to the rest that are ~60us.  

I'm new to the nrf sdk, is this even the right way to accomplish this?  Would appreciate any advice from the community.  Thanks!

Parents
  • You might try SPI; you would still have to use /DRDY as the trigger by changing the pin to the input sense and then changing it back to the SPI MISO function. Unless you require to poll /DRDY at other places in the code then the 25th clock is superfluous; just use 3-byte 24-bit transfer then revert /DRDY to wait for the next transition. The change from the L or H state - depending on last data bit - would be handled by the AFE when it generates the next falling edge, as it automatically pusghes /DRDY high before issuing falling edge.

Reply
  • You might try SPI; you would still have to use /DRDY as the trigger by changing the pin to the input sense and then changing it back to the SPI MISO function. Unless you require to poll /DRDY at other places in the code then the 25th clock is superfluous; just use 3-byte 24-bit transfer then revert /DRDY to wait for the next transition. The change from the L or H state - depending on last data bit - would be handled by the AFE when it generates the next falling edge, as it automatically pusghes /DRDY high before issuing falling edge.

Children
  • thanks for the response!  I'm trying to implement this now but not sure how to appropriately change /DRDY back to SPI MISO function. 

    I have a GPIOTE event when /DRDY transitions HITOLO, and I have that hooked up to PPI to start the SPI transfer. 

    Can I use the GPIOTE interrupt to disable GPIOTE on /DRDY while the SPI transfer takes place?  Or will that interfere with how PPI receives the event? 

  • I had to change this to not be the answer.  I cannot seem to get this solution working reliably.  As far as I know, there is no way to disable the ppi channel after the /DRDY event is triggered to prevent it from re-firing besides using the gpiote event callback.  Unfortunately, the timing is such that every once in a while the PPI event fires twice before the irq can disable it, resulting in erroneous reads. 

    I proved to myself that this is the cause by injecting a delay in the irq before  calling nrfx_gpiote_in_event_disable and noted that the probability of misreads went up as more delay was injected, and it only took 10us to misread every time.

  • I see the issue; that would imply using the /DRDY event to start a hardware timer which would provide a delay long enough to ensure the interrupt would execute before that timer would trigger the SPI transfer via a PPI compare event. Alternatively and simpler since the update rate is so slow - 10 or 80 times per second - the event interrupt could simply set a flag which the wakeup in main() following this interrupt would check and use to first disable the PPI and restore the /DRDY and then manually start the SPI transfer. The SPI completion interrupt would then revert the /DRDY back to PPI wakeup.

  • This is what I ended up doing, except I took out PPI entirely.  My /DRDY event interrupt disables the event and kicks off the SPI transfer, then the SPI completion interrupt re-enables the GPIOTE event.  Very simple and works well. 

Related