Jitter problem, RC servo reverser with ATTiny2313

Thread Starter

sumeryamaner

Joined May 29, 2017
117
I am trying to design and build a simple servo reverser for RC applications. I am using Atmel microcontrollers and write the code in Arduino IDE. This simplifies program uploading to the device for me.
As you already know, RC systems work with PWM pulses. They are usually 500 - 2000 microsecond pulses repeated every 20 milliseconds. 1500 microseconds represents the neutral position of the servo. If we measure the length of the input pulse, we can subtract it from 3000 and this will give us the reverse signal pulse. That means the mirror position of the servo arm with respect to the neutral position.
To have a usable device we need at least 1 microsecond resolution. The more the better.
Reading a pulse width can of course be done in ArduinoIDE using the pulseIn() function. But this function has a resolution of 4 microseconds which is too coarse for this kind of application. Output pulse generation can be done with delayMicroseconds() function but this too has a 4 microseconds resolution.
Reading and generating the pulses must be done precisely to avoid servo jitter which makes the circuit unusable in a real RC model.
Using an ATMega168/328 for such a job seems to be an overkill. In fact an ATTiny45/85 would be the best solution but with only 8 bit timer counters this chip makes the precise pulse measurement and generation very difficult. So I decided to try it with an ATTiny2313 which has a 16 bit timer counter.
To prevent issues with Arduino IDE background functions (like millis(), micros(), etc.) I decided to stop the unused Timer0 and inhibit all interrupts.
I decided to run the MCU at 8 MHz internal clock with Timer1 prescaler of “1”. This gives me a signal resolution of 125 nanoseconds.
(That means a pulse of 1500 microseconds duration is being represented as 8 * 1500 = 12000 internally).
First I tried the signal generation part. I assigned a fixed value to the variable “inPWM” and produced output pulses. A servo connected to the circuit stayed at the appropriate position and was rock solid.
Then I moved on to the signal input. There was no problems. The servo followed the commands from the radio transmitter precisely.
Then I added the buttons and the code for offset adjustment. This function is required for the centering of the reversed slave servo which is a very handy function in real life. This worked also flawlessly.
But...
As I said before, to get the reverse pulse width we must subtract the actual input PWM value from 3000. As we are using 8 MHz this 3000 will be 24000. That means;
revPWM = 24000 – inPWM; (All PWM variables are unsigned integers btw.)
Then we will add the offset to get the final value; revPWM = revPWM + offset
Now my problem: If I use the inPWM value to generate an output pulse the servo works fine without jitter. But if I use the calculated revPWM value, there is a significant jitter.
I could not find a logical explanation for this behaviour and would like to ask whether I am missing something.

What I tried so far:
*Using a 16 MHz crystal and running the system with external clock.
*Eliminating the offset system, EEPROM reads and writes, buttons.
*Reading the incoming pulse using pin change interrupts.

Thank you...

C-like:
/*
Servo Reverser with subtrim

ATTiny2313 @ 8 MHz @ 3.3V

Siignal input PB5

Reverse output PB3

Buttons PD0 (+), PD1 (-)
*/

#include <EEPROM.h>

#define pwmIn PB5

#define revOut PB3

#define button1 PD0
#define button2 PD1

#define revOutMaskHIGH B00001000
#define revOutMaskLOW B11110111

#define inMask B00100000

#define buttonMask B00000011

int offset; // -1000 ... 1000
unsigned int inPWM;
unsigned int revPWM;
byte buttonIn;
unsigned int tempOffset;
boolean EEPROM_flag;

void setup() {
  DDRB = B00001000;    // PB3 OUTPUT
  DDRD = B00000000;    // PD... INPUT
  PORTD |= B00000011;  // PD0 PD1 INPUT_PULLUP
  TCCR0B = B00000000;  // Timer0 stop
  TCCR1A = B00000000;
  TCCR1B = B00000001;  // Timer1 prescaler 1, runs at 8 MHz
  TCCR1C = B00000000;
  TCCR1B = B00000000;  // Timer1 stop
  if ((EEPROM.read(0) == 255) && (EEPROM.read(1) == 255)) { // Provision for a blank EEPROM
    EEPROM.write(0, 3);
    EEPROM.write(1, 232);  // value = 1000, effective offset = 0
  }
  offset = 256 * EEPROM.read(0) + EEPROM.read(1) - 1000;
  if (offset > 1000) {
    offset = 1000;
  }
  if (offset < -1000) offset = -1000;
  tempOffset = offset + 1000;
  EEPROM.update(0, highByte(tempOffset));
  EEPROM.update(1, lowByte(tempOffset));
  cli();
}


