Go Down

Topic: Read RC Channels from RC receiver from 1 to 8 channels. (Read 2 times) previous topic - next topic

FlyAwayFR

Jul 15, 2013, 06:50 pm Last Edit: Jul 16, 2013, 02:10 am by FlyAwayFR Reason: 1
Read RC Channels from RC receiver from 1 to 8 channels:
Read single RC channel on Pin 8 using Input Capture of 16bit Timer1 with precaler.
Read single RC channel on Pin 2 to 7 using Pin Change Interrupt and 16bit Timer1 with precaler.
Read single RC channel on Pin 2 to 7 using Pin Change Interrupt and 8bit Timer2 with precaler.
Read 6 RC channels on Pin 2 to 7 using Pin Change Interrupt and 8bit Timer2 with precaler.

Hi everyone!

This is my first post I hope you will see some interest in it.

I have a main project that involves reading of a single RC channel but this is not the core of my project. Willing to gain some time I have designed and ordered and received a PCB in order to have a tidy result. Not worried about the way to read PWM signal from any receiver I put this input on Pin 4 of ATMega328 based MCU. I was sure enough that it will be "finger in the nose" operation to find some piece of code on the net to do the job. But it was not…

When I start looking for the "easy to get" piece of code I quickly realize that it was more difficult than expected and by far. I end up in the infamous world of Interrupts, Timers, Prescalers and Bitwise operation! If you look for such code you will find some good tutorials about interrupts but without associated code. It is the same for timers. There are some long pages talking about registers. You will find some pieces of code but not what fulfill your needs.
You may use some good libraries around but not easy to use and most of the time difficult to adapt if needed because the code is a bit "dark". By the way I recommend the reading (max you can digest) of http://rcarduino.blogspot.fr/. It covers most of what you need to understand this strange world and you can try the library developed by the author (who seems to be a French compatriot).
I join a helpful pdf table made by myself about Timers and Prescaler Calculations.

Ok…So What now!

I decided to make my own code exploring different ways to read PWM signal from RC receiver. I spend a lot of time doing this but at least I'm now much more comfortable with those tricky stuffs. I also realize that it is not based on how smart you are. But much more on how much time you can spend experiencing and by the way understanding. I forgot to mention that after two years "pumping" code lines from every source I could it was time for me to contribute. So here is the result of my contribution. All code is based and tested on Arduino Uno SMD edition R3.

1 Reading single RC channel on Pin 8 using Input Capture of 16bit Timer1 with precaler.

2 Reading single RC channel on Pin 2 to 7 using Pin Change Interrupt and 16bit Timer1 with precaler.

3 Reading single RC channel on Pin 2 to 7 using Pin Change Interrupt and 8bit Timer2 with precaler.

And finaly "la cerise sur le gateau".

4 Reading 6 RC channels on Pin 2 to 7 using Pin Change Interrupt and 8bit Timer2 with precaler.

NOTE: I made my test with a Futaba 9Channels Rx R149DP PCM1024 and a Futaba 6Channels Rx R156F (PPM). The main problem when it comes to read multi-channel input is that you end up with a single interrupt triggered by one rising plus one falling edge. It don't makes things easy to put together…

NOTE: There is two ways not covered by this post. The first one is the pulseIn() function because it is a code "holding" function and it makes it useless when it comes to get fast and low time consumption code. The second one is build in External Interrupts mainly because only Pins 2 and 3 can do it and those pins where not available on my project (my mistake). External Interrupts are useful if you need only one or two channels. The following code shall be easy to adapt if you want use this feature.

I hope my code is easy to understand and easy to modify to fit your needs. I'll surely take any improvement proposal as I'm not a C++ or Java doctor. I'm also interested in performance testing. So if you are willing to torture this code please give some feedback.

Hope my English is still correct.
Enjoy.

I will answer myself to post de code:

Cheers.

Salutations Amicales (from France)

FlyAwayFR

#1
Jul 15, 2013, 06:51 pm Last Edit: Jul 25, 2013, 10:38 pm by FlyAwayFR Reason: 1
1 Reading single RC channel on Pin 8 using Input Capture of 16bit Timer1 with precaler.
Code: [Select]

/*
Read a single RC Channel out of the receiver.
Wire Receiver Gnd (Black or Brown) to Arduino Gnd.
Wire receiver Signal (White or Yellow) to Pin 8 of ATMega328 based Arduino.
Pin 8 is the Input Capture pin of ATMega328 16bit Timer1.
Use Timer1 Input Capture Methode to Measure RC PWM signal.
Return result PulseLength in µs (Tipicaly 1000 to 2000µs).
On Futaba System PulseLength range from 2000µs (Low) to 1000µs (High)
Return a Fail Safe Value if no signal is detected (2000µs for Futaba System).
Fail Safe Value can be changed to suits other system or requirement.
Micro Controller Clock Frequency shall be according to used MCU.
Prescaler can be changed but 8 is best fit and gives +/-0.5µs resolution.
More over with Prescaler at 8 the timer overflow occurs at 32ms.
This gives a fast Fail Safe responce (all frame is 20ms i.e 50Hz).
NOTE on TIMERS
Timer0 is a 08bit timer: PWM Pin05 and Pin06, timer functions, like delay(), millis() and micros() uses timer0
Timer1 is a 16bit timer: PWM Pin08 and Pin09, Servo library uses timer1 on Arduino Uno (timer5 on Arduino Mega)
Timer2 is a 08bit timer: PWM Pin03 and Pin11, the tone() function uses timer2.
So if you need Servo.Lib to work properly: Forget about this Methode (only Timer1 can do Input Capture)...
DON'T FORGET TO SET SERIAL MONITOR AT 115200 BAUD!
*/

#include <avr/io.h>
#include <avr/interrupt.h>                          //Needed to use interrupts
#include <stdint.h>                                 //Has to be added to use uint8_t...
#include <math.h>                                   //For using log() function

//MICRO CONTROLLER CLOCK FREQUENCY
#define MCU_CLOCKFREQ         16                    //MHz

