I am working on code to create very precise PWM pulses using an Atmega328P on an Arduino Nano. I'm using timer 1 and the output compare in an ISR to make my PWM timing.
I have my example mostly working, except it has a little jitter. I initially suspected it was the millis() ISR, but disabling timer 0 did not make the jitter go away.
So my questions are:
Are there any other ISRs running in the background by default?
Does setting TCCR0A/B to 0 disable the millis interrupt?
Here is my code:
#define sig_pin1 10 //set PWM signal output pin on the arduino
#define onState 1 //set polarity of the pulses: 1 is positive, 0 is negative
int pulse_length_min = 1000;
int pulse_length_max = 2000;
int pulse_length = 1500;
int okay_to_do_i2c = true;
int period = 20000;
int timeout_count = 0;
int start_watchdog=false;
#define PULSE_SCALER 2;
//Calling address 0 will cause device to reset.
void (*resetFunc)(void) = 0;
void setup() {
// put your setup code here, to run once:
pinMode(sig_pin1, OUTPUT);
cli();
TCCR1A = 0; // set entire TCCR1 register to 0
TCCR1B = 0;
TCCR0B = 0; // disable millis() background ISR
TCCR0A = 0; // disable millis() background ISR
OCR1A = 100; // compare match register, change this
TCCR1B |= (1 << WGM12); // turn on CTC mode
TCCR1B |= (1 << CS11); // 8 prescaler: 0,5 microseconds at 16mhz
TIMSK1 |= (1 << OCIE1A); // enable timer compare interrupt
sei();
}
void loop() {
start_watchdog=true;
//Wait for "okay_to_do_i2c to avoid interrupting PWM ISR. To avoid jitter due to i2c ISR.
while (!okay_to_do_i2c){}
while (okay_to_do_i2c){}
timeout_count=0;
}
ISR(TIMER1_COMPA_vect){
static boolean state = true;
okay_to_do_i2c=false;
TCNT1 = 0;
if (state) { //start pulse
digitalWrite(sig_pin1, onState);
OCR1A = pulse_length * PULSE_SCALER;
state = false;
} else{ //end pulse and calculate when to start the next pulse
digitalWrite(sig_pin1, !onState);
OCR1A = (period - pulse_length)*PULSE_SCALER;
state = true;
}
//if something went awry with the I2C in loop, we can trigger a reset.
if (start_watchdog){
timeout_count += 1;
if (timeout_count > 10){
resetFunc();
}
}
}
Of course I found my issue a minute after posting, lol. My little "watch dog" was triggering. I forgot to set my "okay_to_do_i2c" flag back to true at the end of my ISR. Oops. Perfectly rock-solid PWM now.
Actually my "watchdog" idea is more broken than I realized. I'm gonna move it to timer 2 like a normal watchdog would be, rather than try to hack it into timer 1.
Quantifying using an oscilloscope. I want submicrosecond resolution so I can fully test the resolution of some servos.
Basically I'm making a servo tester to test them before they go into an RC plane.
Btw I reenabled millis last night. Need to look closer, but it did not appear to be causing jitter. May just leave it disabled anyway. The source of my "jitter" was primarily the "watchdog".
many have complained about servo jitter. it's not obvious to me what the cause is. i've always assumed it's the electronics and pot in the servo. i guess you're thinking its the jitter in the pulse with.
since an RC servo pulse is from 1 - 2 msec, sub-usec resolution suggests < 0.1 accuracy. assuming the 180 deg < 0.18 deg resolution.
The digitalWrite() function adds unnecessary latency, use direct port manipulation instead. Or, EVEN BETTER, use the time modes that allow the hardware to autonomously toggle an output pin on compare match with no software involvement.
I've thought about it, and find it generally not worth it to do direct port manipulation. I'm more concerned about repeatability of the pulse width, not 100% accuracy. Digitalwrite has consistent latency.
Guys, I've solved my problem, you can stop suggesting solutions
and most importantly - without interruptions. If you want fast and accurate gpio control - do not use interrupts, the entry time of which depends on the system load
It's not in the loop. I'm making a servo tester to (stress) test on the ground before putting them in the plane.
A servo tester is a device I should have gotten years ago. I've seen the ones commercially available and haven't been happy with them, so I'm making my own.
Interrupts can be used along with the hardware-controlled pin change feature. Inside the ISR you can adjust the time for the next pin change event as long as you make it relative to the time that the hardware captured. This way pin change times are always based on hardware events and aren't effected by ISR entry latency.
Entry time isn't something I'd fully considered. By system load do you mean, any interrupts in higher priority, or something else?
Entry time is partially affected by the number of clock cycles the current instruction needs to complete execution, before going to the ISR, right?
So that may introduce a few cycles of jitter since some of the instructions I'm using in my "while" loop aren't going to have consistent execution time.
If I need more resolution than I can get now, setting the timer to change the pin would offer as much precision as the atmega can provide, period. Thanks for the suggestion.
Btw I was looking through the docs, I didn't know this chip had a built in watchdog. I'm going to set that up instead of the nonsense I'm doing now.
The hardware will capture the timer value at the occurrence of the input pin trigger condition (+/- some number of clock cycle variation that you can find in the datasheet). At approximately the same time, the interrupt is queued. So, the latency getting into the ISR is irrelevant as long as you do all your timing based on the captured value.
i tried running a timing loop using micros(). and got following results (i realize you may be interested in configuring processor registers to control the pulse width)