Pages: [1]   Go Down
Author Topic: Read RC Channels from RC receiver from 1 to 8 channels.  (Read 2228 times)
0 Members and 1 Guest are viewing this topic.
Lyon FRANCE
Offline Offline
Newbie
*
Karma: 0
Posts: 11
You can’t put an old head on young shoulders...WHY NOT?
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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.


* Timer Calculation.pdf (45.42 KB - downloaded 45 times.)
« Last Edit: July 15, 2013, 07:10:47 pm by FlyAwayFR » Logged

Salutations Amicales (from France)

Lyon FRANCE
Offline Offline
Newbie
*
Karma: 0
Posts: 11
You can’t put an old head on young shoulders...WHY NOT?
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

1 Reading single RC channel on Pin 8 using Input Capture of 16bit Timer1 with precaler.
Code:
/*
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
« Last Edit: July 25, 2013, 03:38:46 pm by FlyAwayFR » Logged

Salutations Amicales (from France)

Lyon FRANCE
Offline Offline
Newbie
*
Karma: 0
Posts: 11
You can’t put an old head on young shoulders...WHY NOT?
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

2 Reading single RC channel on Pin 2 to 7 using Pin Change Interrupt and 16bit Timer1 with precaler.
Part 1
Code:
/*
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
« Last Edit: July 25, 2013, 03:35:08 pm by FlyAwayFR » Logged

Salutations Amicales (from France)

Lyon FRANCE
Offline Offline
Newbie
*
Karma: 0
Posts: 11
You can’t put an old head on young shoulders...WHY NOT?
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

2 Reading single RC channel on Pin 2 to 7 using Pin Change Interrupt and 16bit Timer1 with precaler.
Part 2
Code:
//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
« Last Edit: July 25, 2013, 03:40:02 pm by FlyAwayFR » Logged

Salutations Amicales (from France)

Lyon FRANCE
Offline Offline
Newbie
*
Karma: 0
Posts: 11
You can’t put an old head on young shoulders...WHY NOT?
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

3 Reading single RC channel on Pin 2 to 7 using Pin Change Interrupt and 8bit Timer2 with precaler.
Part 1
Code:
/*
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
« Last Edit: July 25, 2013, 03:33:26 pm by FlyAwayFR » Logged

Salutations Amicales (from France)

Lyon FRANCE
Offline Offline
Newbie
*
Karma: 0
Posts: 11
You can’t put an old head on young shoulders...WHY NOT?
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

3 Reading single RC channel on Pin 2 to 7 using Pin Change Interrupt and 8bit Timer2 with precaler.
Part 2
Code:
//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
« Last Edit: July 25, 2013, 03:41:58 pm by FlyAwayFR » Logged

Salutations Amicales (from France)

Lyon FRANCE
Offline Offline
Newbie
*
Karma: 0
Posts: 11
You can’t put an old head on young shoulders...WHY NOT?
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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:
/*
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.
« Last Edit: July 25, 2013, 03:30:41 pm by FlyAwayFR » Logged

Salutations Amicales (from France)

Lyon FRANCE
Offline Offline
Newbie
*
Karma: 0
Posts: 11
You can’t put an old head on young shoulders...WHY NOT?
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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:
//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!
« Last Edit: July 25, 2013, 03:29:51 pm by FlyAwayFR » Logged

Salutations Amicales (from France)

Global Moderator
UK
Offline Offline
Brattain Member
*****
Karma: 299
Posts: 26196
I don't think you connected the grounds, Dave.
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Yup, that's why we ask you to use code tags
Regardez
« Last Edit: July 25, 2013, 11:14:23 am by AWOL » Logged

"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.

Pages: [1]   Go Up
Jump to: