Hello everyone! I am trying to use my Nano Every to generate a pulse train - repeated short (10 usec) pulses at a longer (100 ms) interval. The exact duration of each doesn't matter, but the repeatability of the timing between pulses does. I'm programming the 4809 without register emulation, using hardware timers + interrupts and direct port manipulation to create the output pulse. For the 100 ms interval, I observe +/- 30 usec jitter between pulses. Is that really the limit of what's achievable with the internal oscillator, or am I missing a source of uncertainty in my code?
While in the end I would like dynamic control over the interval, below is the simplest code I came up with implementing a fixed interval. Triggering an oscilloscope on one pulse, the next pulse here shows low (~1 usec) random jitter that accumulates over time, such that a pulse 100 ms later shows about 30 usec jitter. It stays about the same even when I increase the system clock prescaler to create fewer overflows/interrupts.
//Create a 10 usec (approx.) pulse every 1 msec
//Use pin 13 (PORTE PIN 2) as output
void setup() {
VPORTE.DIR |= PIN2_bm; // Set pin as output
TCA0.SINGLE.CTRLA &= ~(TCA_SINGLE_ENABLE_bm); // Disable main timer to avoid interrupts
TCB2.CTRLA=TCB_CLKSEL_CLKDIV2_gc; // Set clock source as 8 MHz
TCB2.CTRLB=TCB_CNTMODE_INT_gc; // Set to periodic interrupt mode
TCB2.INTCTRL=TCB_CAPT_bm; // Enable interrupt
TCB2.CCMP=7999; // Set TOP value for 1 ms
TCB2.CNT=0; // Reset count
TCB2.CTRLA |= TCB_ENABLE_bm; // Start
}
void loop() {
}
ISR(TCB2_INT_vect)
{
VPORTE.OUT |= PIN2_bm; // Set output
delayMicroseconds(10);
VPORTE.OUT &= ~(PIN2_bm); // Clear output
TCB2.INTFLAGS = TCB_CAPT_bm; // Reset interrupt flag
}
After browsing the forum for a while for approaches, I have tried:
All approaches seem to leave me at that same ~30 usec jitter, making me think they all suffer from the same issue (which may be oscillator limits). Any help in improving the code or saying it can't be done would be appreciated!
Does jitter appear in the same way if the pulse is output only by the hardware timer without using interrupts?
(That is, PWM mode with very low duty.)
Thereby I think that it can determine whether the jitter is due to the code or the oscillator.
Never use delay or other blocking code in an interrupt!
Interrupts always generate a certain jitter. It depends on when they are really executed. There can be another one just executed before.
Once roughly thought. TCB has several channels. One channel generates the 10µs pulse by timeout and switches its pin without interrupt. A second channel starts the first channel every 1ms.
@chrisknightley I've tried the code below (switched to TCB1 so I could get to an output pin) using the 8 bit PWM without interrupt servicing. I still see the same level of jitter 100 ms out from a reference pulse.
void setup() {
//TCA0.SINGLE.CTRLA &= ~(TCA_SINGLE_ENABLE_bm); // Disable main timer to avoid interrupts
PORTMUX.TCBROUTEA |= PORTMUX_TCB1_bm; // Use output pin D3
TCB1.CTRLA=TCB_CLKSEL_CLKTCA_gc; // Set clock source as 8 MHz
TCB1.CTRLB=TCB_CNTMODE_PWM8_gc|TCB_CCMPEN_bm; // Set to PWM mode and enable output
TCB1.CCMPH=20;
TCB1.CCMPL=255;
TCB1.CNT=0; // Reset count
TCB1.CTRLA |= TCB_ENABLE_bm; // Start
}
void loop() {
}
I also tried clocking TCB1 off of TCA (which is why the timer disable is commented out), no change in behavior.
@Doc_Arduino I switched the ISR from my original code to simply toggle the output pin for now, removing any blocking code, and still see the same issue. Although I shouldn't have been surprised at the result after the above PWM test.
Interestingly, I dug out my Uno and ran a simple PWM test there, and observe <1usec jitter 100 ms after a reference pulse. That board has a crystal (and obviously a different chip) - is that the difference? Maybe I'll just have to switch platforms for this.
ATmega4809 the MCU installed in Nano Every has equipped with a high-precision RC oscillator.
However, although it is highly accurate it is naturally inferior to the crystal oscillator.
And unfortunately Atmel has removed the external oscillator from the AVR of this series.
If you want more accuracy by Xtal, you need Xtal and an external oscillator.
And changing to a board with external oscillator included MCU, is not a bad option.
of course the Nano Every has a different µC on it. A modern Atmega4809. The Every board uses the internal RC. The clock is also very stable. I can't detect any jitter. TCB in PWM mode and WO pin. Everything else is already or still preset by the Arduino framework.
void setup() {
//TCA0.SINGLE.CTRLA = 0; // Stop TCA0
VPORTF.DIR |= PIN5_bm; // Set pin 'D3' as output
TCB1.CTRLA = 0; // Reset & Stop
TCB1.CTRLA=TCB_CLKSEL_CLKDIV2_gc; // Set clock source as 8 MHz
TCB1.CTRLB=TCB_CNTMODE_PWM8_gc | TCB_CCMPEN_bm; // Set to PWM mode and enable output
TCB1.CCMPH=20;
TCB1.CCMPL=255;
TCB1.CNT=0; // Reset count
TCB1.CTRLA |= TCB_ENABLE_bm; // Start
}
void loop() {
}
Thank you for your sample code, which probably correctly stops the timer before configuring it. I ran it on my Nano Every and still see the same issue. Using an oscilloscope to measure from the start of one pulse to the start of the next, there is <100ns jitter. Measuring from the start of one pulse to the start of one 100 ms (and many outputs) later, I see tens of usec variation. I see virtually no jitter on the Uno (using its external crystal).
If you are really seeing no jitter over that 100 ms duration using the Nano Every (within a clock cycle), I'd be interested in figuring out what else could be different in our environments because I'd love to use the Every. I'm powering it over USB and measuring with a 100 MHz/ 2GSa/s oscilloscope, nothing else is connected. I've also tried multiple Nano Every boards (since they come in a 3 pack!).
I did not measure the 100ms jitter but the 10µs pulse jitter. I waited many minutes, but there is no sign of jitter on the oscilloscope.
I think the jitter has less to do with the RC or crystal but more with interrupts that are called in between.
If you compare with the Uno you have to create the same conditions. Otherwise the comparison goes wrong.
Surely you can only measure the jitter with the code from #1? Or?
Edit:
You would also have to describe again how your pulse sequence should look like in total. I have not yet understood the 100ms. How many 10µs pulses for how long? How long is the pause until it repeats?
I also don't really know how you measure a jitter with 10µs pulses on a duration of 100ms on the osci. The screen is full of 10µs pulses, so almost everything is filled. The single pulses are not hardly recognizable.
The important timing for me is the time between the start of output pulses, on the order of 100 ms. The actual duration of the output pulse is less critical, but I appreciate your point about using hardware timers for that. So to answer your question, I would like 10 usec on, 99.99 ms off, and repeat, giving as repeatedly as possible 100 ms between the start of pulses. It's ok if 100 ms exactly isn't exact given the clock frequency or other overhead, as long as it's consistent.
Using your code from #6 to enable the hardware timer PWM and measuring between pulses ~100 ms apart (with many in between since the PWM is faster), I see significant variation in that interval, even when turning all other A/B timers off (in case their interrupts matter). That strongly suggests to me that the internal resonator has too much jitter to even use the hardware timer over that time period.
Unless you're seeing stability over that interval and I'm misunderstanding something, I will plan to mark #5 as my solution - use a board with an external crystal. Thank you for your help!
Sorry, I think you edited your question while I answered so I didn't see the last part. Many oscilloscopes have the ability to delay what's shown on screen from the triggering event, so you can have both a large delay and good time resolution. I am looking 100 ms after the triggering event with 10 usec/div.
Okay, I think I misunderstood about the pulses. You don't want a pulse train. You want a PWM with period 100ms and duty cycle 10µs. The problem is the large bandwidth that results from this.
Do you have TCA0 free? Or do you need millis and delay?
TCA0 in Single Slope PWM, Prescaler 64, Top 24999 and Duty Cycle 2.
1 Count = 4µs resolution. Use the WO Pin, then that is free from interrupts.
Thank you for providing the code to try that, that's extremely helpful.
As intended, it produces a 8 usec output pulse every approximately 100 ms, but I still observe the same output jitter between pulses. I'm attaching the oscilloscope output with persistence on (the gray traces from several seconds of data) so you can see how much it moves between measurements.
I've written code for the Uno (328p+crystal) using Timer1 that has the desired accuracy for my project. I'll share the relevant part below in case it helps anyone else, but I haven't gotten this working with the Nano Every (4809) unfortunately.
#define PIN_OUT 13 // PORTB PIN 5
#define TIMER_MAX 0xFFFF
#define USPERTICK .0625 // For 16 MHz clock, no prescaler
volatile unsigned long total_ticks=0;
volatile unsigned long delayus = 100000;
void setup() {
pinMode(PIN_OUT,OUTPUT);
TIMSK0=0; //Disable main timer (and delay function)
//Set up timer
TIMSK1=0;
TCCR1A=0;
TCCR1B = _BV(WGM12); // CTC, use OCR1A as TOP value
TCCR1B |= _BV(CS10); // No prescaling
if(delayus/USPERTICK>TIMER_MAX) {
OCR1A=TIMER_MAX;
}
else {
OCR1A=delayus/USPERTICK;
}
TCNT1=0;
TIMSK1=_BV(OCIE1A); // Enable interrupt
}
ISR(TIMER1_COMPA_vect) {
total_ticks=total_ticks+OCR1A+1;
if(total_ticks>=delayus/USPERTICK){
PORTB |= _BV(PB5);
delayMicroseconds(5);
PORTB &= ~_BV(PB5);
total_ticks=0;
}
// Reset timer TOP to appropriate value
if(delayus/USPERTICK>total_ticks+TIMER_MAX+1) {
OCR1A=TIMER_MAX;
}
else {
OCR1A=delayus/USPERTICK-TIMER_MAX-1;
}
}
void loop() {
}
For reference, here is the output of that code over several seconds on a 10x finer horizontal scale, less than 1 usec variation:
interesting measurement.
But I have to repeat one thing. No blocking code in an interrupt! You use delay in the interrupt. This needs even a timer interrupt. It is pure coincidence that it works, sometime it comes to the total blockade.
Okay, delayMicroseconds use only nop. Is not nice, but does no harm if you know you're doing. It blocks all other interrupts for this time. For details see wiring.c in the cores directory.
I agree that generally the ISR should be as short as possible, but in this case I don't expect to miss any events (and no other interrupts are running) during that 10 usec. A better solution would be to set up another timer to control that pulse and enable it during the ISR.
The D field on top of your oscilloscope seems to say you're only looking 5 usec after the triggering pulse (meaning we're seeing the triggering pulse), compared to the 99-100 ms later that @chrisknightley and I looked at. Am I misreading it?