Generating triggers with a very precise timing

Post here first, or if you can't find a relevant section!
Post Reply
FGS
Posts: 13
Joined: Mon Jun 08, 2020 5:48 pm

Generating triggers with a very precise timing

Post by FGS »

Hello everyone,

for a project i need to generate triggers at a precise period.
It seems the triggers are drifiting from a reference (a few ms after several minutes which is not ok for my application).

In order to have the more precise period possible, i worked with a timer interupt (at 10kHz / 10 useconds rate). In this interrupt i increment a counter in order to fire the trigger (when the counter reach a certain value). I also worked with the micros() function - in both cases the same problem occurs : the period is a bit longer. I use a custom board with a stm32f103c8t6 and a 8MHz crystal coming from mouser, with the right capacitors values. Only doubt i could have it the crystal is not completely close the CPU (i had to put this crystal at 2 cm from the CPU, the capacitors are at 1 cm from the crystal).

Also i can accept a slight period error between each pulses - the more important is to not have a drift that accumulates with time.

Any idea on what can go wrong ?
dannyf
Posts: 447
Joined: Sat Jul 04, 2020 7:46 pm

Re: Generating triggers with a very precise timing

Post by dannyf »

May want to use pwm for this.
FGS
Posts: 13
Joined: Mon Jun 08, 2020 5:48 pm

Re: Generating triggers with a very precise timing

Post by FGS »

Thanks for the suggestion, besides using the PWM solution i m still scratching my head to understand what's going on - do we agree the timing should be really more accurate ?
Also I have four signals to generate like this with different periods and variable timing (but still that needs to be quite accurate) and already using some timers for other duties...

Any other idea on how to do this or what is wrong with my solution ?
dannyf
Posts: 447
Joined: Sat Jul 04, 2020 7:46 pm

Re: Generating triggers with a very precise timing

Post by dannyf »

what is wrong with my solution ?
impossible to tell, without knowing what your solution (=code) is.

a. seems to me that it is counter-productive to not use a peripheral (=pwm) when one is available.
b. if you have to go down the software implementation route, be careful of isr overhead.
c. the easiest would be to use one (or two) compare interrupts.

a couple ideas for you to run down.

1. using one output compare.

Code: Select all

//oc1 isr
void pwmISR(void) {
	static char cnt=0;					//0->set pin, 1->clr pin
	if (cnt++ & 0x01) {OC1R+=sPWM_DC; GIO_SET(sPWM_PORT, sPWM_PIN);}
	else {OC1R+=sPWM_PR-sPWM_DC; GIO_CLR(sPWM_PORT, sPWM_PIN);}
}
pwmISR() is called periodically (when time base matches OC1R) so you set / clear a pin.

2. using two output compares.

Code: Select all

//set pwm
void pwmSet(void) {
	GIO_SET(sPWM_PORT, sPWM_PIN);
}

//clear pwm
void pwmClr(void) {
	GIO_CLR(sPWM_PORT, sPWM_PIN);
}

//user defined set up code
void setup(void) {

	//approach 2
	oc1Init(sPWM_PR); oc1AttachISR(pwmSet);
	oc2Init(sPWM_PR); oc2AttachISR(pwmClr);
	OC2R = OC1R + sPWM_DC;	//provide the initial offset
}
output compare module 1 is set up to set the pwm output pin, and output compare module 2 is set up to clear the pwm output pin, with identical period defined by sPWM_PR. The two have an offset of sPWM_DC, to produce the desired duty cycle.

while the code works on a dspic33 but the basic concept is the same.
FGS
Posts: 13
Joined: Mon Jun 08, 2020 5:48 pm

Re: Generating triggers with a very precise timing

Post by FGS »

This is the code i am using now

In the setup :

Code: Select all

  
  Timer2.pause() ;
  Timer2.setMode(TIMER_CH1, TIMER_OUTPUTCOMPARE);
  Timer2.setPeriod(100);                                // in microseconds - 10kHz
  Timer2.setCompare(TIMER_CH1, 1);                      // overflow might be small
  Timer2.attachInterrupt(TIMER_CH1, htimer_interrupt);
  Timer2.refresh() ;
  Timer2.resume() ;