//TIMER1 PRESCALER
#define TI1_PRESCALER         8                     // 1, 8, 64, 256 or 1024 for Timer1

//+/- 16µs @ 16MHz with Prescaler = 256 Overflow every 1085ms
//+/-  4µs @ 16MHz with Prescaler = 64  Overflow every 0262ms
//+/-0.5µs @ 16MHz with Prescaler = 8   Overflow every 0032ms (bit more than 20ms (50Hz) of RC PWM frame)

//RADIO SYSTEM DEFAULT FAIL SAFE VALUE (ex Low Throttle)
#define FAIL_SAFE_VAL         2000                  //FUTABA=2000=LOW

//Timer Bitwise Operators Calculated BitShift Left  using Log Base 2
const int ShiftL = (log (TI2_PRESCALER/MCU_CLOCKFREQ) / log (2));
//Timer Bitwise Operators Calculated BitShift Right using Log Base 2
const int ShiftR = (log (MCU_CLOCKFREQ/TI2_PRESCALER) / log (2));
//Capture Flag
volatile boolean Flag = true;
//Variables holding timestamps
volatile uint16_t InTimeStamp;
volatile uint16_t UpTimeStamp;
volatile uint16_t DnTimeStamp;
//PulseLength
volatile uint32_t PulseLength;

void setup() {               
  delay(100);
  Serial.begin(115200);
  delay(100);
  Serial.println(" ");
  delay(100);
  Serial.println("Start");
  delay(100);
  Serial.print("ShiftL=");
  Serial.println(ShiftL);
  Serial.print("ShiftR=");
  Serial.println(ShiftR);
  delay(100);
  //Timer1 (16bits) Setting and Starting
  SetStartTimer1();
  delay(500);
}

void loop() {
  //HERE IS YOUR CODE
  //When Flag is True Read Pulse Length (available)
  if (Flag == true) Serial.println(PulseLength);
}

//Timer1 (16bits) Setting and Starting
void SetStartTimer1(void) {
  //Disable global interrupts
  cli();
  //Clean the registers
  TCCR1A = 0;     
  TCCR1B = 0;
  //First capture on rising edge
  TCCR1B|=(1<<ICES1);
  //The noise canceler is enabled
  TCCR1B|=(1<<ICNC1);
  //Clear Pending Interrupts
  TIFR1|=(1<<ICF1)|(1<<TOV1);
  //Enable input capture and overflow interrupts
  TIMSK1|=(1<<ICIE1)|(1<<TOIE1);
  //Start timer with prescaller
  SetPrescaler(TI1_PRESCALER);
  //Enable global interrutps
  sei();
}

//Set Timer1 Prescaler
void SetPrescaler(int Prescaler) {
   //Select Case
  switch (Prescaler) {
    case 1:
      TCCR1B|=(1<<CS10);
      //Serial.println("Prescaler=1");
      break;
    case 8:
      TCCR1B|=(1<<CS11);
      //Serial.println("Prescaler=8");
      break;
    case 64:
      TCCR1B|=(1<<CS11)|(1<<CS10);
      //Serial.println("Prescaler=64");
      break;
    case 256:
      TCCR1B|=(1<<CS12);
      //Serial.println("Prescaler=256");
      break;
    case 1024:
      TCCR1B|=(1<<CS12)|(1<<CS10);
      //Serial.println("Prescaler=1024");
      break;
    default:
      TCCR1B|=(1<<CS10);
      //Serial.println("Prescaler=1");
    break; 
  } 
}

//Interrupt Service Routine On Edge (Rising/Falling)
ISR(TIMER1_CAPT_vect) {
  //Time Stamp Capture
  InTimeStamp = ICR1;
  if (Flag == true) {
    //Time Stamp Capture First Rising Edge
    UpTimeStamp = InTimeStamp;
    //Change Capture on Falling Edge
    TCCR1B&=~(1<<ICES1);
  }
  if (Flag == false) {
    //Time Stamp Capture First Falling Edge
    DnTimeStamp = InTimeStamp;
    //Change Capture on Rising Edge
    TCCR1B|=(1<<ICES1);
    //Calculate Pulse Lenght (Timer Tick)
    PulseLength = (uint32_t)((uint32_t)(DnTimeStamp - UpTimeStamp));
    //Calculate Pulse Lenght (µs) BitShift Multiply by...
    if (ShiftL > 0) PulseLength = PulseLength<<ShiftL;
    //Calculate Pulse Lenght (µs) BitShift Divide by...
    if (ShiftR > 0) PulseLength = PulseLength>>ShiftR;
    //Remove Unwanted Values
    PulseLength = constrain(PulseLength,750,2250);
    //Reset Counter to avoid Overflow Interrupt (Fail Safe)
    TCNT1=0;
  }
  //Reset Flag
  Flag = ! Flag;
}

//Interrupt Service Routine On Timer Overflow
ISR(TIMER1_OVF_vect) {
  //Fail Safe Value
  PulseLength = FAIL_SAFE_VAL;
  //Set Flag
  Flag = true;
}


See Next
Salutations Amicales (from France)

FlyAwayFR

#2
Jul 15, 2013, 07:06 pm Last Edit: Jul 25, 2013, 10:35 pm by FlyAwayFR Reason: 1
2 Reading single RC channel on Pin 2 to 7 using Pin Change Interrupt and 16bit Timer1 with precaler.
Part 1
Code: [Select]

