Page 2 of 3

Re: Digispark as multiple servo driver.

Posted: 10 Jul 2019, 17:32
by MaxZ
In file included from /Users/maxzuijdendorp/Library/Arduino15/packages/arduino/hardware/avr/1.6.23/cores/arduino/Arduino.h:30:0,
from sketch/Pulsetest.ino.cpp:1:
Pulsetest:1:1: error: expected ')' before '::' token
cli();
^
exit status 1
expected ')' before '::' token

Re: Digispark as multiple servo driver.

Posted: 10 Jul 2019, 21:01
by MaxZ
Phil_G wrote: 10 Jul 2019, 20:11 Both noInterrupts() and cli() compile fine for me ?????
You're putting it in setup() Max?
No I did not apparently, but when I do put it in setup it indeed compiles without errors. But the result is not good, the pulse analyser hangs in the opening screen.

However, this seems to work well, producing a rock steady pulse length, albeit some 10% shorter than expected:
Schermafbeelding 2019-07-10 om 21.56.46.png
I am not sure what will happen when the micros() counter overflows.

Cheers,
Max.

Re: Digispark as multiple servo driver.

Posted: 10 Jul 2019, 21:38
by Phil_G
You keep moving the goalposts Max :D , I thought we were talking about your simple 1500uS generator example?
I would expect this latest iteration to chatter, if (currMicros-prevMicros >= pulselen) is a lengthy statement with lots of processing of longs within the pulse itself (32-bit arithmetic on an 8-bit processor takes a relatively long time to do). I'd suggest doing all the calculations first, rather than within the pulse.
MaxZ wrote: 10 Jul 2019, 21:01 ... the pulse analyser hangs in the opening screen.
again, with "try cli()" I was referring to the simple 1500uS example you posted, sorry

Re: Digispark as multiple servo driver.

Posted: 10 Jul 2019, 21:51
by MaxZ
We are talking about the 1500 us generator, but that is how I check the output, with the pulse analyser that you designed Phil. And I did try noInterrupts() and cli() with the sketch based on delayMicroseconds() and delay().

But there is no harm in investigating other routes, is there?

Cheers,
Max.

Re: Digispark as multiple servo driver.

Posted: 10 Jul 2019, 22:28
by Martin
You'll get the greatest accuracy by using timer interrupts yourself. The timers can be set to work some pins directly, but for other pins they can be set to call your own interrupt routine which then switches the pin immediately and then reconfigures the timer ready for the next interrupt.

It's more complicated, of course, and as the AtTiny micro timers can only count up to 256, you need to keep track of how many complete cycles are necessary, and then what's left over for the final count. It's further complicated by not being able to set a very small target count, because the timer will have counted past that before you finish setting it up - so if there are 266 counts remaining, rather than set the timer for 256 and then 10, you have to set it for, say, two counts of 133.

Best place to start, if you're interested, is to carefully read the Timer sections of the data sheet for the chip (which can be freely downloaded). People like Phil get their expertise by reading those data sheets over and over again, and then spending many many hours experimenting. It's not for everyone! :? :lol:

Re: Digispark as multiple servo driver.

Posted: 05 Aug 2019, 11:09
by MaxZ
My quest continues.....

In the meantime, some of the latest posts have disappeared into the black hole, so I am missing your latest advice. But one of those posts contained a code example for a V-mixer, kindly supplied by Phil.
Luckily I did save that code before it vanished from the forum, and I used that to enter a new path in my route to understand the workings of the Digispark by converting it to a servo reverser. The conversion was mostly leaving bits of code out, and simplifying the rest for just one receiver channel as input.
The main hurdle has been quieting the servo's, as they kept jittering. Not much, but annoying nevertheless. I did investigate the source of the jitter, and I can only conclude it stems from the ISR, where the incoming pulse is read. Replacing that pulse with a fixed value somewhere in the code gave a jitter free servo output pulse (at least not noticeable with my cheapo HXT900 servos).
I resorted to averaging the last four incoming pulses, which at last quieted the servos sufficiently.
Here is the code:

Code: Select all