void loop() {
  //Read incoming pulse
  GTCCR |= B00000001;  // Reset prescalers
  TCNT1 = 0;
  TCCR1B = B00000000;  // Timer1 stop
  while (!(PINB & inMask))
    ;                  // Wait until HIGH pulse start
  TCCR1B = B00000001;  // HIGH pulse started, Timer1 prescaler 1, runs at 8 MHz
  while (PINB & inMask)
    ;                  // Wait until pulse end
  TCCR1B = B00000000;  // Timer1 stop
  inPWM = TCNT1;

  //Check values
  revPWM = 24000 - inPWM;
  revPWM = revPWM + 8 * offset;
  if (revPWM > 20000) revPWM = 20000;
  if (revPWM < 4000) revPWM = 4000;

  // Output reverse pulse
  TCCR1B = B00000000;  // Timer1 stop
  GTCCR |= B00000001;  // Reset prescalers
  TCNT1 = 0;
  PORTB |= revOutMaskHIGH;
  TCCR1B = B00000001;  // Timer1 prescaler 1, runs at 8 MHz
  while (TCNT1 < revPWM)
    ;
  PORTB &= revOutMaskLOW;

  // Check buttons
  buttonIn = PIND & buttonMask;
  if (buttonIn == 1) {
    offset++;
    EEPROM_flag = 1;
  }
  if (buttonIn == 2) {
    offset--;
    EEPROM_flag = 1;
  }
  if (offset > 1000) offset = 1000;
  if (offset < -1000) offset = -1000;
  tempOffset = offset + 1000;
  if (EEPROM_flag) {
    EEPROM.update(0, highByte(tempOffset));
    EEPROM.update(1, lowByte(tempOffset));
    EEPROM_flag = 0;
  }
}
 
Hi sumeryamaner
be80be has a valid point - do you understand why your code is blocking? - all those while statements stop the AVR doing anything else.
I don't want to get dragged into stuff, nor am I trying to 'teach you to suck eggs' but your code will run into other problems - you will need to 'debounce' your button checking code once you sort out the blocking issue or you will find your offsets change in unpredictable ways.
I looked for a data sheet for an ATtiny2313A but the one I found wasn't terribly helpful.
I have more experience with devices such as ATtiny1604 and its datasheet gives you useful information as to how to set up 3 channels of single slope (sometimes called fast) PWM on TCA. This is a surface mount device in a 14 pin package you can mount on a 14 pin DIL carrier - you will need to use UPDI to program it but that's not such a big hurdle.
I don't understand why you just don't supply each servo with it's own signal direct from the TCA - they can be quite independent and you can calculate how to make one the inverse angle to the other. You might also be able to set each one a separate 0 degree offset and also account for how each servo responds slightly differently to the same change in mark space ratio of the PWM signal.
There's an Arduino wrapper for the ATtiny1604 called megaTinyCore written by Spence Konde - you could probably load up the servo library and use Arduino type servo commands if you didn't want to dip into all that timer setup. You'd probably be able to keep using millis() to debounce your push buttons too - there are options for having millis() use other timers on the device.
Only trying to help and certainly not 'step on anyone's toes'.
 

BobaMosfet