/*
Read a single RC Channel out of the receiver.
Wire Receiver Gnd (Black or Brown) to Arduino Gnd.
Wire receiver Signal (White or Yellow) to Pin 4 of ATMega328 based Arduino.
Pin 4 is set as Pin Change Interrupt PCINT20 in Pin Change Mask Register 2 PCMSK2.
No probleme if another Pin on the same port is set as Pin Change Interrupt (It should never be the case!)
Use Timer1 in Normal Mode with Overflow Interrupt to Measure RC PWM signal.
Return result PulseLength in µs (Tipicaly 1000 to 2000µs).
On Futaba System PulseLength range from 2000µs (Low) to 1000µs (High)
Return a Fail Safe Value if no signal is detected (2000µs for Futaba System).
Fail Safe Value can be changed to suits other system or requirement.
Micro Controller Clock Frequency shall be according to used MCU.
Prescaler can be changed but 8 is best fit and gives +/-0.5µs resolution.
More over with Prescaler at 8 the timer overflow occurs at 32.768ms.
This gives a fast Fail Safe responce (all frame is 20ms i.e 50Hz).
NOTE on TIMERS
Timer0 is a 08bit timer: PWM Pin05 and Pin06, timer functions, like delay(), millis() and micros() uses timer0
Timer1 is a 16bit timer: PWM Pin08 and Pin09, Servo library uses timer1 on Arduino Uno (timer5 on Arduino Mega)
Timer2 is a 08bit timer: PWM Pin03 and Pin11, the tone() function uses timer2.
So if you need Servo.Lib to work properly:
-Avoid the use of Timer0, no good to touch delay(), millis() and micros() fonctions
-Use Timer2 but you loose Tone fonction (Tone was important to me so I used Timer1)
DON'T FORGET TO SET SERIAL MONITOR AT 115200 BAUD!
*/

#include <avr/io.h>
#include <avr/interrupt.h>                          //Needed to use interrupts
#include <stdint.h>                                 //Has to be added to use uint8_t...
#include <math.h>                                   //For using log() function

//MICRO CONTROLLER CLOCK FREQUENCY
#define MCU_CLOCKFREQ         16                    //MHz

//TIMER1 PRESCALER
#define TI1_PRESCALER         8                     // 1, 8, 64, 256 or 1024 for Timer1

//+/-0.5µs @ 16MHz with Prescaler = 8   Overflow every  32.768ms (bit more than 20ms (50Hz) of RC PWM frame)
//+/-4.0µs @ 16MHz with Prescaler = 64  Overflow every 262.144ms

//PIN INPUT FOR PIN CHANGE INTERRUPT (Caution! 0 is RXD/1 is TXD)
#define PI_CH_INT_PIN         4                     // 0, 1, 2, 3, 4, 5, 6 or 7 (Port D only for now)

//RADIO SYSTEM DEFAULT FAIL SAFE VALUE (ex Low Throttle)
#define FAIL_SAFE_VAL         2000                  //FUTABA=2000=LOW

//Timer Bitwise Operators Calculated BitShift Left  using Log Base 2
const int ShiftL = (log (TI2_PRESCALER/MCU_CLOCKFREQ) / log (2));
//Timer Bitwise Operators Calculated BitShift Right using Log Base 2
const int ShiftR = (log (MCU_CLOCKFREQ/TI2_PRESCALER) / log (2));
//Capture Flag
volatile boolean Flag = true;
//Variables holding timestamps
volatile uint16_t InTimeStamp;
volatile uint16_t UpTimeStamp;
volatile uint16_t DnTimeStamp;
//PulseLength
volatile uint32_t PulseLength;
//Port D Status and Record
volatile uint8_t StatusPortD;
volatile uint8_t RecordPortD;                       //= 0xFF (Set 11111111 Default is high because the pull-up: Useless?)
volatile uint8_t ChangedBits;

void setup() {
  delay(100);
  Serial.begin(115200);
  delay(100);
  Serial.println(" ");
  delay(100);
  Serial.println("Start");
  delay(100);
  Serial.print("ShiftL=");
  Serial.println(ShiftL);
  Serial.print("ShiftR=");
  Serial.println(ShiftR);
  delay(100);
  //Timer1 (16bits) Setting and Starting
  SetStartTimer1();
  //Initialization Pin Change Interrupt
  InitPinChangeInt();
  delay(500);
}

void loop() {
  //HERE IS YOUR CODE
  //When Flag is True Read Pulse Length (available)
  if (Flag == true) Serial.println(PulseLength);
}

//Timer1 (16bits) Setting and Starting
void SetStartTimer1(void) {
  //Disable global interrupts
  cli();
  //Clean the registers
  TCCR1A = 0;     
  TCCR1B = 0;
  //Clear Pending Interrupts
  TIFR1|=(1<<TOV1);
  //Enable overflow interrupts
  TIMSK1|=(1<<TOIE1);
  //Start timer with prescaller
  SetPrescaler(TI1_PRESCALER);
  //Enable global interrutps
  sei();
}

//Set Timer1 Prescaler
void SetPrescaler(int Prescaler) {
   //Select Case
  switch (Prescaler) {
    case 1:
      TCCR1B|=(1<<CS10);
      //Serial.println("Prescaler=1");
      break;
    case 8:
      TCCR1B|=(1<<CS11);
      //Serial.println("Prescaler=8");
      break;
    case 64:
      TCCR1B|=(1<<CS11)|(1<<CS10);
      //Serial.println("Prescaler=64");
      break;
    case 256:
      TCCR1B|=(1<<CS12);
      //Serial.println("Prescaler=256");
      break;
    case 1024:
      TCCR1B|=(1<<CS12)|(1<<CS10);
      //Serial.println("Prescaler=1024");
      break;
    default:
      TCCR1B|=(1<<CS10);
      //Serial.println("Prescaler=1");
    break; 
  } 
}


See Next
Salutations Amicales (from France)

FlyAwayFR

#3
Jul 15, 2013, 07:08 pm Last Edit: Jul 25, 2013, 10:40 pm by FlyAwayFR Reason: 1
2 Reading single RC channel on Pin 2 to 7 using Pin Change Interrupt and 16bit Timer1 with precaler.
Part 2
Code: [Select]

