robin_hodgson:
Here is a working example. This is designed for a stand-alone build. It could be incorporated into Arduino except that Arduino already defines its own capture ISR which will collide with the one defined in this example. There is probably a way around that, but I don’t feel like digging through the Arduino code to see what it wants me to do.
#ifdef __cplusplus
extern “C” {
#endif
// C++ compilations must declare the ISR as a “C” routine or else its name will get mangled
// and the linker will not use this routine to replace the default ISR
void am_stimer_isr();
#ifdef __cplusplus
}
#endif
volatile uint32_t capturedCount;
#define ISR_SIGNAL_PAD 48
void am_stimer_isr()
{
// Drive a GPIO so that a scope or logic analyzer can see when the interrupt actually runs
am_hal_gpio_output_set(ISR_SIGNAL_PAD);
// ‘true’ indicates that the returned status should only reflect those interrupts that are both enabled and asserted.
// ‘false’ would indicated that the status will reflect all asserted interrupts, even those that are not enabled.
uint32_t requestStatus = am_hal_stimer_int_status_get(true);
// Clear all enabled/asserted interrupts
am_hal_stimer_int_clear(requestStatus);
// All we care about is the capture A/0 interrupt:
if (requestStatus & AM_HAL_STIMER_INT_CAPTUREA) {
// Read the captured count
uint32_t count = CTIMER->SCAPT0;
// A real program would do something with the count like stick it in an RTOS queue or a
// circular buffer where the foreground task would be waiting for it to arrive.
// For this test program, we simply store the captured value in a global
// and set a special variable to indicate that the global has new data:
capturedCount = count;
}
am_hal_gpio_output_clear(ISR_SIGNAL_PAD);
}
// We will be capturing the STIMER when a rising transition occurs on this pad.
// For the purposes of this test program, the pad will be driven by our own software
// instead of some external device. To simplify the test setup, we simultaneously use
// the same pad as an output to create the capture request, and as an input that
// drives the capture interrupt request. Using the same pin means no external
// wiring required so this test will work for any Artemis board as long as this pad
// is not connected to any hardware that would be harmed by us driving it.
#define CAPTURE_PAD 7
void captureExample()
{
// Set up the ISR_SIGNAL_PAD as an output. We will be driving ISR_SIGNAL_PAD high during the interval
// while the ISR is running so we can see the time relationship between the edge of the capture request
// and the actual capture event on a scope.
am_hal_gpio_pinconfig(ISR_SIGNAL_PAD, g_AM_HAL_GPIO_OUTPUT_4);
// Configure our CAPTURE_PAD as an output that is also readable. We start by configuring it as an output.
// A more typical application would probably just configure the CAPTURE_PAD as an input.
am_hal_gpio_pinconfig(CAPTURE_PAD, g_AM_HAL_GPIO_OUTPUT_4);
// Jigger the GPIO configuration we just set up so that we can also read the output pad state
GPIO->PADKEY = GPIO_PADKEY_PADKEY_Key;
GPIO->PADREGB_b.PAD7INPEN = GPIO_PADREGB_PAD7INPEN_EN;
GPIO->PADKEY = 0;
// Make sure our capture request output starts off at ‘0’
am_hal_gpio_output_clear(CAPTURE_PAD);
// Pick a clock source for the STIMER. There are lots of choices.
// We will use a really slow clock source just to make the timing relationship between the capture request
// and the capture event extremely obvious.
am_hal_stimer_config(AM_HAL_STIMER_LFRC_1KHZ /AM_HAL_STIMER_XTAL_32KHZ/ /AM_HAL_STIMER_XTAL_1KHZ/);
// Optional: Clear & restart the STIMER
am_hal_stimer_counter_clear();
// Enable capture 0/A to watch the CAPTURE_PAD input to capture rising edges (if false) or falling edges (if true).
am_hal_stimer_capture_start(0, CAPTURE_PAD, false); // capture on rising edges
// Flush any pre-existing capture interrupt, then enable CAPTURE A/0 events
am_hal_stimer_int_clear(AM_HAL_STIMER_INT_CAPTUREA);
am_hal_stimer_int_enable(AM_HAL_STIMER_INT_CAPTUREA);
//NVIC_SetPriority(STIMER_IRQn, 0);
NVIC_EnableIRQ(STIMER_IRQn);
while (1) {
// Generate a capture request by driving the CAPTURE_PAD GPIO to ‘1’, then back to ‘0’ again
am_hal_gpio_output_set(CAPTURE_PAD);
am_hal_flash_delay(FLASH_CYCLES_US(10));
am_hal_gpio_output_clear(CAPTURE_PAD);
// The capture request gets latched immediately by the silicon, but it is not actually recognized
// and acted on by the STIMER until the next STIMER clock tick.
// In the worst case, this can take as much as 1 clock period of the STIMER clock!
// We are not allowed to request another capture until at least 1 clock period of the STIMER has expired
// We will wait an extra-long time here before we generate another capture request:
am_hal_flash_delay(FLASH_CYCLES_US(10000));
}
}
If it was not clear reading through the code, the capture mechanism was not exactly intuitive to me. The main issue is that when a signal generates a capture event request, an oscilloscope shows that actual capture will not occur until the <B>**next tick**</B> of the STIMER clock. If you are using a slow 1 KHz clock, that could be over 1 millisecond later. Since the STIMER clock is not synchronized to the HFRC processor clock, the precise time between any given request and the capture will be essentially random, but contained within 1 STIMER clock period. See the attached photo for what I am trying to say.
In the photo, the top trace shows the signal that is requesting the capture. The bottom trace is set up to show when the ISR runs. The bottom trace is in a 'persist' mode where the traces persist on the scope display over time. Since the STIMER clock is not synchronized with the processor clock, the ISR will occur at an almost random time afterwards. The persistence display allows us capture thousands of events where each narrow pulse remains on the display as a dim event. Over time, the scope display builds up a display of all of the capture ISR events that it ever saw. From that, you can determine the minimum time between requesting the capture and the ISR servicing the event (about 500 uSec) and the maximum time (about 1500 uSec). This is with a slow 1 KHz STIMER clock, specifically chosen to make this lag effect between the request and the ISR obvious. If STIMER was running from a crystal at 32 KHz, you would still see a block of possible ISR times, but the block would be much closer to the request, and would be much narrower in width.
The main takeaways regarding the Capture process:
<LIST><LI>- don't expect the capture event to occur when the request occurs, but rather, when the STIMER gets around to it</LI>
<LI>- there is an upper limit on how often you can generate a capture operation: if the system takes 1 STIMER clock to respond, you obviously can't possibly capture faster than the STIMER clock rate. In truth, it appears that the max capture rate is probably 1/2 the STIMER clock rate. More info on that topic is here: [viewtopic.php?p=223574#p223574](https://forum.sparkfun.com/viewtopic.php?p=223574#p223574)</LI>
<LI>- If you are trying for super accuracy with your capturing, beware that the STIMER has an undocumented bug which can make this a profoundly difficult proposition. More info here: [viewtopic.php?p=223733#p223733](https://forum.sparkfun.com/viewtopic.php?p=223733#p223733)</LI></LIST>
Thank you so much Robin for sharing your expertise on this matter. I regret that I took so long to check back here and appeared unappreciative of your contribution. It is a shame a functionality so valuable is so difficult to configure. I am convinced though you have given me an excellent starting point. When I use naive digital ISRs to record pulse times and then save those buffered pulse timings to SD card, I always get corrupted pulse timings during when the SD card writing is happening (having tried this on Apollo but in general). Whereas using Teensy's FreqMeasure library that does real-deal input capture buffered, the results are separately and pristinely buffered by the hardware timestamppings themselves and not affected by other blocking operations. I'm hoping I can adapt the code you've provided above to do just that on the Artemis Apollo3.