This post builds on previous posts to show a technique for reading multiple radio control receiver channels using an Arduino.
Update 27/03/2013 - The Arduino Leonardo and Micro use the ATMega 32u4 chip which supports interrupts on fewer pins than the ATMega 328 used in Arduino UNOs and Minis. The pins used by the sketch have been rearranged so that the code can now be run the Leonardo and Micro as well.
If you have previously used the code, please ensure that you update your connecteds by swapping the input and output pins.
Reading RC Channel Signals
Reading a single RC channel is relatively simple and can be outlined as -
1) Attach a pin change interrupt to the signal pin.
2) Create an interrupt service routine which will be called whenever the signal pin changes from high to low or low to high.
In the interrupt service routine we need to -
1) Check if the signal pin is high or low
2) If its high, this is the rising edge of the pulse, record the time using micros()
3) If its low, this is the falling edge. We are interested in the pulse duration, so if we call micros() to get the current time and then subtract from it the time we recorded in 2) for the rising edge we get the pulse duration.
The pulse duration calculated in 3) Above represents the input signal for the channel, refer to this post for a refresher - http://rcarduino.blogspot.co.uk/2012/01/how-to-read-rc-receiver-with.html
To attach the interrupt using the pin change interrupt library we call -
PCintPort::attachInterrupt(THROTTLE_IN_PIN, calcThrottle,CHANGE);
The interrupt service routine can be written as follows -
So all we need to do is cut and paste the attachInterrupt and interrupt service routine code three times to read three channels ?
No. Its not that simple, but its really not very complicated either.
There are four considerations which apply when using interrupts -
1) Volatile Shared Variables
2) Variable Access
3) Fast Interrupt Service Routines
4) Timing
1) Volatile Shared Variables
When you upload a program with the Arduino IDE, your code will be compiled into an executable program that can be run on your Arduino. As part of this process the compiler will optimise the executable code to be smaller and/or faster. If we are not careful, this optimization can have unexpected results when using interrupts.
Example optimisations -
1) Replacing variables with constants
One of the optimisations a compiler will attempt is to look through your functions for any variables that are not being updated, the compiler can replace these with constant values.
2) Using registers to hold variables within functions
Another optimisation is to assign a variable to a register within a function rather than reading it from memory repeatedly.
The compiler makes optimisation decisions based on blocks of code, not entire projects. A common optimisation problem with interrupts occurs when a variable is updated in an interrupt service routine and read in the main code. Here the compiler may look at the main code and decide 'hey this variable never gets updated so I am going to replace it with a constant value'. In this case whatever the ISR does to the shared variable, your main code will never be effected because as far as the compiler could see, there was nothing that could update the variable within loop and so the executable code that was downloaded to you Arduino was generated to refer to a constant value - see 1) Above.
A similar situation occurs when a variable is optimised to be held in a register rather than read from memory. One part of your executable code will be reading a value held in memory while another part will be updating a value held in a register. The end effect is that it appears as if your variable is not getting updated despite your ISR being called correctly and your code appearing correct - see 2) above.
Fortunately there is a keyword we can use in our code which tells the compiler 'never try and store this variable in a register, or replace it with a constant' the keyword is volatile. Any variables we wish to access from both our main code and an interrupt service routine must be declared volatile.
In our code we must declare the channel input values as volatile in order that we can update them in the ISR and read them in loop -
2) Variable Access
New Arduino programmers are often aware of the volatile keyword and assume that it is all that is needed to access shared variables. It isn't.
An interrupt service routine can be run at any point in your main code. Its called an interrupt because it interrupts your main code to do something else. This can lead to strange and difficult to trace errors if you do not control when your variables are being accessed.
To prevent this problem we need to ensure that whenever we are reading, writting or comparing a values larger than a single byte we cannot be interrupted until the operation is complete. The data types int, long and float are all multi byte data types ( int = 2 bytes, long = 4 bytes, float = 4 bytes) and therefore access must be controlled between the your main code and loop. The recommended approach to controlling access is to temporarily disable interrupts in order that your main code can perform a read, write or compare without being interrupted (remember the danger is that the interrupt will change part of the value that we have already read,written or compared in which case the results will be unpredictable and most likley lead to program errors - disabling interrupts for the read, write or compare prevents this).
To disable interrupts we call nointerrupts(), to enable then again we call interrupts().
Note - While interrupts are disabled we will not be able to read incoming signals and this can effect our accuracy. See the example code where local variables as used to minimize the time that interrupts are disabled.
3) Fast Interrupt Service Routines
When an interrupt is triggered the microprocessor disables further interrupts before calling the interrupt service routine. While we are inside our interrupt service routine, we cannot process any new incoming events, therefore it is important for the accuracy of our input signal that we keep our interrupt service routines as short as possible.
As a guideline the interrupt service routine should record the occurrence of an event, but should not attempt to perform any extended processing in response to the event.
In our example of reading three receiver channels and outputting 3 servo signals we are processing 500 interrupts per second. The difference between an idle throttle and full throttle is only 0.5 thousandths of a second - timing is going to be important.
If we want our main code to process an event, we need some mechanism for the interrupt service routine to flag that an event has occurred, one option is the use of bit flags.
Bit Flags
We can use bit flags to signal to our main code that new information is available from an ISR. Bit flags take advantage of the fact that a micro processor can read a single byte without being interrupted.
How do but flags work ?
If we treat a byte as being made up of eight bits, b0 to b7, we can use each bit as a signal between our main code and ISRs.The bits have the decimal values 1,2,4,8,16,32,64,128 which we can use to set, test and clear the individual bit flags.
to test, set and unset bit flags we can use -
Bit flags provide us with a simple mechanism to communicate new events between the ISRs and loop. This is particularly important where we need to perform an extended calculation in response to the event. In the example code, we use the bit flag to tell loop that a new signal is available from a channel, loop then uses this information to updated the output for the channel.
4) Timing
We have already covered two of the issues which will effect our accuracy, we can add library functions for a more complete list as presented below -
1) Library functions that temporarily disable interrupts - some library functions will temporarily disable interrupts we cant control this but we can be careful about where we use these functions
2) Disabling interrupts in our own code - we will need to do this, but we can take care to minimise the time that interrupts are disabled.
3) Interrupt Service Routines - the microprocessor automatically disables interrupts before calling them and reenables them afterwards. Again we have to live with this but can be careful to minimize the time spent inside the ISR so that interrupts are enabled again as quickly as possible.
Putting it all together
The following code sample can be used to read three RC Channels through one set of Arduino pins and output those same channels on another set of pins. The code uses dedicated interrupt service routines for each channel, this is for clarity and could be changed to use a single interrupt service routine for all channels.
The code demonstrates the use of -
- Bit flags to communicated events between the ISRs and Loop
- Interrupt service routines to read rc channel signals
- Enabling and disabling interrupts
- Volatile shared variables
- Taking local copies of variables to minimise the time that interrupts are disabled
- Using the servo library to output RC Channels to servos or electronic speed controllers
- Using the pin change interrupt library to access additional interrupts.
The sample code below the video was used in creating the video. A step by step guide to connecting the ESC, Steering servo, RC Receiver and Arduino will be provided in a followup post.
With two channels - throttle and steering - being read from the receiver and output to the car by Arduino
Sample Code - MultiChannels rcarduino.blogspot.com
Coming soon - A walk through connecting the three radio channels to the Arduino and then the processed signals back out to a car, robot or other application.
Duane B
Update 27/03/2013 - The Arduino Leonardo and Micro use the ATMega 32u4 chip which supports interrupts on fewer pins than the ATMega 328 used in Arduino UNOs and Minis. The pins used by the sketch have been rearranged so that the code can now be run the Leonardo and Micro as well.
If you have previously used the code, please ensure that you update your connecteds by swapping the input and output pins.
The following posts provide the necessary background to this post -
In the first post in this series we looked at the nature of an RC
Receiver signal and provided a non blocking interrupt based approach for reading
this signal with Arduino -
In the next post we looked at the Arduino servo library and how this
allows us to generate the same signals that a radio control receiver generates. We can use this library to drive upto 12 servos or electronic speed controllers -
In the final post, we looked at the pinchangeint library which provides a convenient mechanism to access over twenty interrupts with a standard Arduino UNO. This overcomes the limitation of having only two external interrupts and allows us to use a standard Arduino to read more than twenty RC Channels -
Reading RC Channel Signals
Reading a single RC channel is relatively simple and can be outlined as -
1) Attach a pin change interrupt to the signal pin.
2) Create an interrupt service routine which will be called whenever the signal pin changes from high to low or low to high.
In the interrupt service routine we need to -
1) Check if the signal pin is high or low
2) If its high, this is the rising edge of the pulse, record the time using micros()
3) If its low, this is the falling edge. We are interested in the pulse duration, so if we call micros() to get the current time and then subtract from it the time we recorded in 2) for the rising edge we get the pulse duration.
The pulse duration calculated in 3) Above represents the input signal for the channel, refer to this post for a refresher - http://rcarduino.blogspot.co.uk/2012/01/how-to-read-rc-receiver-with.html
To attach the interrupt using the pin change interrupt library we call -
PCintPort::attachInterrupt(THROTTLE_IN_PIN, calcThrottle,CHANGE);
The interrupt service routine can be written as follows -
void calcThrottle()
{
// if the pin is high, its a rising edge of the signal pulse,
{
// if the pin is high, its a rising edge of the signal pulse,
// so lets record its value
if(digitalRead(THROTTLE_IN_PIN) == HIGH)
{
ulThrottleStart = micros();
}
else
{
// else it must be a falling edge, so lets get the time and subtract the time
if(digitalRead(THROTTLE_IN_PIN) == HIGH)
{
ulThrottleStart = micros();
}
else
{
// else it must be a falling edge, so lets get the time and subtract the time
// of the rising edge this gives use the time between the rising and falling
// edges i.e. the pulse duration.
unThrottleInShared = (uint16_t)(micros() - ulThrottleStart);
}
}
unThrottleInShared = (uint16_t)(micros() - ulThrottleStart);
}
}
So all we need to do is cut and paste the attachInterrupt and interrupt service routine code three times to read three channels ?
No. Its not that simple, but its really not very complicated either.
There are four considerations which apply when using interrupts -
1) Volatile Shared Variables
2) Variable Access
3) Fast Interrupt Service Routines
4) Timing
1) Volatile Shared Variables
When you upload a program with the Arduino IDE, your code will be compiled into an executable program that can be run on your Arduino. As part of this process the compiler will optimise the executable code to be smaller and/or faster. If we are not careful, this optimization can have unexpected results when using interrupts.
Example optimisations -
1) Replacing variables with constants
One of the optimisations a compiler will attempt is to look through your functions for any variables that are not being updated, the compiler can replace these with constant values.
2) Using registers to hold variables within functions
Another optimisation is to assign a variable to a register within a function rather than reading it from memory repeatedly.
The compiler makes optimisation decisions based on blocks of code, not entire projects. A common optimisation problem with interrupts occurs when a variable is updated in an interrupt service routine and read in the main code. Here the compiler may look at the main code and decide 'hey this variable never gets updated so I am going to replace it with a constant value'. In this case whatever the ISR does to the shared variable, your main code will never be effected because as far as the compiler could see, there was nothing that could update the variable within loop and so the executable code that was downloaded to you Arduino was generated to refer to a constant value - see 1) Above.
A similar situation occurs when a variable is optimised to be held in a register rather than read from memory. One part of your executable code will be reading a value held in memory while another part will be updating a value held in a register. The end effect is that it appears as if your variable is not getting updated despite your ISR being called correctly and your code appearing correct - see 2) above.
Fortunately there is a keyword we can use in our code which tells the compiler 'never try and store this variable in a register, or replace it with a constant' the keyword is volatile. Any variables we wish to access from both our main code and an interrupt service routine must be declared volatile.
Really ?
Yes, I recently built an Audino, its an easy to build, fun to use synthesizer based on Arduino. The loop function reads five analogue inputs which control the sound. These variables are also used in a timer based interrupt service routine which actually creates the sound.
When I first built the project it didn't work. Reading through the source code I noticed that the analog inputs read in loop and used in the ISR were not declared as volatile. Adding the volatile declaration to these five variables fixed the problem.
Interestingly, the Audino has been around for years, it is likely that it worked perfectly with earlier versions of the Arduino compiler, but newer version may be optimizing differently. Whatever the case, the volatile keyword should always be used to tell the compiler not to optimize our shared variables.
In our code we must declare the channel input values as volatile in order that we can update them in the ISR and read them in loop -
volatile uint16_t unThrottleInShared;
volatile uint16_t unSteeringInShared;
volatile uint16_t unAuxInShared;
volatile uint16_t unSteeringInShared;
volatile uint16_t unAuxInShared;
2) Variable Access
New Arduino programmers are often aware of the volatile keyword and assume that it is all that is needed to access shared variables. It isn't.
An interrupt service routine can be run at any point in your main code. Its called an interrupt because it interrupts your main code to do something else. This can lead to strange and difficult to trace errors if you do not control when your variables are being accessed.
How do these strange and difficult to trace errors happen ?
The simplest explanation is that if your main code is reading a variable when it is interrupted and the interrupting service routine changes the variable value, your main code will be left with part of the old value and part of the new value which is not what you intended or your code expected. This is often experienced as glitches blamed on hardware, but is in fact an easily solved software problem.
As a simple example a two byte integer changing from 256 to 255 in an interrupt service routine could be seen in loop as 511.
Byte 1 Byte 2
Original value = 256 = 00000001 00000000
Updated value = 255 = 00000000 11111111
Loop reads byte 1 = 00000001
The ISR is called and updates the 2 byte integer from 256 to 255
Loop reads byte 2 = 11111111
Loop sees byte 1 + byte 2 as integer 0000000111111111 = 511
This unexpected value is a result of reading byte 1 before the interrupt and byte 2 after the interrupt. We must always ensure that our multi byte shared variables are read, updated or compared without any interrupts occuring.
As a simple example a two byte integer changing from 256 to 255 in an interrupt service routine could be seen in loop as 511.
Byte 1 Byte 2
Original value = 256 = 00000001 00000000
Updated value = 255 = 00000000 11111111
Loop reads byte 1 = 00000001
The ISR is called and updates the 2 byte integer from 256 to 255
Loop reads byte 2 = 11111111
Loop sees byte 1 + byte 2 as integer 0000000111111111 = 511
This unexpected value is a result of reading byte 1 before the interrupt and byte 2 after the interrupt. We must always ensure that our multi byte shared variables are read, updated or compared without any interrupts occuring.
To prevent this problem we need to ensure that whenever we are reading, writting or comparing a values larger than a single byte we cannot be interrupted until the operation is complete. The data types int, long and float are all multi byte data types ( int = 2 bytes, long = 4 bytes, float = 4 bytes) and therefore access must be controlled between the your main code and loop. The recommended approach to controlling access is to temporarily disable interrupts in order that your main code can perform a read, write or compare without being interrupted (remember the danger is that the interrupt will change part of the value that we have already read,written or compared in which case the results will be unpredictable and most likley lead to program errors - disabling interrupts for the read, write or compare prevents this).
To disable interrupts we call nointerrupts(), to enable then again we call interrupts().
// turn interrupts off
noInterrupts();
// Access your shared variables here ...
// finished with the shared variables, so turn interrupts back on
interrupts();
Note - While interrupts are disabled we will not be able to read incoming signals and this can effect our accuracy. See the example code where local variables as used to minimize the time that interrupts are disabled.
Using the status register directly vs interrupts() and noInterrupts()
Note - There is some criticism of noInterrupts and interrupts on the basis that the interrupts function reenables interrupts regardless of whether they were enabled or disabled before noInterrupts was called. This could cause problems where your code is called by a library which requires interrupts to be disabled. The preferred technique is to record the global status register, update the status register to disable interrupts, then restore it to its previous value - this maintains the status that was inplace before your code rather than blindly (and unsafely) restoring interrupts.
Note - There is some criticism of noInterrupts and interrupts on the basis that the interrupts function reenables interrupts regardless of whether they were enabled or disabled before noInterrupts was called. This could cause problems where your code is called by a library which requires interrupts to be disabled. The preferred technique is to record the global status register, update the status register to disable interrupts, then restore it to its previous value - this maintains the status that was inplace before your code rather than blindly (and unsafely) restoring interrupts.
byte sregRestore = SREG;
cli() // clear the global interrupt enable flag
// Access your shared variables here ....
SREG = sregRestore; // restore the status register to its previous value
3) Fast Interrupt Service Routines
When an interrupt is triggered the microprocessor disables further interrupts before calling the interrupt service routine. While we are inside our interrupt service routine, we cannot process any new incoming events, therefore it is important for the accuracy of our input signal that we keep our interrupt service routines as short as possible.
As a guideline the interrupt service routine should record the occurrence of an event, but should not attempt to perform any extended processing in response to the event.
In our example of reading three receiver channels and outputting 3 servo signals we are processing 500 interrupts per second. The difference between an idle throttle and full throttle is only 0.5 thousandths of a second - timing is going to be important.
If we want our main code to process an event, we need some mechanism for the interrupt service routine to flag that an event has occurred, one option is the use of bit flags.
Bit Flags
We can use bit flags to signal to our main code that new information is available from an ISR. Bit flags take advantage of the fact that a micro processor can read a single byte without being interrupted.
How do but flags work ?
If we treat a byte as being made up of eight bits, b0 to b7, we can use each bit as a signal between our main code and ISRs.The bits have the decimal values 1,2,4,8,16,32,64,128 which we can use to set, test and clear the individual bit flags.
// These bit flags are set in bUpdateFlagsShared to indicate which
// channels have new signals
#define THROTTLE_FLAG 1
#define STEERING_FLAG 2
#define AUX_FLAG 4
// a single byte we can use to hold the update bit flags defined above
volatile uint8_t bUpdateFlagsShared;
// channels have new signals
#define THROTTLE_FLAG 1
#define STEERING_FLAG 2
#define AUX_FLAG 4
// a single byte we can use to hold the update bit flags defined above
volatile uint8_t bUpdateFlagsShared;
to test, set and unset bit flags we can use -
// test to see if a flag has been set - use the bitwise AND operator &
if(bUpdateFlags & THROTTLE_FLAG)
{
// flag was set ...
// flag was set ...
}
// set a bit flag - use the bitwise OR operator |
// set the throttle flag to indicate that a new throttle signal has been received
bUpdateFlagsShared |= THROTTLE_FLAG;
bUpdateFlagsShared |= THROTTLE_FLAG;
// clear a bit flag - use the bitwise XOR operator ^
bUpdateFlagsShared ^= THROTTLE_FLAG;
Bit flags provide us with a simple mechanism to communicate new events between the ISRs and loop. This is particularly important where we need to perform an extended calculation in response to the event. In the example code, we use the bit flag to tell loop that a new signal is available from a channel, loop then uses this information to updated the output for the channel.
4) Timing
We have already covered two of the issues which will effect our accuracy, we can add library functions for a more complete list as presented below -
1) Library functions that temporarily disable interrupts - some library functions will temporarily disable interrupts we cant control this but we can be careful about where we use these functions
2) Disabling interrupts in our own code - we will need to do this, but we can take care to minimise the time that interrupts are disabled.
3) Interrupt Service Routines - the microprocessor automatically disables interrupts before calling them and reenables them afterwards. Again we have to live with this but can be careful to minimize the time spent inside the ISR so that interrupts are enabled again as quickly as possible.
Putting it all together
The following code sample can be used to read three RC Channels through one set of Arduino pins and output those same channels on another set of pins. The code uses dedicated interrupt service routines for each channel, this is for clarity and could be changed to use a single interrupt service routine for all channels.
The code demonstrates the use of -
- Bit flags to communicated events between the ISRs and Loop
- Interrupt service routines to read rc channel signals
- Enabling and disabling interrupts
- Volatile shared variables
- Taking local copies of variables to minimise the time that interrupts are disabled
- Using the servo library to output RC Channels to servos or electronic speed controllers
- Using the pin change interrupt library to access additional interrupts.
The sample code below the video was used in creating the video. A step by step guide to connecting the ESC, Steering servo, RC Receiver and Arduino will be provided in a followup post.
With two channels - throttle and steering - being read from the receiver and output to the car by Arduino
Sample Code - MultiChannels rcarduino.blogspot.com
// MultiChannels
//
// rcarduino.blogspot.com
//
// A simple approach for reading three RC Channels using pin change interrupts
//
// See related posts -
// http://rcarduino.blogspot.co.uk/2012/01/how-to-read-rc-receiver-with.html
// http://rcarduino.blogspot.co.uk/2012/03/need-more-interrupts-to-read-more.html
// http://rcarduino.blogspot.co.uk/2012/01/can-i-control-more-than-x-servos-with.html
//
// rcarduino.blogspot.com
//
// include the pinchangeint library - see the links in the related topics section above for details
#include <PinChangeInt.h>
#include <Servo.h>
// Assign your channel in pins
#define THROTTLE_IN_PIN 8
#define STEERING_IN_PIN 9
#define AUX_IN_PIN 10
// Assign your channel out pins
#define THROTTLE_OUT_PIN 5
#define STEERING_OUT_PIN 6
#define AUX_OUT_PIN 7
// Servo objects generate the signals expected by Electronic Speed Controllers and Servos
// We will use the objects to output the signals we read in
// this example code provides a straight pass through of the signal with no custom processing
Servo servoThrottle;
Servo servoSteering;
Servo servoAux;
// These bit flags are set in bUpdateFlagsShared to indicate which
// channels have new signals
#define THROTTLE_FLAG 1
#define STEERING_FLAG 2
#define AUX_FLAG 4
// holds the update flags defined above
volatile uint8_t bUpdateFlagsShared;
// shared variables are updated by the ISR and read by loop.
// In loop we immediatley take local copies so that the ISR can keep ownership of the
// shared ones. To access these in loop
// we first turn interrupts off with noInterrupts
// we take a copy to use in loop and the turn interrupts back on
// as quickly as possible, this ensures that we are always able to receive new signals
volatile uint16_t unThrottleInShared;
volatile uint16_t unSteeringInShared;
volatile uint16_t unAuxInShared;
// These are used to record the rising edge of a pulse in the calcInput functions
// They do not need to be volatile as they are only used in the ISR. If we wanted
// to refer to these in loop and the ISR then they would need to be declared volatile
uint32_t ulThrottleStart;
uint32_t ulSteeringStart;
uint32_t ulAuxStart;
void setup()
{
Serial.begin(9600);
Serial.println("multiChannels");
// attach servo objects, these will generate the correct
// pulses for driving Electronic speed controllers, servos or other devices
// designed to interface directly with RC Receivers
servoThrottle.attach(THROTTLE_OUT_PIN);
servoSteering.attach(STEERING_OUT_PIN);
servoAux.attach(AUX_OUT_PIN);
// using the PinChangeInt library, attach the interrupts
// used to read the channels
PCintPort::attachInterrupt(THROTTLE_IN_PIN, calcThrottle,CHANGE);
PCintPort::attachInterrupt(STEERING_IN_PIN, calcSteering,CHANGE);
PCintPort::attachInterrupt(AUX_IN_PIN, calcAux,CHANGE);
}
void loop()
{
// create local variables to hold a local copies of the channel inputs
// these are declared static so that thier values will be retained
// between calls to loop.
static uint16_t unThrottleIn;
static uint16_t unSteeringIn;
static uint16_t unAuxIn;
// local copy of update flags
static uint8_t bUpdateFlags;
// check shared update flags to see if any channels have a new signal
if(bUpdateFlagsShared)
{
noInterrupts(); // turn interrupts off quickly while we take local copies of the shared variables
// take a local copy of which channels were updated in case we need to use this in the rest of loop
bUpdateFlags = bUpdateFlagsShared;
// in the current code, the shared values are always populated
// so we could copy them without testing the flags
// however in the future this could change, so lets
// only copy when the flags tell us we can.
if(bUpdateFlags & THROTTLE_FLAG)
{
unThrottleIn = unThrottleInShared;
}
if(bUpdateFlags & STEERING_FLAG)
{
unSteeringIn = unSteeringInShared;
}
if(bUpdateFlags & AUX_FLAG)
{
unAuxIn = unAuxInShared;
}
// clear shared copy of updated flags as we have already taken the updates
// we still have a local copy if we need to use it in bUpdateFlags
bUpdateFlagsShared = 0;
interrupts(); // we have local copies of the inputs, so now we can turn interrupts back on
// as soon as interrupts are back on, we can no longer use the shared copies, the interrupt
// service routines own these and could update them at any time. During the update, the
// shared copies may contain junk. Luckily we have our local copies to work with :-)
}
// do any processing from here onwards
// only use the local values unAuxIn, unThrottleIn and unSteeringIn, the shared
// variables unAuxInShared, unThrottleInShared, unSteeringInShared are always owned by
// the interrupt routines and should not be used in loop
// the following code provides simple pass through
// this is a good initial test, the Arduino will pass through
// receiver input as if the Arduino is not there.
// This should be used to confirm the circuit and power
// before attempting any custom processing in a project.
// we are checking to see if the channel value has changed, this is indicated
// by the flags. For the simple pass through we don't really need this check,
// but for a more complex project where a new signal requires significant processing
// this allows us to only calculate new values when we have new inputs, rather than
// on every cycle.
if(bUpdateFlags & THROTTLE_FLAG)
{
if(servoThrottle.readMicroseconds() != unThrottleIn)
{
servoThrottle.writeMicroseconds(unThrottleIn);
}
}
if(bUpdateFlags & STEERING_FLAG)
{
if(servoSteering.readMicroseconds() != unSteeringIn)
{
servoSteering.writeMicroseconds(unSteeringIn);
}
}
if(bUpdateFlags & AUX_FLAG)
{
if(servoAux.readMicroseconds() != unAuxIn)
{
servoAux.writeMicroseconds(unAuxIn);
}
}
bUpdateFlags = 0;
}
// simple interrupt service routine
void calcThrottle()
{
// if the pin is high, its a rising edge of the signal pulse, so lets record its value
if(digitalRead(THROTTLE_IN_PIN) == HIGH)
{
ulThrottleStart = micros();
}
else
{
// else it must be a falling edge, so lets get the time and subtract the time of the rising edge
// this gives use the time between the rising and falling edges i.e. the pulse duration.
unThrottleInShared = (uint16_t)(micros() - ulThrottleStart);
// use set the throttle flag to indicate that a new throttle signal has been received
bUpdateFlagsShared |= THROTTLE_FLAG;
}
}
void calcSteering()
{
if(digitalRead(STEERING_IN_PIN) == HIGH)
{
ulSteeringStart = micros();
}
else
{
unSteeringInShared = (uint16_t)(micros() - ulSteeringStart);
bUpdateFlagsShared |= STEERING_FLAG;
}
}
void calcAux()
{
if(digitalRead(AUX_IN_PIN) == HIGH)
{
ulAuxStart = micros();
}
else
{
unAuxInShared = (uint16_t)(micros() - ulAuxStart);
bUpdateFlagsShared |= AUX_FLAG;
}
}
//
// rcarduino.blogspot.com
//
// A simple approach for reading three RC Channels using pin change interrupts
//
// See related posts -
// http://rcarduino.blogspot.co.uk/2012/01/how-to-read-rc-receiver-with.html
// http://rcarduino.blogspot.co.uk/2012/03/need-more-interrupts-to-read-more.html
// http://rcarduino.blogspot.co.uk/2012/01/can-i-control-more-than-x-servos-with.html
//
// rcarduino.blogspot.com
//
// include the pinchangeint library - see the links in the related topics section above for details
#include <PinChangeInt.h>
#include <Servo.h>
// Assign your channel in pins
#define THROTTLE_IN_PIN 8
#define STEERING_IN_PIN 9
#define AUX_IN_PIN 10
// Assign your channel out pins
#define THROTTLE_OUT_PIN 5
#define STEERING_OUT_PIN 6
#define AUX_OUT_PIN 7
// Servo objects generate the signals expected by Electronic Speed Controllers and Servos
// We will use the objects to output the signals we read in
// this example code provides a straight pass through of the signal with no custom processing
Servo servoThrottle;
Servo servoSteering;
Servo servoAux;
// These bit flags are set in bUpdateFlagsShared to indicate which
// channels have new signals
#define THROTTLE_FLAG 1
#define STEERING_FLAG 2
#define AUX_FLAG 4
// holds the update flags defined above
volatile uint8_t bUpdateFlagsShared;
// shared variables are updated by the ISR and read by loop.
// In loop we immediatley take local copies so that the ISR can keep ownership of the
// shared ones. To access these in loop
// we first turn interrupts off with noInterrupts
// we take a copy to use in loop and the turn interrupts back on
// as quickly as possible, this ensures that we are always able to receive new signals
volatile uint16_t unThrottleInShared;
volatile uint16_t unSteeringInShared;
volatile uint16_t unAuxInShared;
// These are used to record the rising edge of a pulse in the calcInput functions
// They do not need to be volatile as they are only used in the ISR. If we wanted
// to refer to these in loop and the ISR then they would need to be declared volatile
uint32_t ulThrottleStart;
uint32_t ulSteeringStart;
uint32_t ulAuxStart;
void setup()
{
Serial.begin(9600);
Serial.println("multiChannels");
// attach servo objects, these will generate the correct
// pulses for driving Electronic speed controllers, servos or other devices
// designed to interface directly with RC Receivers
servoThrottle.attach(THROTTLE_OUT_PIN);
servoSteering.attach(STEERING_OUT_PIN);
servoAux.attach(AUX_OUT_PIN);
// using the PinChangeInt library, attach the interrupts
// used to read the channels
PCintPort::attachInterrupt(THROTTLE_IN_PIN, calcThrottle,CHANGE);
PCintPort::attachInterrupt(STEERING_IN_PIN, calcSteering,CHANGE);
PCintPort::attachInterrupt(AUX_IN_PIN, calcAux,CHANGE);
}
void loop()
{
// create local variables to hold a local copies of the channel inputs
// these are declared static so that thier values will be retained
// between calls to loop.
static uint16_t unThrottleIn;
static uint16_t unSteeringIn;
static uint16_t unAuxIn;
// local copy of update flags
static uint8_t bUpdateFlags;
// check shared update flags to see if any channels have a new signal
if(bUpdateFlagsShared)
{
noInterrupts(); // turn interrupts off quickly while we take local copies of the shared variables
// take a local copy of which channels were updated in case we need to use this in the rest of loop
bUpdateFlags = bUpdateFlagsShared;
// in the current code, the shared values are always populated
// so we could copy them without testing the flags
// however in the future this could change, so lets
// only copy when the flags tell us we can.
if(bUpdateFlags & THROTTLE_FLAG)
{
unThrottleIn = unThrottleInShared;
}
if(bUpdateFlags & STEERING_FLAG)
{
unSteeringIn = unSteeringInShared;
}
if(bUpdateFlags & AUX_FLAG)
{
unAuxIn = unAuxInShared;
}
// clear shared copy of updated flags as we have already taken the updates
// we still have a local copy if we need to use it in bUpdateFlags
bUpdateFlagsShared = 0;
interrupts(); // we have local copies of the inputs, so now we can turn interrupts back on
// as soon as interrupts are back on, we can no longer use the shared copies, the interrupt
// service routines own these and could update them at any time. During the update, the
// shared copies may contain junk. Luckily we have our local copies to work with :-)
}
// do any processing from here onwards
// only use the local values unAuxIn, unThrottleIn and unSteeringIn, the shared
// variables unAuxInShared, unThrottleInShared, unSteeringInShared are always owned by
// the interrupt routines and should not be used in loop
// the following code provides simple pass through
// this is a good initial test, the Arduino will pass through
// receiver input as if the Arduino is not there.
// This should be used to confirm the circuit and power
// before attempting any custom processing in a project.
// we are checking to see if the channel value has changed, this is indicated
// by the flags. For the simple pass through we don't really need this check,
// but for a more complex project where a new signal requires significant processing
// this allows us to only calculate new values when we have new inputs, rather than
// on every cycle.
if(bUpdateFlags & THROTTLE_FLAG)
{
if(servoThrottle.readMicroseconds() != unThrottleIn)
{
servoThrottle.writeMicroseconds(unThrottleIn);
}
}
if(bUpdateFlags & STEERING_FLAG)
{
if(servoSteering.readMicroseconds() != unSteeringIn)
{
servoSteering.writeMicroseconds(unSteeringIn);
}
}
if(bUpdateFlags & AUX_FLAG)
{
if(servoAux.readMicroseconds() != unAuxIn)
{
servoAux.writeMicroseconds(unAuxIn);
}
}
bUpdateFlags = 0;
}
// simple interrupt service routine
void calcThrottle()
{
// if the pin is high, its a rising edge of the signal pulse, so lets record its value
if(digitalRead(THROTTLE_IN_PIN) == HIGH)
{
ulThrottleStart = micros();
}
else
{
// else it must be a falling edge, so lets get the time and subtract the time of the rising edge
// this gives use the time between the rising and falling edges i.e. the pulse duration.
unThrottleInShared = (uint16_t)(micros() - ulThrottleStart);
// use set the throttle flag to indicate that a new throttle signal has been received
bUpdateFlagsShared |= THROTTLE_FLAG;
}
}
void calcSteering()
{
if(digitalRead(STEERING_IN_PIN) == HIGH)
{
ulSteeringStart = micros();
}
else
{
unSteeringInShared = (uint16_t)(micros() - ulSteeringStart);
bUpdateFlagsShared |= STEERING_FLAG;
}
}
void calcAux()
{
if(digitalRead(AUX_IN_PIN) == HIGH)
{
ulAuxStart = micros();
}
else
{
unAuxInShared = (uint16_t)(micros() - ulAuxStart);
bUpdateFlagsShared |= AUX_FLAG;
}
}
Coming soon - A walk through connecting the three radio channels to the Arduino and then the processed signals back out to a car, robot or other application.
Duane B
Hi, I really like your code and I think you did a great job and it works perfectly. However, I have been reading through your code for the last few hours and I am having trouble figuring out how I could complete this task:
ReplyDeleteI have an 6DOF IMU that is connected to my arduino and is outputting euler angles. I would like to be able to use the unAuxIn value to switch from manually controlling the servos with my transmitter to using the angles from the IMU to control the position of the servo. In essence all I think I might need would be an if statements that would say something like "if unAuxIn > 1800" but I don't know where this would go and I don't know if there is a better way to do this. I know this is a bit of a big question but I have been following you posts over the past few months and I think you are doing a great job. If you want to find out a bit more about the IMU I am using so that you can better understand my question, you can go to this website:
http://bildr.org/2012/03/stable-orientation-digital-imu-6dof-arduino/
Hello everyone feel free to reach out to Mr barry on Barrysilbert540 @ gmail. com if you need help or guidance on how profitable trades is been done. And also if you want to recover your lost funds using his masterclass strategy he can help you. I'm earning $10,250 weekly using his masterclass.. you can also reach him out on whatsapp + 4 4 7 5 0 8 2 9 8 6 9 1.c
DeleteRcarduino: How To Read Multiple Rc Channels >>>>> Download Now
Delete>>>>> Download Full
Rcarduino: How To Read Multiple Rc Channels >>>>> Download LINK
>>>>> Download Now
Rcarduino: How To Read Multiple Rc Channels >>>>> Download Full
>>>>> Download LINK M4
DeleteReally Work Fast,******************
Fast and reliable solution for Herpes Cure
I was cured from Herpes with herbal med..
Contact him for Relationship/marital problem,
He will give you the best..
Thanks to [[[robinsonbucler @ gmail com]]]
Hi,
ReplyDeleteThanks for the feedback.
Your idea to test for AuxIn > 1800 is correct. I would add a new variable to track the current mode. I would set the mode to AUTO_PILOT if AuxIn > 1800 and to MANUAL_PILOT if AuxIn < 1200 - I assume that your auxiliary channel is a two position swicth like mine. You will then need to a new if condition which will only update the throttle and steering channel if mode = MANUAL_PILOT. As for what you do with the IMU, thats up to you.
Duane B.
Hello,
ReplyDeleteI'm a beginner and I'm trying your code...
I'm surpised because the AUX channel has a very slow response time and I don't no why, do you think this is normal ?
Sev
Hi,
ReplyDeleteAs a quick test, try swapping the connection of your Aux channel with one of the other channels, for example, connect your throttle channel to aux and aux to the throttle, is the response of aux still slow ? This will tell whether it is a transmitter setting or the problem is in the code.
Duane B
Hello,
ReplyDeleteI allready tested to move PIN attribution from :
// Assign your channel in pins
#define THROTTLE_IN_PIN 5
#define STEERING_IN_PIN 6
#define AUX_IN_PIN 7
to :
#define THROTTLE_IN_PIN 4
#define STEERING_IN_PIN 5
#define AUX_IN_PIN 6
My receiver has been tested without Arduino, connected to 3 servo.
I allready try to plug "Aux Servo" and "Steering Servo" one on each other.
And this is allways the
That's why I'm seriously thinking about a code specification on Aux Channel. (AUX_OUT_PIN 10)
Maybe I'll try to put Steering-out on 10 and Aux-out on 9...
Hi,
ReplyDeleteCan you clarify that it is always the AUX_OUT_PIN which is slow whatever is connected to it and can you be more specific about the slow response
Thanks
I've tested changing output pin (9/10), supressing conditions flags... and this always happening with Aux Channel, but finaly I found something strange :
ReplyDeleteThis is mostly happening when Throttle Channel is at the middle point...
Maybe the "GoingUp Aux signal" is "masked" by the "GoingDown Throttle signal", is this possible ?
Hi,
ReplyDeleteone thing I wanted to eliminate is 'channel mixing' where your radio is set up to adjust the output of one channel based on another channel. What radio and receiver are you using and what do you have connected to the aux channel ?
also, what type of vehicle ?
Thanks
Duane
One more thing, have you tried using the original code as it is posted here, but with the throttle input and output physically swapped with the aux input and output. If the code is causing the response this should now effect the throttle not aux.
ReplyDeleteDuane b
I'm using Turnigy9CH-Tx with Eurgle3ch-Rx, for the moment, it is just for testing, so I just plug 2 servos (Steer&Aux) and 1 little 8A/ESC with type400 motor, (the ESC/BEC feed the servos 5V).
ReplyDeleteAs you said, Next test will be to swap PIN again (Thro & Aux, IN and OUT), if this is a masking effect, I should be fixed with this test (I'll try to post it on YouTube)
Hi, is it this radio, if so the link suggests that version 1 has problems
ReplyDeletehttp://www.hobbyking.com/hobbyking/store/__8992__Turnigy_9X_9Ch_Transmitter_w_Module_8ch_Receiver_Mode_2_v2_Firmware_.html
Duane B
Link to manual - http://www.filadelfosrc.com/Manuals/TX-9X-M2-Manual.pdf
ReplyDeleteHi,
ReplyDeleteFrom the serial output you posted, it looks as if everything is working fine as far as the Arduino is concerned, the AUX Channel shows a steady input of 1468.
I notice that you are using a 9 channel transmitter with a 3 channel receiver, my guess is that the receiver processes the first 3 pulses it receives and ignores any further pulses until the next frame begins. It is possible that what is happening is that your transmitter AUX signal is not within the first three pulses so the transmitter is receiving throttle, steering, and then some other channel which is not Aux. The Aux pulse is later in the frame and being ignored by the receiver.
Try your test again but using the full range of controls/channels available on the transmitter, my guess is that one of them should cause the full range of movement in AUX.
To keep the serial output short and clear, you can stop outputting columns 1 and 3 we are only interested in the channel value.
Duane B.
Thank for your responses, My Rx/Tx system is fully fonctionnal AND programmable, I'm using V2 radio with custom firmware (er9x), so the original manual is not usefull for me.
ReplyDeleteI'm using this system with planes, AR Drone (with MiruMod Arduino), and lots of little things I builded.
Before this test, I've programmed my radio to send what I want on the first 3 Channels, the Rx is only reading 3 first Channels.
When I connect everything directly on the Tx, everything is working fine, even the 3rd channel with a servo.
On my serialOutput, please take a look at the numbers I just colored in red, I think this is the problem.
The strange thing is that ulAuxStart is no more frequently updated BUT unAuxInShared continue to be (how is it possible ?)... if Arduino really do not see AuxGoingUp signal, unAuxInShared should be unrealist (too big) !!
The servo lagging problem is occuring precisely at this moment.
Hi,
ReplyDeleteCan you post your loop function or the ISR, whichever it is that you are generating the serial from
Thanks
Solved ! I hope.
ReplyDeleteI have just breadboarded a quick set up here and I am saddened to say I had exactly the same problem. Sometimes the Aux would respond instantly, other times it would be two or more seconds before Aux would respond.
My test set up was Aux into the Arduino and out to a servo.
As a second test, I connected the steering out of my receiver into the Aux in of my Arduino, the response was again lagging.
Cut a long story short, I was missing pulses due to a bad connection. Looking at your trace it looks as if you are also missing pulses.
To get my original connection to work I have to hold it into the receiver with hand pressure, like this there is no lag. If I take my hand off, the connection is not so good and I can expect to miss pulses resulting in what looks like lag.
I hope this is the solution for you also, let me know.
Duane
You were totally right !!
ReplyDeleteMy receiver do not have a real 3rd channel, I've done a test with the original 8ch Rx and everything is okay...
I now understand why I crashed a fast plane with this kind of receiver...
Thank you for all your help.
Sev
i have a small question i am trying to control 2 DC motors using RC transmitter and receiver adn i want the motors to go forward backward and stop without controlling the speed or anything (will move with digital signal high or low only) and i dont know how to read the signal from the receiver and transform it to digital signal to drive the DC motors?
ReplyDeleteTry this -
ReplyDeletehttp://rcarduino.blogspot.com/2012/05/interfacing-rc-channels-to-l293d-motor.html
Duane B
Hi,
ReplyDeleteI've worked with your code on using a single RC signal and one interrupt and it worked fine for me. (http://rcarduino.blogspot.co.nz/2012/01/how-to-read-rc-receiver-with.html) But now that I'm reading this code on multiple inputs, I've tried to copy the code and try it straight away before playing around with it, but it comes up with an error saying " 'PCintPort' has not been declared " I've gone through and installed the library again for PinChangeInt but that hasn't changed it. This code is all new to me, though I've played with arduino stuff as a hobby for a little while. Any ideas on what I'm doing wrong?
The code that I'm using is an exact copy of the big sample code above.
Cheers,
Ash
Hi,
ReplyDeleteIt should be something simple, what version of the library are you using and where did you get it from ? Also what version of Arduino are you using ?
Duane B
I'm using an arduino Uno R3, bought about 2 months ago, and the version of PinChangeInt is 1.72 from this source (http://code.google.com/p/arduino-pinchangeint/downloads/detail?name=pinchangeint-v1.72.zip) and the version of arduino software is 1.0.1 for windows.
ReplyDeleteWhen I click on 'sketch' up the top and then 'import library' it now shows me that I now have the option to import the PinChangeInt library, but I've just realised that when I click it doesn't write the '#include' statement in the sketch. Could this just be an install error?
Ash
Hi Duane,
ReplyDeleteYour code works perfectly, it was my fault for not installing the library properly, I accidently put the PinChangeInt library an extra file deep when i unzipped it.
Thanks for your suggestions, they made me think a bit simpler.
Ash
Hey Duane, I tried out the concept behind your code for controlling four servos. The code can be found here:https://docs.google.com/open?id=0BzJW8_XBaNMRUm9FX3JZRElVRGc
ReplyDeleteWhat I am trying to do is to control 4 servos using two channels and one channel for controlling the motor. My underlying aim is to control the servos such that if I deflect the analog stick of the transmitter in one direction, it will rotate one servo a full 180 degrees and if i deflect it in the opposite direction then i will get another 180 deflection is another servo. This is because the transmitter isnt able to provide the throw that i need.
The problem that i am facing, is that two servos can be controlled very easily, but the third servo is moving eratically.
Please check if there is a blunder in the code or if you have a better solution to the task that i mentioned.
Thanks
Summit
Hi,
ReplyDeleteI can see a lot wrong with your code, I do not have time to go through line by line and so your best bet might be to re-read this post, and use the code provided as the base for your own project.
Sorry,
Duane B
Hey
DeleteThanks for the reply. I re read the post many times but the code that i have written is for Arduino Mega not for Uno and hence i could use the available interrupts on it rather than using the software interrupts that are generated using the PinChange library.
If you could just give me the gist of what is wrong with the code (even a very light gist) or how to proceed in correcting it then it would be great.
Thanks again
Summit
Hi,
ReplyDeleteThe code I have provided in this post has been used by many people on both UNO's and Mega's. To use the code on a Mega replace PCintPort::attachInterrupt with attachInterrupt and make sure that the pin you use is one of the Mega interrupt pins.
I have provided this code for people to use, lots do and all of the feedback indicates that it works very well.
My suggestion for you is to replace the PCintPort::attachInterrupt with attachInterrupt, use the code as it is provided here to control your project in the normal way, then once you have it working make whatever project specific changes you need.
Duane
Hi,
ReplyDeleteI am trying to use the example you provided with an Arduino Mega Pro, and it doesn't want to read any of the interrupts. I have the signals from my radio receiver in ports 2, 3, and 18 which according to this: http://arduino.cc/it/Reference/AttachInterrupt
are all interrupt ports. When I measure the voltages from the signal cables I get values from ~.2 to ~.4 volts depending on what the remote is doing (changing throttle/steering). Are these just too low for my arduino to read?
Hi,
ReplyDeleteRead part one here to understand why you are seeing those low values and use the code provided in part one to test your basic connectivity.
http://rcarduino.blogspot.com/2012/01/how-to-read-rc-receiver-with.html
Duane B
Hi,
ReplyDeleteThe example from part one works fine. I noticed that the example for three inputs doesn't have both the interrupt pin id and the digital id. So I changed the attatchInterrupts to use the interrupt id (0, 1, and 5 for me) and now calcAux gets called one time, and then the program stops looping.
Should I just be using the interrupt port id and not the actual digital port id?
Hi,
ReplyDeleteIf you are using a Mega, you have enough interrupts available so you do not need to use the PinChangeInt library. To use the interrupts directly replace PCintPort::attachInterrupt with just attachInterrupt in the setup function. You should not be able to use the interrupts. The attachInterrupt function accepts the digital pin number rather than the interrupt number. On my UNO i need to pass 2 as the pin number in order to attach to int0 which is digital pin 2.
Duane B
I'm using the digital pin numbers, but no matter what is input, the output remains .38 volts. Also none of my Serial.prints are actually being printed which makes me think something is hanging up somewhere. Any more ideas?
ReplyDeleteIts probably something simple, post your code and forget about the volts. Servo signals are mostly empty space as explained in part one - your multi meter can't tell you this so it comes up with .38 volts - 5 volts for 10% of the time = more or less .5 volts.
ReplyDeleteDuane.
I can't post it all. It is the example code with the ports changed to 3, 2, and 18 and the PCintPort::s removed.
ReplyDelete// include the pinchangeint library - see the links in the related topics section above for details
//#include
#include
// Assign your channel in pins
#define THROTTLE_IN_PIN 3
#define STEERING_IN_PIN 2
#define AUX_IN_PIN 18
// Assign your channel out pins
#define THROTTLE_OUT_PIN 8
#define STEERING_OUT_PIN 9
#define AUX_OUT_PIN 10
void setup()
{
Serial.begin(9600);
Serial.println("multiChannels");
// attach servo objects, these will generate the correct
// pulses for driving Electronic speed controllers, servos or other devices
// designed to interface directly with RC Receivers
servoThrottle.attach(THROTTLE_OUT_PIN);
servoSteering.attach(STEERING_OUT_PIN);
servoAux.attach(AUX_OUT_PIN);
// using the PinChangeInt library, attach the interrupts
// used to read the channels
attachInterrupt(THROTTLE_IN_PIN, calcThrottle,CHANGE);
attachInterrupt(STEERING_IN_PIN, calcSteering,CHANGE);
attachInterrupt(AUX_IN_PIN, calcAux,CHANGE);
}
Try this test, first disconnect any external circuitry, then uses the defines below. What I am doing is using the Servo object to output a test signal on the interrupt pins, the output will quickly go out of range because small errors will feedback into themselves but you should see readings starting at around 1500us and quickly ramping up or down as the errors accumulate. Once this works we can figure out which pins to use to drive external components.
ReplyDelete// Assign your channel in pins
#define THROTTLE_IN_PIN 0
#define STEERING_IN_PIN 1
#define AUX_IN_PIN 2
// Assign your channel out pins
#define THROTTLE_OUT_PIN 2
#define STEERING_OUT_PIN 3
#define AUX_OUT_PIN 21
As above, the output pins are mapped to themselves using the Mega interrupt to digital pin number mapping outlined in the first paragraph of this link - http://arduino.cc/it/Reference/AttachInterrupt
Duane B.
Hi Steven,
ReplyDeleteLooking at your Mega code and the online documentation for attachInterrupt -
Most Arduino boards have two external interrupts: numbers 0 (on digital pin 2) and 1 (on digital pin 3). The Arduino Mega has an additional four: numbers 2 (pin 21), 3 (pin 20), 4 (pin 19), and 5 (pin 18).
I can see that you are attaching your interrupts to the wrong pins.
The problem comes from the fact that attachInterrupt takes the interrupt number, but digitalRead uses the digital pin number. Its not a problem with pinchangeint because the library uses digital pin numbers, but using attachInterrupt you will need to do the following -
To fix this, make the following changes -
// Assign your channel in pins, these are pin numbers for use in digitalRead
#define THROTTLE_IN_PIN 2 // Pin 2 = Interrupt 0
#define STEERING_IN_PIN 3 // pin 3 = Interrupt 1
#define AUX_IN_PIN 21 // pin 21 = Interrupt 2
Add this -
// Assign interrupt numbers, we will only use these in setup
#define THROTTLE_IN_INTERRUPT 0 // Pin 2 = Interrupt 0
#define STEERING_IN_INTERRUPT 1 // pin 3 = Interrupt 1
#define AUX_IN_INTERRUPT 2 // pin 21 = Interrupt 2
Now in setup use -
attachInterrupt(THROTTLE_IN_INTERRUPT, calcThrottle,CHANGE);
attachInterrupt(STEERING_IN_INTERRUPT, calcSteering,CHANGE);
attachInterrupt(AUX_IN_INTERRUPT, calcAux,CHANGE);
If you want to change pins, refer to the attachInterrupt documentation for the mapping between Mega pins and interrupts.
Duane B.
This comment has been removed by the author.
DeleteThank you so much Duane. I had tried adding in the interrupt and pin ports earlier, but it didn't work. Maybe I missed something last time. Thanks again for all your work developing and supporting your guides.
ReplyDeleteThanks for this great posts!
ReplyDeleteAs I see you are using 3 pins for the 3 signals. Is it possible to use this approach (http://diydrones.com/profiles/blogs/705844:BlogPost:38393) - reading the sum signal - with your method? If yes, how?
Do you think, it will help to increase performance?
Hi, Personally I prefer to use three pins if I have the three decoded signals available. I have tried reading the single stream, but have found it less resilient to noise - everything works on the kitchen table, but at speed and distance its a lot more complicated - so if you have the three, four, five or six channels available individually from you receiver I would personally trust the existing decode software/hardware rather than take it out of the loop and reinvent it just to save a few pins. Duane B
ReplyDeleteHi, There are a few things that can be done to optimize this code, but I spent a long time thinking about whether to optimize it or make it as readable as possible. In the end I went for readable and given the number of people that are now successfully using the code I think it was the right choice. In a few weeks I will post a follow up with two or three optimizations for people who are familiar with the code as it stands and want to progress. One of them will be accessing SREG directly, the others will trim a few microseconds from the ISRs - however given that there are faster Arduino compatibles based on different processors coming soon - teensie 3.0 and Arduino Due - it might be better to stay at the high level for portability. Duane B
ReplyDeleteHello,
ReplyDeleteI am having an unusual problem. When I load the code from my laptop and leave it plugged in, everything works perfectly. I am powering my receiver from the 5v output from the arduino. However, when I unplug the board and power it from my battery, my servos do not move at all. Same issue if I power the receiver from a seperate battery. Any advice??
Thanks in advance.
Hi,
ReplyDeleteThis is normally something to do with the way your standalone project is wired, I am snowed under at the moment so your best bet will be to start a topic on the Arduino forum and include a diagram of your standalone circuit.
Duane B
If you do, let me know and I will take a look at it
ReplyDeleteDuane B
Hi Duane
ReplyDeleteThanks for this extensive in-depth explanation.
I have 2 questions about the code that are (as far as I see) not yet answered. I need some text to explain my questions so please bear with me.
In your main loop in the part that copies the volatile reads to the local reads there id a test on the flag for each copy.
if(bUpdateFlags & THROTTLE_FLAG)
{
RC_Channel_1_Value = unThrottleInShared;
}
From what I understand the test is not needed and is increasing the time you are in the interrupt blocking code.
I think the condition is not needed because interrupts themselves can not be interrupted so you can not read incorrect values. As to my understanding the flag and the value are always in sync.
There may be a startup sync problem (before the very first read on each channel) but as your code uses the flag to action the values your code as is would handle this properly.
My first question is: Do you see any drawbacks of replacing the conditional copy by a plain copy.
Continue thinking in the same way: Do you see a real life drawback of replacing
if (bUpdateFlags & THROTTLE_FLAG){
copy volatile vars
}
conditionally do actions
by
if (bUpdateFlags & (THROTTLE_FLAG | STEERING_FLAG | AUX_FLAG ) ) {
copy volatile vars
do actions
}
I realize this will reduce the response time as you are waiting for all channels to arrive before acting upon them. I can imagine that someone who wants to get everything out of his material prefers your proposal. But would this make a difference to someone who does not want to squeeze everything out of his material?
So my second question is: Can you as an RC specialist give your opinion whether "waiting for all channels" will make any difference in the world of "standard users".
Best regards
Jantje
Rofl
ReplyDeleteSorry just read this in the code
// in the current code, the shared values are always populated
// so we could copy them without testing the flags
// however in the future this could change, so lets
// only copy when the flags tell us we can.
My second question still stands (for now)
Best regards
Jantje
To answer this question - Can you as an RC specialist give your opinion whether "waiting for all channels" will make any difference in the world of "standard users". - It really depends on the application, for example if you are trying to stabalize something (quadcopters) or stop a skid developing (RC Car/yaw control) or optimise traction the faster you can respond to a developing condition the better. The difference between the code here and what you suggest is small, the difference between the code and using pulseIn or some other blocking approach is massive. I tend to treat the channels individually so that I can intercept a throttle or steering signal as soon as I have it rather than waiting a few additional uS for a complete frame worth of signals. Last night I came across some code I wrote in the summer that uses an alternative servo class to provide faster servo refresh and optimise the time spent in both the Servo and RC Channel ISRS. I will publish it in the next few days. Duane B
ReplyDeleteDuane
ReplyDeleteThanks for the answer.
I'm currently (amongst others) working on a library based on the code above.
The idea is to hide the complexity in the library and keep the functional part in the code.
The pattern of a sketch would look like this:
#include "PinChangeInt.h"
#define NUM_RC_CHANNELS 5
// Assign your pins to channels
PROGMEM const byte RC_Channel_Pin[NUM_RC_CHANNELS]={5,6,7,8,9};
// create local variables to hold a local copies of the channel inputs
uint16_t RC_Channel_Value[NUM_RC_CHANNELS];
//assign interrupt functions to channels
#include "RCLib.h"
void setup()
{
//Serial.begin(9600);
//Serial.println("multiChannels");
// attach the interrupts used to read the channels
SetRCInterrupts();
}
void loop()
{
// If you want to wait for all channels to have reported the change you can use the code below
if(haveAllRCChannelsReceivedInfo())
{
//do your channel actions here
//The values received are in RC_Channel_Value[]
}
//End of: If you want to wait for all channels to have reported the change you can use the code below
//If you prefer to react as soon as possible
uint8_t UpdateFlag = getChannelsReceiveInfo();
if (UpdateFlag & 1)
{
//Channel 1 changed so do what needs to be done for a channel 1 change
}
//end of: If you prefer to react as soon as possible
}
Can you tell me what you think?
Best regards
Jantje
I have stayed away from releasing a library because all of the array access and pin mapping slows things down a lot inside the ISR - too much for me to have ever been happy enough to release something - I have built a few different approaches including some fast low level ones but too much of it is either reinventing the wheel (re implementing pin change int ) or just not fast enough. The thing I like about this code is that it will move straight over to the new ARM platforms because its at a sufficiently high level and still gives good performance on the AVRs. Duane B
ReplyDeleteDuane
ReplyDeleteI came to the conclusion that the answer to my question ""waiting for all channels" will make any difference in the world of "standard users" is Yes, it does. Why? One broken line and all RC control is gone :-(. So I dumped the idea.
The library is going fine
The usage is simple (internal it is not :-( ) and I have been able to test it on a duemilanove with the PinChangeInt library and without. I want to test it with a mega and leonardo this weekend.
I'm fighting PROGMEM because it changes the actual values read. I'm not sure what to think about that.
Best regards
Jantje
Why PROGMEM ?
DeleteDuane
Hi Duane
DeleteSorry I missed this remark.
I need PROGMEM to store fixed values needed. one example is to be able to map pins to interrupts.
It works fine now but I get to many warnings in regards to progmem which hides the warnings I create.
You can see my lib at https://github.com/jantje/libraries
I didn't do any testing but I assume I get very similar speed measurements as to your code and it is lots easier to write.
For instance if you do not include pinchangeint you use the arduino attachInterrupt() and pins.
If you include pinchangeint you automatically use the pinchangeint attachInterrupt() and pins.
Och the code is dirty (I mean hard to read) because I extensively used MACRO's to solve problems because I wanted "fast code" rather that "readable code"
Tell me what you think about it.
Best regards
Jantje
I'm not sure I see why you're using Servo as an output routine. What would be wrong with simply echoing what the input pins do?
ReplyDeleteFor example, the throttle input rises and triggers the ISR to simply set the throttle output pin. Likewise, a falling pin would simply trigger the ISR to clear the throttle output pin.
If all you ever wanted to do was read the input and pass it straight to the output, there would be no need for the Arduino at all. My expectation is that once people are reading the input and are confident that the servo library output does what they expect, they will do something more interesting, as an example see this -
ReplyDeletehttp://rcarduino.blogspot.com/2012/07/rcarduino-yaw-control-part-2.html
or this -
http://rcarduino.blogspot.com/2012/05/interfacing-rc-channels-to-l293d-motor.html
or this -
http://rcarduino.blogspot.com/2012/01/traction-control-part-13-we-have.html
Duane B
When reading the serial output, should the be a specific value or is a small variance normal..
ReplyDeletei.e.
Throttle (F) 1908/1912
Throttle (N) 1508/1512
Throttle (R) 1112/1116
Steering (R) 1908/1912
Steering (N) 1512/1516
Steering (L) 1112/1116
The small variance is normal, you can find an explanation and recommendations for reducing it here but note that its generally not enough to move a servo or ESC and is also smaller than the general level of radio interference you can expect, see -
ReplyDeleteexplanation and reduction
http://rcarduino.blogspot.com/2012/11/how-to-read-rc-channels-rcarduinofastlib.html
radio interference illustration
http://rcarduino.blogspot.com/2012/03/reading-from-rc-reveiver-do-you-need.html
Duane B
Duane, thank you for your code and for all of the help you offer to all of us!
ReplyDeleteHi Duane,
ReplyDelete+1 to anonymous ^. Thanks for this set of posts, they're really informative and well written.
I've been programming with visual C# for a while now so I'm competent in basic programming, but have very little idea about interrupts.
For a university project I'm trying to make an autopilot routine for a quadcopter, plugging an arduino uno between the Rx and the quadcopter's brain.
With my extremely limited knowledge of interrupts I've tried to extend your code above to more channels, but it's not working.
Could I ask you a favour? Would it take up much of your time to put up a second example somewhere that does the same thing but for, say, 8 channels?
I've searched everywhere ever for similar code that works for 6 or 8 channels, but there is only stuff involving using Timer1, which, from what i've read (see: some of your own comments) cannot be used in conjunction with the servo library. I think there would be a lot of people out there including me who would be very grateful for an 8 channel PWM reader + passthrough.
Preemptive Thanks,
Patrick L
P.S. This Arduino stuff seems really cool and I'm definitely going to learn how to use interrupts in the future but for this particular projects time is short and the only thing holding me up is making the arduino invisible when autopilot is inactive.
To add more channels, its simply a case of cutting and pasting. The code is organized into clear sections, for example the three channel input pins are define, then the output put pins, then the variables to hold the values and so it continues throughout. To add an additional channel, look through the code line by line and wherever you see the Aux channel referenced, copy it and rename it to add your new channel. It really is that easy. Duane B
ReplyDeleteWhat you mean I actually have to work to get what I want?! Even if 'working' is just copying and pasting XD
ReplyDeleteFirst I have a couple of questions.
1) Why did you not start at pin 1?
2) Do the Flags assigned at 1,2 and 4 have any relation to pins, or are they their own set of numbers? (i.e. can I have a pin 1 as well as a flag 1?)
3)Why did you skip flag 3?
So I did added another channel the way you said, but my quadcopter won't arm, meaning the 4th Channel isn't right. When I print servoAux2.readMicroseconds and unAux2In to serial they're exactly the same but the correct PWM is definitely not getting to the 'copter.
Any ideas why?
I'm hoping you're going to say 'ah this is a common problem...'
The code is here if you're interested
http://www.pastebucket.com/5242
Thanks again,
Patrick
The flag is the only thing you have wrong, the flags are 'bit flags' meaning each one relates to a different bit in a byte. Bits have the values 1,2,4,8,16,32,64 and 128, these are the values you should use for your flags, change 5 to 8 and your code looks as if it should work.
ReplyDeleteDuane B
haha not my code, /your/ code.
ReplyDeleteNope I changed the flag to 8 and still nothing happens.
If I print servoAux2.readMicroseconds to serial, is what's on my screen definitely what's coming out of the pin? or not necessarily so?
It is what is coming out of the pin, what does it show ? does it change if you move any or all of the transmitter controls ?
ReplyDeleteDuane B
All the pins are putting out expected results - minimum ~1100 maximum ~1900.
DeleteI don't know if you know anything about quadcopters, but you have to arm them so they don't start spinning when you're too close.
This is done by moving throttle to minimum and rudder to maximum.
With my arduino plugged in, the arming light comes on when throttle is full regardless of rudder position, but after that no response from anything, and it disarms when throttle goes to minimum.
Could it be a conflict because the arduino is not outputting into esc's but into another microcontroller? is there something slightly off with timing or something which confuses the quadcopter brain?
Patrick
Hi, So what you have is the Arduino accepting and passing through all of your channels correct, but the second uC (microcontroller) is not passing these correctly on to the ESCs. Can you check that you have a common ground connection between the two uCs and also do you have an oscilloscope that you can check that the output of the second uC is following the output from the Arduino ? if not, try a servo.
ReplyDeleteDuane B
Hi!
ReplyDeleteDumb question, but where IS the library on your site? I see no download area. Sorry if i missed it.
Tom
There are quite a few libraries dotted around, which one are you looking for ?
ReplyDeleteDuane B
Hi Duane,
ReplyDeleteThanks for the great explanations. Still a newb, but learning fast. Wondering if you have any thoughts on how to work with 760 microsecond, 560 Hz high speed servos. I've been mucking with the servo.h library to try and get it to refresh faster, but I'm honestly in way over my head on that :)
Cheers,
Jon
Hi Duane, i am a super new beginner. I am also struggling to get a RC car moving using a Futaba recevier and transmitter. For the above project , do you happen to have the full schematic diagram? A Thousand thanks for your assistance
ReplyDeleteHi, I dont have a schematic, but the only thing you really need to think about is how are you going to power the Arduino. WhAt type of baTtery is powering the car and how many volts is it ?
ReplyDeleteDuane B
Hi I am new to all of this, i load the single channel code and all worked fine. Now having load the above code all i see in the serial monitor is tyhe word "multi channels" i have confirmed the the RC RX is working and giving oit data. I have tried using a MEGA 2560 and UNO board and it does the same?
ReplyDeleteHi, If you read through the code you will see that it does not attempt to output to serial, instead it directly controls servos attached to pins 8,9 or 10.
ReplyDeleteIf you dont have any servos and just want to see serial output instead, you can add it next to the writeMicroseconds.
Duane B
Hi Duane, I am using an Arduino Uno and whenever I try to compile the code you provided, I get the following error msg
ReplyDeleteServo\Servo.cpp.o: In function `__vector_11':
E:\arduino-1.0.1\libraries\Servo/Servo.cpp:103: multiple definition of `__vector_11'
RCArduinoFastLib\RCArduinoFastLib.cpp.o:E:\arduino-1.0.1\libraries\RCArduinoFastLib/RCArduinoFastLib.cpp:51: first defined here
It seems that both RCArduinoFastLib and Servo Library use the same timer
May I know if there is anything I'm missing?
Thanks
Thomas
Hi,
ReplyDeleteThe RCArduinoFastLib is an alternative to the servo library which has been optimised for use in RC Projects. As its an alternative to the Servo library, it uses the same Timer resource on the basis that where RCArduinoFastLib is used, the servo library will not be used. Not sure what code you are compiling to see an error, the code in this post does not include RCArduinoFastLib so there should be no clashes.
Duane B
This is awesome, thanks for sharing! I really learned a lot about the Arduino language and signal processing from your post and from playing around with my controller/servos this weekend.
ReplyDeleteThis is, this sketch does not work at all on an Arduino Micro. It works perfect on my Duemilanove and Mini, but not on the (Leonardo based) Micro. Any ideas on a work-around? Thanks!
-Anthony
Hi,
ReplyDeleteI do not have a micro but have an idea of what your problem is - it also ties into a post I was planning to write soon.
As a first step, lets try reading a single channel using some simpler code here - http://rcarduino.blogspot.ae/2012/01/how-to-read-rc-receiver-with.html
You will need to make some small changes to be able to write to serial on the micro, I think you know these already.
Let me know if this works, it might not in which case I know what the problem is.
Duane B.
Okay, let's do this!
ReplyDeleteFirst though, I just uploaded the "MultiChannel" code again with the "while (!Serial) {;}" and a "Serial.println("test")" thrown in. This is the exact same code as Sunday, but now it's actually printing "test". Throttle is still showing all zeros though :(
Now on to the code from "How To Read an RC Receiver With A Microcontroller - Part 1". At first, no Serial at all. After adding, "while (!Serial) {;}" I can print "test" if inserted into the void loop(). Nothing else prints though. If the Serial.println("test"); command is inside the "if(bNewThrottleSignal)" function it again does not print even while manipulating the controller throttle.
Ideas?
-Anthony
Hi, What I think is happening is that the Micro has a different pin mappings between interrupts and digital pins.
ReplyDeleteIt looks as if the Micro has interrupts 0 to 3 mapped to digital pins 0 to 3 the UNO has interrupts 0 to 1 mapped to pins 2 to 3. The code is based on interrupt 0 being mapped to pin 2 which it is not on the Micro, to test this, try changing the following line -
attachInterrupt(THROTTLE_SIGNAL_IN,calcInput,CHANGE);
to
attachInterrupt(INT2,calcInput,CHANGE);
Let me know what happens.
For anyone else following this, please note that this refers to the code in this post
http://rcarduino.blogspot.ae/2012/01/how-to-read-rc-receiver-with.html
which represents the simplest test of an Arduino to RC Receiver connection, not the code above which uses a slightly different approach.
Duane B
Hi, Looking at the documentation here -
ReplyDeletehttp://arduino.cc/en/Reference/AttachInterrupt
It looks as if INT1 is mapped to pin 2 on the Leonardo, so if INT2, doesnt work as suggest above try
attachInterrupt(INT1,calcInput,CHANGE);
Duane B
Hi Duane B,
ReplyDeleteINT2 did nothing that I could tell, but INT1 produced a huge Serial print, but nothing no pattern appeared from manipulating the controller's throttle.
Here is a segment of what was printed. This string just repeats over and over again:
6484
9036
8916
8824
8768
8764
8772
8576
8356
8296
8276
8308
8424
8624
8700
8704
8700
8708
8708
8712
8708
8720
8712
8708
8720
8720
8712
8724
8716
8716
8716
8716
8708
8720
8716
8708
8716
8768
9008
9248
9476
9708
140
452
8428
88
80
8400
76
8536
72
8644
60
8720
52
8692
36
8696
24
8708
20
8700
20
8712
24
8704
20
8704
20
8712
20
8712
20
8708
16
8712
20
8712
24
8712
48
8712
44
8724
44
8712
52
8716
52
8720
56
8724
60
8716
60
8712
72
8712
84
8732
208
9044
And then I remembered to ground the Arduino to the RC receiver. After that, Serial.print is outputting numbers I expect: 1500s for neutral, 1100s for reverse, 1900s for forward.
Now I plan to use the below bit of code to translate the throttle signal into PWM to use on a MOSFET for speed control.
if ((unThrottleIn < 1665) && (unThrottleIn > 1365))
{
Throttle = 0;
analogWrite (MOTORFWD, Throttle);
analogWrite (MOTORRVS, Throttle);
}
else if(unThrottleIn > 1665)
{
Throttle = (unThrottleIn - 1665);
analogWrite(MOTORFWD, Throttle);
analogWrite(MOTORRVS, 0);
}
else if (unThrottleIn < 1365)
{
Throttle = (1365 - unThrottleIn);
analogWrite(MOTORFWD, 0);
analogWrite(MOTORRVS, Throttle);
}
}
Thanks for your help! I really appreciate your time; it's folks like you willing to help a complete stranger across the globe that make this community so great. I look forward to the day when I am able to help the next n00b!
-Anthony
I may have spoke too soon... Switching to INT1 worked on "How To Read an RC Receiver With A Microcontroller - Part 1" but not in How To Read Multiple RC Channels.
ReplyDeleteAlso, INT0 on Pint 3 worked great for AUX channel.
Hi,
ReplyDeleteYour getting one step ahead, so far all we have done is adapt one sketch to work with the Arduino Micro, now that we know the pin mappings on the Micro are different than on the UNO and Mega, we can adapt the second multi channel sketch to work on the micro.
Looking at the pin mapping for the micro, it should be possible to read six channels without using any external libraries.
I will put together a post showing how to do this on the UNO (5 interrupts) Leonardo/Mico (6 interrupts) and Mega (Loads of interrupts). I will share the draft post with you so that you can test it first.
Can you send me you email address through the Arduino forum and I will send you a link to the draft post.
Duane B
Hi,
ReplyDeleteThank you for sharing your hard work. Your blog is very inspiring and i've learned so much. There is still one question though. Can you tell me if one can use all this stuff along with the wire library? I mean do these all libraries use different timers and other resources so that they can be used together in one program?
Cheers,
Peter
Hi,
ReplyDeleteI spent a good part of today trying to figure out why the single servo program would work and I couldn't get the multiple servo program working. I read through all of the comments and it's possible that I missed something, but I thought I would share here to try to get a better understanding.
In the end it seems like only 1 variable is used for both the interrupt and the pin # in the multiple servo program whereas in the single servo program there is a pin definition and an interrupt definition. When I set the interrupt number to 0 in the setup everything works (attachInterrupt(0, calcThrottle,CHANGE); with the throttle pin set to 2)
Am I overlooking something obvious here?
Thanks for posting your code and comments, they have been extremely helpful!
Solved by following this procedure -
Delete1) Use the original code from the blog here but with the following changes -
http://rcarduino.blogspot.ae/2012/04/how-to-read-multiple-rc-channels-draft.html
2) Change this
// Assign your channel in pins
#define THROTTLE_IN_PIN 5
#define STEERING_IN_PIN 6
#define AUX_IN_PIN 7
// Assign your channel out pins
#define THROTTLE_OUT_PIN 8
#define STEERING_OUT_PIN 9
#define AUX_OUT_PIN 10
to this -
// Assign your channel in pins
#define THROTTLE_IN_PIN 8
#define STEERING_IN_PIN 9
#define AUX_IN_PIN 10
// Assign your channel out pins
#define THROTTLE_OUT_PIN 5
#define STEERING_OUT_PIN 6
#define AUX_OUT_PIN 7
Basically pins 8,9,10 and 11 are the only ones on a the micro that support pin change interrupts. For this to work you will also need to reconnect your circuit so that the RC Channels are coming into pins 8,9,10 and the servo signals are going out through 5,6 and 7.
Duane B
Yes, your overlooking something.
ReplyDeleteThe multi channel example above uses the pinchangeint library, you can see this where the interrupt is attached using -
PCintPort::attachInterrupt(AUX_IN_PIN, calcAux,CHANGE);
instead of the standard syntax as used in the single channel example -
attachInterrupt(AUX_IN_PIN, calcAux,CHANGE);
I always found it a bit confusing that the standard Arduino attachInterrupt function uses the interrupt number rather than the pin number. That is why in the single channel example we need two variables, the first is the interrupt number, the second is the pin number. It is also the reason why the UNO example did not work on your Arduino Micro. If the Arduino team had used pin numbers instead, the function could have easily figured out which interrupt is associated with which pin on which board.
At the moment, my assumption is that the pinchangeint library is not supported on the Arduino Micro and that is why this example is not working for you.
I have an alternative approach which is more efficient that I hope to post later today, stay tuned.
Duane B
Thanks for the response. I forgot to mention that I am using a Arduino mego so I turned off the pinchangeint library.
DeleteI'm getting a weird twitch every couple of seconds where the arduino is reading a non-center stick value (~10-20% of full scale) for what appears to be a single reading. Do you know if there is any way to eliminate twitch at the expense of accuracy? I'll be driving some high power motors with this and would like to avoid pulsing them when they shouldn't be moving.
Thanks again!
Hi,
DeleteIt gets hard to follow when people comment as anonymous, can you use a nickname in your comments so that I can at least follow which anonymous is which.
The twitch can be one of two things -
1) Its a result of interrupt clashes between the servo library timer interrupt and the incoming RC Signal interrupts
I would expect a variation of about 5-10us in this case, thats one or two percent of the servo range, enough to tick, not jerk.
2) An error in your modified code - I am assuming this is not the case.
If its just a twitch its 1) above, if its more than a twitch is probably 2)
Are you planning to use the servo library to drive ESCs in your final project or are you using analogWrite to PWM Motors through a shield ?
If you are not planning to use servos, remove the code and the clashes will go away. If you are planning to to use servos, try this approach, it has come optimizations to reduce clashes, it also has an explanation of where the clashes come from which might be helpful
http://rcarduino.blogspot.ae/2012/11/how-to-read-rc-channels-rcarduinofastlib.html
As you are not using pinchangeint, you will need to replace
if(PCintPort::pinState)
with the direct port manipulation equivalent i.e.
if(PORTD & (1<<3)) // test INT3 on Mega Digital Pin 18
If you need help with this, let me know
Duane B
Thanks again for all of your help. I will not be using the servos so I tried taking the code out along with the code that I had added other than:
Delete//Mid point bounces between 1556 and 1560
if(unThrottleIn > 1560)
{
Serial.println(unThrottleIn);
}
With this code in there I would get bundles of 15 numbers that would print to serial, they ranged from 1564 to 1588 and would come every few seconds or so. That's roughly 30 us of error on the large end so I'm thinking I must be doing something wrong.
To find out if it was just the multiple servo code I went back to the single servo program and added only the above section of code in the part where it says "// other processing ...". Once again the numbers came in bunches of 15, but the number was always 1564, so only ~5 us of error.
I appreciate the help but I understand if this is something you don't have time to chase down.
Thanks again,
Woody (person using the mega and getting a twitchy response)
Hi Woody,
DeleteIt looks as if its a problem with the modified sketch you are using, can you send me a copy to have a look, the easiest way is to send it to me through the PM system on Arduino forum.
Duane B
if have some glitch in the signal after have connected the grawnd of receiver to arduino grawnd, put a 2,2nF capacitor between signal and grown, it will delete all glitch and make the signal stable :)
ReplyDeleteHi, I need to control two servos and a esc. The receiver is 2.4 ghz called tactic. Here is a photo of the receiver and servos.
ReplyDeletehttp://www.aquacraftmodels.com/boats/aqub0200-vela/aqub0200-servo-lg.jpg
Love your Library.
ReplyDeleteI have used it to make an Autonomous car/boat/plane with Adafruit Ultimate GPS.
The difficulty was the conflicts with SoftwareSerial. Problem solved!
Let me know if you want to see the code.
It uses less than 2% of the Uno CPU resources.
How about some pictures or a video, I would like to see more of what people end up building
DeleteDuane B
I'm trying to build my own Quadcopter using Arduino. This article on reading receiver signals using interrupts is super useful in this regard. Its also really well written...thanks a ton Duane!!!
ReplyDeleteThank you for your work and sharing it. My project is a quadcopter. That´s my new hobby and I hope see it flying... The frame is ready and now I´m studing the Arduino Uno and how work the escs, signal from rc spektrum dx6i, gps, imu, and so many things. I have learned very much with your work.
ReplyDeleteCarlos/Lisbon
Hi Duane,
ReplyDeleteI have a question regarding timer interrupt. I'm already using your code above to read various channels from my RC receiver and pass the signals to the 4 motors of my quadcopter.
I would like to now read in accelerometer and gyro values at a constant time interval for attitude determination and control using a timer interrupt.
Will the timer interrupt work alongside your code which also uses interrupts.
Thanks once again for sharing your code and knowledge for free!
I am very impressed with your code and thank you for describing it.
ReplyDeleteI would like to know how the FLAGS such as THROTTLE_FLAG 1 are assigned to the
volatile uint8_t bUpdateFlagsShared, since there is no reference to the memory location name in the define statement nor is there a mention of the the flag in the declaration of the flagregister.
Wally
Wally, your over complicating things. Its just C Code, there is no direct access to memory or registers.
ReplyDeleteDuane
Thank you Duane. Sorry for my inexperience with flags in C. I see that I misunderstood the comment "holds the update flags defined above" . That had me going in the wrong direction.
ReplyDelete#define THROTTLE_FLAG 1
#define STEERING_FLAG 2
#defineAUX_FLAG 4
volatile unit8_t bUpdateFlagsShared;//holds the update flags defined above
I see now that it is the flag place values that are "defined above", and these will be &'ed with the value in the bUpdateFlags image of the bUpdateFlagsShared to test for the flag.
Wally
HI,
ReplyDeleteThanks for sharing your great work.
I'm using it to build a Kid mode for my RC short course truck.
I started from your example with tree channels, and got it to work perfectly.
I've combined with two extra interrupts for calculating distance and speed from a sonar and a button to set limited throttle modes.
All work fine, I'm able to limit speed and based on the distance and speed readings and prevent frontal impacts while a 3 year old drives...
The only issue I struggling width, is that my radio only has two channels (in fact for this purpose I only need 1), so I tried to remove the AUX channel by the following changes :
1- Commenting everything regarding the AUX channel (Attach and interrupts):
2 - #define SERVO_FRAME_SPACE 2
3 - CRCArduinoFastServos::setFrameSpaceA(SERVO_FRAME_SPACE,8*2000);
But after this change it stops working. Can you give me a clue for the reason?
Thanks in advance.
Hello!
ReplyDeleteI and My Friends are Trying to Build a RC Quadcopter and i need Some Help on the Code for Arduino to Control The Quadcopter.
It would be great If You can help me with a Sample code or a Tutorial to Arduino as I'm New to Arduino.
Thanks!
Hola ya probe en la 1- parte pero estas estas un poco dificil en la 2- parte
ReplyDeleteporque me marca error?
Arduino: 1.5.4 (Windows NT (unknown)), Board: "Arduino Mega (ATmega1280)"
multicanales.ino: In function 'void setup()':
multicanales:78: error: 'PCintPort' has not been declared
multicanales:79: error: 'PCintPort' has not been declared
multicanales:80: error: 'PCintPort' has not been declared
This report would have more information with
"Show verbose output during compilation"
enabled in File > Preferences.
Hey, Thanks for the wonderful tutorial. I've got one problem. Whenever i try to see the pulse width of any channel in serial terminal of arduino, it returns 0. I am calling Serial.print() within an interrupt.
ReplyDeleteThe code for the ISR is same as yours, except for the command for printing the pulseWidth.
void calcThrottle()
{
// if the pin is high, its a rising edge of the signal pulse, so lets record its value
if(digitalRead(THROTTLE_IN_PIN) == HIGH)
{
ulThrottleStart = micros();
}
else
{
// else it must be a falling edge, so lets get the time and subtract the time of the rising edge
// this gives use the time between the rising and falling edges i.e. the pulse duration.
unThrottleInShared = (uint16_t)(micros() - ulThrottleStart);
// use set the throttle flag to indicate that a new throttle signal has been received
bUpdateFlagsShared |= THROTTLE_FLAG;
Serial.println(unThrottleInShared);
}
}
Did you try this first ?
Deletehttp://rcarduino.blogspot.ae/2012/01/how-to-read-rc-receiver-with.html
Duane
Yup I got it. Serial data is captured using interrupts and hence it cannot be used in another ISR.
DeleteHope i got it right.
Thank you!!
I tried to read print multiple channels, but noticed a vague behaviour. I the code that you have uploaded, whenever I declare the input pins (like throttleInPin, etc) below pin number 9(i.e. from 2 to 8) and then when i try to print the Time period read by them n serial window, I either get garbage values or nothing at all. But if I do the same for the input pins above 9 (9 till 13), I get the proper values on the serial terminal of arduino.
ReplyDeleteDon't know if it is because of the PinChangeInt library or because of some serial comm buffer's connection with these pins.
Can you help me out?
Code snippet for printing is-
// on every cycle.
if(bUpdateFlags & THROTTLE_FLAG)
{
servoThrottle.writeMicroseconds(unThrottleIn);
Serial.println(unThrottleIn);
}
I know there's nothing wrong in it, as it prints the values if input pins are defined between 9 and 13, both inclusive.
Thank you!!
and yes, I read and tried your earlier post. It worked.
DeleteThank You.
Which model of Arduino are you using ? Not all of them support PCInt on every pin, see here - http://rcarduino.blogspot.ae/2013/04/the-problem-and-solutions-with-arduino.html
ReplyDeleteDuane B
Thank you very much yet again!!!
DeleteHi,
ReplyDeleteMany thanks for the time you have put into developing this code.
I'd like to copy this and expand it to at least 5 channels and run it on the mega 2560 board. Other than the changes to how the interrupt routines are called, I gather it will still work fine if I just duplicate the code for the extra channels I need?
Mike
Hi, It will work on the Mega using the built in AttachInterrupt function on the pins listed here http://rcarduino.blogspot.ae/2013/04/the-problem-and-solutions-with-arduino.html Here is an example of 8 channels using PCInt on an Arduino UNO http://rcarduino.blogspot.ae/2013/04/problem-reading-rc-channels-rcarduino.html
ReplyDeleteDuane.
Hi, i want to control 4 dc motors which are 3 motors in both directions and the other motor just in one direction. I planned to use the Walkera TX5805 and RX701 which both have 7 channels. Is it possible to do the configuration with the DEVO F7 transmitter? If yes, how does the configuration looks like? Hope to get a feedback esepcially on the coding. Thank you
ReplyDeleteHi, I'm trying to read the signal from AR600 Spektrum rc receiver but i can't seem to find a solution. Nothing works... i tried pulseIn(), using a timer - micros(), usiong a timer with interrupts, reading from analog input. I'm working on a school project on APM 2.5, using input pin 1 = 13 and A0 = 97. I can't get something from the receiver and I know the receiver works since without the arduino board i manage to control servos only with it. I've been struggling for a few days now and I don't know what to do. Any help? Thanks.
ReplyDeletes.paul1408@gmail.com
No idea what an APM 2.5 is, but if its anything like and Arduino, the most common mistake is not connecting the ground of the receiver to the ground of the Arduino to form a common ground between the two.
ReplyDeleteDuane
...Or you could use a spectrum satelite receiver, and connect it to the serial port.
ReplyDeletehttp://forum.arduino.cc/index.php/topic,22266.0.html
edit
apm 2.5 is ardupilot mega, which does not have a serial-in from spectrum satelite
that is - if you are using the normal arducopter firmware
Try this, just replace "ar8000" with "ar600" both receivers send the same type of servo signal
http://diydrones.com/forum/topics/connecting-apm-2-5-with-spektrum-ar8000
Hi Dear,
ReplyDeleteI practiced this sketch with Arduino Mega 2560 and was working well. BUT I noticed that it's working for 10,11 and 12 pins only and use standard interrupts with pins 2 and 3. does it means we can have five pulse In pins only. please advise me if something missing.
Thanks a lot,
Fateh
I'm using this sketch to act as a "cruise control" for a model train. Haven't yet got my RC equipment but will in the future. At this point i'm using a infrared switch to count wheel rotations. I'm counting "pulses" from the switch in a 1/10 second time frame. There is a delay(100) in the loop. Will this mess up how the sketch works to sense pulse widths from the RX when i get the RC stuff? I don't need super responsive servo action; a slight delay is OK. Just need to make sure i'm reading the correct signal from the RX.
ReplyDeleteThx.
Marty
No, a delay(100) will not effect the RC Reading as it uses interrupts. However you could easily adapt one of the RC Channels in the code above to read the wheel speed using interrupts.
ReplyDeleteDuane B
hi, i have following problem: when i disconnect a rc receiver wire, there should be GND over a Pulldown Resistor on the Pin. sometimes it is recognized some times not. i don't know what to do. pulldown is about 20kohm
ReplyDeletethanks
Try a pull up instead and/or Try a smaller value pull down like a 10K and/or Use a multimeter to make sure that the ground is a good connection to the same ground the Arduino is using.
ReplyDeleteDuane B
Thank you for sharing this information this is very nice blog thank you for giving this info.
ReplyDeleteI share your post link in my new site ,
Duane, thx for all your work w/ your code. It's working well for me on my little steam engine. I've received my R/C stuff and am deciding how i want to use it. The Arduino gives so much flexibility. Here is a little video of my loco in "cruise control" mode. http://youtu.be/jntED_VEsMk
ReplyDeleteMarty
Hello Duane B
ReplyDeleteI design a code based on your example that sample 10 RC signals. 4 of them I pass directly to 4 servos using servo library. I use a Pro Mini card with ATMEGA328 5V/16M. It works fine but a get a tickle on servos. This tickle persist even when I send a constant value of 1500 at the servos on setup function and on loop function I don't actualize this value, and of course the pin change interrupt is working. When I disable the RC signals (close the radio Tx) the tickle disappear. So you have any idea how I can solve the problem of the servo tickling?
Thank you very much
Sorin
Hi, If I understand, you write 1500 to your servos in the setup function and then do not change this value. You use interrupts to read RC Signals but do not use the signals you read to update servo positions. Your servos make tiny movements as if there is some small interference. These tiny movements stop if you turn the radio off.
DeleteThere is an explanation and solution here - http://rcarduino.blogspot.ae/2012/11/how-to-read-rc-channels-rcarduinofastlib.html Duane B on How To Read Multiple RC Channels
Duane B
This comment has been removed by the author.
ReplyDeleteI'm just beginning to look at this, and by no means an expert, but why don't we use a filter (PWM to analog voltage say a 750k resistor and a 1000u cap?) and plug the RC directly to analog pins instead of reading with pulseIn or interrupts?
ReplyDeleteRC PWM is a very different signal than, the PWM Signal you get from analogWrite. RC PWM is a way of multiplexing lots of individual RC Channels into a single radio channel. If you look at the content of a single channel you will see that it is mostly empty space. There is a lot more background here -
ReplyDeletehttp://rcarduino.blogspot.ae/2012/01/how-to-read-rc-receiver-with.html
and
http://rcarduino.blogspot.ae/2012/11/how-to-read-rc-receiver-ppm-stream.html
So long story short, RC Signals are designed to pack upto 10 Servo (PWM) channels into a single (PPM) Radio channel, transmitting analogue type signals would not allow more than one servo per radio channel.
As a final thought, what would neutral and reverse look like if it was as simple as sending PWM ?
Duane.
I think we are looking at it differently. I am talking about capturing the signal from the pin outputs of the rc receiver, since you hook up ESCs and servos to them directly. I get the radio signal itself is PWM or PPM, but I thought the receivers always output PWM on the individual channels.
DeleteIf we tapped into the pin outs on the rc receiver to the analog in on the arduino, wouldn't it be the same signal a servo would get, a PWM? Then putting the circuit above inbetween to level the signal to beable to jsut do an analogread?
Please read or reread the link I posted in my previous response, particularly the background section starting with the comment
Delete"What are we looking for when we read from an RC Receiver ? Its not what I originally expected, my guess was that it was an analogue signal that would be amplified by the speed controller or servo controller to drive the motors. Its not analogue at all, its actually mostly empty space.
"
Duane.
Hello Duane B!
ReplyDeleteYour code works fine, thank you. I use it to measure two RC channel and control an PWM output. I tried to control some not-pwm-compatible pins, so I included a SoftPWM library too. Is it possible to work together with your code? Thank you.
Mec
If all you are doing is reading incoming RC Signals and outputting PWM through a soft PWM Library, it should be possible. If you were outputting servo signals as well as soft PWM there would probably be conflicts in the use of timer interrupts by both libraries, this would not work well.
ReplyDeleteThe soft PWM Libraries make use of high frequency timer interrupts which will have some effect on the accuracy of reading incoming signals as the Arduino can only deal with one interrupt at a time (RC Input interrupt or SoftPWM Output interrupt).
I suggest that you give it a try, in theory it should work if you can tolerate the small (1 or 2%) errors in the input.
Duane.
Thank you. I tried, it works, but some times it seems that program is stopped (SoftPWM stuck on a fixed value). I will debug my code. but in another configuration, where I don't use SoftPWM same program runs fine.
DeleteMec
Its probably a timer clash between the two libraries, check which timer registers the softPWM library uses and if it gives you an easy option to use a different set.
ReplyDeleteDuane.
Hi, My RC Transmitter-Receiver set still works fine but seeing as how it's pretty old now, every now and then the signal glitches and the receiver receives an outlier signal. I monitored the signal input in serial monitor and the normal range (from moving the stick on the transmitter all the way left and then all the way right) was 1100 to 1900 in serial monitor and when this "glitch" occurred, it would send something like
ReplyDelete22312
12916
24112
18324
Which would in effect make the servos attached to the output pins spaz out and this damaged the servos as they are not continuous rotating ones and when they reach their maximum and try to keep going, gears break and so on.
My question is how do I prevent the arduino from outputting these outlier numbers to the motors and how do I set an Operating Range or threshold for the input signal?
Thanks in advance
The Anonymous Sometimes Late Spectating Potato
See the 'constrain' function on arduino.cc but those values suggest there is something wrong in your code or circuit.
ReplyDeleteDuane B
Good information. I've been editing some code that reads controllers in interrupts as you described and it makes no attempt to safeguard against updates at the wrong time. It was originally meant for an Arduino Uno but I'm porting to a 32 bit Teensy 3.1. Firstly all the channels were going into a single interrupt and then it did a lot of work to decide which one tripped it. I have since made it one routine for each channel which simplifies the code a lot and makes each interrupt as fast as possible. Most obvious is the variables can be updated at any time and used inconsistently in the main code. There is also nothing declared as volatile.
ReplyDeleteHi Dear
ReplyDeleteI am testing your upper code (Sample Code - MultiChannels rcarduino.blogspot.com) with an arduino Uno. I am not using a receiver for the input signal to the arduino, but the ESC output of a Pixhawk. The sample code is for 3 servo, and it's work quite fine. I have modify the code to use with 4 servos, and now it is slow and hacking.
Did you have an idea on whats wrong?
Thanks,
Olivier
Aquí se encuentran el volatile control, red de control de murciélagos , la red del pájaro , protección de las aves y la protección de las aves.
ReplyDeleteI am following your blog regularly and got great information.
ReplyDeleteThis comment has been removed by the author.
ReplyDeleteDoes anyone know what would be needed to use this on an 8mhz/3.3v Pro Mini (Atmega168 chip)? It drives all servos to their limits, and I've tried sending fixed values betwee 100 and 5000.
ReplyDeleteIt works fine on the 328P 16mhz/5v boards, and the 168 8mhz/3.3v boards work find with simple code like Blink.
Thank you so much for this code. Had to modify it for the mega, but it is working great!
ReplyDeletethank you very much for sharing the code. I'm trying and it seems to work very well.
ReplyDeleteThanks 1000
Hey i cant figure out whats wrong ive been at it for 3 hours now i cant get the example sketch to work with my Spektrum AR6100e using an arduino Mega
ReplyDeleteHi Duane,
ReplyDeleteI have copied and modified the code and it works well except for one thing, i cant use the local copies (unthrottlein) etc when trying to verify thecode using the local copies, I get a error for (unthrottlein not declared in this scope) it works just fine using unthrottleinshared though.
I am new to arduino, ultimately what i am trying to do is to set some conditions on the value of the inputs using some if statements, i really dont need the servo out puts and wish i could eliminate the servo nassociated parts altogether.
I realise this article is now 5 years old, but it only seems fair to comment as it's quite possibly the most well written ardunio based article I've ever had the fortune of reading.
ReplyDeleteThoroughly commented, every aspect explained and nothing left out!
Hi Dear,
ReplyDeletei Like Your Blog Very Much..I see Daily Your Blog ,is A Very Useful For me.
Thunder Remote Control RC Truck Truggy Car Light up Wheels Ready to Run INCLUDES RECHARGEABLE BATTERY 1:16 Size Toy (Green)
Visit Now - https://www.amazon.com/Thunder-Remote-Control-RECHARGEABLE-BATTERY/dp/B01MCRP82Z/ref=sr_1_1?m=A3KIGJ5D7AQK8I&s=merchant-items&ie=UTF8&qid=1504848439&sr=1-1&keywords=B01MCRP82Z
Remote Control Truck
Hi Duane,..I am a beginner in this,
ReplyDeleteI have been using pulseIn to read channels, but after your useful blog I changed it to interrupts, I modified your code for 4 channels but I can't get output in seriel monitor. I am using arduino mega. Can you please share a code by which I can able to read the 4channels and display it in seriel monitor. Please help me.
Thanks in advance.
Hi Duane,
ReplyDeleteMany thank for a great guide - may I ask whether the passing of bUpdateFlagsShared to bUpdateFlags could occur prior to noInterrupts() command (further reducing the time with interruupts disabled). If my understanding is correct, the passing of this single byte cant be interrupted as per your example of a uint16_t, which is made of two bytes?
Thanks
Thanks for the blog loaded with so many information. Stopping by your blog helped me to get what I was looking for. best rc cars for kids
ReplyDelete
ReplyDeleteThank you so much for sharing. Your content was very helpful. You are a marvelous writer. Good work!
Click Here : Crawler dozers for sale Cat D5KXL KWW00358
I also wrote an article on a similar subject will find it at write what you think. RC car steering setup
ReplyDeleteIt is possible to manage renewal and your subscription in your Norton account provides a Norton Utility tool to test and optimize your own PC to you. Norton utility is a completely free tool that could be downloaded by the manufacturer site.
ReplyDeletenorton.com/setup
norton.com/setup
norton.com/setup
I really happy found this website eventually. Really informative and inoperative, Thanks for the post and effort! Please keep sharing more such blog.
ReplyDeletewww.norton.com/setup
www.spectrum.net/login
kaspersky activation
aolmail.com
mcafee.com/activate
I really happy found this website eventually. Really informative and inoperative, Thanks for the post and effort! Please keep sharing more such blog.
ReplyDeletewww.norton.com/setup
www.spectrum.net/login
aolmail.com
mcafee.com/activate
ester and christmas ornaments
Hello everyone feel free to reach out to Mr barry on Barrysilbert540 @ gmail. com if you need help or guidance on how profitable trades is been done. And also if you want to recover your lost funds using his masterclass strategy he can help you. I'm earning $10,250 weekly using his masterclass.. you can also reach him out on whatsapp + 4 4 7 5 0 8 2 9 8 6 9 1.
ReplyDeleteHi Duane.
ReplyDeleteIt's been a long time.
I hope it's not too late.
I'd like to ask you a question, too.
Using the code in this post, we are attempting to control the signal from Arduino and the RC car using the controller at the same time.
The ultimate goal is to create a system that uses controllers in emergency situations for RC cars that use Arduino to drive autonomously.
However, I have no idea which part of the above code I should input the signal from Arduino.
Even though I tried a lot of different things, Arduino's Servo.Write~ didn't work at all.
I leave a question if you can come up with a solution.
Thank you always.
-K-
Rcarduino: How To Read Multiple Rc Channels >>>>> Download Now
ReplyDelete>>>>> Download Full
Rcarduino: How To Read Multiple Rc Channels >>>>> Download LINK
>>>>> Download Now
Rcarduino: How To Read Multiple Rc Channels >>>>> Download Full
>>>>> Download LINK
Магазин спортивного питания, официальный портал которого доступен по адресу: SportsNutrition-24.Com, реализует обширный выбор товаров, которые принесут пользу и заслуги как проф спортсменам, так и любителям. Интернет-магазин осуществляет свою деятельность большой промежуток времени, предоставляя клиентам со всей Рф качественное спортивное питание, а помимо этого витамины и особые препараты - https://sportsnutrition-24.com/gejnery/. Спортпит представляет собой категорию товаров, которая призвана не только сделать лучше спортивные заслуги, да и благоприятно влияет на здоровье организма. Подобное питание вводится в повседневный рацион с целью получения микро- и макроэлементов, витаминов, аминокислот и белков, а помимо этого многих других недостающих веществ. Не секрет, что организм спортсмена в процессе наращивания мышечной массы и адаптации к повышенным нагрузкам, остро нуждается в должном количестве полезных веществ. При этом, даже правильное питание и употребление растительной, а также животной пищи - не гарантирует того, что организм получил нужные аминокислоты или белки. Чего нельзя сказать о качественном питании для спорта. Об наборе товаров Интернет-магазин "SportsNutrition-24.Com" реализует качественную продукцию, которая прошла ряд проверок и получила сертификаты качества. Посетив магазин, заказчики могут получить себе товары из следующих категорий: - L-карнитинг (Л-карнитин) представляет собой вещество, схожее витамину B, синтез которого осуществляется в организме; - гейнеры, представляющие собой, белково-углеводные консистенции; - BCAA - средства, содержащие в собственном составе три важные аминокислоты, стимулирующие рост мышечной массы; - протеин - чистый белок, употреблять который можно в виде коктейлей; - разные аминокислоты; - а также ряд других товаров (нитробустеры, жиросжигатели, специальные препараты, хондропротекторы, бустеры гормона роста, тестобустеры и многое другое). Об оплате и доставке Интернет-магазин "SportsNutrition-24.Com" предлагает огромное обилие товаров, которое в полной мере способно удовлетворить проф и начинающих спортсменов, включая любителей. Большой опыт позволил фирмы сделать связь с крупнейшими поставщиками и изготовителями питания для спорта, что позволило сделать политику цен гибкой, а цены - демократичными! К примеру, аминокислоты или гейнер приобрести вы можете по цене, которая на 10-20% ниже, чем у конкурентов. Оплата возможна как наличным, так и безналичным расчетом. Магазин предлагает огромный выбор методов оплаты, включая оплату разными электронными платежными системами, а кроме этого дебетовыми и кредитными картами. Главный офис компании расположен в Санкт-Петербурге, однако доставка товаров осуществляется во все населенные пункты РФ. Помимо самовывоза, получить товар можно при помощи любой транспортной компании, выбрать которую каждый клиент может в личном порядке.
ReplyDeleteМагазин питания для спорта, официальный веб-сайт которого доступен по адресу: SportsNutrition-24.Com, реализует обширный выбор спортивного питания, которые принесут пользу и достижения как профессиональным спортсменам, так и любителям. Интернет-магазин производит свою деятельность большой промежуток времени, предоставляя клиентам со всей Рф высококачественное питание для спорта, а также витамины и специальные препараты - Предтренировачный комплекс. Спортпит представляет собой категорию продуктов, которая призвана не только улучшить спортивные достижения, да и благоприятно влияет на здоровье организма. Подобное питание вводится в ежедневный рацион с целью получения микро- и макроэлементов, витаминов, аминокислот и белков, а также прочих недостающих веществ. Не секрет, что организм спортсмена в процессе наращивания мышечной массы и адаптации к повышенным нагрузкам, остро нуждается в должном количестве полезных веществ. При этом, даже правильное питание и употребление растительной, а кроме этого животной пищи - не гарантирует того, что организм получил нужные аминокислоты или белки. Чего нельзя сказать о высококачественном питании для спорта. Об ассортименте товаров Интернет-магазин "SportsNutrition-24.Com" реализует качественную продукцию, которая прошла ряд проверок и получила сертификаты качества. Посетив магазин, заказчики смогут найти себе товары из следующих категорий: - L-карнитинг (Л-карнитин) представляет собой вещество, родственное витамину B, синтез которого осуществляется в организме; - гейнеры, представляющие собой, белково-углеводные смеси; - BCAA - средства, содержащие в собственном составе три важные аминокислоты, стимулирующие рост мышечной массы; - протеин - чистый белок, употреблять который можно в виде коктейлей; - всевозможные аминокислоты; - а также ряд прочих товаров (нитробустеры, жиросжигатели, особые препараты, хондропротекторы, бустеры гормона роста, тестобустеры и многое другое). Об оплате и доставке Интернет-магазин "SportsNutrition-24.Com" предлагает огромное разнообразие товаров, которое полностью способно удовлетворить профессиональных и начинающих спортсменов, включая любителей. Большой опыт дозволил компании сделать связь с наикрупнейшими поставщиками и изготовителями спортивного питания, что позволило сделать ценовую политику гибкой, а цены - демократичными! К примеру, аминокислоты либо гейнер купить вы можете по цене, которая на 10-20% ниже, чем у конкурентов. Оплата возможна как наличным, так и безналичным расчетом. Магазин предлагает широкий выбор методов оплаты, включая оплату различными электронными платежными системами, а помимо этого дебетовыми и кредитными картами. Главный офис компании размещен в Санкт-Петербурге, однако доставка товаров осуществляется во все населенные пункты РФ. Помимо самовывоза, получить товар можно при помощи любой транспортной фирмы, выбрать которую каждый клиент может в индивидуальном порядке.
ReplyDeleteНаш сайт рассказывает о новостях бокса и ММА. Читатель узнает о ближайших и прошедших боях - ufc прогнозы. Эксперты делятся своими прогнозами на ближайшие события, проводят разбор окончившихся поединков. Вы можете прочитать интервью со известными бойцами и их тренерами. Новости ММА, бокса Все предстоящие и прошедшие бои освещаются с подробным разбором, анализом и комментариями спортивных экспертов. Подписавшись на Octagon, вы будете в курсе всех событий в области боевых искусств. Самые интересные, запоминающиеся бои без правил в нашем видеоконтенте. Ознакомьтесь с выводами, разбором захватывающих моментов поединков. События, расписание, видео На странице указано расписание предстоящих боев. Даты, время начала боя вы можете изучить на сайте. Предлагается просмотр видео самых увлекательных моментов схваток. Кто не желает заниматься скучным чтением, может изучить видео прогноз. Легкая, юмористическая подача инфы легко воспринимается и усваивается у подписчиков. Видеоряд постоянно обновляется, предлагается только качественный контент. Биографии, рейтинги боксеров Предоставлены подробные биографии знаменитостей, рейтинг UFC. Вы узнаете, каким был путь героя к покорению Олимпа. Приводятся все весовые характеристики спортсменов. Предоставляется информация о самочувствии боксера до и после поединка. Проводится разборка стиля и техники бойцов. Octagon.express предлагает только полностью достоверную информацию. На нашем веб-сайте вы ознакомитесь и узнаете о всех тонкостях смешанных боевых искусств. Точные прогнозы, достоверные рейтинги - наше основное кредо. Подписавшись на Telegram канал, вы будете в курсе всех новостей боевых искусств. Все последние бои UFC не ускользнут от вас.
ReplyDeleteСайт Mostbethit.net предлагает посетителям поближе ознакомиться с одноименной БК. Интернет-ресурс посвящен описанию главных преимуществ конторы. На проекте гости узнают другие принципиально важные моменты - Мостбет регистрация в букмекерской компании. Букмекерская контора для продвижения зеркальных ссылок использует партнерские веб-сайты. Одним из таких ресурсов стал описываемый проект. На страницах портала посетители найдут: - ссылки на зеркала букмекера; - описание БК; - правила работы с конторой; - особенности бонусной политики; - действующие промокоды; - описание способов пополнения баланса; - анализ отзывов о букмекере; - рекомендации для создания аккаунта. Проект предлагает детализированный «разбор» оператора ставок. Регистрация для использования не нужна. Посетитель может свободно переходить по разделам, получать информацию. Языковая доступность - плюс сайта. Русскоязычная версия раскрывается по дефлоту. Гость может установить любой из 23 доступных языков. Описания на портале детальные. Создатели постарались отметить каждый нюанс. Для улучшения пользовательского восприятия тексты снабжены скриншотами. Информативность максимальная. Предложенные на проекте ссылки направят игроков на действующее зеркало БК. Это обеспечивает круглосуточный доступ к конторе. Об использовании VPN и прочих способов обхода санкций Роскомнадзора удается забыть. Ссылки на дублирующий веб-сайт Мостбет предложены на каждой странице сайта. Долго искать действующие адреса не придется. Разработчики выпустили мобильную версию сайта. Проект оптимизирован под девайсы с разной диагональю дисплея. Пользоваться ресурсом комфортно со смартфонов и планшетов. Онлайн-портал не предлагает спортивных ставок и других азартных развлечений. Закрыть интернет-сайт регулятор не может. В итоге ресурс обеспечивает круглосуточный доступ к БК.
ReplyDeleteНаш онлайн-кинотеатр предлагает своим зрителям окунуться в мир интересных турецких сериалов. Онлайн-портал предлагает широкий каталог многосерийных кинофильмов на любой вкус, а главное, они все имеют русское озвучивание. Мы регулярно пополняем медиатеку, чтобы нашему гостю не приходилось искать новинки на сторонних ресурсах - турецкий сериалы на русском языке смотреть онлайн. Что предлагает онлайн-кинотеатр своим посетителям? - Полное отсутствие рекламы. Ничего не сможет отвлечь от игры любимых актеров! - Постоянные обновления каталога. Теперь вы не пропустите новинки турецкого кинематографа! - Высококачественное озвучивание - долой русско-турецкий словарь! - Возможность смотреть сериалы на разных устройствах. Дорога на работу теперь станет увлекательной и интересной! - Удобство просмотра. Наш видеопроигрыватель может остановить только низкая скорость интернет-соединения. Интернет-портал турецких сериалов создан специально для тех, кто всегда находится в поиске ресурсов, позволяющих смотреть зарубежные многосерийные киноленты без ограничений. Сейчас в вашем распоряжении огромное количество кинофильмов с хорошим качеством изображения и русским переводом. Начать просмотр можно любому гостю интернет-сайта без каких-либо ограничений. Но мы все таки рекомендуем вам пройти минутную регистрацию - это позволит вам создавать закладки, оставлять комменты и обсуждать просмотренные картины с другими гостями портала. А в случае если вы только начали знакомство с турецкими телесериалами, сделать выбор в пользу той либо иной киноленты поможет краткая аннотация. Погрузится в мир головокружительной любви, трагических встреч, кровопролитной вражды и опасных игр с самой судьбой поможет наш онлайн-кинотеатр телесериалов из Турции. Позитивного просмотра и положительных эмоций!
ReplyDeleteСтавки на спорт с каждым днем становятся все популярнее. У беттеров (игроков на ставках) из Узбекистана появилась возможность делать ставки в государственной валюте на веб-сайте - вывод средств с Mostbet для Узбекистана. При регистрации в этой букмекерской конторе пользователь выбирает игровую валюту - узбекский сум, российский рубль, доллар либо евро. Всего доступно 19 валют! Играть на портале смогут не только лишь жители Узбекистана, ресурс переведен на 25 языков. Ставки на спорт На портале Мостбет вы можете сделать ставки на все главные события мира спорта - футбол, баскетбол, хоккей, волейбол, теннис и другие, включая зимние виды спорта, легкую атлетику и единоборства. Для того, что бы делать ставки следует пройти функцию регистрации, которая займет менее 5 минут. Для клиентов этой букмекерской конторы доступны ставки до матча (прематч) и в режиме реального времени (Live). Казино В Мостбет можно так же сыграть в азартные игры - карты, рулетку и слоты (игровые автоматы с вращающимися барабанами). Слотов очень много - на любой вкус, от классических вишенок-клубничек до современного софта с несколькими игровыми полями и бонусными раундами. На Мостбет у игроков единый счет для ставок и для казино. Приложение на интернет-сайте Мостбет можно скачать приложение для игры с мобильных устройств - планшетов и телефонов под управлением операционной системы Андроид и iOS. Ссылка для скачивания этих программ находится в самом верху главной странички. На веб-сайте можно внимательно ознакомиться с отзывами игроков. В случае если есть проблема со входом на портал, то на странице «Зеркало» можно можете узнать об альтернативных методах входа, в случае блокировки ресурса. Для связи с пользователями действует онлайн чат, в каком можно задать любой интересующий вопрос техническим консультантам букмекерской конторы.
ReplyDeleteСтавки на спорт с каждым днем становятся все популярнее. У беттеров (игроков на ставках) из Узбекистана появилась возможность делать ставки в государственной валюте на портале - Мостбет ставки в Киргизии. При регистрации в этой букмекерской конторе пользователь выбирает игровую валюту - узбекский сум, российский рубль, доллар или евро. Всего доступно 19 валют! Играть на веб-сайте сумеют не только жители Узбекистана, ресурс переведен на 25 языков. Ставки на спорт На портале Мостбет можно сделать ставки на все главные события мира спорта - футбол, баскетбол, хоккей, волейбол, теннис и прочие, включая зимние виды спорта, легкую атлетику и единоборства. Для того, что бы делать ставки нужно пройти функцию регистрации, которая займет не более 5 минут. Для гостей этой букмекерской конторы доступны ставки до матча (прематч) и в режиме реального времени (Live). Казино В Мостбет можно так же сыграть в азартные игры - карты, рулетку и слоты (игровые автоматы с вращающимися барабанами). Слотов очень много - на любой вкус, от традиционных вишенок-клубничек до современного софта с несколькими игровыми полями и бонусными раундами. На Мостбет у игроков единый счет для ставок и для казино. Приложение на интернет-сайте Мостбет вы можете скачать приложение для игры с мобильных устройств - планшетов и телефонов под управлением операционной системы Андроид и iOS. Ссылка для скачивания этих программ располагается в самом верху главной страницы. На портале вы можете ознакомиться с отзывами игроков. Если есть проблема со входом на веб-сайт, то в разделе «Зеркало» вы можете выяснить об других способах входа, в случае блокировки ресурса. Для связи с пользователями действует онлайн чат, в котором можно задать любой интересующий вопрос техническим консультантам букмекерской конторы.
ReplyDeleteСтавки на спорт с каждым днем становятся все популярнее. У беттеров (игроков на ставках) из Узбекистана появилась возможность делать ставки в национальной валюте на интернет-сайте - Мостбет регистрация в Таджикистане. При регистрации в этой букмекерской конторе пользователь выбирает игровую валюту - узбекский сум, русский рубль, доллар или евро. Всего доступно 19 валют! Играть на портале смогут не только жители Узбекистана, ресурс переведен на 25 языков. Ставки на спорт На веб-сайте Мостбет можно сделать ставки на все главные события мира спорта - футбол, баскетбол, хоккей, волейбол, теннис и прочие, включая зимние виды спорта, легкую атлетику и единоборства. Для того, что бы делать ставки надо пройти процедуру регистрации, которая займет не более 5 минут. Для гостей этой букмекерской конторы доступны ставки до матча (прематч) и в режиме реального времени (Live). Казино В Мостбет можно так же сыграть в азартные игры - карты, рулетку и слоты (игровые автоматы с вращающимися барабанами). Слотов очень много - на любой вкус, от классических вишенок-клубничек до современного софта с несколькими игровыми полями и бонусными раундами. На Мостбет у игроков единый счет для ставок и для казино. Приложение на сайте Мостбет можно загрузить приложение для игры с мобильных устройств - планшетов и телефонов под управлением операционной системы Андроид и iOS. Ссылка для скачивания этих программ располагается в самом верху главной страницы. На сайте вы можете внимательно ознакомиться с отзывами игроков. В случае если есть проблема со входом на портал, то на странице «Зеркало» вы можете узнать об альтернативных методах входа, в случае блокировки ресурса. Для связи с гостями работает онлайн чат, в котором можно задать любой интересующий вопрос техническим консультантам букмекерской конторы.
ReplyDeleteRetivabet - максимально перспективный и простой букмекерский веб-сайт - Ретивабет пополнение счета, депозиты. На интернет-сайте вы можете подобрать разнообразные виды развлечений на любой вкус: казино, букмекерские ставки, игры. Если вы любитель казино, здесь для вас предусмотрено все необходимое. Легкий доступ к игровым слотам, карточным играм и рулетке, а кроме этого возможность поучаствовать в турнире. Главная задача турнира - вырваться на лидирующие позиции в турнирной таблице, потому что крупный призовой фонд будет разделен между первыми тридцатью победителями. И чем выше ваша позиция в таблице, тем больший кусок пирога вам достанется. Для ценителей спорта Retivabet предлагает обширный спектр ставок. Здесь находятся не только всемирно популярные чемпионаты и команды, но и новички из разных лиг и стран, что делает результат еще более волнительным и интересным. Футбол, баскетбол, хоккей, теннис, снукер, дартс, скачки, гонки и многое другое, на чем можно заработать хорошие деньги. Выбор за вами. Киберспорт кроме этого стал неотъемлемой частью букмекерской компании. Теперь клиент может делать ставки не только на Пре-Матчи, но и на игры в режиме онлайн, в любое время дня и ночи. Для того, чтобы стать клиентом огромной семьи Revitabet, требуется пройти регистрацию на веб-сайте Retivabet.net. Это не займет более десяти минут, после чего новый клиент получает бонусы от фирмы, которыми в последствие, может пользоваться на свое усмотрение. Виды бонусов: - Приветственные; - Кешбэк с сумы ставок; - Бонус за серию проигрышей; - Экспресс бонус. Пополнение счета возможно разными способами, что делает это максимально удобным для клиентов. Вы можете использовать для пополнения и вывода средств банковские карты, мобильные платежи, интернет банкинг. Никаких комиссий со стороны букмекерской компании, и даже приятные бонусы за пополнения. Никаких подводных камней, только прозрачно чистое и выгодное развлечение!
ReplyDeleteВ последние годы известность зарубежных сериалов и полнометражных фильмов бьет все рейтинги. Но где их вы можете посмотреть? В интернете много онлайн-кинотеатров, которые предлагают просмотр увлекательных киноработ от зарубежных кинокомпаний - смотреть турецкие сериалы на русском языке онлайн. Большой выбор на любой вкус Но особенно удобно смотреть зарубежные сериалы и фильмы на сайте Turksezon.net. На этой платформе представлена большая подборка самых популярных фильмов. На площадке вы можете бесплатно смотреть различные проекты: - турецкие; - колумбийские; - индийские; - бразильские. Все зарубежные проекты на площадке идут на русском языке. Привлекательно не только отличное качество перевода, но и высокое разрешение. Многие киносериалы и полнометражное кино на портале идут в формате 720 HD. Широкое разнообразие зарубежных фильмов - одна из особенностей онлайн-кинотеатра. На портале представлены сериалы и полнометражное кино в самых разных жанрах. Здесь непременно найдутся интересные проекты для ценителей драм, фэнтези, боевиков, комедий. Имеется огромное количество криминальных, исторических, семейных и военных киноработ. В соответствующих рубриках расположены актуальные детективы, триллеры, мелодрамы. Фантастическое кино, в том числе киносериалы, вы можете найти в отдельном блоке. Такое ранжирование по жанрам очень удобно для выбора подходящего фильма. Удобство поиска интересных кинофильмов Еще одна особенность онлайн-кинотеатра заключается в удобстве поиска интересного фильма. Каждый гость может получить кино, которое выпущено в определенный период. На сайте в отдельных блоках располагаются проекты, выпущенные с 2016 до 2022 годы соответственно. К каждому кинофильму на площадке идет небольшое, но емкое описание. Благодаря ему каждый гость сумеет подобрать именно тот проект, что ему максимально интересен.
ReplyDeleteSwizz company Proton Technology AG with the world’s strongest privacy law. ProtonVPN is being the first VPN service provider that shows Open-Source Code. Full Transparency so that you are able to know who i
ReplyDelete시흥스웨디시
군포스웨디시
의왕스웨디시
하남스웨디시
Каждый современный человек в той или иной степени желает известности. Особенно в соц сетях. Наиболее популярная соц сеть на сегодняшний момент - Instagram. Страницы используются в качестве личных профилей, для блогинга и ведения бизнеса. Каково бы ни было назначение профиля, любому пользователю хочется увеличить количество лайков и подписчиков. Для этого стоит использовать сервис накрутки KrutimInst.ru - накрутка инстаграм подписчики бесплатно
ReplyDeletecatorrent
ReplyDeleteslot siteleri
ReplyDeletekralbet
betpark
tipobet
betmatik
kibris bahis siteleri
poker siteleri
bonus veren siteler
mobil ödeme bahis
SZK8
kralbet
ReplyDeletebetpark
tipobet
slot siteleri
kibris bahis siteleri
poker siteleri
bonus veren siteler
mobil ödeme bahis
betmatik
B6G
If you don't remember this, your car may be stolen!
ReplyDeleteConsider that your car was taken! When you approach the police, they inquire about a specific "VIN search"
Describe a VIN decoder.
Similar to a passport, the "VIN decoder" allows you to find out when the car was born and who its "parent"( manufacturing plant) is. You can also find out:
1.The type of engine
2.Model of a car
3.The DMV and the limitations it imposes
4.Number of drivers in this vehicle
You will be able to locate the car, and keeping in mind the code ensures your safety. The code can be examined in the online database. The VIN is situated on various parts of the car to make it harder for thieves to steal, such as the first person seated on the floor, the frame (often in trucks and SUVs), the spar, and other areas.
What happens if the VIN is harmed on purpose?
There are numerous circumstances that can result in VIN damage, but failing to have one will have unpleasant repercussions because it is illegal to intentionally harm a VIN in order to avoid going to jail or calling the police. You could receive a fine of up to 80,000 rubles and spend two years in jail. You might be stopped by an instructor on the road.
Conclusion.
The VIN decoder may help to save your car from theft. But where can you check the car reality? This is why we exist– VIN decoders!
Система бонусов и поощрений в БК 1хбет значительно повышает привлекательность компании в глазах пользователей. Весьма выгодные предложения доступны и новеньким, и гостям, уже имеющим опыт работы на платформе. Среди впечатляющего ассортимента бонусной программы очень легко потеряться. Каждый промокод 1хбет гарантирует право на определенные преференции - промокод на 1хбет.
ReplyDeletekars
ReplyDeletesinop
sakarya
ankara
çorum
FZUDMW
https://titandijital.com.tr/
ReplyDeletebayburt parça eşya taşıma
afyon parça eşya taşıma
düzce parça eşya taşıma
erzincan parça eşya taşıma
5VZ
This comment has been removed by the author.
ReplyDeleteGreat post! I really enjoyed reading this. The information you shared is so helpful, and I appreciate the clear explanations. Looking forward to more content like this. Keep up the good work!
ReplyDeleteLegal Separation Virginia
88A45
ReplyDeleteClubhouse Takipçi Hilesi
Bitcoin Çıkarma
Binance Hangi Ülkenin
Soundcloud Dinlenme Satın Al
Yeni Çıkacak Coin Nasıl Alınır
Mexc Borsası Güvenilir mi
Soundcloud Takipçi Hilesi
Binance Borsası Güvenilir mi
Binance Referans Kodu
شركة مكافحة النمل الابيض بالدمام 9vxRzSSuKC
ReplyDeletesc3lfwtxRp
ReplyDeleteشركة عزل اسطح بالقطيف mVmSUoqrw3
ReplyDeleteشركة عزل اسطح OvVJAtdXfv
ReplyDeleteشركة مكافحة حشرات nbAG95CXw0
ReplyDeleteشركة صيانة افران بعنيزة yQHFu45ivA
ReplyDelete