//Make Pin 4 an External Interrupt (Pin 04 = I/O 04 on Port D (PD4) = PCINT20 on PCMSK2)
//Same Pin 3 for discrimination test only (should never be the case)
void InitPinChangeInt(void) {
 //Disable global interrupts
 cli();
 // Clear the PD4 pin: PD4 is now an input
 DDRD&=~(1<<DDD4);
 // Turn On the Pull-up Resistor: PD4 is now an input with pull-up enabled
 PORTD|=(1<<PORTD4);
 //Enable Pin Change Interrupt on Pin 4 i.e PCINT20 in Pin Change Mask Register 2 PCMSK2
 PCMSK2|=(1<<PCINT20);
 // Clear the PD3 pin: PD3 is now an input
 /*TEMPORARY Pin Change Interrupt on Pin 3 TO CHECK DISCRIMINATION*/ DDRD&=~(1<<DDD3);
 // Turn On the Pull-up Resistor: PD3 is now an input with pull-up enabled
 /*TEMPORARY Pin Change Interrupt on Pin 3 TO CHECK DISCRIMINATION*/ PORTD|=(1<<PORTD3);
  //Enable Pin Change Interrupt on Pin 3 i.e PCINT19 in Pin Change Mask Register 2 PCMSK2
 /*TEMPORARY Pin Change Interrupt on Pin 3 TO CHECK DISCRIMINATION*/ PCMSK2|=(1<<PCINT19);
 //Enable PCIE2 in Pin Change Interrupt Control Register PCICR to enable PCMSK2 scan
 PCICR|=(1<<PCIE2);
 //Enable global interrutps
 sei();
}

//Interrupt Service Routine On Edge Change Pin 4 (Rising or Falling)
ISR (PCINT2_vect) {
 //Time Stamp Capture
 InTimeStamp = TCNT1;
 //Status of Port D taken a.s.a.p.
 StatusPortD = PIND;
 //Check bits that changed since previous ISR
 ChangedBits = StatusPortD ^ RecordPortD;
 //Reject bits that are out of Pin Change Mask Register 2 scope
 ChangedBits &= PCMSK2;
 //Record bits status for next ISR
 RecordPortD = StatusPortD;
 //Make sure that Input4 actualy triggered the ISR and discriminate Rising/Falling Edges
 if ((ChangedBits & (1<<PI_CH_INT_PIN)) && (StatusPortD & (1<<PI_CH_INT_PIN))) {
   //Time Stamp Capture First Rising Edge
   UpTimeStamp = InTimeStamp;
   //Reset Flag
   Flag = false;
 }
 if ((ChangedBits & (1<<PI_CH_INT_PIN)) && (!(StatusPortD & (1<<PI_CH_INT_PIN)))) {
   //Time Stamp Capture First Falling Edge
   DnTimeStamp = InTimeStamp;
   //Calculate Pulse Lenght (Timer Tick)
   PulseLength = (uint32_t)((uint32_t)(DnTimeStamp - UpTimeStamp));
   //Calculate Pulse Lenght (µs) BitShift Multiply by...
   if (ShiftL > 0) PulseLength = PulseLength<<ShiftL;
   //Calculate Pulse Lenght (µs) BitShift Divide by...
   if (ShiftR > 0) PulseLength = PulseLength>>ShiftR;
   //Remove Unwanted Values
   PulseLength = constrain(PulseLength,750,2250);
   //Reset Timer Count to avoid Overflow Interrupt (Fail Safe)
   TCNT1=0;
   //Set Flag
   Flag = true;
 }
}

//Interrupt Service Routine On Timer Overflow
ISR(TIMER1_OVF_vect) {
 //Fail Safe Value
 PulseLength = FAIL_SAFE_VAL;
 //Set Flag
 Flag = true;
}


See Next
Salutations Amicales (from France)

FlyAwayFR

#4
Jul 15, 2013, 07:12 pm Last Edit: Jul 25, 2013, 10:33 pm by FlyAwayFR Reason: 1
3 Reading single RC channel on Pin 2 to 7 using Pin Change Interrupt and 8bit Timer2 with precaler.
Part 1
Code: [Select]

/*
Read a single RC Channel out of the receiver.
Wire Receiver Gnd (Black or Brown) to Arduino Gnd.
Wire receiver Signal (White or Yellow) to Pin 4 of ATMega328 based Arduino.
Pin 4 is set as Pin Change Interrupt PCINT20 in Pin Change Mask Register 2 PCMSK2.
No probleme if another Pin on the same port is set as Pin Change Interrupt (It should never be the case!)
Use Timer2 in Normal Mode with Overflow Interrupt to Measure RC PWM signal.
Return result PulseLength in µs (Tipicaly 1000 to 2000µs).
On Futaba System PulseLength range from 2000µs (Low) to 1000µs (High)
Return a Fail Safe Value if no signal is detected (2000µs for Futaba System).
Fail Safe Value can be changed to suits other system or requirement.
Micro Controller Clock Frequency shall be according to used MCU.
Prescaler can be changed but 64 is best fit and gives +/-4.0µs resolution.
More over with Prescaler at 64 the timer overflow occurs at 1024µs.
Counting 25 Overflows gives Fail Safe responce of 25.6ms (all frame is 20ms i.e 50Hz).
NOTE on TIMERS
Timer0 is a 08bit timer: PWM Pin05 and Pin06, timer functions, like delay(), millis() and micros() uses timer0
Timer1 is a 16bit timer: PWM Pin08 and Pin09, Servo library uses timer1 on Arduino Uno (timer5 on Arduino Mega)
Timer2 is a 08bit timer: PWM Pin03 and Pin11, the tone() function uses timer2.
So if you need tone() function to work properly:
-Avoid the use of Timer0, no good to touch delay(), millis() and micros() fonctions
-Use Timer1 but you loose Servo.Lib ability
DON'T FORGET TO SET SERIAL MONITOR AT 115200 BAUD!
*/

#include <avr/io.h>
#include <avr/interrupt.h>                          //Needed to use interrupts
#include <stdint.h>                                 //Has to be added to use uint8_t...
#include <math.h>                                   //For using log() function

//MICRO CONTROLLER CLOCK FREQUENCY
#define MCU_CLOCKFREQ         16                    // Hz

//TIMER2 PRESCALER
#define TI2_PRESCALER         64                    // 1, 8, 32, 64, 128, 256 or 1024 for Timer2

