Arduino due / ppm

there is a blog dedicated to RC and Arduino, visit

they have examples of using timer interrupt to capture PPM and then using Servo lib (or decade counter) to pulse servos. Not sure how many DUE examples though ...

Here is what we need to do:

1 - start only one timer in ctc mode
2 - change the trigger
3 - reset the timer to 0

and back to step 2 over and over again.

This is what the code poster above is doing. I hope someone has a clue on how to do this on a Due.

Best,

Francois

Thanks for the message, I've had a go at altering my previous timer code to do something similar to what you are asking. It's only a small change :slight_smile:

Try this: put an LED+resistor from pin 2 to GND and run this sketch. The LED should light for 0.5 seconds then extinguish for 0.5, 1.5, 2.5 ... seconds in sequence (so total time is 1,2,3... seconds) for 7 blinks then repeat.

// timer is clocked at 42MHz, so 42 ticks per us
uint32_t periods[]={1000000*42,2000000*42,3000000*42,4000000*42,5000000*42,6000000*42,7000000*42};
uint32_t num_periods=7;

void TC0_Handler()
{
    long dummy=REG_TC0_SR0; // vital - reading this clears some flag
                            // otherwise you get infinite interrupts
    static int i=0;
    REG_TC0_RC0=periods[i++];
    if (i>=num_periods)i=0;
}

void setup(){
  pinMode(2,OUTPUT);    // port B pin 25  
  analogWrite(2,255);   // sets up some other registers I haven't worked out yet
  REG_PIOB_PDR = 1<<25; // disable PIO, enable peripheral
  REG_PIOB_ABSR= 1<<25; // select peripheral B
  REG_TC0_WPMR=0x54494D00; // enable write to registers
//REG_TC0_CMR0=0b00000000000010011100010000000000; // set channel mode register (see datasheet)
  REG_TC0_CMR0=0b00000000000001101100010000000000; // alternative CMR for inverted output
  REG_TC0_RC0=100000000; // counter period
  REG_TC0_CCR0=0b101;    // start counter
  REG_TC0_IER0=0b00010000; // enable interrupt on counter=rc
  REG_TC0_IDR0=0b11101111; // disable other interrupts
  
  REG_TC0_RA0=500000*42;  // PWM value - 500000us

  NVIC_EnableIRQ(TC0_IRQn); // enable TC0 interrupts
}

void loop(){
  // you can safely write to the periods[] array here, eg
  // periods[0]=1000*map(analogRead(0),0,1023,1000*42,7000*42);
}

Does this look like what you are trying to do? If so, you should be able to get the exact signal you need just by changing the timings in the REG_TC0_RA0 line and the periods[] array (these can be adjusted at run-time safely)

Here is an untested (but compiled) DUE snippet that reads PPM on pin 7 and drives 6 servos (pins 8-13).

// take RC tranmsitter PPM on pin 7, drive servos on pins 8-13
#include <Servo.h>

#define PPM_PIN 7
#define PPM_IDLE 3000
#define MAX_CHANNELS 6
uint32_t channels[MAX_CHANNELS];

Servo servos[MAX_CHANNELS];

// PPM interrupt
void handler() {
	static uint32_t prev=0,chno=0;
	uint32_t width,t;

	t = micros();
	width = t - prev;
	if (width > PPM_IDLE) {
		chno = 0;   // next pulse is channel 0
	} else if (chno < MAX_CHANNELS) {
		servos[chno].writeMicroseconds(width);
		channels[chno++] = width;    // for printing
	}
	prev = t;
}

void setup() {
	int i, pin=8;
	Serial.begin(9600);
	for (i=0; i<MAX_CHANNELS; i++) {
		servos[i].attach(pin++);
	}
    attachInterrupt(PPM_PIN,handler,RISING);
}

void loop() {
	int i;

	delay(3000);
	for (i=0; i<MAX_CHANNELS; i++) Serial.println(channels[i]); //debug
}

Hmmm, I guess you wanted to go from servos to PPM. Here is another untested DUE snippet that reads 6 servo channel pins and outputs a PPM stream. You need to order the servo channels so the PPM output is what the PPM receiver expects. The loop() is busy doing the PPM output. If you have other work to do, you can use stimmer's code above to generate the PPM stream in the background.

// generate a ppm stream from 6 channels of "servo" input, e.g. RC receiver
//  you'll need to know servo order that ppm receiver is expecting
// PPM output on pin 3, servos in order pins 4-9