// Servo signal reverser by Max Zuijdendorp, based on
// 50:50 Vtail mixer for ATTiny85 such as the 16mhz DigiSpark board - Phil_G
// august 2019
#define Rxch 0 // receiver channel connected to P0 (ATTiny physical pin 5)
#define servo1 1    // servo 1 connected to P1 ATTiny physical pin 6
#define servo2 2    // servo 2 connected to P2 ATTiny physical pin 7
const int midpulse_time = 1500; // midpulse_time = pulse at zero trim
volatile unsigned long timer_mark; // all timer variables are unsigned long
volatile int Rx_pulse = 1500;
int inpulse = 1500, oldpulse1 = 1500, oldpulse2 = 1500, oldpulse3 = 1500;
int outpulse_time1 = 1500, outpulse_time2 = 1500;
volatile byte sync = 0;

void setup()
{
  pinMode(servo1, OUTPUT);
  pinMode(servo2, OUTPUT);
  pinMode(Rxch, INPUT);

  timer_mark = 0;
  GIMSK = (1 << PCIE);   // Enable Pin Change Interrupts
  PCMSK = (1 << Rxch); // Mask interrupts for Rx channel input only, use bitwise-OR for multiple inputs
  interrupts(); // enable interrupts
}

void loop()
{
  while (sync == 0); // wait until new pulse info arrives (output timing set by Rx pulse timing)
  sync = 0;
  delay(6);  // do the output pulses mid-frame, reduces jitter...
  oldpulse3 = oldpulse2;
  oldpulse2 = oldpulse1;
  oldpulse1 = inpulse; 
  noInterrupts(); // disable interrupts
  inpulse = Rx_pulse; // do an atomic copy in the quickest way
  interrupts(); // enable interrupts
  // then do arithmetic on the copies...
  outpulse_time1 = (oldpulse3 + oldpulse2 + oldpulse1 + inpulse)/4;  
  outpulse_time2 = midpulse_time*2 - outpulse_time1;
  constrain(outpulse_time1, 850, 2150);
  constrain(outpulse_time2, 850, 2150);
  digitalWrite(servo1, 1); delayMicroseconds(outpulse_time1); digitalWrite(servo1, 0); // servo 1 out = Rx out
  digitalWrite(servo2, 1); delayMicroseconds(outpulse_time2); digitalWrite(servo2, 0); // servo 2 out = Rx out, reversed
}

ISR(PCINT0_vect)
{ // Interrupt on Rx pin state change
  if (PINB & (1 << Rxch)) { // Check if input pin has changed state 0 > 1
     timer_mark = micros();
  }
  else { // state change must be 1 > 0
     Rx_pulse = ((volatile int)micros() - timer_mark);
     sync = 1; // triggers output loop
  }
}
I did look at the ATtiny datasheet as Martin suggested, but I am afraid it is too daunting a task for me to understand its workings. It helped me to understand the interrupt settings though.
My hunch is that it is the micros timer itself, and its limitation to counting beyond 256, but I fail to see how this could be used in an alternative way, without losing the resolution of the pulse timing.

Cheers,
Max.

Re: Digispark as multiple servo driver.

Posted: 06 Aug 2019, 14:36
by MaxZ
Hi all,

I tidied up the code a bit, I can now increase or decrease the number of frames being averaged by changing a single value. But 4 frames turned out to be a good compromise, almost totally suppressing jitter. Adding more frames leads to a noticeable sluggishness of the servo movement.

I also programmed a couple of bare ATtinys and tested those. I found out by error that it is really necessary to clock the chip at 16 MHz. Leaving it as supplied (8 MHz?) leads to horrible servo movements, nothing like what it should be.

Cheers,
Max.

Re: Digispark as multiple servo driver.

Posted: 06 Aug 2019, 16:59
by MaxZ
Phil_G wrote: 06 Aug 2019, 15:32 Hi Max,
One option is to only average if the values are quite close. If two consecutive values are significantly different, say for example by over 10%, then the new value is used directly rather than an average of the last four values. This maintains the speed for sudden large movements whilst smoothing any tendency to 'hunt'.
Cheers
Phil
Good advice Phil, but for now I am staying with a simple average. I can slam the stick from one end to the other, and I cannot see any hesitation in the servo movement. Mind you, it is an analog type, things might be different for digital types ( I am staying away from those, no need for it for the kind of craft I am controlling... :D )

Cheers,
Max.

Re: Digispark as multiple servo driver.

Posted: 06 Aug 2019, 17:17
by Martin
Rather than take an average by summing four frames and dividing the answer by four, you get a similar result with less delay by the following method:

new output value = 3/4 of previous output value + 1/4 of most recent sample.

This updates the output every sample but still reduces any noise/jitter. Obviously you can alter the 'averaging' time by altering it to say 7/8 of previous output + 1/8 of recent sample.

You have to be careful about rounding errors on the divide - it's actually better to maintain a 'total' that is (say) 4 times the sample and output value - that way the code becomes:

total = total - total/4
total = total + latest sample
output = total/4

Obviously you need to make sure that the type of 'total' is big enough to hold the larger numbers - so if the samples and outputs are ints you might need to use longs for the total.

If you stick with powers of two for the "averaging" then the divides can be efficiently done by bit-shifting

a >> 2 is the same as a/4, a >> 3 is the same as a/8 and so on.

If you use this 'leaky integrator' averaging method, you can still use Phil's suggestion to get a fast response when the input changes suddenly by more than some threshold - you just add some code like

if (abs(output-sample)>threshold) {
total = sample << 2; // set total to 4 * latest value (assuming "averaging" of four)
output = sample; // jump straight to new value rather than ramping gradually towards it
}

Re: Digispark as multiple servo driver.

Posted: 06 Aug 2019, 17:50
by MaxZ
Thanks Martin,

I have to study that, but I think the result is more or less the same to what I did. I stuffed four readings in an array, and then update those on a rotational basis with the latest sample. After that I run a for-loop summing the contents of the array and divide that by 4:

Code: Select all

// Servo signal reverser by Max Zuijdendorp, based on
// 50:50 Vtail mixer for ATTiny85 such as the 16mhz DigiSpark board - Phil_G
// august 2019
#define Rxch 2 // receiver channel connected to P0 (ATTiny physical pin 5)
#define servo1 3    // servo 1 connected to P1 ATTiny physical pin 6
#define servo2 3    // servo 2 connected to P2 ATTiny physical pin 7
const int midpulse = 1500; // midpulse = pulse at zero trim
const int avg = 4; // number of pulses in running average
volatile unsigned long timer_mark; // all timer variables are unsigned long
volatile int Rx_pulse = midpulse;
int inpulse = midpulse;
int oldpulse[avg], x; // previous pulses array and increment variable
int servopulse1 = midpulse, servopulse2 = midpulse;
volatile byte sync = 0;

void setup()
{
  pinMode(servo1, OUTPUT);
  pinMode(servo2, OUTPUT);
  pinMode(Rxch, INPUT);

  for (int i=0;i<avg;i++) oldpulse[i] = midpulse; // set initial values for previous pulses array
  x=0;

  timer_mark = 0;
  GIMSK = (1 << PCIE);   // Enable Pin Change Interrupts
  PCMSK = (1 << Rxch); // Mask interrupts for Rx channel input only, use bitwise-OR for multiple inputs
  interrupts(); // enable interrupts
}

void loop()
{
  while (sync == 0); // wait until new pulse info arrives (output timing set by Rx pulse timing)
  sync = 0;
  delay(6);  // do the output pulses mid-frame, reduces jitter...

  noInterrupts(); // disable interrupts
  inpulse = Rx_pulse; // do an atomic copy in the quickest way
  interrupts(); // enable interrupts
  // then do arithmetic on the copies...
  
  // replace previous pulses with latest pulse on a rotational basis
  oldpulse[x] = inpulse;
  x += 1;
  if (x==avg) x=0;

  inpulse = 0;
  for (int i=0;i<avg;i++) inpulse += oldpulse[i]; // totalize previous pulses
  servopulse1 = inpulse / avg;  // output pulse for servo 1 is averaged previous pulses
  servopulse2 = midpulse*2 - servopulse1;
  constrain(servopulse1, 850, 2150);
  constrain(servopulse2, 850, 2150);
  digitalWrite(servo1, 1); delayMicroseconds(servopulse1); digitalWrite(servo1, 0); // servo 1 out = Rx out
  digitalWrite(servo2, 1); delayMicroseconds(servopulse2); digitalWrite(servo2, 0); // servo 2 out = Rx out, reversed
}

ISR(PCINT0_vect)
{ // Interrupt on Rx pin state change
  if (PINB & (1 << Rxch)) { // Check if input pin has changed state 0 > 1
     timer_mark = micros();
  }
  else { // state change must be 1 > 0
     Rx_pulse = ((volatile int)micros() - timer_mark);
     sync = 1; // triggers output loop
  }
}
So, should I conclude that there are no further tricks that will reduce the jitter in the first place?
Cheers,
Max.