//DOES NOT WORK    with Prescaler =  32 (MISS OVERFLOW COUNT)
//+/-4.0µs @ 16MHz with Prescaler =  64 Overflow every 1024µs 25 Overflows x 1024µs = 25.6ms (bit more than 20ms (50Hz) of RC PWM frame)
//+/-8.0µs @ 16MHz with Prescaler = 128 Overflow every 2048µs 12 Overflows x  512µs = 24.6ms

//PIN INPUT FOR PIN CHANGE INTERRUPT (Caution! 0 is RXD/1 is TXD)
#define PI_CH_INT_PIN         4                     // 0, 1, 2, 3, 4, 5, 6 or 7 (Port D only for now)

//RADIO SYSTEM DEFAULT FAIL SAFE VALUE (ex Low Throttle)
#define FAIL_SAFE_VAL         2000

//Timer Bitwise Operators Calculated BitShift Left  using Log Base 2
const int ShiftL = (log (TI2_PRESCALER/MCU_CLOCKFREQ) / log (2));
//Timer Bitwise Operators Calculated BitShift Right using Log Base 2
const int ShiftR = (log (MCU_CLOCKFREQ/TI2_PRESCALER) / log (2));
//Capture Flag
volatile boolean Flag = true;
//Variables holding timestamps
volatile uint16_t InTimeStamp;
volatile uint16_t UpTimeStamp;
volatile uint16_t DnTimeStamp;
//Timer2 Overflow Counter
volatile uint8_t T2_OverFlow;
//Timer2 Overflow SnapShot
volatile uint8_t OvFSnapShot;
//PulseLength
volatile int16_t PulseLength;
//Port D Status and Record
volatile uint8_t StatusPortD;
volatile uint8_t RecordPortD;                       //= 0xFF (Set 11111111 Default is high because the pull-up: Useless?)
volatile uint8_t ChangedBits;

void setup() {
  delay(100);
  Serial.begin(115200);
  delay(100);
  Serial.println(" ");
  delay(100);
  Serial.println("Start");
  delay(100);
  Serial.print("ShiftL=");
  Serial.println(ShiftL);
  Serial.print("ShiftR=");
  Serial.println(ShiftR);
  delay(100);
  //Timer2 (8bits) Setting and Starting
  SetStartTimer2();
  //Initialization Pin Change Interrupt
  InitPinChangeInt();
  delay(500);
}

void loop() {
  //HERE IS YOUR CODE
  //When Flag is True Read Pulse Length (available)
  if (Flag == true) Serial.println(PulseLength);
}

//Timer2 (08bits) Setting and Starting
void SetStartTimer2(void) {
  //Disable global interrupts
  cli(); 
  //Clean the registers
  TCCR2A = 0;     
  //Clear Pending Interrupts
  TIFR2|=(1<<TOV2);
  //Enable overflow interrupts
  TIMSK2|=(1<<TOIE2);
  //Start timer with prescaller
  SetPrescaler(TI2_PRESCALER);
  //Enable global interrutps
  sei();
}

//Set Timer2 Prescaler
void SetPrescaler(int Prescaler) {
   //Select Case
  switch (Prescaler) {
    case 1:
      TCCR2B|=(1<<CS20);
      //Serial.println("Prescaler=1");
      break;
    case 8:
      TCCR2B|=(1<<CS21);
      //Serial.println("Prescaler=8");
      break;
    case 32:
      TCCR2B|=(1<<CS21)|(1<<CS20);
      //Serial.println("Prescaler=32");
      break;
    case 64:
      TCCR2B|=(1<<CS22);
      //Serial.println("Prescaler=64");
      break;
    case 128:
      TCCR2B|=(1<<CS22)|(1<<CS20);
      //Serial.println("Prescaler=128");
      break;
    case 256:
      TCCR2B|=(1<<CS22)|(1<<CS21);
      //Serial.println("Prescaler=256");
      break;
    case 1024:
      TCCR2B|=(1<<CS22)|(1<<CS21)|(1<<CS20);
      //Serial.println("Prescaler=1024");
      break;
    default:
      TCCR2B|=(1<<CS20);
      //Serial.println("Prescaler=1");
    break; 
  } 
}


See next
Salutations Amicales (from France)

FlyAwayFR

#5
Jul 15, 2013, 07:14 pm Last Edit: Jul 25, 2013, 10:41 pm by FlyAwayFR Reason: 1
3 Reading single RC channel on Pin 2 to 7 using Pin Change Interrupt and 8bit Timer2 with precaler.
Part 2
Code: [Select]

//Make Pin 4 an External Interrupt (Pin 04 = I/O 04 on Port D (PD4) = PCINT20 on PCMSK2)
//Same Pin 3 for discrimination test only (should never be the case)
void InitPinChangeInt(void) {
 //Disable global interrupts
 cli();
 // Clear the PD4 pin: PD4 is now an input
 DDRD&=~(1<<DDD4);
 // Turn On the Pull-up Resistor: PD4 is now an input with pull-up enabled
 PORTD|=(1<<PORTD4);
 //Enable Pin Change Interrupt on Pin 4 i.e PCINT20 in Pin Change Mask Register 2 PCMSK2
 PCMSK2|=(1<<PCINT20);
 // Clear the PD3 pin: PD3 is now an input
 /*TEMPORARY Pin Change Interrupt on Pin 3 TO CHECK DISCRIMINATION*/ DDRD&=~(1<<DDD3);
 // Turn On the Pull-up Resistor: PD3 is now an input with pull-up enabled
 /*TEMPORARY Pin Change Interrupt on Pin 3 TO CHECK DISCRIMINATION*/ PORTD|=(1<<PORTD3);
  //Enable Pin Change Interrupt on Pin 3 i.e PCINT19 in Pin Change Mask Register 2 PCMSK2
 /*TEMPORARY Pin Change Interrupt on Pin 3 TO CHECK DISCRIMINATION*/ PCMSK2|=(1<<PCINT19);
 //Enable PCIE2 in Pin Change Interrupt Control Register PCICR to enable PCMSK2 scan
 PCICR|=(1<<PCIE2);
 //Enable global interrutps
 sei();
}