#define MAX_SERVOS 6
uint32_t servos[MAX_SERVOS];   // pulse width in microseconds

#define PW 400
#define W 24000

#define PPM_PIN 3

void handler1() {
	static uint32_t prev=0;
	uint32_t width,t;
	t = micros();
	if (digitalRead(4) == LOW) servos[0] = t - prev;  // fallen
	prev = t;
}

void handler2() {
	static uint32_t prev=0;
	uint32_t width,t;
	t = micros();
	if (digitalRead(5) == LOW) servos[1] = t - prev;  // fallen
	prev = t;
}

void handler3() {
	static uint32_t prev=0;
	uint32_t width,t;
	t = micros();
	if (digitalRead(6) == LOW) servos[2] = t - prev;  // fallen
	prev = t;
}

void handler4() {
	static uint32_t prev=0;
	uint32_t width,t;
	t = micros();
	if (digitalRead(7) == LOW) servos[3] = t - prev;  // fallen
	prev = t;
}

void handler5() {
	static uint32_t prev=0;
	uint32_t width,t;
	t = micros();
	if (digitalRead(8) == LOW) servos[4] = t - prev;  // fallen
	prev = t;
}

void handler6() {
	static uint32_t prev=0;
	uint32_t width,t;
	t = micros();
	if (digitalRead(9) == LOW) servos[5] = t - prev;  // fallen
	prev = t;
}

void setup() {
	int i;

	for(i=0;i<MAX_SERVOS;i++) servos[i] = 1500;  //  idle
	pinMode(PPM_PIN,OUTPUT);
	attachInterrupt(4,handler1,CHANGE);
	attachInterrupt(5,handler2,CHANGE);
	attachInterrupt(6,handler3,CHANGE);
	attachInterrupt(7,handler4,CHANGE);
	attachInterrupt(8,handler5,CHANGE);
	attachInterrupt(9,handler6,CHANGE);
}

void loop() {
	int i,t;

	t=0;
	for (i=0; i< MAX_SERVOS; i++){		// PPM out
		digitalWrite(PPM_PIN,HIGH);
		delayMicroseconds(PW);
		digitalWrite(PPM_PIN,LOW);
		delayMicroseconds(servos[i]-PW);
		t += servos[i];
	}
	digitalWrite(PPM_PIN,HIGH);   // idle fill
	delayMicroseconds(PW);
	digitalWrite(PPM_PIN,LOW);
	delayMicroseconds(W-t);
}

Thank you so much guys. I'll run the codes over the weekend and let you know how it goes.

Victory!