The htimer_interrupt routine :

Code: Select all

void htimer_interrupt()
{
  smicrotimer = smicrotimer + 100 ;  //64 bits 
}
And here is the code that generates the trigger in the loop (later in the loop the output_buffer is sent to the output)

Code: Select all


        if ( (smicrotimer - timer_last_trigger[0]) >=   period )
        {
          output_buffer[0] = HIGH ;
          timer_last_trigger[0] = smicrotimer ;
          timer_last_trigger_length[0] = smicrotimer ;
        }

    if (smicrotimer - timer_last_trigger_length[0]) >=   trigger_length )
    {
      output_buffer[0] = LOW ;
    }
    
period is a multiple of 100.

of course this code can cause a bit of variable delay for each trigger but this is not a problem. It seems the code accumulates an error which is audible after a few minutes.
dannyf
Posts: 447
Joined: Sat Jul 04, 2020 7:46 pm

Re: Generating triggers with a very precise timing

Post by dannyf »

something for you to consider:
1. your implementation requires very fast loop, much faster than 100 ticks;
2. your code may be too complicated: try to use DWT (if available) or SysTick;
3. "It seems the code accumulates an error which is audible after a few minutes."

no kidding.

try this:

Code: Select all

timer_last_trigger[0] += period ;
overall, the implementation is quite flawed.
ag123
Posts: 1655
Joined: Thu Dec 19, 2019 5:30 am
Answers: 24

Re: Generating triggers with a very precise timing

Post by ag123 »

FGS wrote: Sat Apr 01, 2023 1:14 am Thanks for the suggestion, besides using the PWM solution i m still scratching my head to understand what's going on - do we agree the timing should be really more accurate ?
Also I have four signals to generate like this with different periods and variable timing (but still that needs to be quite accurate) and already using some timers for other duties...

Any other idea on how to do this or what is wrong with my solution ?
The accuracy is only as accurate as (1) the crystal, (2) conditions which may vary the frequency (e.g. temperature, less likely for a few minutes) or more permanently, the capacitors which connects the oscillator network (those can pull the frequency lower).

Hence, the oscillation frequency inaccuracy is a (possible) reason you may observe a difference / drift.
There are TCXOs for that matter
https://www5.epsondevice.com/en/products/tcxo/
possibly expensive, I've not tried those things.

Other possible ways could be to measure and compensate for the *inaccurate* frequency, say of your crystal, and account for that difference, e.g. 7.999 Mhz may after all make a sufficient difference for your observed drift. you may need to count more or less cycles dependent on your measurements / calibration.

of course if you bother to go the distance, you could do things like NTP
https://docs.arduino.cc/library-example ... pNtpClient
https://github.com/IndustrialShields/arduino-NTP
but that it may take quite a bit of custom programming, but that it is a way to work that as a time reference, more appropriately for *calibration* e.g. of your crystal.
dannyf
Posts: 447
Joined: Sat Jul 04, 2020 7:46 pm

Re: Generating triggers with a very precise timing

Post by dannyf »

the issue is with the code.

here is what's happening. let's say that you want to execute a piece of code every 1ms. one way would be to use a timer to call that piece of code periodicially.

if you want to execute that in the loop, you should set up a marker, say tick0, and compare that marker with a tick generator. when the tick genreator matches or exceeds the marker, you execute that code, and then advance the marker to the next execution point. something like this:

Code: Select all

  if (ticks() - tick0 > TICK_CNT) {
    tick0 += TICK_CNT; //advance the marker
    do_something();
  }
TICK_CNT is a constant. This approach assures that do_something() is executed at the first instance where tick0 is exceeded, regardless of prior timing errors.

that's the essense of what I proposed earlier.
ag123
Posts: 1655
Joined: Thu Dec 19, 2019 5:30 am
Answers: 24

Re: Generating triggers with a very precise timing

Post by ag123 »

