SparkFun Forums 

Where electronics enthusiasts find answers.

Topics pertaining to the Arduino Core & software used with the Artemis module and Artemis development boards.
User avatar
By senaex
#218577
Hi, so I have been working on a project where I really need a way to measure external pulses without using the pulseIn() Funktion from arduino. I searched in the Datasheet of the Apollo 3(I'm using a Redboard Artemis) and found some hints that it is possible, but somehow i can't really understand how I need to set it up.
Here is the picture: Image
Thanks in advance
User avatar
By PaulM007
#218696
I fully agree with this request. I am still using Teensy because of their polished FreqMeasureMulti library which gives easy access to input capture pulse time buffering. Please do NOT tell me to use a general ISR and use micros(). I need the STIMER input capture registers to give me a continuous pulse time buffer as poorly and confusingly explained in Section 14 of the 909 page PDF for the Apollo3.

Can an expert from SparkFun please elucidate proper usage of these registers to time pulses?

If I can get the equivalent of FreqMeasureMulti working on the Artemis with the Apollo's 4 capture registers, I can foresee throwing away my Teensy and switching to SparkFun Apollo.

Thank you!
User avatar
By TS-Brandon
#218722
Hello senaex and PaulM007,

Artemis is an open source hardware platform that we are developing with lots of input from our community. As such, it's constantly changing and improving. I have reached out to our internal team members about your specific question. They advised me that your specific features would be better served using the AmbiqSuite SDK: https://github.com/sparkfun/AmbiqSuiteSDK. Using the SDK you can use the methods there, or just directly configure registers themselves to achieve advanced functionality.
Can an expert from SparkFun please elucidate proper usage of these registers to time pulses?
Unfortunately, with the nature of this open source product we are constantly improving and making tweaks. We don't have a full educational guide for the entire module. However, our resources get you started and the software tools help you expand into new parts of the module outside of the starting guides. Feel free to share your experience for others as well.
User avatar
By PaulM007
#224194
TS-Brandon wrote: Tue Sep 08, 2020 2:59 pm Hello senaex and PaulM007,

Artemis is an open source hardware platform that we are developing with lots of input from our community. As such, it's constantly changing and improving. I have reached out to our internal team members about your specific question. They advised me that your specific features would be better served using the AmbiqSuite SDK: https://github.com/sparkfun/AmbiqSuiteSDK. Using the SDK you can use the methods there, or just directly configure registers themselves to achieve advanced functionality.
Can an expert from SparkFun please elucidate proper usage of these registers to time pulses?
Unfortunately, with the nature of this open source product we are constantly improving and making tweaks. We don't have a full educational guide for the entire module. However, our resources get you started and the software tools help you expand into new parts of the module outside of the starting guides. Feel free to share your experience for others as well.
Hey Brandon, 6 months later I am wondering if by chance there has been any example/library/demonstration of using the Apollo 3/Artemis chip to do input capture timer buffered pulse timestampping/frequency measurement? Using a regular ISR and calling micros() will not cut it. I need to log all pulse timings to SD Card, and doing other work like this will hang and corrupt regular digital ISR timings. I need input capture like Teensy's FreqMeasureMulti library. It would be awesome if I could have the small form factor and low-power of the Artemis combined with this mission critical ability of the Teensy.
User avatar
By TS-Chris
#224409
Unfortunately I can't find any examples demonstrating this, I don't believe we've written any.
User avatar
By robin_hodgson
#224437
I have a working example. I use the capture mechanism to determine the actual frequency of the 32KHz XTAL so that my system can continuously calculate the proper XTAL cal factor setting. Give me a bit of time to come up with a more stand-alone example for you.

What are your capture timing requirements? If you clock STIMER with HFRC, you can get sub-microsecond resolution, but the accuracy is limited to the basic 2% accuracy of the HFRC. If you clock STIMER with XTAL, then your tick resolution is about 30.5 microseconds, but with crystal accuracy of maybe 50 parts per million.
User avatar
By robin_hodgson
#224439
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.
Code: Select all
#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 next tick 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:
  • don't expect the capture event to occur when the request occurs, but rather, when the STIMER gets around to it
  • 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: https://forum.sparkfun.com/viewtopic.ph ... 74#p223574
  • 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: https://forum.sparkfun.com/viewtopic.ph ... 33#p223733
You do not have the required permissions to view the files attached to this post.
User avatar
By robin_hodgson
#224441
Here is a scope shot of the uncertainty between a capture request and an ISR service event with the timer running at 32 KHz. As expected, the ISR still falls into a basic uncertainty of one STIMER clock, but the 32 KHz clock is much faster than the 1 KHz clock in the original example so the uncertainty interval is a lot shorter.
You do not have the required permissions to view the files attached to this post.
User avatar
By PaulM007
#226494
robin_hodgson wrote: Sat Mar 27, 2021 3:31 pm 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.
Code: Select all
#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 next tick 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:
  • don't expect the capture event to occur when the request occurs, but rather, when the STIMER gets around to it
  • 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
  • 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
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.
 Topic permissions

You can post new topics in this forum
You can reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum