Precision servo control with ATTiny85

sumeryamaner

Joined May 29, 2017
111
First of all, I know that the normal way to create 1000 - 2000 microsecond pulses is to use a 16 bit timer and I can do it with an Atmega328 without any problems.
I just wanted a new challenge which would help me to understand the Atmel series of microcontrollers better. So I decided to build a mini servo reverser using an ATTiny85.
A servo reverser has to read the incoming PWM pulses from the receiver and calculate the output pulse width, then produce pulses between 800 to 2200 microseconds every 20 milliseconds in 1 microsecond steps. To be able to have a very compact product I decided to use the internal 8 MHz oscillator of the ATTiny85.
I planned to use Timer1 and set up Timer1 overflow interrupts to increment a "counter" (byte) to be able to count beyond the 8 bit limit of the Timer1.
Code:
ISR(TIMER1_OVF_vect)
{
counter++;
}
I decided to use a prescaler of 4 for Timer1 which gives me a frequency of 2 MHz. That means I can have a resolution of 0.5 microseconds.
So, 800 microseconds are 1600 Timer1 ticks and 2200 microseconds are 4400 Timer1 ticks.

This portion of the code reads the incoming PWM pulse width:
Code:
  cli();
TCCR1 = 0; // Timer1 stop
TCNT1 = 0;
TIFR |= B00000100; // Reset TOV1 flag
counter = 0;
GTCCR |= B00000010; // Timer1 prescaler reset
while ((PINB & B00001000)); // Read PB3 until PB3 is LOW
while (!(PINB & B00001000)); // Read PB3 until PB3 is HIGH -> Start of a new pulse
sei();
TCCR1 = B00000011; // Start Timer1 with prescaler 4 => 2 MHz
while ((PINB & B00001000)); // Read PB3 until PB2 is LOW -> End of pulse
TCCR1 = 0; // Timer1 stop
cli();
temp2 = counter;
temp3 = TIFR & B00000100; // Read Timer1 Overflow Interrupt Flag
if (temp3) // If there is an unhandled interrupt, counter will be incremented by one
{
counter ++;
TIFR |= B00000100; // Reset TOV1 flag
}
GTCCR |= B00000010; // Timer1 prescaler reset
temp = 256 * counter + TCNT1;
if (temp > 1499 && temp < 4401) inpwm = temp; //If no valid input signal, previous inpwm will be used
This part is the reversing code:

Code:
outpwm = 6000 - inpwm;
This is the code portion to produce the necessary pulse width:
The variable "outpwm" (int) contains the pulse width x 2 (in microseconds).

Code:
  outH = highByte(outpwm);
outL = lowByte(outpwm);
TCNT1 = 0;
counter = 0;
GTCCR |= B00000010; // Timer1 prescaler reset
if (outL < 1)
{
sei(); // Timer1 Overflow interrupt enabled
PORTB |= B00000100; // PB2 HIGH
TCCR1 = B00000011; // Start Timer1 with prescaler 4 => 2 MHz
while (counter < outH);
PORTB &= B11111011; // PB2 LOW
}
else
{
TIMSK = B00000000; // Timer1 Overflow Interrupt Disable
PORTB |= B00000100; // PB2 HIGH
TCCR1 = B00000011; // Start Timer1 with prescaler 4 => 2 MHz
while (TCNT1 < outL);
TCNT1 = 0;
TIMSK = B00000100; // Timer1 Overflow Interrupt Enable
sei(); // Timer1 Overflow interrupt active
while (counter < outH);
PORTB &= B11111011; // PB2 LOW
}
TCCR1 = 0; // Stop Timer1
This code produces the required pulses at the output with an acceptable accuracy (that means the connected servo does not jitter and stays steady). An oscilloscope check also shows that the pulses are OK.
But...
If the low byte of the outpwm is 254 or 255, the output pulse gets out of control. The servo makes uncontrolled movements. On the oscilloscope the pulse width is continuously changing.
I cannot find any reason why every value of outL except 254 and 255 produces clean output pulses but those two values lead to an erratic behaviour of the code.
Any hints are welcome...

Chris65536

Joined Nov 11, 2019
159
When outL is 254 or 255, the TOV1 flag gets set before TCNT1=0 is executed:

while (TCNT1 < outL);
TCNT1 = 0;

That causes counter to increment immediately when interrupts are re-enabled, so the outH wait loop will be one count shorter (128uS). Try adding a line to clear the TOV1 bit right after setting TCNT1=0.

sumeryamaner

Joined May 29, 2017
111
When outL is 254 or 255, the TOV1 flag gets set before TCNT1=0 is executed:

while (TCNT1 < outL);
TCNT1 = 0;

That causes counter to increment immediately when interrupts are re-enabled, so the outH wait loop will be one count shorter (128uS). Try adding a line to clear the TOV1 bit right after setting TCNT1=0.
Great advice. This solved the problem. Thank you very much.