//Interrupt Service Routine On Edge Change Pin 4 (Rising or Falling)
ISR (PCINT2_vect) {
 //Time Stamp Capture
 InTimeStamp = TCNT2;
 //Record Timer2 OverFlow Count
 OvFSnapShot = T2_OverFlow;
 //Status of Port D taken a.s.a.p.
 StatusPortD = PIND;
 //Check bits that changed since previous ISR
 ChangedBits = StatusPortD ^ RecordPortD;
 //Reject bits that are out of Pin Change Mask Register 2 scope
 ChangedBits &= PCMSK2;
 //Record bits status for next ISR
 RecordPortD = StatusPortD;
 //Make sure that Input4 actualy triggered the ISR and discriminate Rising/Falling Edges
 if ((ChangedBits & (1<<PI_CH_INT_PIN)) && (StatusPortD & (1<<PI_CH_INT_PIN))) {
   //Time Stamp Capture First Rising Edge
   UpTimeStamp = InTimeStamp;
   //Reset Overflow Counter for PulseLength Calculation
   T2_OverFlow=0;
   //Reset Flag
   Flag = false;
 }
 if ((ChangedBits & (1<<PI_CH_INT_PIN)) && (!(StatusPortD & (1<<PI_CH_INT_PIN)))) {
   //Time Stamp Capture First Falling Edge
   DnTimeStamp = InTimeStamp;
   //Calculate Pulse Lenght (Timer Tick)
   PulseLength = (int16_t)((int16_t)(DnTimeStamp + ((uint16_t)0x100L * OvFSnapShot)) - UpTimeStamp);
   //Calculate Pulse Lenght (µs) BitShift Multiply by...
   if (ShiftL > 0) PulseLength = PulseLength<<ShiftL;
   //Calculate Pulse Lenght (µs) BitShift Divide by...
   if (ShiftR > 0) PulseLength = PulseLength>>ShiftR;
   //Remove Unwanted Values
   PulseLength = constrain(PulseLength,750,2250);
   //Reset Overflow Counter to avoid Fail Safe
   T2_OverFlow=0;
   //Set Flag
   Flag = true;
 }
}

//Interrupt Service Routine On Timer Overflow
ISR(TIMER2_OVF_vect) {
 //Increment Overflow Counter
 T2_OverFlow++;
 if (T2_OverFlow > 25) {
   //Fail Safe Value
   PulseLength = FAIL_SAFE_VAL;
   //Set Flag
   Flag = true;
 }
}


See next
Salutations Amicales (from France)

FlyAwayFR

#6
Jul 25, 2013, 05:43 pm Last Edit: Jul 25, 2013, 10:30 pm by FlyAwayFR Reason: 1
And finaly "la cerise sur le gateau".
4 Reading 6 RC channels on Pin 2 to 7 using Pin Change Interrupt and 8bit Timer2 with precaler.
Part 1
Code: [Select]

/*
Read a multi RC Channels out of the receiver.
Wire Receiver Gnd (Black or Brown) to Arduino Gnd.
Wire receiver Signal (White or Yellow) to any PortD Pin (0 to 7) of ATMega328 based Arduino.
Becarefull if you want to use Pin 0 and 1 as they are needed for RXD(USART Input) and TXD(USART Output)
Pins are set as Pin Change Interrupt PCINTxx in Pin Change Mask Register 2 PCMSK2.
Use Timer2 in Normal Mode with Overflow Interrupt to Measure RC PWM signal.
Return result PulseLenght in µs (Tipicaly 1000 to 2000µs).
On Futaba System PulseLenght range from 2000µs (Low) to 1000µs (High)
Return a Fail Safe Value if no signal is detected (2000µs for Futaba System).
Fail Safe Value can be changed to suits other system or requirement.
Micro Controller Clock Frequency shall be according to used MCU.
Prescaler can be changed but 64 is best fit and gives +/-4.0µs resolution.
More over with Prescaler at 64 the timer overflow occurs at 1024µs.
Counting 25 Overflows gives Fail Safe responce of 25.6ms (all frame is 20ms i.e 50Hz).
NOTE on TIMERS
Timer0 is a 08bit timer: PWM Pin05 and Pin06, timer functions, like delay(), millis() and micros() uses timer0
Timer1 is a 16bit timer: PWM Pin08 and Pin09, Servo library uses timer1 on Arduino Uno (timer5 on Arduino Mega)
Timer2 is a 08bit timer: PWM Pin03 and Pin11, the tone() function uses timer2.
So if you need tone() function to work properly:
-Avoid the use of Timer0, no good to touch delay(), millis() and micros() fonctions
-Use Timer1 but you loose Servo.Lib ability
DON'T FORGET TO SET SERIAL MONITOR AT 115200 BAUD!
*/

#include <avr/io.h>
#include <avr/interrupt.h>                          //Needed to use interrupts
#include <stdint.h>                                 //Has to be added to use uint8_t...
#include <math.h>                                   //For using log() function

//MICRO CONTROLLER CLOCK FREQUENCY
#define MCU_CLOCKFREQ         16                    // Hz

//TIMER2 PRESCALER
#define TI2_PRESCALER         64                    // 1, 8, 32, 64, 128, 256 or 1024 for Timer2

//DOES NOT WORK    with Prescaler =  32 (MISS OVERFLOW COUNT)
//+/-4.0µs @ 16MHz with Prescaler =  64 Overflow every 1024µs 25 Overflows x 1024µs = 25.6ms (bit more than 20ms (50Hz) of RC PWM frame)
//+/-8.0µs @ 16MHz with Prescaler = 128 Overflow every 2048µs 12 Overflows x  512µs = 24.6ms