another thing though, I think interrupts do have some real limits.
There is a rather 'old' project where i tried to make an 'oscilloscope'.
https://github.com/ag88/GirinoSTM32F103duino
here, I tried to drive the ADC in one of a few modes:
a variable sample rate, this is driven by the timer, i.e. when the timer interrupt fires, I call
https://github.com/ag88/GirinoSTM32F103 ... r.cpp#L129

Code: Select all

void CAdcMgr::adctimerhandle(void) {
	//start conversion when called
	ADC1->regs->CR2 |= ADC_CR2_SWSTART;
}
that sets the register to take a sample. it max out at some 500 khz !
that's on a stm32f103
then for the higher sample rates, I used the native adc fixed sample rates configurations.

using timer interrupts to create signals offers the highest flexibility in terms of how you can define the signals.
e.g. using "set output compare" to set the next trigger point, but that I think they'd be limited by the interrupt response time constraint.
there are no easy solutions if you want to generate complex arbitrary signals at a high frequency.

among the ways attempted is to use SPI and DMA, where the signals is maintained in a DMA buffer and transmitted via SPI with DMA.
I'd think there'd still be various limits and race conditions.

there are other more 'exoteric' ways e.g. to use PWM as suggested by @dannyf and even combining timers so that the signal is literally generated by hardware timers. I've not actually tried those as complex signals may be difficult to define by means of hardware. But if you managed to do so, it may achieve the combination of high accuracy and complex signals, at the extreme, completely hardware, i.e. set the registers and go, the timers does the complete signal generation, this would likely not be achievable for arbitrary signals that vary with data.

I think someone (here) tried to drive neopixels using timers
https://cdn-shop.adafruit.com/datasheets/WS2812B.pdf
the challenges among which is that the '1' (on) code and '0' (off) code periods are different. Hence, even pwm won't define it perfectly, as pwm mainly vary the duty cycle. But that I'd guess by combining that with software e.g. to set the next pwm symbol while the timer is constructing and transmitting a current cycle while period is fixed, it may after all be possible to generate those complex signals. A sort of pipelining.
Accordingly, that project is successful and stm32 timers can drive neopixels even though the period specs are given in fraction of a microsecond.

oh and among the things to try, try a different MCU like the STM32F401 or F4xx series. F4xx has that 'ART accelerator' (on chip cache), which purportedly if the commands is in cache, turns the instruction stream from say like 3-5 wait states (assume to be that many system clock cycles) to zero wait states, so if the chip does 80 mhz, it may be 1 ins per cycle, executed out of the cache, 80 Mips. it may make a difference, especially if codes is used to generate signals.
I 'discovered' the performance of STM32F401 and maybe F4xx in this logic analyzer project
viewtopic.php?t=116
I think it (stm32f401) managed close to 5 M samples per sec x 8 bits parallel sampling the pins, maxed out around there. driven by DMA
that is practically 5 MB per sec, the bottlenneck is usb full speed won't be able to transmit that across, so it can only sample a buffer worth of it at a time.
FGS
Posts: 13
Joined: Mon Jun 08, 2020 5:48 pm

Re: Generating triggers with a very precise timing

Post by FGS »

Thanks everyone for your contribution - I will definitively try some of those solutions, also after some more enquiries i suspect the crystal to be indeed not at 8MHz. The error i measured is way less than 0,1%, another solution i m into is to calibrate the timing : sending a precise reference for several minutes and having the module compensate its internal error.
dannyf wrote: Wed Apr 05, 2023 10:05 pm something for you to consider:
1. your implementation requires very fast loop, much faster than 100 ticks;
2. your code may be too complicated: try to use DWT (if available) or SysTick;
3. "It seems the code accumulates an error which is audible after a few minutes."

no kidding.

try this:

Code: Select all

timer_last_trigger[0] += period ;
overall, the implementation is quite flawed.
About this comment, not sure to understand why my loop should be faster than 100 ticks if you mean 100 usec, i made sure my loop works faster than the interrupt period, i mean 100usec allows to do a lot of things ;)
Post Reply

Return to “General discussion”