Joined Jul 1, 2009
2,211
I am trying to design and build a simple servo reverser for RC applications. I am using Atmel microcontrollers and write the code in Arduino IDE. This simplifies program uploading to the device for me.
As you already know, RC systems work with PWM pulses. They are usually 500 - 2000 microsecond pulses repeated every 20 milliseconds. 1500 microseconds represents the neutral position of the servo. If we measure the length of the input pulse, we can subtract it from 3000 and this will give us the reverse signal pulse. That means the mirror position of the servo arm with respect to the neutral position.
To have a usable device we need at least 1 microsecond resolution. The more the better.
Reading a pulse width can of course be done in ArduinoIDE using the pulseIn() function. But this function has a resolution of 4 microseconds which is too coarse for this kind of application. Output pulse generation can be done with delayMicroseconds() function but this too has a 4 microseconds resolution.
Reading and generating the pulses must be done precisely to avoid servo jitter which makes the circuit unusable in a real RC model.
Using an ATMega168/328 for such a job seems to be an overkill. In fact an ATTiny45/85 would be the best solution but with only 8 bit timer counters this chip makes the precise pulse measurement and generation very difficult. So I decided to try it with an ATTiny2313 which has a 16 bit timer counter.
To prevent issues with Arduino IDE background functions (like millis(), micros(), etc.) I decided to stop the unused Timer0 and inhibit all interrupts.
I decided to run the MCU at 8 MHz internal clock with Timer1 prescaler of “1”. This gives me a signal resolution of 125 nanoseconds.
(That means a pulse of 1500 microseconds duration is being represented as 8 * 1500 = 12000 internally).
First I tried the signal generation part. I assigned a fixed value to the variable “inPWM” and produced output pulses. A servo connected to the circuit stayed at the appropriate position and was rock solid.
Then I moved on to the signal input. There was no problems. The servo followed the commands from the radio transmitter precisely.
Then I added the buttons and the code for offset adjustment. This function is required for the centering of the reversed slave servo which is a very handy function in real life. This worked also flawlessly.
But...
As I said before, to get the reverse pulse width we must subtract the actual input PWM value from 3000. As we are using 8 MHz this 3000 will be 24000. That means;
revPWM = 24000 – inPWM; (All PWM variables are unsigned integers btw.)
Then we will add the offset to get the final value; revPWM = revPWM + offset
Now my problem: If I use the inPWM value to generate an output pulse the servo works fine without jitter. But if I use the calculated revPWM value, there is a significant jitter.
I could not find a logical explanation for this behaviour and would like to ask whether I am missing something.

What I tried so far:
*Using a 16 MHz crystal and running the system with external clock.
*Eliminating the offset system, EEPROM reads and writes, buttons.
*Reading the incoming pulse using pin change interrupts.

Thank you...