//Used Pins for RC Channels Input
const int ChannelPin[] = {2,3,4,5,6,7};             //{0,1,2,3,4,5,6,7} if all used (Caution! 0 is RXD/1 is TXD)
//Radio System Default Fail Safe Value (ex Low Throttle)
const int FailSafeVa[] = {2000,2000,2000,2000,2000,2000};
//Number of Channels
const int ChannelNbr = (sizeof(ChannelPin)/sizeof(int));
//Markers
volatile uint8_t PinID[8];                          //if ChannelPin[] is 2,3,4,6 Pin ID: 2=0, 3=1, 4=2, 6=3
//Timer Bitwise Operators Calculated BitShift Left  using Log Base 2
const int ShiftL = (log (TI2_PRESCALER/MCU_CLOCKFREQ) / log (2));
//Timer Bitwise Operators Calculated BitShift Right using Log Base 2
const int ShiftR = (log (MCU_CLOCKFREQ/TI2_PRESCALER) / log (2));
//Capture Flag                                      //(One Extra Needed to avoid Strange BUG)
volatile boolean Flag[ChannelNbr];
//Variables holding Interrupt TimeStamps
volatile uint8_t InTimeStamp;
volatile uint8_t UpTimeStamp[ChannelNbr];           //0 to Channel Number (One Extra Needed to avoid Strange BUG)
volatile uint8_t DnTimeStamp[ChannelNbr];           //0 to Channel Number (One Extra Needed to avoid Strange BUG)
//Timer2 Overflow Counter
volatile uint8_t T2_OverFlow[ChannelNbr];           //0 to Channel Number (One Extra Needed to avoid Strange BUG)
//Timer2 Overflow SnapShot
volatile uint8_t OverFlow_P1,OverFlow_P2;
//PulseLenght
volatile int16_t PulseLenght[ChannelNbr];           //0 to Channel Number (One Extra Needed to avoid Strange BUG)
//Port D Status a
//Port D Status and Record
volatile uint8_t StatusPortD;
volatile uint8_t RecordPortD;                       //= 0xFF (Set 11111111 Default is high because the pull-up: Useless?)
volatile uint8_t ChangedBits;
volatile uint8_t BitPosition;

void setup() {
 delay(100);
 Serial.begin(115200);
 delay(100);
 Serial.println(" ");
 delay(100);
 Serial.println("Start");
 delay(100);
 Serial.print("ShiftL=");
 Serial.println(ShiftL);
 Serial.print("ShiftR=");
 Serial.println(ShiftR);
 delay(100);
 Serial.print("ChannelNb=");
 Serial.println(ChannelNbr);
 delay(100);
 //Timer2 (8bits) Setting and Starting
 SetStartTimer2();
 //Initialization Pin Change Interrupt
 InitPinChangeInt();
 delay(100);
}

void loop() {
 //HERE IS YOUR CODE
 for (int i=0; i<ChannelNbr; i++){
   //When Flag is True Read Pulse Length (available)
   if (Flag[i] == true) {
     Serial.print(i);
     Serial.print(":");
     Serial.println(PulseLenght[i]);
   }
 }
}

//Timer2 (08bits) Setting and Starting
void SetStartTimer2(void) {
 //Disable global interrupts
 cli();
 //Clean the registers
 TCCR2A = 0;      
 //Clear Pending Interrupts
 TIFR2|=(1<<TOV2);
 //Enable overflow interrupts
 TIMSK2|=(1<<TOIE2);
 //Start timer with prescaller
 SetPrescaler(TI2_PRESCALER);
 //Enable global interrutps
 sei();
}

See next.
Salutations Amicales (from France)

FlyAwayFR

#7
Jul 25, 2013, 05:55 pm Last Edit: Jul 25, 2013, 10:29 pm by FlyAwayFR Reason: 1
And finaly "la cerise sur le gateau".
4 Reading 6 RC channels on Pin 2 to 7 using Pin Change Interrupt and 8bit Timer2 with precaler.
Part2
Code: [Select]

//Set Timer2 Prescaler
void SetPrescaler(int Prescaler) {
  //Select Case
 switch (Prescaler) {
   case 1:
     TCCR2B|=(1<<CS20);
     //Serial.println("Prescaler=1");
     break;
   case 8:
     TCCR2B|=(1<<CS21);
     //Serial.println("Prescaler=8");
     break;
   case 32:
     TCCR2B|=(1<<CS21)|(1<<CS20);
     //Serial.println("Prescaler=32");
     break;
   case 64:
     TCCR2B|=(1<<CS22);
     //Serial.println("Prescaler=64");
     break;
   case 128:
     TCCR2B|=(1<<CS22)|(1<<CS20);
     //Serial.println("Prescaler=128");
     break;
   case 256:
     TCCR2B|=(1<<CS22)|(1<<CS21);
     //Serial.println("Prescaler=256");
     break;
   case 1024:
     TCCR2B|=(1<<CS22)|(1<<CS21)|(1<<CS20);
     //Serial.println("Prescaler=1024");
     break;
   default:
     TCCR2B|=(1<<CS20);
     //Serial.println("Prescaler=1");
   break;  
 }  
}

//Make Pin External Interrupts
void InitPinChangeInt(void) {
 //Disable global interrupts
 cli();
 //Make Pin External Interrupts
 for (int i=0; i<ChannelNbr; i++){
   // Clear the pins: Pins are now inputs
   DDRD&=~(1<<ChannelPin[i]);
   // Turn On the Pull-up Resistor: Pins are now an inputs with pull-up enabled
   PORTD|=(1<<ChannelPin[i]);
   //Enable Pin Change Interrupt on Pins i.e PCINTxx in Pin Change Mask Register 2 PCMSK2
   PCMSK2|=(1<<ChannelPin[i]);
 }
 //Enable PCIE2 in Pin Change Interrupt Control Register PCICR to enable PCMSK2 scan
 PCICR|=(1<<PCIE2);
 //Enable global interrutps
 sei();
 Serial.print("PCIMask=");
 Serial.println(PCMSK2,BIN);
 //Fill PinID
 Serial.println("PinID=");
 int j=0;
 for (int i=0; i<8; i++){
   if (PCMSK2 & (1<<i)) {
     PinID[i]=j;
     Serial.println(PinID[i]);
     j++;
   }
   else {
     PinID[i]=0;
     Serial.println(PinID[i]);
   }
 }
}

