Go Down

Topic: Arduino due / ppm (Read 3337 times) previous topic - next topic

Francois

I would like port some code generating ppm for an RC transmitter from the Uno to the Due. The code is not mine, but it is so simple and elegant that I am not planing to change the structure. It can be downloaded here:  https://code.google.com/p/generate-ppm-signal/downloads/detail?name=Generate_PPM_signal_nodelay.ino&can=2&q=

I wish i could set up a counter in the same manner and change the trigger every time it is necessary, it sounds simple when the code is written, but i am kind of stuck. (I spend quite some time reading : http://www.atmel.com/Images/doc11057.pdf and
http://asf.atmel.com/docs/latest/api.html).

Any help to jump start my progress would be appreciated.

Thank you in advance,

Francois

PS: Here is the code

Code: [Select]
//this programm will put out a PPM signal

//////////////////////CONFIGURATION///////////////////////////////
#define chanel_number 8           //set the number of chanels
#define default_servo_value 1500  //set the default servo value
#define PPM_FrLen 22500           //set the PPM frame length in microseconds (1ms = 1000┬Ás)
#define PPM_PulseLen 300          //set the pulse length
#define onState 1                 //set polarity of the pulses: 1 is positive, 0 is negative
#define sigPin 10                 //set PPM signal output pin on the arduino
//////////////////////////////////////////////////////////////////


/*this array holds the servo values for the ppm signal
change theese values in your code (usually servo values move between 1000 and 2000)*/
int ppm[chanel_number];

void setup(){ 
  //initiallize default ppm values
  for(int i=0; i<chanel_number; i++){
    ppm[i]= default_servo_value;
  }

  pinMode(sigPin, OUTPUT);
  digitalWrite(sigPin, !onState);  //set the PPM signal pin to the default state (off)
 
  cli();
  TCCR1A = 0; // set entire TCCR1 register to 0
  TCCR1B = 0;
 
  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(){
  while(1){ }
}

ISR(TIMER1_COMPA_vect){ 
  static boolean state = true;
 
  TCNT1 = 0;
 
  if(state) {   //start pulse
    digitalWrite(sigPin, onState);
    OCR1A = PPM_PulseLen * 2;
    state = false;
  }
  else{         //end pulse and calculate when to start the next pulse
    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;//
      OCR1A = (PPM_FrLen - calc_rest) * 2;
      calc_rest = 0;
    }
    else{
      OCR1A = (ppm[cur_chan_numb] - PPM_PulseLen) * 2;
      calc_rest = calc_rest + ppm[cur_chan_numb];
      cur_chan_numb++;
    }     
  }
}



mantoui

The code you have (as noted) is UNO (AVR) specific.  The ARM IO architecture is radically different.

So why not use the provided Arduino Servo library?  It does what you want on both UNO and DUE.

For your edification, you could peek at the DUE's PWM code in
hardware/arduino/sam/cores/arduino/wiring_analog.c

Greetings!

The easiest solution depends on what you are trying to do. If you are simply trying to control servos, then the due servo library will be the most elegant solution. It can be found here: http://arduino.cc/en/Reference/Servo

If you are trying to simply call a function or pulse a pin, etc at varying time intervals or frequencies, then the following library is a great tool. I merged all of my code with it a while back, and it is the most up to date. It has great documentation and examples on how to use it. It can be found on github here: https://github.com/ivanseidel/DueTimer

This allows you to set up as many as 9 different timers that can run different functions within your program. You configure the timer with either a frequency in Hz or a period in microseconds. Then you set which function the timer will call.

Am I understanding your question right?

Cheers!
SRG

Francois

#3
Dec 03, 2013, 11:48 pm Last Edit: Dec 04, 2013, 04:34 am by Francois Reason: 1
Thank you for you answers. I am trying to generate a ppm signal for an RX transmiter. The "servo"  and the "due timer" (which is amazing by the way) libraries works great but can generate only one pulse (pwm). I was able to generate pwm easily, but ppm is more complicated, because the distance (time) between pics change all the time.

Only one timer with a variable trigger is necessary (as shown in the code above), but i am not sure how to set up the timer and change the trigger on the fly.

Thank you all.

Francois

Just to remove any confusion (ppm/pwm), a picture is worth a thousand words

mantoui

there is a blog dedicated to RC and Arduino, visit
  http://rcarduino.blogspot.com/
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 ...

Francois

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

stimmer

#6
Dec 06, 2013, 03:47 pm Last Edit: Dec 06, 2013, 03:51 pm by stimmer Reason: 1
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 :)

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.

Code: [Select]

// 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)
Due VGA library - http://arduino.cc/forum/index.php/topic,150517.0.html

mantoui

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

Code: [Select]

// 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
}

mantoui

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.

Code: [Select]

// 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);
}

Francois

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




Francois

#10
Dec 09, 2013, 09:47 pm Last Edit: Dec 09, 2013, 10:02 pm by Francois Reason: 1
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:

Code: [Select]
// 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

Francois

#11
Dec 09, 2013, 09:59 pm Last Edit: Dec 09, 2013, 10:13 pm by Francois Reason: 1
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

Sijjim

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:

Code: [Select]
//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.

Francois

#13
Dec 10, 2013, 03:33 pm Last Edit: Dec 10, 2013, 03:40 pm by Francois Reason: 1
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.

Sijjim

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

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

Inside:


Steering:



Go Up