C-like:
/*
snipped for brevity
}
In case your jitter is simply related to Arduino speed, and not code, I suggest (a bit of a learning curve, but *very* rewarding):

If you're using Atmel, use Atmel's Studio 7 IDE, write your code in C, and use their AVRMkII In System Programmer (ISP) adjunct to flash your chip directly from the IDE via USB. You only need a header on your breadboard like this to connect it to the MCU.

1740505556542.png

Here is the AVRMkII ISP Programmer:

1740505768336.png

You can buy it from MicroChip (who now owns Atmel products). Here is a link to the setup & user Guide:

https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-42093-AVR-ISP-mkII_UserGuide.pdf
 
Last edited:
Hi sumeryamaner
be80be has a valid point - do you understand why your code is blocking? - all those while statements stop the AVR doing anything else.
I don't want to get dragged into stuff, nor am I trying to 'teach you to suck eggs' but your code will run into other problems - you will need to 'debounce' your button checking code once you sort out the blocking issue or you will find your offsets change in unpredictable ways.
I looked for a data sheet for an ATtiny2313A but the one I found wasn't terribly helpful.
I have more experience with devices such as ATtiny1604 and its datasheet gives you useful information as to how to set up 3 channels of single slope (sometimes called fast) PWM on TCA. This is a surface mount device in a 14 pin package you can mount on a 14 pin DIL carrier - you will need to use UPDI to program it but that's not such a big hurdle.
I don't understand why you just don't supply each servo with it's own signal direct from the TCA - they can be quite independent and you can calculate how to make one the inverse angle to the other. You might also be able to set each one a separate 0 degree offset and also account for how each servo responds slightly differently to the same change in mark space ratio of the PWM signal.
There's an Arduino wrapper for the ATtiny1604 called megaTinyCore written by Spence Konde - you could probably load up the servo library and use Arduino type servo commands if you didn't want to dip into all that timer setup. You'd probably be able to keep using millis() to debounce your push buttons too - there are options for having millis() use other timers on the device.
Only trying to help and certainly not 'step on anyone's toes'.
If the ATtiny1604 is too big, the ATtiny402 can fit on an 8 pin DIL carrier - still has 3 outputs from a 16bit TCA, it's still SMD device with UPDI programming. These parts don't need external crystals (but if you want to use them for serial data, you may need to 'tune' them - there's stuff on line how to do this.)
 

Thread Starter

sumeryamaner

Joined May 29, 2017
117
Thank you all for these responses. I have read them carefully. I think I could not explain my problem properly.
First of all, I exactly know what a blocking code is. It is true that during the "while" statements the MPU does nothing but if you consider the rationale of the circuit, the MCU does not need to do anything during those "while" loops. This is a very basic circuit, it will read a pulse and then generate a pulse. These two functions are not concurrent. They are sequential. So I think I can use a simple blocking code. Besides this I cannot understand in which way this blocking code leeds to jitter. I need a little bit detail.
I have the necessary equipment for programming the Atmel MCUs but it has been easier to use Arduino IDE. Can you explain what the difference will be and it's implications on jitter considering that I am switching off all of the possible background functions of Arduino IDE? (Those background functions are millis(), micros(), delay() functions, which use Timer0 and Timer0 overflow interrupts. There is no serial communication active. Actually I am inhibiting interrupts globally).
Bounce checking the buttons is not a problem. It works fine. There is a built in pseudo-debounce. As the input signal comes in at 20 mS intervals, the main loop runs 50 times a second. That means even if the buttons bounce (of course they do) it is not being registered before 20 mS which is a very good time for debouncing.
Let me try to explain the main point here once again:
If I measure the input pulse and generate the same pulse at the output there is no problem. The servo responds very well and stays where it is without jitter. That means there is no problem with reading the pulse lenght and generating the same pulse. But as soon as I try to reverse the servo direction (this necessitates subtraction of the incoming pulse length from 24000) there is servo jitter. This is highly illogical but I can prepare videos if needed.
 

MrChips

Joined Oct 2, 2009
34,628
I have not read through all the posts carefully but my suggestions would be along the lines others have suggested.

1) Move away from Arduino and write code in C.
2) Don't use library timer functions. Use interrupt driven hardware timer modules.
3) Don't use blocking code. Use interrupts.
4) Watch out for integer overflows.
 

Thread Starter

sumeryamaner

Joined May 29, 2017
117
Thank you for the reply.

1) I am trying to understand what is going on. First of all, would you please kindly explain the difference between Arduino IDE and native C. Please bear in mind that I am disabling all Arduino IDE related background functions.
Please do not think that I am being rude. The same code outputs two PWM pulses. One is OK, the other causes jitter. If there were a problem with Arduino IDE I would expect that both would lead to jitter. Am I wrong?

2) There are no library timer functions used. All timer functions are accessed by directly manpulating the corresponding registers of the MCU. Signal generation is being done by direct port manipulation.

3) The code MUST wait somwhere for the input pulse to be read and for the output pulses to be generated. It has nothing else to do. So what are the consequences of a blocking code in this scenario?

4) All PWM variables are declared as unsigned integers. That means they have a range from 0 to 65535. Even if they would be declared as signed integers they would have a range of 0 to 32767 on the positive side. The longest expected pulse length is 2500 microseconds which corresponds to a value of 20000. The reversing process is to subtract the pulse length from 3000 (microseconds) and this corresponds to a value of 24000 in timer ticks. So an integer overflow seems to be unlikely.

The most interesting part is that RC servos have a deadband. It is 1 microsecond most of the time. In some programmable servos you can change this value. This means the servo does not move if the incoming pulse changes less than 1 microsecond. One microsecond corresponds to 8 timer ticks in my case. That means the error of the output signal is more than 8 units.

P.S.: Behind the answers in this thread I can feel a hint of "scolding the novice" so please let me explain that I am not a professional but not a novice either. I am a seasoned RC pilot, with more than 30 different microcontroller circuits designed and built by myself. These range from simple smart kill switches to complex retractable landing gear control systems. All of their code have been written under Arduino IDE and work flawlessly. More than a hundred of them is in use throughout our country in numerous model aircraft.

Thank you again for your help.
 

MrChips

Joined Oct 2, 2009
34,628
At line 76, you have:

revPWM = revPWM + 8 * offset;

offset is a signed integer and can have a range -1000 to +1000.
If offset is ever negative you will have an overflow problem at line 76.
 

Sensacell

Joined Jun 19, 2012
3,768
The idea of using polled 'while' loops to wait for the pulse implies uncertainty.
The loop itself takes a few microseconds to execute, you cannot eliminate this source of timing jitter- there will always be jitter equal to the loop execution time - inherently.

On the other hand, if you use interrupts, the events will always align in time - exactly.
There is a small interrupt latency, but this is a fixed and predictable delay.

Interrupts are the only way to go if you require precision timing with minimal jitter.

-OR- use a part that has gated hardware timers/counters that can be setup to automate the whole thing, or at least the time-critical portions.

As you are trying to reverse the pulse by doing math on the captured pulse, you need to account for the variations in execution times caused by the non-uniform time the math takes, depending on carry operations.
You need to start the output pulse BEFORE you calculate the ending time, so the calculation loop delay is NOT part of the time sequence.

A timer with a COMPARE function would allow you to start the timer, then calculate the new pulse end while the timer is running, this prevents the calculation delay from impacting the timing.
The calculation time is folded into the cycle, rather than being sequential.
 
Last edited:
Top