//Interrupt Service Routine On Edge Change Pin x (Rising or Falling)
ISR (PCINT2_vect) {
 //Interrupt Time Stamp Capture
 InTimeStamp = TCNT2;
 //Status of Port D taken a.s.a.p.
 StatusPortD = PIND;
 //Check bits that changed since previous ISR and Reject bits out of Pin Change Mask Register 2
 ChangedBits = (StatusPortD ^ RecordPortD) & PCMSK2;
 //Count Number of Bits set in AnalyseBits (Brian Kernighan's way)
 volatile uint8_t c,p,P1,P2;
 for (c = 0; ChangedBits; c++) {
   //Get number of trailing 0 like this              1st Row Ex:00110000 => 00001111 then count set bits
   BitPosition = (ChangedBits & -ChangedBits) - 1; //2nd Row Ex:00100000 => 00011111 then count set bits
   for (p = 0; BitPosition; p++) {
     BitPosition &= BitPosition - 1;
   }
   ChangedBits &= ChangedBits - 1;
   if (c==0) {P1 = p; P2 = 0;}                     //One Edge  Received P1 is position of bit from left
   if (c==1)  P2 = p;                              //Two Edges Received P1 is position of bit from left
 }
 //Where to store 1st Edge Data
 int i = PinID[P1];
 //Record Timer2 OverFlow Count for P1
 OverFlow_P1 = T2_OverFlow[i];
 //Where to store 2nd Edge Data
 int j = PinID[P2];
 //Record Timer2 OverFlow Count for P1
 OverFlow_P2 = T2_OverFlow[j];
 //Record bits status for next ISR
 RecordPortD = StatusPortD;
 //One Edge Received then discriminate Rising/Falling Edges
 if (c>=1) {                                       //c=1 for One Edge Received
   //Time Stamp Capture First Rising Edge
   if   (StatusPortD & (1<<P1))  {
     //Time Stamp Capture
     UpTimeStamp[i] = InTimeStamp;
     //Reset Overflow Counter for PulseLenght Calculation
     T2_OverFlow[i] = 0;
     //Reset Flag
     Flag[i] = false;
   }
   //Time Stamp Capture First Falling Edge
   if (!(StatusPortD & (1<<P1))) {
     //Time Stamp Capture
     DnTimeStamp[i] = InTimeStamp;
     //Calculate Pulse Lenght (Timer Tick)
     PulseLenght[i] = (int16_t)((int16_t)(DnTimeStamp[i] + ((uint16_t)0x100L * OverFlow_P1)) - UpTimeStamp[i]);
     //Calculate Pulse Lenght (µs) BitShift Multiply by...
     if (ShiftL > 0) PulseLenght[i] = PulseLenght[i]<<ShiftL;
     //Calculate Pulse Lenght (µs) BitShift Divide by...
     if (ShiftR > 0) PulseLenght[i] = PulseLenght[i]>>ShiftR;
     //Remove Unwanted Values
     PulseLenght[i] = constrain(PulseLenght[i],750,2250);
     //Reset Overflow Counter to avoid Fail Safe
     T2_OverFlow[i] = 0;
     //Set Flag
     Flag[i] = true;
   }
 }
 //Two Edges Received Simultaneously then discriminate Rising/Falling Edges
 if (c>=2) {                                       //c=2 for One Edge Received
   //Time Stamp Capture Second Rising Edge
   if   (StatusPortD & (1<<P2))  {
     //Time Stamp Capture
     UpTimeStamp[j] = InTimeStamp;
     //Reset Overflow Counter for PulseLenght Calculation
     T2_OverFlow[j] = 0;
     //Reset Flag
     Flag[j] = false;
   }
   //Time Stamp Capture Second Falling Edge
   if (!(StatusPortD & (1<<P2))) {
     //Time Stamp Capture
     DnTimeStamp[j] = InTimeStamp;
     //Calculate Pulse Lenght (Timer Tick)
     PulseLenght[j] = (int16_t)((int16_t)(DnTimeStamp[j] + ((uint16_t)0x100L * OverFlow_P2)) - UpTimeStamp[j]);
     //Calculate Pulse Lenght (µs) BitShift Multiply by...
     if (ShiftL > 0) PulseLenght[j] = PulseLenght[j]<<ShiftL;
     //Calculate Pulse Lenght (µs) BitShift Divide by...
     if (ShiftR > 0) PulseLenght[j] = PulseLenght[j]>>ShiftR;
     //Remove Unwanted Values
     PulseLenght[j] = constrain(PulseLenght[j],750,2250);
     //Reset Overflow Counter to avoid Fail Safe
     T2_OverFlow[j] = 0;
     //Set Flag
     Flag[j] = true;
   }
 }
}

//Interrupt Service Routine On Timer Overflow
ISR(TIMER2_OVF_vect) {
 for (int i=0; i<ChannelNbr; i++){
   //Increment Overflow Counter
   T2_OverFlow[i]++;
   if (T2_OverFlow[i] > 25) {
     //Fail Safe Value
     PulseLenght[i] = FailSafeVa[i];
     //Set Flag
     Flag[i] = true;
   }
 }
}

That's all for now!
Salutations Amicales (from France)

AWOL

#8
Jul 25, 2013, 06:12 pm Last Edit: Jul 25, 2013, 06:14 pm by AWOL Reason: 1
Yup, that's why we ask you to use code tags
Regardez
"Pete, it's a fool looks for logic in the chambers of the human heart." Ulysses Everett McGill.
Do not send technical questions via personal messaging - they will be ignored.

Go Up
 

Quick Reply

With Quick-Reply you can write a post when viewing a topic without loading a new page. You can still use bulletin board code and smileys as you would in a normal post.

Warning: this topic has not been posted in for at least 120 days.
Unless you're sure you want to reply, please consider starting a new topic.

Note: this post will not display until it's been approved by a moderator.
Name:
Email:

shortcuts: alt+s submit/post or alt+p preview