Stimmer your code was awesome, I modified it to generate 8 channel ppm with 0.5ms pulse and 22ms total length. It is working perfectly ( I'll click a few times on your name to increase your karma lol)

Here is the code 8 CHANNELS PPM FOR THE ARDUINO DUE:

// timer is clocked at 42MHz, so 42 ticks per us

uint32_t periods[]={1000*42,1000*42,1000*42,1000*42,1000*42,1000*42,1000*42,1000*42,1000*42};
uint32_t num_periods=8+1;      // number of channels +1

int ppm_channels[8];
long Frame;
long Sum;
int val = 1;

void TC0_Handler()
{
    long dummy=REG_TC0_SR0;    // vital - reading this clears some flag
                               // otherwise you get infinite interrupts
    static int i=0;
    REG_TC0_RC0=periods[i++];
    if (i>=num_periods)i=0;
}

void setup(){
  pinMode(2,OUTPUT);           // port B pin 25  
  analogWrite(2,255);          // sets up some other registers I haven't worked out yet
  REG_PIOB_PDR  = 1<<25;       // disable PIO, enable peripheral
  REG_PIOB_ABSR = 1<<25;       // select peripheral B
  REG_TC0_WPMR  = 0x54494D00;  // enable write to registers
//REG_TC0_CMR0  = 0b00000000000010011100010000000000; // set channel mode register (see datasheet)
  REG_TC0_CMR0  = 0b00000000000001101100010000000000; // alternative CMR for inverted output
  REG_TC0_RC0   = 100000000;   // counter period
  REG_TC0_CCR0  = 0b101;       // start counter
  REG_TC0_IER0  = 0b00010000;  // enable interrupt on counter = rc
  REG_TC0_IDR0  = 0b11101111;  // disable other interrupts
   
  REG_TC0_RA0   = 0.5*1000*42; // Pulse lenght     = .5ms
  Frame         = 22 *1000*42; // ppm frame lenght = 22ms  
  
  ppm_channels[0]= 0;          // channel 1 from 0 to 255
  ppm_channels[1]= 0;          // channel 2 from 0 to 255
  ppm_channels[2]= 255;        // channel 3 from 0 to 255
  ppm_channels[3]= 0;          // channel 4 from 0 to 255
  ppm_channels[4]= 0;          // channel 5 from 0 to 255
  ppm_channels[5]= 0;          // channel 6 from 0 to 255
  ppm_channels[6]= 0;          // channel 7 from 0 to 255
  ppm_channels[7]= 0;          // channel 8 from 0 to 255
  
  
  NVIC_EnableIRQ(TC0_IRQn);    // enable TC0 interrupts
}

void loop(){
  
  // 1. Setup 8 channels (changing only channel 1 here just for fun)
  ppm_channels[0]= ppm_channels[0] + val; 
  if(ppm_channels[0] >= 255){ val = -1; }
  if(ppm_channels[0] <= 0)  { val = 1;  }
  delay(25);
  
  // 2. Calculate the 8 channels
  Sum = 0;
  for (int i = 0; i < 8; i++)  {
    periods[i] = map(ppm_channels[i], 0, 255, 1000*42, 2000*42);
    Sum = Sum + periods[i]; 
  }
  // 3. Calculate the sync frame
  periods[8] = Frame - Sum;
}

And a picture (worth a 1000 words):

mantoui , I did not comment your code because it use delays to generate ppm. It's OK if your mc does only one thing but it is a problem if you run something else at the same time. Thank you for your help, I'll click a few times on your karma too lol.

Francois

I would like to ask one more question to our group. Would it be possible to do something similar to the code posted right at the beginning of this thread?

Something like:

1 start counter
2 when counter reach trigger value then digitalWrite(pin, HIGH)
3 reset counter and trigger value
4 when counter reach new trigger value then digitalWrite(pin, LOW)

writing the code like this allows much more flexibility (for example futaba transmitters require negative shift ppm).

Thank you for your help.

Francois

Hi,
I plan the same operation, give my Mega-transmitter a Due heart, this thread is really helpful. I used the same, really good, code for ppm-generating.

If i understand the "new" Due-timer right, i think u missed read this lines from stimmer:

//REG_TC0_CMR0  = 0b00000000000010011100010000000000; // set channel mode register (see datasheet)
  REG_TC0_CMR0  = 0b00000000000001101100010000000000; // alternative CMR for inverted output

Look at the Datasheet of sam3x/a, page 904.
Only switch the comment tag to invert the signal.

An other point: give your ppm more sensitive than 256. I use 1024 now (later 4096 with Due). Try it, you will feel the different.

And i say it too: Thanks stimmer, for this helpful job.

Thanks Sijjim,

you are right, i should learn to read comments! Do you have a picture of your transmitter?

Here is mine:

version 0 (cardboard lol)

Version 1

Still...

I would like to use a counter and i do not know how to do it. Something that use the due version of

ISR(TIMER1_COMPA_vect){ } & OCR1A

Any help will be appreciated.

I think your code is almost the same like what you wish, this 32-bit Atmel speak a little other language. :slight_smile:

My transmitter, like posted, now its with Mega, Due comes next year. But allready practiceproofed

Inside:

Steering:

Ok, you really wants the same code back? Please test it, i dont have an Due now. But hope this problem isnt one, when i have my own.
Get the Lib from ivanseidel/DueTimer
and test this:

#include <DueTimer.h>

#define chanel_number 8
#define default_servo_value 1500
#define PPM_FrLen 22500
#define PPM_PulseLen 300
#define sigPin 10
#define onState 1

int ppm[chanel_number];

void doit(){
  static boolean state = true;
  if(state){
    digitalWrite(sigPin, onState);
    Timer3.attachInterrupt(doit).setPeriod(PPM_PulseLen);
    state = false;
  } else {
    static byte cur_chan_numb;
    static unsigned int calc_rest;
    
    digitalWrite(sigPin, !onState);
    state = true;
  
    if(cur_chan_numb >= chanel_number){
      cur_chan_numb = 0;
      calc_rest = calc_rest + PPM_PulseLen;
      Timer3.attachInterrupt(doit).setPeriod(PPM_FrLen - calc_rest);
      calc_rest = 0;
    } else {
      Timer3.attachInterrupt(doit).setPeriod(ppm[cur_chan_numb] - PPM_PulseLen);
      calc_rest = calc_rest + ppm[cur_chan_numb];
      cur_chan_numb++;
    }
  }      
}

void setup(){
  for (int i = 0; i < chanel_number; i++){
    ppm[i] = default_servo_value;
  }
  pinMode(sigPin, OUTPUT);
  digitalWrite(sigPin, !onState);
  Timer3.attachInterrupt(doit).start(1000);
}

void loop(){
  while(1){	}
}

Thank you very much for your help, I will be able to check the code on Friday (I do not have an osciloscope at home =( ).

Here is what i would like to learn to do on the arduino due ( in green is the equivalent code for the arduino uno):

**1. Setup a timer on the due & Start a timer **
cli();
TCCR1A = 0; // set entire TCCR1 register to 0
TCCR1B = 0;
OCR1A = 100; // compare match register
TCCR1B |= (1 << WGM12); // turn on CTC mode
TIMSK1 |= (1 << OCIE1A); // enable timer compare interrupt
sei();

2. Set a presacaler
TCCR1B |= (1 << CS11); // 8 prescaler: 0,5 microseconds at 16mhz

3. Change the match register (trigger)
OCR1A = XXX // what every value i decide

4. Run into a loop every time the match register is reached
ISR(TIMER1_COMPA_vect){ code }

Is it possible to do ?

First, if u have an other Arduino, i use this sketch on a Uno for fast bugfixing:

#define channumber 7 //How many channels have your radio?
int value[channumber];

void setup()
{
Serial.begin(57600); //Serial Begin
pinMode(3, INPUT); //Pin 3 as input
}
void loop()
{
while(pulseIn(3, LOW) < 5000){} //Wait for the beginning of the frame
// while(pulseIn(3, HIGH) < 5000){} //alt for invert signal
for(int x=0; x<=channumber-1; x++)//Loop to store all the channel position
{
value[x]=pulseIn(3, LOW);
// value[x]=pulseIn(3, HIGH); //alt for invert signal
}
for(int x=0; x<=channumber-1; x++)//Loop to print and clear all the channel readings
{
Serial.print(value[x]); //Print the value
Serial.print(" ");
value[x]=0; //Clear the value afeter is printed
}
Serial.println(""); //Start a new line
}

Just hang pin 3 from this one to ppm-out on Due an connect ground together.

and the other points, look to stimmers code too:

  1. Setup a timer on the due & Start a timer
REG_PIOB_PDR = 1<<25; // disable PIO, enable peripheral
  REG_PIOB_ABSR= 1<<25; // select peripheral B
  REG_TC0_WPMR=0x54494D00; // enable write to registers
//REG_TC0_CMR0=0b00000000000010011100010000000000; // set channel mode register (see datasheet)
  REG_TC0_CMR0=0b00000000000001101100010000000000; // alternative CMR for inverted output
  REG_TC0_RC0=100000000; // counter period
  REG_TC0_CCR0=0b101;    // start counter
  REG_TC0_IER0=0b00010000; // enable interrupt on counter=rc
  REG_TC0_IDR0=0b11101111; // disable other interrupts
  1. Set a presacaler

not sure, think its inside this part, the TCCLKS, look in datasheet at page 869, Table 37-1 and page 904

REG_TC0_CMR0=0b00000000000001101100010000000000;
  1. Change the match register (trigger)
REG_TC0_RC0= XXX
  1. Run into a loop every time the match register is reached
void TC0_Handler() { code }

greetings

Good day.
I'm not an experienced programmer Arduino, had a lot to deal with timers Atmel SAM3X8E ARM Cortex-M3 NC.
Helped Frankois code in this post.
Thank very much for Frankois working code.
I redid it and simplified.
Code 100% working on Arduino Due.
My code changes PPM signal to the respective signals at the inputs A0-A2.

#define PPM_PIN 2             // выход PPM
#define MAX_PPM_CHANNELS 8
#define PPM_FrLen 22500
#define PPM_PulseLen 250
int ppm[MAX_PPM_CHANNELS];
int PPMmin = 900;
byte PPM_cur_ch = 0;
unsigned int PPM_sum = 0;

void setup() {
  pinMode(PPM_PIN, OUTPUT);          // port B pin 25
  analogWrite(PPM_PIN, 255);         // sets up some other registers I haven't worked out yet

  // timer is clocked at 42MHz, so 42 ticks per us
  REG_PIOB_PDR  = 1 << 25;     // disable PIO, enable peripheral
  REG_PIOB_ABSR = 1 << 25;     // select peripheral B
  REG_TC0_WPMR  = 0x54494D00;  // enable write to registers
  // REG_TC0_CMR0  = 0b00000000000010011100010000000000; // set channel mode register (see datasheet)
  REG_TC0_CMR0  = 0b00000000000001101100010000000000; // alternative CMR for inverted output
  REG_TC0_RC0   = 100000000;   // counter period
  REG_TC0_CCR0  = 0b101;       // start counter
  REG_TC0_IER0  = 0b00010000;  // enable interrupt on counter = rc
  REG_TC0_IDR0  = 0b11101111;  // disable other interrupts

  REG_TC0_RA0   = PPM_PulseLen * 42; // Pulse lenght
  NVIC_EnableIRQ(TC0_IRQn);    // enable TC0 interrupts

  for (int i = 0; i < MAX_PPM_CHANNELS; i++) ppm[i] = PPMmin;
}

void TC0_Handler()
{ 
  long dummy = REG_TC0_SR0;  // vital - reading this clears some flag, otherwise you get infinite interrupts
  if (PPM_cur_ch < MAX_PPM_CHANNELS)
  {
    REG_TC0_RC0 = ppm[PPM_cur_ch] * 42;
    PPM_sum += ppm[PPM_cur_ch];
    PPM_cur_ch++;
  }
  else
  {
    REG_TC0_RC0 = (PPM_FrLen - PPM_sum) * 42;
    PPM_cur_ch = 0;
    PPM_sum = 0;
  }
}

void loop()
{
  ppm[0] = PPMmin + analogRead(0);
  ppm[1] = PPMmin + analogRead(1);
  ppm[2] = PPMmin + analogRead(2);
  
  
}

Sijjim's code in this post must be changed.
Each row in the:
Timer3.attachInterrupt (doit) .setPeriod (PPM_PulseLen);
Timer3.attachInterrupt (doit) .setPeriod (PPM_FrLen - calc_rest);
Timer3.attachInterrupt (doit) .setPeriod (ppm [cur_chan_numb] - PPM_PulseLen);
it is necessary to add the start ()

Corrected row looks like this:
Timer3.attachInterrupt (doit) .setPeriod (PPM_PulseLen). start ();
Timer3.attachInterrupt (doit) .setPeriod (PPM_FrLen - calc_rest). start ();
Timer3.attachInterrupt (doit) .setPeriod (ppm [cur_chan_numb] - PPM_PulseLen). start ();

To read the PPM signal in my project using this code:

#define MAX_PPM_CHANNELS 8          // we are working with an 8-ch-Transmitter
#define PPM_PIN 3           // Input Pin Number 

const int FrameSpace = 2500; 
unsigned long Last_Time;
unsigned long Current_Time;
unsigned long Delta_Time;
volatile byte Current_Channel = 0;
volatile int Channel[MAX_PPM_CHANNELS];

void setup() 
{
    attachInterrupt(1, CalcPPM, RISING);
    Last_Time = micros();  
    Serial.begin(9600);
}

void CalcPPM()
{ 
  Current_Time = micros();
  Delta_Time = Current_Time - Last_Time; 
  Last_Time = Current_Time; 
  if (Delta_Time > FrameSpace) Current_Channel = 0;
  else 
  {
    Channel[Current_Channel]=Delta_Time;
    Current_Channel++;
  }    
}  

void loop() 
{ 
  for ( byte x = 0; x < MAX_PPM_CHANNELS; x++)
  {
    Serial.print("Ch. ");
    Serial.print(x);
    Serial.print(": ");
    Serial.print(Channel[x]);  
    Serial.print("      ");
  }
  Serial.println();
 }

Checked this code to Arduino Due, Mega and Pro Mini.
This code works 100%.

To use the Due need to change the line:

attachInterrupt(1, CalcPPM, RISING);

to:

attachInterrupt(PPM_PIN, CalcPPM, RISING);

I recently wrote a PPM signal library called PulsePosition.

http://www.pjrc.com/teensy/td_libs_PulsePosition.html

It's designed around Teensy's timers, but if someone wanted to get a good start on a Due-based library, maybe this could at least help?

Can confirm Alex's code works great on the Due!

so something like Serial.begin(9600); fails saying serial is not defined or something along those lines.

Instead of saying "something like" or "along the lines of" why don't you simply post the code you've got and the error message(s) you're getting?

Anything else is just time-wasting.